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] = (&paramLogger{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  }