diff --git a/ctx.go b/ctx.go index d0fad0b..7d15965 100644 --- a/ctx.go +++ b/ctx.go @@ -110,6 +110,14 @@ func newSpan(ctx context.Context, f *Func, args []interface{}, trace *Trace, sctx = observer.Start(sctx, s) } + var finishers []SpanPluginFinisher + if plugins := s.f.scope.r.getSpanPlugins(); plugins != nil { + finishers = make([]SpanPluginFinisher, len(plugins)) + for i, plugin := range plugins { + finishers[i] = plugin.Start(s) + } + } + return sctx, func(errptr *error) { rec := recover() panicked := rec != nil @@ -151,6 +159,10 @@ func newSpan(ctx context.Context, f *Func, args []interface{}, trace *Trace, observer.Finish(sctx, s, err, panicked, finish) } + for _, finisher := range finishers { + finisher.Finish(err, panicked, finish) + } + if panicked { panic(rec) } diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..42f2604 --- /dev/null +++ b/plugin.go @@ -0,0 +1,51 @@ +// Copyright (C) 2026 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monkit + +import "time" + +type SpanPluginFinisher interface { + Finish(err error, panicked bool, done time.Time) +} + +type SpanPlugin interface { + Start(span *Span) SpanPluginFinisher +} + +func (r *Registry) AddSpanPlugin(h SpanPlugin) { + for { + pluginsPtr := r.plugins.Load() + if pluginsPtr == nil { + r.plugins.CompareAndSwap(nil, new([]SpanPlugin)) + continue + } + plugins := *pluginsPtr + + nextPlugins := make([]SpanPlugin, len(plugins)+1) + copy(nextPlugins, plugins) + nextPlugins[len(plugins)] = h + + if r.plugins.CompareAndSwap(pluginsPtr, &nextPlugins) { + return + } + } +} + +func (r *Registry) getSpanPlugins() []SpanPlugin { + if pluginsPtr := r.plugins.Load(); pluginsPtr != nil { + return *pluginsPtr + } + return nil +} diff --git a/registry.go b/registry.go index 01de124..f8be231 100644 --- a/registry.go +++ b/registry.go @@ -17,6 +17,7 @@ package monkit import ( "sort" "sync" + "sync/atomic" ) type traceWatcherRef struct { @@ -39,6 +40,8 @@ type registryInternal struct { orphanMtx sync.Mutex orphans map[*Span]struct{} + + plugins atomic.Pointer[[]SpanPlugin] } // Registry encapsulates all of the top-level state for a monitoring system. diff --git a/span.go b/span.go index 1fcb881..a171006 100644 --- a/span.go +++ b/span.go @@ -102,6 +102,9 @@ func (s *Span) Children(cb func(s *Span)) { } } +// RawArgs returns the arguments given to the Task that created this Span. +func (s *Span) RawArgs() []interface{} { return s.args } + // Args returns the list of strings associated with the args given to the // Task that created this Span. func (s *Span) Args() (rv []string) {