github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/logging/logging.go (about) 1 // Package logging includes utilities used to log function calls. This is in 2 // an independent package to avoid dependency cycles. 3 package logging 4 5 import ( 6 "context" 7 "encoding/hex" 8 "fmt" 9 "io" 10 "strconv" 11 "strings" 12 13 "github.com/wasilibs/wazerox/api" 14 ) 15 16 // ValueType is an extended form of api.ValueType, used to control logging in 17 // cases such as bitmasks or strings. 18 type ValueType = api.ValueType 19 20 const ( 21 ValueTypeI32 = api.ValueTypeI32 22 ValueTypeI64 = api.ValueTypeI64 23 ValueTypeF32 = api.ValueTypeF32 24 ValueTypeF64 = api.ValueTypeF64 25 ValueTypeV128 ValueType = 0x7b // same as wasm.ValueTypeV128 26 ValueTypeFuncref ValueType = 0x70 // same as wasm.ValueTypeFuncref 27 ValueTypeExternref = api.ValueTypeExternref 28 29 // ValueTypeMemI32 is a non-standard type which writes ValueTypeI32 from the memory offset. 30 ValueTypeMemI32 = 0xfd 31 // ValueTypeMemH64 is a non-standard type which writes 64-bits fixed-width hex from the memory offset. 32 ValueTypeMemH64 = 0xfe 33 // ValueTypeString is a non-standard type describing an offset/len pair of a string. 34 ValueTypeString = 0xff 35 ) 36 37 type LogScopes uint64 38 39 const ( 40 LogScopeNone = LogScopes(0) 41 LogScopeClock LogScopes = 1 << iota 42 LogScopeProc 43 LogScopeFilesystem 44 LogScopeMemory 45 LogScopePoll 46 LogScopeRandom 47 LogScopeSock 48 LogScopeAll = LogScopes(0xffffffffffffffff) 49 ) 50 51 func scopeName(s LogScopes) string { 52 switch s { 53 case LogScopeClock: 54 return "clock" 55 case LogScopeProc: 56 return "proc" 57 case LogScopeFilesystem: 58 return "filesystem" 59 case LogScopeMemory: 60 return "memory" 61 case LogScopePoll: 62 return "poll" 63 case LogScopeRandom: 64 return "random" 65 case LogScopeSock: 66 return "sock" 67 default: 68 return fmt.Sprintf("<unknown=%d>", s) 69 } 70 } 71 72 // IsEnabled returns true if the scope (or group of scopes) is enabled. 73 func (f LogScopes) IsEnabled(scope LogScopes) bool { 74 return f&scope != 0 75 } 76 77 // String implements fmt.Stringer by returning each enabled log scope. 78 func (f LogScopes) String() string { 79 if f == LogScopeAll { 80 return "all" 81 } 82 var builder strings.Builder 83 for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance 84 target := LogScopes(1 << i) 85 if f.IsEnabled(target) { 86 if name := scopeName(target); name != "" { 87 if builder.Len() > 0 { 88 builder.WriteByte('|') 89 } 90 builder.WriteString(name) 91 } 92 } 93 } 94 return builder.String() 95 } 96 97 // LoggerKey is a context.Context Value key with a FunctionLogger value. 98 type LoggerKey struct{} 99 100 type ParamLogger func(ctx context.Context, mod api.Module, w Writer, params []uint64) 101 102 type ParamSampler func(ctx context.Context, mod api.Module, params []uint64) bool 103 104 type ResultLogger func(ctx context.Context, mod api.Module, w Writer, params, results []uint64) 105 106 type Writer interface { 107 io.Writer 108 io.StringWriter 109 io.ByteWriter 110 } 111 112 // ValWriter formats an indexed value. For example, if `vals[i]` is a 113 // ValueTypeI32, this would format it by default as signed. If a 114 // ValueTypeString, it would read `vals[i+1]` and write the string from memory. 115 type ValWriter func(ctx context.Context, mod api.Module, w Writer, i uint32, vals []uint64) 116 117 func Config(fnd api.FunctionDefinition) (paramLoggers []ParamLogger, resultLoggers []ResultLogger) { 118 types := fnd.ParamTypes() 119 names := fnd.ParamNames() 120 if paramLen := uint32(len(types)); paramLen > 0 { 121 paramLoggers = make([]ParamLogger, paramLen) 122 hasParamNames := len(names) > 0 123 for i, t := range types { 124 if hasParamNames { 125 paramLoggers[i] = NewParamLogger(uint32(i), names[i], t) 126 } else { 127 paramLoggers[i] = (¶mLogger{idx: uint32(i), valWriter: ValWriterForType(t)}).Log 128 } 129 } 130 } 131 if resultLen := uint32(len(fnd.ResultTypes())); resultLen > 0 { 132 resultLoggers = make([]ResultLogger, resultLen) 133 hasResultNames := len(fnd.ResultNames()) > 0 134 for i, t := range fnd.ResultTypes() { 135 if hasResultNames { 136 resultLoggers[i] = NewResultLogger(uint32(i), fnd.ResultNames()[i], t) 137 } else { 138 resultLoggers[i] = (&resultLogger{idx: uint32(i), valWriter: ValWriterForType(t)}).Log 139 } 140 } 141 } 142 return 143 } 144 145 type paramLogger struct { 146 idx uint32 147 valWriter ValWriter 148 } 149 150 func (n *paramLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) { 151 n.valWriter(ctx, mod, w, n.idx, params) 152 } 153 154 func NewParamLogger(idx uint32, name string, t ValueType) ParamLogger { 155 return (&namedParamLogger{idx: idx, name: name, valWriter: ValWriterForType(t)}).Log 156 } 157 158 type namedParamLogger struct { 159 idx uint32 160 name string 161 valWriter ValWriter 162 } 163 164 func (n *namedParamLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) { 165 w.WriteString(n.name) //nolint 166 w.WriteByte('=') //nolint 167 n.valWriter(ctx, mod, w, n.idx, params) 168 } 169 170 type resultLogger struct { 171 idx uint32 172 valWriter ValWriter 173 } 174 175 func (n *resultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) { 176 n.valWriter(ctx, mod, w, n.idx, results) 177 } 178 179 func NewResultLogger(idx uint32, name string, t ValueType) ResultLogger { 180 return (&namedResultLogger{idx, name, ValWriterForType(t)}).Log 181 } 182 183 type namedResultLogger struct { 184 idx uint32 185 name string 186 valWriter ValWriter 187 } 188 189 func (n *namedResultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) { 190 w.WriteString(n.name) //nolint 191 w.WriteByte('=') //nolint 192 n.valWriter(ctx, mod, w, n.idx, results) 193 } 194 195 func ValWriterForType(vt ValueType) ValWriter { 196 switch vt { 197 case ValueTypeI32: 198 return writeI32 199 case ValueTypeI64: 200 return writeI64 201 case ValueTypeF32: 202 return writeF32 203 case ValueTypeF64: 204 return writeF64 205 case ValueTypeV128: 206 return writeV128 207 case ValueTypeExternref, ValueTypeFuncref: 208 return writeRef 209 case ValueTypeMemI32: 210 return writeMemI32 211 case ValueTypeMemH64: 212 return writeMemH64 213 case ValueTypeString: 214 return writeString 215 default: 216 panic(fmt.Errorf("BUG: unsupported type %d", vt)) 217 } 218 } 219 220 func writeI32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 221 v := vals[i] 222 w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint 223 } 224 225 func writeI64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 226 v := vals[i] 227 w.WriteString(strconv.FormatInt(int64(v), 10)) //nolint 228 } 229 230 func writeF32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 231 v := vals[i] 232 s := strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32) 233 w.WriteString(s) //nolint 234 } 235 236 func writeF64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 237 v := vals[i] 238 s := strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64) 239 w.WriteString(s) //nolint 240 } 241 242 // logV128 logs in fixed-width hex 243 func writeV128(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 244 v1, v2 := vals[i], vals[i+1] 245 w.WriteString(fmt.Sprintf("%016x%016x", v1, v2)) //nolint 246 } 247 248 // logRef logs in fixed-width hex 249 func writeRef(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) { 250 v := vals[i] 251 w.WriteString(fmt.Sprintf("%016x", v)) //nolint 252 } 253 254 func writeMemI32(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) { 255 offset := uint32(vals[i]) 256 byteCount := uint32(4) 257 if v, ok := mod.Memory().ReadUint32Le(offset); ok { 258 w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint 259 } else { // log the positions that were out of memory 260 WriteOOM(w, offset, byteCount) 261 } 262 } 263 264 func writeMemH64(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) { 265 offset := uint32(vals[i]) 266 byteCount := uint32(8) 267 if s, ok := mod.Memory().Read(offset, byteCount); ok { 268 hex.NewEncoder(w).Write(s) //nolint 269 } else { // log the positions that were out of memory 270 WriteOOM(w, offset, byteCount) 271 } 272 } 273 274 func writeString(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) { 275 offset, byteCount := uint32(vals[i]), uint32(vals[i+1]) 276 WriteStringOrOOM(mod.Memory(), w, offset, byteCount) 277 } 278 279 func WriteStringOrOOM(mem api.Memory, w Writer, offset, byteCount uint32) { 280 if s, ok := mem.Read(offset, byteCount); ok { 281 w.Write(s) //nolint 282 } else { // log the positions that were out of memory 283 WriteOOM(w, offset, byteCount) 284 } 285 } 286 287 func WriteOOM(w Writer, offset uint32, byteCount uint32) { 288 w.WriteString("OOM(") //nolint 289 w.WriteString(strconv.Itoa(int(offset))) //nolint 290 w.WriteByte(',') //nolint 291 w.WriteString(strconv.Itoa(int(byteCount))) //nolint 292 w.WriteByte(')') //nolint 293 }