github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/gojs/logging/logging.go (about) 1 package logging 2 3 import ( 4 "context" 5 "fmt" 6 "io/fs" 7 "os" 8 "strconv" 9 10 "github.com/bananabytelabs/wazero/api" 11 "github.com/bananabytelabs/wazero/internal/gojs" 12 "github.com/bananabytelabs/wazero/internal/gojs/custom" 13 "github.com/bananabytelabs/wazero/internal/gojs/goarch" 14 "github.com/bananabytelabs/wazero/internal/gojs/goos" 15 "github.com/bananabytelabs/wazero/internal/logging" 16 "github.com/bananabytelabs/wazero/internal/sys" 17 ) 18 19 // IsInLogScope returns true if the current function is in any of the scopes. 20 func IsInLogScope(fnd api.FunctionDefinition, scopes logging.LogScopes) bool { 21 if scopes.IsEnabled(logging.LogScopeClock) { 22 switch fnd.Name() { 23 case custom.NameRuntimeNanotime1, custom.NameRuntimeWalltime: 24 return true 25 case custom.NameSyscallValueCall: // e.g. Date.getTimezoneOffset 26 return true 27 } 28 } 29 30 if scopes.IsEnabled(logging.LogScopeProc) { 31 switch fnd.Name() { 32 case custom.NameRuntimeWasmExit: 33 return true 34 case custom.NameSyscallValueCall: // e.g. proc.* 35 return true 36 } 37 } 38 39 if scopes.IsEnabled(logging.LogScopeFilesystem) { 40 if fnd.Name() == custom.NameSyscallValueCall { 41 return true // e.g. fs.open 42 } 43 } 44 45 if scopes.IsEnabled(logging.LogScopeMemory) { 46 switch fnd.Name() { 47 case custom.NameRuntimeResetMemoryDataView: 48 return true 49 } 50 } 51 52 if scopes.IsEnabled(logging.LogScopePoll) { 53 switch fnd.Name() { 54 case custom.NameRuntimeScheduleTimeoutEvent, custom.NameRuntimeClearTimeoutEvent: 55 return true 56 } 57 } 58 59 if scopes.IsEnabled(logging.LogScopeRandom) { 60 switch fnd.Name() { 61 case custom.NameRuntimeGetRandomData: 62 return true 63 case custom.NameSyscallValueCall: // e.g. crypto.getRandomValues 64 return true 65 } 66 } 67 68 return scopes == logging.LogScopeAll 69 } 70 71 func Config(fnd api.FunctionDefinition, scopes logging.LogScopes) (pSampler logging.ParamSampler, pLoggers []logging.ParamLogger, rLoggers []logging.ResultLogger) { 72 switch fnd.Name() { 73 case custom.NameRuntimeWasmExit: 74 pLoggers = []logging.ParamLogger{runtimeWasmExitParamLogger} 75 // no results 76 case custom.NameRuntimeWasmWrite: 77 return // Don't log NameRuntimeWasmWrite as it is used in panics 78 case custom.NameRuntimeResetMemoryDataView: 79 // no params or results 80 case custom.NameRuntimeNanotime1: 81 // no params 82 rLoggers = []logging.ResultLogger{runtimeNanotime1ResultLogger} 83 case custom.NameRuntimeWalltime: 84 // no params 85 rLoggers = []logging.ResultLogger{runtimeWalltimeResultLogger} 86 case custom.NameRuntimeScheduleTimeoutEvent: 87 pLoggers = []logging.ParamLogger{runtimeScheduleTimeoutEventParamLogger} 88 rLoggers = []logging.ResultLogger{runtimeScheduleTimeoutEventResultLogger} 89 case custom.NameRuntimeClearTimeoutEvent: 90 pLoggers = []logging.ParamLogger{runtimeClearTimeoutEventParamLogger} 91 // no results 92 case custom.NameRuntimeGetRandomData: 93 pLoggers = []logging.ParamLogger{runtimeGetRandomDataParamLogger} 94 // no results 95 case custom.NameSyscallValueCall: 96 p := &syscallValueCallParamSampler{scopes: scopes} 97 pSampler = p.isSampled 98 pLoggers = []logging.ParamLogger{syscallValueCallParamLogger} 99 rLoggers = []logging.ResultLogger{syscallValueCallResultLogger} 100 default: // TODO: make generic logger for gojs 101 } 102 return 103 } 104 105 func runtimeGetRandomDataParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { 106 paramIdx := 1 /* there are two params, only write the length */ 107 writeParameter(w, custom.NameRuntimeGetRandomData, mod, params, paramIdx) 108 } 109 110 func runtimeScheduleTimeoutEventParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { 111 writeParameter(w, custom.NameRuntimeScheduleTimeoutEvent, mod, params, 0) 112 } 113 114 func runtimeClearTimeoutEventParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { 115 writeParameter(w, custom.NameRuntimeClearTimeoutEvent, mod, params, 0) 116 } 117 118 func runtimeWasmExitParamLogger(_ context.Context, mod api.Module, w logging.Writer, params []uint64) { 119 writeParameter(w, custom.NameRuntimeWasmExit, mod, params, 0) 120 } 121 122 func writeParameter(w logging.Writer, funcName string, mod api.Module, params []uint64, paramIdx int) { 123 paramNames := custom.NameSection[funcName].ParamNames 124 125 stack := goos.NewStack(funcName, mod.Memory(), uint32(params[0])) 126 w.WriteString(paramNames[paramIdx]) //nolint 127 w.WriteByte('=') //nolint 128 writeI32(w, stack.ParamUint32(paramIdx)) 129 } 130 131 func runtimeNanotime1ResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { 132 writeResults(w, custom.NameRuntimeNanotime1, mod, params, 0) 133 } 134 135 func runtimeWalltimeResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { 136 writeResults(w, custom.NameRuntimeWalltime, mod, params, 0) 137 } 138 139 func runtimeScheduleTimeoutEventResultLogger(_ context.Context, mod api.Module, w logging.Writer, params, _ []uint64) { 140 writeResults(w, custom.NameRuntimeScheduleTimeoutEvent, mod, params, 1) 141 } 142 143 func writeResults(w logging.Writer, funcName string, mod api.Module, params []uint64, resultOffset int) { 144 stack := goos.NewStack(funcName, mod.Memory(), uint32(params[0])) 145 146 resultNames := custom.NameSection[funcName].ResultNames 147 results := make([]interface{}, len(resultNames)) 148 for i := range resultNames { 149 results[i] = stack.ParamUint32(i + resultOffset) 150 } 151 152 w.WriteByte('(') //nolint 153 writeVals(w, resultNames, results) 154 w.WriteByte(')') //nolint 155 } 156 157 type syscallValueCallParamSampler struct { 158 scopes logging.LogScopes 159 } 160 161 func (s *syscallValueCallParamSampler) isSampled(ctx context.Context, mod api.Module, params []uint64) bool { 162 vRef, m, args := syscallValueCallParams(ctx, mod, params) 163 164 switch vRef { 165 case goos.RefJsCrypto: 166 return logging.LogScopeRandom.IsEnabled(s.scopes) 167 case goos.RefJsDate: 168 return logging.LogScopeClock.IsEnabled(s.scopes) 169 case goos.RefJsfs: 170 if !logging.LogScopeFilesystem.IsEnabled(s.scopes) { 171 return false 172 } 173 // Don't amplify logs with stdio reads or writes 174 switch m { 175 case custom.NameFsWrite, custom.NameFsRead: 176 fd := goos.ValueToInt32(args[0]) 177 return fd > sys.FdStderr 178 } 179 return true 180 case goos.RefJsProcess: 181 return logging.LogScopeProc.IsEnabled(s.scopes) 182 } 183 184 return s.scopes == logging.LogScopeAll 185 } 186 187 func syscallValueCallParamLogger(ctx context.Context, mod api.Module, w logging.Writer, params []uint64) { 188 vRef, m, args := syscallValueCallParams(ctx, mod, params) 189 190 switch vRef { 191 case goos.RefJsCrypto: 192 logSyscallValueCallArgs(w, custom.NameCrypto, m, args) 193 case goos.RefJsDate: 194 logSyscallValueCallArgs(w, custom.NameDate, m, args) 195 case goos.RefJsfs: 196 logFsParams(m, w, args) 197 case goos.RefJsProcess: 198 logSyscallValueCallArgs(w, custom.NameProcess, m, args) 199 default: 200 // TODO: other scopes 201 } 202 } 203 204 func logFsParams(m string, w logging.Writer, args []interface{}) { 205 if m == custom.NameFsOpen { 206 w.WriteString("fs.open(") //nolint 207 w.WriteString("path=") //nolint 208 w.WriteString(args[0].(string)) //nolint 209 w.WriteString(",flags=") //nolint 210 writeOFlags(w, int(args[1].(float64))) 211 w.WriteString(",perm=") //nolint 212 w.WriteString(fs.FileMode(uint32(args[2].(float64))).String()) //nolint 213 w.WriteByte(')') //nolint 214 return 215 } 216 217 logSyscallValueCallArgs(w, custom.NameFs, m, args) 218 } 219 220 func logSyscallValueCallArgs(w logging.Writer, n, m string, args []interface{}) { 221 argNames := custom.NameSectionSyscallValueCall[n][m].ParamNames 222 w.WriteString(n) //nolint 223 w.WriteByte('.') //nolint 224 w.WriteString(m) //nolint 225 w.WriteByte('(') //nolint 226 writeVals(w, argNames, args) 227 w.WriteByte(')') //nolint 228 } 229 230 func syscallValueCallParams(ctx context.Context, mod api.Module, params []uint64) (goos.Ref, string, []interface{}) { 231 mem := mod.Memory() 232 funcName := custom.NameSyscallValueCall 233 stack := goos.NewStack(funcName, mem, uint32(params[0])) 234 vRef := stack.ParamRef(0) //nolint 235 m := stack.ParamString(mem, 1 /*, 2 */) //nolint 236 args := stack.ParamVals(ctx, mem, 3 /*, 4 */, gojs.LoadValue) 237 return vRef, m, args 238 } 239 240 func syscallValueCallResultLogger(ctx context.Context, mod api.Module, w logging.Writer, params, results []uint64) { 241 mem := mod.Memory() 242 funcName := custom.NameSyscallValueCall 243 stack := goos.NewStack(funcName, mem, goarch.GetSP(mod)) 244 vRef := stack.ParamRef(0) //nolint 245 m := stack.ParamString(mem, 1 /*, 2 */) //nolint 246 247 var resultNames []string 248 var resultVals []interface{} 249 switch vRef { 250 case goos.RefJsCrypto: 251 resultNames = custom.CryptoNameSection[m].ResultNames 252 rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding 253 resultVals = []interface{}{rRef} 254 case goos.RefJsDate: 255 resultNames = custom.DateNameSection[m].ResultNames 256 rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding 257 resultVals = []interface{}{rRef} 258 case goos.RefJsfs: 259 resultNames = custom.FsNameSection[m].ResultNames 260 resultVals = gojs.GetLastEventArgs(ctx) 261 case goos.RefJsProcess: 262 resultNames = custom.ProcessNameSection[m].ResultNames 263 rRef := stack.ParamVal(ctx, 6, gojs.LoadValue) // val is after padding 264 resultVals = []interface{}{rRef} 265 default: 266 // TODO: other scopes 267 } 268 269 w.WriteByte('(') //nolint 270 writeVals(w, resultNames, resultVals) 271 w.WriteByte(')') //nolint 272 } 273 274 func writeVals(w logging.Writer, names []string, vals []interface{}) { 275 valLen := len(vals) 276 if valLen > 0 { 277 writeVal(w, names[0], vals[0]) 278 for i := 1; i < valLen; i++ { 279 name := names[i] 280 val := vals[i] 281 282 switch name { 283 case custom.NameCallback: 284 return // last val 285 case "buf": // always equal size with byteCount 286 continue 287 } 288 289 w.WriteByte(',') //nolint 290 writeVal(w, name, val) 291 } 292 } 293 } 294 295 func writeVal(w logging.Writer, name string, val interface{}) { 296 if b, ok := val.(*goos.ByteArray); ok { 297 // Write the length instead of a byte array. 298 w.WriteString(name) //nolint 299 w.WriteString("_len=") //nolint 300 writeI32(w, uint32(len(b.Unwrap()))) //nolint 301 return 302 } 303 switch name { 304 case "uid", "gid": 305 w.WriteString(name) //nolint 306 w.WriteByte('=') //nolint 307 id := int(goos.ValueToInt32(val)) 308 w.WriteString(strconv.Itoa(id)) //nolint 309 case "mask", "mode", "oldmask", "perm": 310 w.WriteString(name) //nolint 311 w.WriteByte('=') //nolint 312 perm := custom.FromJsMode(goos.ValueToUint32(val), 0) 313 w.WriteString(perm.String()) //nolint 314 default: 315 w.WriteString(name) //nolint 316 w.WriteByte('=') //nolint 317 w.WriteString(fmt.Sprintf("%v", val)) //nolint 318 } 319 } 320 321 func writeOFlags(w logging.Writer, f int) { 322 // Iterate a subflagset in order to avoid OS differences, notably for windows 323 first := true 324 for i, sf := range oFlags { 325 if f&sf != 0 { 326 if !first { 327 w.WriteByte('|') //nolint 328 } else { 329 first = false 330 } 331 w.WriteString(oflagToString[i]) //nolint 332 } 333 } 334 } 335 336 var oFlags = [...]int{ 337 os.O_RDONLY, 338 os.O_WRONLY, 339 os.O_RDWR, 340 os.O_APPEND, 341 os.O_CREATE, 342 os.O_EXCL, 343 os.O_SYNC, 344 os.O_TRUNC, 345 } 346 347 var oflagToString = [...]string{ 348 "RDONLY", 349 "WRONLY", 350 "RDWR", 351 "APPEND", 352 "CREATE", 353 "EXCL", 354 "SYNC", 355 "TRUNC", 356 } 357 358 func writeI32(w logging.Writer, v uint32) { 359 w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint 360 }