package router import ( "context" "net/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // tracer is looked up per use rather than kept, so a provider installed after // the router was built — as tests do — is the one spans go to. func tracer() trace.Tracer { return otel.Tracer("gitea.mynodes.xyz/ilker/llmgw/internal/router") } // startInference opens the span for one inference request, continuing the // caller's trace when it sent a traceparent. func startInference(req *http.Request, path, requestID string) (context.Context, trace.Span) { ctx := otel.GetTextMapPropagator().Extract(req.Context(), propagation.HeaderCarrier(req.Header)) return tracer().Start(ctx, "inference "+path, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes( attribute.String("llmgw.request_id", requestID), attribute.String("http.route", path), )) } // endSpan closes a span, marking it failed when err is set. func endSpan(span trace.Span, err error) { if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) } span.End() } // withTraceparent is h with the current span's context in it, for the engine. // A copy: the request's header is sent on every attempt, each its own span. func withTraceparent(ctx context.Context, h http.Header) http.Header { if !trace.SpanContextFromContext(ctx).IsValid() { return h } out := h.Clone() if out == nil { out = http.Header{} } otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(out)) return out }