package bunrouterotel import ( "net/http" "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.1.0" ) 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 }) } // 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() } tracer := otel.Tracer(tracerName, trace.WithInstrumentationVersion("semver:"+version)) 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 != "" { spanName = req.Method + " " + route } else { spanName = "HTTP " + req.Method + " route not found" } tarcerStartOpts := []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, tarcerStartOpts...) 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...) defer span.End() return next(w, req) } } }