This changes the listener signature to accept context and calling module, so that all possible parameters and results can be logged. This also changes the logging listener to make parameters visible when logging results. This infrastructure supports some helpful use cases, such as logging WASI result parameters, such as the prestat path, which is only knowable after the function has been called. The context parameter supposed reading results of gojs functions, which are stored host-side in a go context object. Future pull requests will complete this as well backfill unit tests. This is raised independently mainly to keep the PR size down of the upcoming filesystem logger. Signed-off-by: Adrian Cole <adrian@tetrate.io>
66 lines
2.8 KiB
Go
66 lines
2.8 KiB
Go
package experimental
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
)
|
|
|
|
// FunctionListenerFactoryKey is a context.Context Value key. Its associated value should be a FunctionListenerFactory.
|
|
//
|
|
// See https://github.com/tetratelabs/wazero/issues/451
|
|
type FunctionListenerFactoryKey struct{}
|
|
|
|
// FunctionListenerFactory returns FunctionListeners to be notified when a
|
|
// function is called.
|
|
type FunctionListenerFactory interface {
|
|
// NewListener returns a FunctionListener for a defined function. If nil is
|
|
// returned, no listener will be notified.
|
|
NewListener(api.FunctionDefinition) FunctionListener
|
|
// ^^ A single instance can be returned to avoid instantiating a listener
|
|
// per function, especially as they may be thousands of functions. Shared
|
|
// listeners use their FunctionDefinition parameter to clarify.
|
|
}
|
|
|
|
// FunctionListener can be registered for any function via
|
|
// FunctionListenerFactory to be notified when the function is called.
|
|
type FunctionListener interface {
|
|
// Before is invoked before a function is called. The returned context will
|
|
// be used as the context of this function call.
|
|
//
|
|
// # Params
|
|
//
|
|
// - ctx: the context of the caller function which must be the same
|
|
// instance or parent of the result.
|
|
// - mod: the calling module.
|
|
// - def: the function definition.
|
|
// - paramValues: api.ValueType encoded parameters.
|
|
//
|
|
// Note: api.Memory is meant for inspection, not modification.
|
|
Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, paramValues []uint64) context.Context
|
|
|
|
// After is invoked after a function is called.
|
|
//
|
|
// # Params
|
|
//
|
|
// - ctx: the context returned by Before.
|
|
// - mod: the calling module.
|
|
// - def: the function definition.
|
|
// - err: nil if the function didn't err
|
|
// - resultValues: api.ValueType encoded results.
|
|
//
|
|
// Note: api.Memory is meant for inspection, not modification.
|
|
After(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error, resultValues []uint64)
|
|
}
|
|
|
|
// TODO: We need to add tests to enginetest to ensure contexts nest. A good test can use a combination of call and call
|
|
// indirect in terms of depth and breadth. The test could show a tree 3 calls deep where the there are a couple calls at
|
|
// each depth under the root. The main thing this can help prevent is accidentally swapping the context internally.
|
|
|
|
// TODO: Errors aren't handled, and the After hook should accept one along with the result values.
|
|
|
|
// TODO: The context parameter of the After hook is not the same as the Before hook. This means interceptor patterns
|
|
// are awkward. e.g. something like timing is difficult as it requires propagating a stack. Otherwise, nested calls will
|
|
// overwrite each other's "since" time. Propagating a stack is further awkward as the After hook needs to know the
|
|
// position to read from which might be subtle.
|