|
|
|
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.2.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 == "" {
|
|
|
|
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...)
|
|
|
|
defer span.End()
|
|
|
|
return next(w, req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|