Exported functions are easier to use as a map vs making the callers do it. Signed-off-by: Adrian Cole <adrian@tetrate.io>
156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
package experimental
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
|
|
)
|
|
|
|
// NewLoggingListenerFactory implements FunctionListenerFactory to log all
|
|
// functions that have a name to the writer.
|
|
func NewLoggingListenerFactory(writer io.Writer) FunctionListenerFactory {
|
|
return &loggingListenerFactory{writer}
|
|
}
|
|
|
|
type loggingListenerFactory struct{ writer io.Writer }
|
|
|
|
// NewListener implements the same method as documented on
|
|
// experimental.FunctionListener.
|
|
func (f *loggingListenerFactory) NewListener(fnd api.FunctionDefinition) FunctionListener {
|
|
return &loggingListener{writer: f.writer, fnd: fnd, isWasi: fnd.ModuleName() == "wasi_snapshot_preview1"}
|
|
}
|
|
|
|
// nestLevelKey holds state between logger.Before and loggingListener.After to ensure
|
|
// call depth is reflected.
|
|
type nestLevelKey struct{}
|
|
|
|
// loggingListener implements experimental.FunctionListener to log entrance and exit
|
|
// of each function call.
|
|
type loggingListener struct {
|
|
writer io.Writer
|
|
fnd api.FunctionDefinition
|
|
isWasi bool
|
|
}
|
|
|
|
// Before logs to stdout the module and function name, prefixed with '-->' and
|
|
// indented based on the call nesting level.
|
|
func (l *loggingListener) Before(ctx context.Context, vals []uint64) context.Context {
|
|
nestLevel, _ := ctx.Value(nestLevelKey{}).(int)
|
|
|
|
l.writeIndented(true, nil, vals, nestLevel+1)
|
|
|
|
// Increase the next nesting level.
|
|
return context.WithValue(ctx, nestLevelKey{}, nestLevel+1)
|
|
}
|
|
|
|
// After logs to stdout the module and function name, prefixed with '<--' and
|
|
// indented based on the call nesting level.
|
|
func (l *loggingListener) After(ctx context.Context, err error, vals []uint64) {
|
|
// Note: We use the nest level directly even though it is the "next" nesting level.
|
|
// This works because our indent of zero nesting is one tab.
|
|
l.writeIndented(false, err, vals, ctx.Value(nestLevelKey{}).(int))
|
|
}
|
|
|
|
// writeIndented writes an indented message like this: "-->\t\t\t$indentLevel$funcName\n"
|
|
func (l *loggingListener) writeIndented(before bool, err error, vals []uint64, indentLevel int) {
|
|
var message strings.Builder
|
|
for i := 1; i < indentLevel; i++ {
|
|
message.WriteByte('\t')
|
|
}
|
|
if before {
|
|
if l.fnd.IsHostFunction() {
|
|
message.WriteString("==> ")
|
|
} else {
|
|
message.WriteString("--> ")
|
|
}
|
|
l.writeFuncEnter(&message, vals)
|
|
} else { // after
|
|
if l.fnd.IsHostFunction() {
|
|
message.WriteString("<== ")
|
|
} else {
|
|
message.WriteString("<-- ")
|
|
}
|
|
l.writeFuncExit(&message, err, vals)
|
|
}
|
|
message.WriteByte('\n')
|
|
|
|
_, _ = l.writer.Write([]byte(message.String()))
|
|
}
|
|
|
|
func (l *loggingListener) writeFuncEnter(message *strings.Builder, vals []uint64) {
|
|
valLen := len(vals)
|
|
message.WriteString(l.fnd.DebugName())
|
|
message.WriteByte('(')
|
|
switch valLen {
|
|
case 0:
|
|
default:
|
|
i := l.writeParam(message, 0, vals)
|
|
for i < valLen {
|
|
message.WriteByte(',')
|
|
i = l.writeParam(message, i, vals)
|
|
}
|
|
}
|
|
message.WriteByte(')')
|
|
}
|
|
|
|
func (l *loggingListener) writeFuncExit(message *strings.Builder, err error, vals []uint64) {
|
|
if err != nil {
|
|
message.WriteString("error: ")
|
|
message.WriteString(err.Error())
|
|
return
|
|
} else if l.isWasi {
|
|
message.WriteString(wasi_snapshot_preview1.ErrnoName(uint32(vals[0])))
|
|
return
|
|
}
|
|
valLen := len(vals)
|
|
message.WriteByte('(')
|
|
switch valLen {
|
|
case 0:
|
|
default:
|
|
i := l.writeResult(message, 0, vals)
|
|
for i < valLen {
|
|
message.WriteByte(',')
|
|
i = l.writeResult(message, i, vals)
|
|
}
|
|
}
|
|
message.WriteByte(')')
|
|
}
|
|
|
|
func (l *loggingListener) writeResult(message *strings.Builder, i int, vals []uint64) int {
|
|
return l.writeVal(message, l.fnd.ResultTypes()[i], i, vals)
|
|
}
|
|
|
|
func (l *loggingListener) writeParam(message *strings.Builder, i int, vals []uint64) int {
|
|
if len(l.fnd.ParamNames()) > 0 {
|
|
message.WriteString(l.fnd.ParamNames()[i])
|
|
message.WriteByte('=')
|
|
}
|
|
return l.writeVal(message, l.fnd.ParamTypes()[i], i, vals)
|
|
}
|
|
|
|
func (l *loggingListener) writeVal(message *strings.Builder, t api.ValueType, i int, vals []uint64) int {
|
|
v := vals[i]
|
|
i++
|
|
switch t {
|
|
case api.ValueTypeI32:
|
|
message.WriteString(strconv.FormatUint(uint64(uint32(v)), 10))
|
|
case api.ValueTypeI64:
|
|
message.WriteString(strconv.FormatUint(v, 10))
|
|
case api.ValueTypeF32:
|
|
message.WriteString(strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32))
|
|
case api.ValueTypeF64:
|
|
message.WriteString(strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64))
|
|
case 0x7b: // wasm.ValueTypeV128
|
|
message.WriteString(fmt.Sprintf("%016x%016x", v, vals[i])) // fixed-width hex
|
|
i++
|
|
case api.ValueTypeExternref, 0x70: // wasm.ValueTypeFuncref
|
|
message.WriteString(fmt.Sprintf("%016x", v)) // fixed-width hex
|
|
}
|
|
return i
|
|
}
|