unitel/tracing.go

137 lines
4.2 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
}
const (
SentryTraceHeader = "sentry-trace"
SentryBaggageHeader = "sentry-baggage"
)
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(SentryTraceHeader, sentrySpan.ToSentryTrace())
headers.Set(SentryBaggageHeader, sentrySpan.ToBaggage())
}
}
func (t *Telemetry) InjectIntoMap(ctx context.Context, m map[string]string) {
t.propagator.Inject(ctx, propagation.MapCarrier(m))
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 {
m[SentryTraceHeader] = sentrySpan.ToSentryTrace()
m[SentryBaggageHeader] = sentrySpan.ToBaggage()
}
}
func (t *Telemetry) ContinueFromHeaders(h http.Header) ConfigureSpanStartFunc {
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
ctx = t.propagator.Extract(ctx, propagation.HeaderCarrier(h))
return ctx,
[]trace.SpanStartOption{},
[]sentry.SpanOption{
sentry.ContinueFromHeaders(h.Get(sentry.SentryTraceHeader), h.Get(sentry.SentryBaggageHeader)),
sentry.WithTransactionSource(sentry.SourceURL),
}
}
}
func (t *Telemetry) ContinueFromMap(m map[string]string) ConfigureSpanStartFunc {
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
ctx = t.propagator.Extract(ctx, propagation.MapCarrier(m))
sentryTrace := m[sentry.SentryTraceHeader]
sentryBaggage := m[sentry.SentryBaggageHeader]
return ctx,
[]trace.SpanStartOption{},
[]sentry.SpanOption{
sentry.ContinueFromHeaders(sentryTrace, sentryBaggage),
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) TracedFuncResult[T]
type TracedFuncResult[T any] struct {
Result T
Err error
}
func RunTraced[T any](t *Telemetry, op string, fn func(context.Context, ...any) TracedFuncResult[T]) TracedFunc[T] {
return func(ctx context.Context, name string, args ...any) TracedFuncResult[T] {
span := t.StartSpan(ctx, op, name)
defer span.End()
ctx = span.Context()
return fn(ctx, args...)
}
}