wa-lang.org/wazero@v1.0.2/experimental/logging/log_listener.go (about) 1 package logging 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "strconv" 8 "strings" 9 10 "wa-lang.org/wazero/api" 11 "wa-lang.org/wazero/experimental" 12 "wa-lang.org/wazero/internal/wasi_snapshot_preview1" 13 ) 14 15 // NewLoggingListenerFactory is an experimental.FunctionListenerFactory that 16 // logs all functions that have a name to the writer. 17 // 18 // Use NewHostLoggingListenerFactory if only interested in host interactions. 19 func NewLoggingListenerFactory(writer io.Writer) experimental.FunctionListenerFactory { 20 return &loggingListenerFactory{writer: writer} 21 } 22 23 // NewHostLoggingListenerFactory is an experimental.FunctionListenerFactory 24 // that logs exported and host functions to the writer. 25 // 26 // This is an alternative to NewLoggingListenerFactory, and would weed out 27 // guest defined functions such as those implementing garbage collection. 28 // 29 // For example, "_start" is defined by the guest, but exported, so would be 30 // written to the writer in order to provide minimal context needed to 31 // understand host calls such as "fd_open". 32 func NewHostLoggingListenerFactory(writer io.Writer) experimental.FunctionListenerFactory { 33 return &loggingListenerFactory{writer: writer, hostOnly: true} 34 } 35 36 type loggingListenerFactory struct { 37 writer io.Writer 38 hostOnly bool 39 } 40 41 // NewListener implements the same method as documented on 42 // experimental.FunctionListener. 43 func (f *loggingListenerFactory) NewListener(fnd api.FunctionDefinition) experimental.FunctionListener { 44 exported := len(fnd.ExportNames()) > 0 45 if f.hostOnly && // choose functions defined or callable by the host 46 fnd.GoFunction() == nil && // not defined by the host 47 !exported { // not callable by the host 48 return nil 49 } 50 return &loggingListener{writer: f.writer, fnd: fnd, isWasi: fnd.ModuleName() == "wasi_snapshot_preview1"} 51 } 52 53 // nestLevelKey holds state between logger.Before and loggingListener.After to ensure 54 // call depth is reflected. 55 type nestLevelKey struct{} 56 57 // loggingListener implements experimental.FunctionListener to log entrance and exit 58 // of each function call. 59 type loggingListener struct { 60 writer io.Writer 61 fnd api.FunctionDefinition 62 isWasi bool 63 } 64 65 // Before logs to stdout the module and function name, prefixed with '-->' and 66 // indented based on the call nesting level. 67 func (l *loggingListener) Before(ctx context.Context, _ api.FunctionDefinition, vals []uint64) context.Context { 68 nestLevel, _ := ctx.Value(nestLevelKey{}).(int) 69 70 l.writeIndented(true, nil, vals, nestLevel+1) 71 72 // Increase the next nesting level. 73 return context.WithValue(ctx, nestLevelKey{}, nestLevel+1) 74 } 75 76 // After logs to stdout the module and function name, prefixed with '<--' and 77 // indented based on the call nesting level. 78 func (l *loggingListener) After(ctx context.Context, _ api.FunctionDefinition, err error, vals []uint64) { 79 // Note: We use the nest level directly even though it is the "next" nesting level. 80 // This works because our indent of zero nesting is one tab. 81 l.writeIndented(false, err, vals, ctx.Value(nestLevelKey{}).(int)) 82 } 83 84 // writeIndented writes an indented message like this: "-->\t\t\t$indentLevel$funcName\n" 85 func (l *loggingListener) writeIndented(before bool, err error, vals []uint64, indentLevel int) { 86 var message strings.Builder 87 for i := 1; i < indentLevel; i++ { 88 message.WriteByte('\t') 89 } 90 if before { 91 if l.fnd.GoFunction() != nil { 92 message.WriteString("==> ") 93 } else { 94 message.WriteString("--> ") 95 } 96 l.writeFuncEnter(&message, vals) 97 } else { // after 98 if l.fnd.GoFunction() != nil { 99 message.WriteString("<== ") 100 } else { 101 message.WriteString("<-- ") 102 } 103 l.writeFuncExit(&message, err, vals) 104 } 105 message.WriteByte('\n') 106 107 _, _ = l.writer.Write([]byte(message.String())) 108 } 109 110 func (l *loggingListener) writeFuncEnter(message *strings.Builder, vals []uint64) { 111 valLen := len(vals) 112 message.WriteString(l.fnd.DebugName()) 113 message.WriteByte('(') 114 switch valLen { 115 case 0: 116 default: 117 i := l.writeParam(message, 0, vals) 118 for i < valLen { 119 message.WriteByte(',') 120 i = l.writeParam(message, i, vals) 121 } 122 } 123 message.WriteByte(')') 124 } 125 126 func (l *loggingListener) writeFuncExit(message *strings.Builder, err error, vals []uint64) { 127 if err != nil { 128 message.WriteString("error: ") 129 message.WriteString(err.Error()) 130 return 131 } else if l.isWasi { 132 message.WriteString(wasi_snapshot_preview1.ErrnoName(uint32(vals[0]))) 133 return 134 } 135 valLen := len(vals) 136 message.WriteByte('(') 137 switch valLen { 138 case 0: 139 default: 140 i := l.writeResult(message, 0, vals) 141 for i < valLen { 142 message.WriteByte(',') 143 i = l.writeResult(message, i, vals) 144 } 145 } 146 message.WriteByte(')') 147 } 148 149 func (l *loggingListener) writeResult(message *strings.Builder, i int, vals []uint64) int { 150 return l.writeVal(message, l.fnd.ResultTypes()[i], i, vals) 151 } 152 153 func (l *loggingListener) writeParam(message *strings.Builder, i int, vals []uint64) int { 154 if len(l.fnd.ParamNames()) > 0 { 155 message.WriteString(l.fnd.ParamNames()[i]) 156 message.WriteByte('=') 157 } 158 return l.writeVal(message, l.fnd.ParamTypes()[i], i, vals) 159 } 160 161 func (l *loggingListener) writeVal(message *strings.Builder, t api.ValueType, i int, vals []uint64) int { 162 v := vals[i] 163 i++ 164 switch t { 165 case api.ValueTypeI32: 166 message.WriteString(strconv.FormatUint(uint64(uint32(v)), 10)) 167 case api.ValueTypeI64: 168 message.WriteString(strconv.FormatUint(v, 10)) 169 case api.ValueTypeF32: 170 message.WriteString(strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32)) 171 case api.ValueTypeF64: 172 message.WriteString(strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64)) 173 case 0x7b: // wasm.ValueTypeV128 174 message.WriteString(fmt.Sprintf("%016x%016x", v, vals[i])) // fixed-width hex 175 i++ 176 case api.ValueTypeExternref, 0x70: // wasm.ValueTypeFuncref 177 message.WriteString(fmt.Sprintf("%016x", v)) // fixed-width hex 178 } 179 return i 180 }