package tracex import ( "context" "fmt" "runtime/debug" "go.opentelemetry.io/otel/attribute" otelcodes "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) type Options struct { OkStatusDescription string } var defaultOptions = Options{ OkStatusDescription: "success", } type Option func(*Options) func WithOkStatusDescription(desc string) Option { return func(o *Options) { o.OkStatusDescription = desc } } type Span[T any] struct { ctx context.Context span trace.Span } func Start[T any](ctx context.Context, tracer trace.Tracer, spanName string, opts ...trace.SpanStartOption) *Span[T] { ctx, span := tracer.Start(ctx, spanName, opts...) return &Span[T]{ ctx: ctx, span: span, } } func (s *Span[T]) Wrap(fn func(ctx context.Context) (T, error), opts ...Option) (T, error) { defer func() { if r := recover(); r != nil { s.span.RecordError( fmt.Errorf("panic: %v", r), trace.WithStackTrace(true), trace.WithAttributes( attribute.String("panic.stacktrace", fmt.Sprintf("%v", string(debug.Stack()))), ), ) s.span.SetStatus(otelcodes.Error, "panic") s.span.End() panic(r) } }() options := defaultOptions for _, opt := range opts { opt(&options) } result, err := fn(s.ctx) if err != nil { s.span.RecordError(err) s.span.SetStatus(otelcodes.Error, err.Error()) } else { s.span.SetStatus(otelcodes.Ok, options.OkStatusDescription) } s.span.End() return result, err } type SpanNoValue struct { ctx context.Context span trace.Span } func StartWithNoValue(ctx context.Context, tracer trace.Tracer, spanName string, opts ...trace.SpanStartOption) *SpanNoValue { ctx, span := tracer.Start(ctx, spanName, opts...) return &SpanNoValue{ ctx: ctx, span: span, } } func (s *SpanNoValue) Wrap(fn func(ctx context.Context) error, opts ...Option) error { span := &Span[any]{ ctx: s.ctx, span: s.span, } _, err := span.Wrap(func(ctx context.Context) (any, error) { return nil, fn(ctx) }, opts...) return err }