unitel/tracing.go

95 lines
2.9 KiB
Go

package unitel
import (
"context"
"net/http"
"github.com/getsentry/sentry-go"
"github.com/rs/zerolog/log"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
type contextKey struct {
Key string
}
var (
tracerContextKey = contextKey{"tracer"}
)
type ConfigureSpanStartFunc = func(context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption)
func (t *Telemetry) StartSpan(ctx context.Context, operation, name string, cfgs ...ConfigureSpanStartFunc) *Span {
otelStartOpts := make([]trace.SpanStartOption, 0)
sentryStartOpts := []sentry.SpanOption{sentry.WithTransactionName(name), sentry.WithDescription(name)}
for _, opt := range cfgs {
var otel []trace.SpanStartOption
var sentry []sentry.SpanOption
ctx, otel, sentry = opt(ctx)
otelStartOpts = append(otelStartOpts, otel...)
sentryStartOpts = append(sentryStartOpts, sentry...)
}
var otelTracer trace.Tracer
if tracer, ok := ctx.Value(tracerContextKey).(trace.Tracer); ok && tracer != nil {
otelTracer = tracer
} else {
otelTracer = t.Tracer
}
ctx, otelSpan := otelTracer.Start(ctx, name, otelStartOpts...)
sentrySpan := sentry.StartSpan(ctx, operation, sentryStartOpts...)
return &Span{
otel: otelSpan,
sentry: sentrySpan,
}
}
func WithOtelOptions(opts ...trace.SpanStartOption) ConfigureSpanStartFunc {
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
return ctx, opts, []sentry.SpanOption{}
}
}
func (t *Telemetry) InjectIntoHeaders(ctx context.Context, headers http.Header) {
t.propagator.Inject(ctx, propagation.HeaderCarrier(headers))
if sentrySpan := sentry.SpanFromContext(ctx); sentrySpan == nil {
// this should never happen...
log.Trace().Msgf("failed to inject Sentry span ID, Sentry span could not be extracted from the context.Context")
} else {
headers.Set("sentry-trace", sentrySpan.ToSentryTrace())
headers.Set("sentry-baggage", sentrySpan.ToBaggage())
}
}
func (t *Telemetry) ContinueFromRequest(r *http.Request) ConfigureSpanStartFunc {
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
ctx = t.propagator.Extract(ctx, propagation.HeaderCarrier(r.Header))
return ctx, []trace.SpanStartOption{}, []sentry.SpanOption{sentry.ContinueFromRequest(r), sentry.WithTransactionSource(sentry.SourceURL)}
}
}
func WithOtelTracer(tracer trace.Tracer) ConfigureSpanStartFunc {
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
return context.WithValue(ctx, tracerContextKey, tracer), []trace.SpanStartOption{}, []sentry.SpanOption{}
}
}
type TracedFunc[T any] func(context.Context, string, ...any) T
func RunTraced[T any](t *Telemetry, op string, fn func(context.Context, ...any) T) TracedFunc[T] {
return func(ctx context.Context, name string, args ...any) T {
span := t.StartSpan(ctx, op, name)
defer span.End()
ctx = span.Context()
return fn(ctx, args...)
}
}