You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crate/bunrouterotel/middleware.go

98 lines
2.7 KiB
Go

10 months ago
package bunrouterotel
import (
"net/http"
"strings"
10 months ago
"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"
10 months ago
)
var tracer = otel.Tracer(tracerName, trace.WithInstrumentationVersion("semver:"+version))
10 months ago
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
}
10 months ago
// 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"
}
10 months ago
}
spanName = req.Method + " " + route
tracerStartOpts := []trace.SpanStartOption{
10 months ago
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...)
10 months ago
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))
}
10 months ago
defer span.End()
return next(w, req)
}
}
}