| 678 | } |
| 679 | |
| 680 | func (s *Server) tracingMiddleware(next http.Handler) http.Handler { |
| 681 | tracer := s.tracerProvider.Tracer("llm-mock") |
| 682 | |
| 683 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { |
| 684 | // Wrap response writer with StatusWriter for tracing |
| 685 | sw := &tracing.StatusWriter{ResponseWriter: rw} |
| 686 | |
| 687 | // Extract trace context from headers |
| 688 | propagator := otel.GetTextMapPropagator() |
| 689 | hc := propagation.HeaderCarrier(r.Header) |
| 690 | ctx := propagator.Extract(r.Context(), hc) |
| 691 | |
| 692 | // Start span with initial name (will be updated after handler) |
| 693 | ctx, span := tracer.Start(ctx, fmt.Sprintf("%s %s", r.Method, r.RequestURI)) |
| 694 | defer span.End() |
| 695 | r = r.WithContext(ctx) |
| 696 | |
| 697 | // Inject trace context into response headers |
| 698 | if span.SpanContext().HasTraceID() && span.SpanContext().HasSpanID() { |
| 699 | rw.Header().Set("X-Trace-ID", span.SpanContext().TraceID().String()) |
| 700 | rw.Header().Set("X-Span-ID", span.SpanContext().SpanID().String()) |
| 701 | |
| 702 | hc := propagation.HeaderCarrier(rw.Header()) |
| 703 | propagator.Inject(ctx, hc) |
| 704 | } |
| 705 | |
| 706 | // Execute the handler |
| 707 | next.ServeHTTP(sw, r) |
| 708 | |
| 709 | // Update span with final route and response information |
| 710 | route := r.URL.Path |
| 711 | span.SetName(fmt.Sprintf("%s %s", r.Method, route)) |
| 712 | span.SetAttributes(netconv.Transport("tcp")) |
| 713 | span.SetAttributes(httpconv.ServerRequest("llm-mock", r)...) |
| 714 | span.SetAttributes(semconv.HTTPRouteKey.String(route)) |
| 715 | |
| 716 | status := sw.Status |
| 717 | if status == 0 { |
| 718 | status = http.StatusOK |
| 719 | } |
| 720 | span.SetAttributes(semconv.HTTPStatusCodeKey.Int(status)) |
| 721 | span.SetStatus(httpconv.ServerStatus(status)) |
| 722 | }) |
| 723 | } |