package bunrouterotel import ( "net/http" "strings" "github.com/uptrace/bunrouter" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.12.0" "go.opentelemetry.io/otel/trace" ) const ( tracerName = "git.blauwelle.com/go/crate/bunrouterotel" version = "0.5.0" ) var tracer = otel.Tracer(tracerName, trace.WithInstrumentationVersion("semver:"+version)) type config struct { propagators propagation.TextMapPropagator } type Option interface { apply(cfg *config) } type optionFunc func(cfg *config) func (o optionFunc) apply(cfg *config) { o(cfg) } func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { cfg.propagators = propagators }) } func getForwardedFor(r *http.Request) []string { h := r.Header.Get("X-Forwarded-For") a := strings.Split(h, ",") for i, s := range a { a[i] = strings.Trim(s, " \t") } return a } // Middleware create a span, which record the request, // HTTP status code is NOT recorded. func Middleware(serverName string, opts ...Option) bunrouter.MiddlewareFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.propagators == nil { cfg.propagators = otel.GetTextMapPropagator() } return func(next bunrouter.HandlerFunc) bunrouter.HandlerFunc { return func(w http.ResponseWriter, req bunrouter.Request) error { ctx := cfg.propagators.Extract(req.Context(), propagation.HeaderCarrier(req.Header)) var spanName string params := req.Params() route := params.Route() if route == "" { if req.Request != nil && req.Request.URL != nil && req.Request.URL.Path != "" { route = req.Request.URL.Path } else { route = "route not found" } } spanName = req.Method + " " + route tracerStartOpts := []trace.SpanStartOption{ trace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", req.Request)...), trace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(req.Request)...), trace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(serverName, route, req.Request)...), trace.WithSpanKind(trace.SpanKindServer), } ctx, span := tracer.Start(ctx, spanName, tracerStartOpts...) req.Request = req.Request.WithContext(ctx) paramSlice := params.Slice() attrs := make([]attribute.KeyValue, 0, len(paramSlice)) for _, param := range paramSlice { attrs = append(attrs, attribute.String("http.route.param."+param.Key, param.Value)) } span.SetAttributes(attrs...) forwardedFor := getForwardedFor(req.Request) if forwardedFor != nil { span.SetAttributes(attribute.StringSlice("http.forward_route", forwardedFor)) } defer span.End() return next(w, req) } } }