Files
wazero/internal/logging/logging.go
Edoardo Vacchi 97d0d70b73
Some checks failed
Release CLI / Pre-release build (push) Has been cancelled
Release CLI / Pre-release test (macos-12) (push) Has been cancelled
Release CLI / Pre-release test (ubuntu-22.04) (push) Has been cancelled
Release CLI / Pre-release test (windows-2022) (push) Has been cancelled
Release CLI / Release (push) Has been cancelled
wasi: add support for sockets (#1493)
Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com>
Co-authored-by: Achille <achille.roussel@gmail.com>
Co-authored-by: Adrian Cole <adrian@tetrate.io>
2023-06-02 20:45:42 +08:00

294 lines
8.3 KiB
Go

// Package logging includes utilities used to log function calls. This is in
// an independent package to avoid dependency cycles.
package logging
import (
"context"
"encoding/hex"
"fmt"
"io"
"strconv"
"strings"
"github.com/tetratelabs/wazero/api"
)
// ValueType is an extended form of api.ValueType, used to control logging in
// cases such as bitmasks or strings.
type ValueType = api.ValueType
const (
ValueTypeI32 = api.ValueTypeI32
ValueTypeI64 = api.ValueTypeI64
ValueTypeF32 = api.ValueTypeF32
ValueTypeF64 = api.ValueTypeF64
ValueTypeV128 ValueType = 0x7b // same as wasm.ValueTypeV128
ValueTypeFuncref ValueType = 0x70 // same as wasm.ValueTypeFuncref
ValueTypeExternref = api.ValueTypeExternref
// ValueTypeMemI32 is a non-standard type which writes ValueTypeI32 from the memory offset.
ValueTypeMemI32 = 0xfd
// ValueTypeMemH64 is a non-standard type which writes 64-bits fixed-width hex from the memory offset.
ValueTypeMemH64 = 0xfe
// ValueTypeString is a non-standard type describing an offset/len pair of a string.
ValueTypeString = 0xff
)
type LogScopes uint64
const (
LogScopeNone = LogScopes(0)
LogScopeClock LogScopes = 1 << iota
LogScopeProc
LogScopeFilesystem
LogScopeMemory
LogScopePoll
LogScopeRandom
LogScopeSock
LogScopeAll = LogScopes(0xffffffffffffffff)
)
func scopeName(s LogScopes) string {
switch s {
case LogScopeClock:
return "clock"
case LogScopeProc:
return "proc"
case LogScopeFilesystem:
return "filesystem"
case LogScopeMemory:
return "memory"
case LogScopePoll:
return "poll"
case LogScopeRandom:
return "random"
case LogScopeSock:
return "sock"
default:
return fmt.Sprintf("<unknown=%d>", s)
}
}
// IsEnabled returns true if the scope (or group of scopes) is enabled.
func (f LogScopes) IsEnabled(scope LogScopes) bool {
return f&scope != 0
}
// String implements fmt.Stringer by returning each enabled log scope.
func (f LogScopes) String() string {
if f == LogScopeAll {
return "all"
}
var builder strings.Builder
for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance
target := LogScopes(1 << i)
if f.IsEnabled(target) {
if name := scopeName(target); name != "" {
if builder.Len() > 0 {
builder.WriteByte('|')
}
builder.WriteString(name)
}
}
}
return builder.String()
}
// LoggerKey is a context.Context Value key with a FunctionLogger value.
type LoggerKey struct{}
type ParamLogger func(ctx context.Context, mod api.Module, w Writer, params []uint64)
type ParamSampler func(ctx context.Context, mod api.Module, params []uint64) bool
type ResultLogger func(ctx context.Context, mod api.Module, w Writer, params, results []uint64)
type Writer interface {
io.Writer
io.StringWriter
io.ByteWriter
}
// ValWriter formats an indexed value. For example, if `vals[i]` is a
// ValueTypeI32, this would format it by default as signed. If a
// ValueTypeString, it would read `vals[i+1]` and write the string from memory.
type ValWriter func(ctx context.Context, mod api.Module, w Writer, i uint32, vals []uint64)
func Config(fnd api.FunctionDefinition) (paramLoggers []ParamLogger, resultLoggers []ResultLogger) {
types := fnd.ParamTypes()
names := fnd.ParamNames()
if paramLen := uint32(len(types)); paramLen > 0 {
paramLoggers = make([]ParamLogger, paramLen)
hasParamNames := len(names) > 0
for i, t := range types {
if hasParamNames {
paramLoggers[i] = NewParamLogger(uint32(i), names[i], t)
} else {
paramLoggers[i] = (&paramLogger{idx: uint32(i), valWriter: ValWriterForType(t)}).Log
}
}
}
if resultLen := uint32(len(fnd.ResultTypes())); resultLen > 0 {
resultLoggers = make([]ResultLogger, resultLen)
hasResultNames := len(fnd.ResultNames()) > 0
for i, t := range fnd.ResultTypes() {
if hasResultNames {
resultLoggers[i] = NewResultLogger(uint32(i), fnd.ResultNames()[i], t)
} else {
resultLoggers[i] = (&resultLogger{idx: uint32(i), valWriter: ValWriterForType(t)}).Log
}
}
}
return
}
type paramLogger struct {
idx uint32
valWriter ValWriter
}
func (n *paramLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) {
n.valWriter(ctx, mod, w, n.idx, params)
}
func NewParamLogger(idx uint32, name string, t ValueType) ParamLogger {
return (&namedParamLogger{idx: idx, name: name, valWriter: ValWriterForType(t)}).Log
}
type namedParamLogger struct {
idx uint32
name string
valWriter ValWriter
}
func (n *namedParamLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) {
w.WriteString(n.name) //nolint
w.WriteByte('=') //nolint
n.valWriter(ctx, mod, w, n.idx, params)
}
type resultLogger struct {
idx uint32
valWriter ValWriter
}
func (n *resultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) {
n.valWriter(ctx, mod, w, n.idx, results)
}
func NewResultLogger(idx uint32, name string, t ValueType) ResultLogger {
return (&namedResultLogger{idx, name, ValWriterForType(t)}).Log
}
type namedResultLogger struct {
idx uint32
name string
valWriter ValWriter
}
func (n *namedResultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) {
w.WriteString(n.name) //nolint
w.WriteByte('=') //nolint
n.valWriter(ctx, mod, w, n.idx, results)
}
func ValWriterForType(vt ValueType) ValWriter {
switch vt {
case ValueTypeI32:
return writeI32
case ValueTypeI64:
return writeI64
case ValueTypeF32:
return writeF32
case ValueTypeF64:
return writeF64
case ValueTypeV128:
return writeV128
case ValueTypeExternref, ValueTypeFuncref:
return writeRef
case ValueTypeMemI32:
return writeMemI32
case ValueTypeMemH64:
return writeMemH64
case ValueTypeString:
return writeString
default:
panic(fmt.Errorf("BUG: unsupported type %d", vt))
}
}
func writeI32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint
}
func writeI64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
w.WriteString(strconv.FormatInt(int64(v), 10)) //nolint
}
func writeF32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
s := strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32)
w.WriteString(s) //nolint
}
func writeF64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
s := strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64)
w.WriteString(s) //nolint
}
// logV128 logs in fixed-width hex
func writeV128(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v1, v2 := vals[i], vals[i+1]
w.WriteString(fmt.Sprintf("%016x%016x", v1, v2)) //nolint
}
// logRef logs in fixed-width hex
func writeRef(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
v := vals[i]
w.WriteString(fmt.Sprintf("%016x", v)) //nolint
}
func writeMemI32(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
offset := uint32(vals[i])
byteCount := uint32(4)
if v, ok := mod.Memory().ReadUint32Le(offset); ok {
w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint
} else { // log the positions that were out of memory
WriteOOM(w, offset, byteCount)
}
}
func writeMemH64(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
offset := uint32(vals[i])
byteCount := uint32(8)
if s, ok := mod.Memory().Read(offset, byteCount); ok {
hex.NewEncoder(w).Write(s) //nolint
} else { // log the positions that were out of memory
WriteOOM(w, offset, byteCount)
}
}
func writeString(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
offset, byteCount := uint32(vals[i]), uint32(vals[i+1])
WriteStringOrOOM(mod.Memory(), w, offset, byteCount)
}
func WriteStringOrOOM(mem api.Memory, w Writer, offset, byteCount uint32) {
if s, ok := mem.Read(offset, byteCount); ok {
w.Write(s) //nolint
} else { // log the positions that were out of memory
WriteOOM(w, offset, byteCount)
}
}
func WriteOOM(w Writer, offset uint32, byteCount uint32) {
w.WriteString("OOM(") //nolint
w.WriteString(strconv.Itoa(int(offset))) //nolint
w.WriteByte(',') //nolint
w.WriteString(strconv.Itoa(int(byteCount))) //nolint
w.WriteByte(')') //nolint
}