github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/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/tetratelabs/wazero/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  type ParamLogger func(ctx context.Context, mod api.Module, w Writer, params []uint64)
    98  
    99  type ParamSampler func(ctx context.Context, mod api.Module, params []uint64) bool
   100  
   101  type ResultLogger func(ctx context.Context, mod api.Module, w Writer, params, results []uint64)
   102  
   103  type Writer interface {
   104  	io.Writer
   105  	io.StringWriter
   106  	io.ByteWriter
   107  }
   108  
   109  // ValWriter formats an indexed value. For example, if `vals[i]` is a
   110  // ValueTypeI32, this would format it by default as signed. If a
   111  // ValueTypeString, it would read `vals[i+1]` and write the string from memory.
   112  type ValWriter func(ctx context.Context, mod api.Module, w Writer, i uint32, vals []uint64)
   113  
   114  func Config(fnd api.FunctionDefinition) (paramLoggers []ParamLogger, resultLoggers []ResultLogger) {
   115  	types := fnd.ParamTypes()
   116  	names := fnd.ParamNames()
   117  	if paramLen := uint32(len(types)); paramLen > 0 {
   118  		paramLoggers = make([]ParamLogger, paramLen)
   119  		hasParamNames := len(names) > 0
   120  		var offset int64
   121  		for i, t := range types {
   122  			if hasParamNames {
   123  				paramLoggers[i] = NewParamLogger(uint32(offset), names[i], t)
   124  			} else {
   125  				paramLoggers[i] = (&paramLogger{offsetInStack: uint32(offset), valWriter: ValWriterForType(t)}).Log
   126  			}
   127  			offset++
   128  			if t == ValueTypeV128 {
   129  				offset++
   130  			}
   131  		}
   132  	}
   133  	if resultLen := uint32(len(fnd.ResultTypes())); resultLen > 0 {
   134  		resultLoggers = make([]ResultLogger, resultLen)
   135  		hasResultNames := len(fnd.ResultNames()) > 0
   136  		var offset int64
   137  		for i, t := range fnd.ResultTypes() {
   138  			if hasResultNames {
   139  				resultLoggers[i] = NewResultLogger(uint32(offset), fnd.ResultNames()[i], t)
   140  			} else {
   141  				resultLoggers[i] = (&resultLogger{offsetInStack: uint32(offset), valWriter: ValWriterForType(t)}).Log
   142  			}
   143  			offset++
   144  			if t == ValueTypeV128 {
   145  				offset++
   146  			}
   147  		}
   148  	}
   149  	return
   150  }
   151  
   152  type paramLogger struct {
   153  	offsetInStack uint32
   154  	valWriter     ValWriter
   155  }
   156  
   157  func (n *paramLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) {
   158  	n.valWriter(ctx, mod, w, n.offsetInStack, params)
   159  }
   160  
   161  func NewParamLogger(offsetInStack uint32, name string, t ValueType) ParamLogger {
   162  	return (&namedParamLogger{offsetInStack: offsetInStack, name: name, valWriter: ValWriterForType(t)}).Log
   163  }
   164  
   165  type namedParamLogger struct {
   166  	offsetInStack uint32
   167  	name          string
   168  	valWriter     ValWriter
   169  }
   170  
   171  func (n *namedParamLogger) Log(ctx context.Context, mod api.Module, w Writer, params []uint64) {
   172  	w.WriteString(n.name) //nolint
   173  	w.WriteByte('=')      //nolint
   174  	n.valWriter(ctx, mod, w, n.offsetInStack, params)
   175  }
   176  
   177  type resultLogger struct {
   178  	offsetInStack uint32
   179  	valWriter     ValWriter
   180  }
   181  
   182  func (n *resultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) {
   183  	n.valWriter(ctx, mod, w, n.offsetInStack, results)
   184  }
   185  
   186  func NewResultLogger(idx uint32, name string, t ValueType) ResultLogger {
   187  	return (&namedResultLogger{idx, name, ValWriterForType(t)}).Log
   188  }
   189  
   190  type namedResultLogger struct {
   191  	offsetInStack uint32
   192  	name          string
   193  	valWriter     ValWriter
   194  }
   195  
   196  func (n *namedResultLogger) Log(ctx context.Context, mod api.Module, w Writer, _, results []uint64) {
   197  	w.WriteString(n.name) //nolint
   198  	w.WriteByte('=')      //nolint
   199  	n.valWriter(ctx, mod, w, n.offsetInStack, results)
   200  }
   201  
   202  func ValWriterForType(vt ValueType) ValWriter {
   203  	switch vt {
   204  	case ValueTypeI32:
   205  		return writeI32
   206  	case ValueTypeI64:
   207  		return writeI64
   208  	case ValueTypeF32:
   209  		return writeF32
   210  	case ValueTypeF64:
   211  		return writeF64
   212  	case ValueTypeV128:
   213  		return writeV128
   214  	case ValueTypeExternref, ValueTypeFuncref:
   215  		return writeRef
   216  	case ValueTypeMemI32:
   217  		return writeMemI32
   218  	case ValueTypeMemH64:
   219  		return writeMemH64
   220  	case ValueTypeString:
   221  		return writeString
   222  	default:
   223  		panic(fmt.Errorf("BUG: unsupported type %d", vt))
   224  	}
   225  }
   226  
   227  func writeI32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
   228  	v := vals[i]
   229  	w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint
   230  }
   231  
   232  func writeI64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
   233  	v := vals[i]
   234  	w.WriteString(strconv.FormatInt(int64(v), 10)) //nolint
   235  }
   236  
   237  func writeF32(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
   238  	v := vals[i]
   239  	s := strconv.FormatFloat(float64(api.DecodeF32(v)), 'g', -1, 32)
   240  	w.WriteString(s) //nolint
   241  }
   242  
   243  func writeF64(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
   244  	v := vals[i]
   245  	s := strconv.FormatFloat(api.DecodeF64(v), 'g', -1, 64)
   246  	w.WriteString(s) //nolint
   247  }
   248  
   249  // logV128 logs in fixed-width hex
   250  func writeV128(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
   251  	v1, v2 := vals[i], vals[i+1]
   252  	w.WriteString(fmt.Sprintf("%016x%016x", v1, v2)) //nolint
   253  }
   254  
   255  // logRef logs in fixed-width hex
   256  func writeRef(_ context.Context, _ api.Module, w Writer, i uint32, vals []uint64) {
   257  	v := vals[i]
   258  	w.WriteString(fmt.Sprintf("%016x", v)) //nolint
   259  }
   260  
   261  func writeMemI32(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
   262  	offset := uint32(vals[i])
   263  	byteCount := uint32(4)
   264  	if v, ok := mod.Memory().ReadUint32Le(offset); ok {
   265  		w.WriteString(strconv.FormatInt(int64(int32(v)), 10)) //nolint
   266  	} else { // log the positions that were out of memory
   267  		WriteOOM(w, offset, byteCount)
   268  	}
   269  }
   270  
   271  func writeMemH64(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
   272  	offset := uint32(vals[i])
   273  	byteCount := uint32(8)
   274  	if s, ok := mod.Memory().Read(offset, byteCount); ok {
   275  		hex.NewEncoder(w).Write(s) //nolint
   276  	} else { // log the positions that were out of memory
   277  		WriteOOM(w, offset, byteCount)
   278  	}
   279  }
   280  
   281  func writeString(_ context.Context, mod api.Module, w Writer, i uint32, vals []uint64) {
   282  	offset, byteCount := uint32(vals[i]), uint32(vals[i+1])
   283  	WriteStringOrOOM(mod.Memory(), w, offset, byteCount)
   284  }
   285  
   286  func WriteStringOrOOM(mem api.Memory, w Writer, offset, byteCount uint32) {
   287  	if s, ok := mem.Read(offset, byteCount); ok {
   288  		w.Write(s) //nolint
   289  	} else { // log the positions that were out of memory
   290  		WriteOOM(w, offset, byteCount)
   291  	}
   292  }
   293  
   294  func WriteOOM(w Writer, offset uint32, byteCount uint32) {
   295  	w.WriteString("OOM(")                       //nolint
   296  	w.WriteString(strconv.Itoa(int(offset)))    //nolint
   297  	w.WriteByte(',')                            //nolint
   298  	w.WriteString(strconv.Itoa(int(byteCount))) //nolint
   299  	w.WriteByte(')')                            //nolint
   300  }