// Copyright 2022 E99p1ant. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package tracing import ( "fmt" "net/http" "github.com/flamego/flamego" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.12.0" oteltrace "go.opentelemetry.io/otel/trace" ) // instrumentationName is the name of this instrumentation package. const instrumentationName = "github.com/tamakyi/TamaBox/internal/tracing" // Middleware returns a flamego Handler to trace requests to the server. func Middleware(service string, opts ...Option) flamego.Handler { cfg := newConfig(opts) tracer := cfg.TracerProvider.Tracer( instrumentationName, oteltrace.WithInstrumentationVersion("1.0.0"), ) return func(res http.ResponseWriter, req *http.Request, c flamego.Context) { savedCtx := c.Request().Request.Context() defer func() { c.Request().Request = c.Request().Request.WithContext(savedCtx) }() request := c.Request().Request ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request().Header)) method := request.Method if method == "" { method = http.MethodGet } httpAttrs := []attribute.KeyValue{ semconv.HTTPMethodKey.String(method), semconv.HTTPTargetKey.String(request.URL.Path), } if service != "" { httpAttrs = append(httpAttrs, semconv.HTTPServerNameKey.String(service)) } if request.TLS != nil { httpAttrs = append(httpAttrs, semconv.HTTPSchemeHTTPS) } else { httpAttrs = append(httpAttrs, semconv.HTTPSchemeHTTP) } switch request.ProtoMajor { case 1: httpAttrs = append(httpAttrs, semconv.HTTPFlavorKey.String(fmt.Sprintf("1.%d", request.ProtoMinor))) case 2: httpAttrs = append(httpAttrs, semconv.HTTPFlavorHTTP20) } opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", request)...), oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(request)...), oteltrace.WithAttributes(httpAttrs...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } // span 名只取 URL.Path,不含 query string: // RequestURI 会把 ?t=<问题token>、找回密码 ?code= 等私密参数原样写入 trace。 spanName := request.URL.Path if spanName == "" { spanName = fmt.Sprintf("HTTP %s route not found", c.Request().Method) } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context c.Request().Request = c.Request().Request.WithContext(ctx) // serve the request to the next middleware c.Next() status := c.ResponseWriter().Status() attrs := semconv.HTTPAttributesFromHTTPStatusCode(status) spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCodeAndSpanKind(status, oteltrace.SpanKindServer) span.SetAttributes(attrs...) span.SetStatus(spanStatus, spanMessage) } }