Skip to content

[draft] Migrate to semconv for otelmux #6638 #6653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import (
"net/http"

"go.opentelemetry.io/otel/metric"

"go.opentelemetry.io/otel/propagation"
oteltrace "go.opentelemetry.io/otel/trace"
)

// config is used to configure the mux middleware.
type config struct {
TracerProvider oteltrace.TracerProvider
meterProvider metric.MeterProvider
Propagators propagation.TextMapPropagator
spanNameFormatter func(string, *http.Request) string
PublicEndpoint bool
Expand Down Expand Up @@ -97,3 +100,13 @@
c.Filters = append(c.Filters, f)
})
}

// WithMeterProvider specifies a meter provider to use for creating a meter.
// If none is specified, the global provider is used.
func WithMeterProvider(provider metric.MeterProvider) Option {
return optionFunc(func(cfg *config) {
if provider != nil {
cfg.meterProvider = provider
}

Check warning on line 110 in instrumentation/github.com/gorilla/mux/otelmux/config.go

View check run for this annotation

Codecov / codecov/patch

instrumentation/github.com/gorilla/mux/otelmux/config.go#L106-L110

Added lines #L106 - L110 were not covered by tests
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 h1:jBpDk4HAUsrnVO1FsfCfCOTEc/MkInJmvfCHYLFiT80=
Expand Down
5 changes: 3 additions & 2 deletions instrumentation/github.com/gorilla/mux/otelmux/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ module go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmu
go 1.22.0

require (
github.com/felixge/httpsnoop v1.0.4
github.com/gorilla/mux v1.8.1
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0
go.opentelemetry.io/otel v1.34.0
go.opentelemetry.io/otel/metric v1.34.0
go.opentelemetry.io/otel/trace v1.34.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
Expand Down
158 changes: 60 additions & 98 deletions instrumentation/github.com/gorilla/mux/otelmux/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"

import (
"fmt"
"net/http"
"sync"

"github.com/felixge/httpsnoop"
"github.com/gorilla/mux"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"

"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

"github.com/gorilla/mux"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
"go.opentelemetry.io/otel/trace"
)
Expand All @@ -34,7 +36,8 @@ func Middleware(service string, opts ...Option) mux.MiddlewareFunc {
if cfg.TracerProvider == nil {
cfg.TracerProvider = otel.GetTracerProvider()
}
tracer := cfg.TracerProvider.Tracer(
// TODO: check if this is necessary
_ = cfg.TracerProvider.Tracer(
ScopeName,
trace.WithInstrumentationVersion(Version()),
)
Expand All @@ -44,92 +47,40 @@ func Middleware(service string, opts ...Option) mux.MiddlewareFunc {
if cfg.spanNameFormatter == nil {
cfg.spanNameFormatter = defaultSpanNameFunc
}
if cfg.meterProvider == nil {
cfg.meterProvider = otel.GetMeterProvider()
}

return func(handler http.Handler) http.Handler {
return traceware{
return middleware{
service: service,
tracer: tracer,
propagators: cfg.Propagators,
handler: handler,
spanNameFormatter: cfg.spanNameFormatter,
publicEndpoint: cfg.PublicEndpoint,
tracerProvider: cfg.TracerProvider,
meterProvider: cfg.meterProvider,
propagators: cfg.Propagators,
publicEndpointFn: cfg.PublicEndpointFn,
publicEndpoint: cfg.PublicEndpoint,
filters: cfg.Filters,
}
}
}

type traceware struct {
service string
tracer trace.Tracer
propagators propagation.TextMapPropagator
type middleware struct {
handler http.Handler
service string
spanNameFormatter func(string, *http.Request) string
publicEndpoint bool
tracerProvider trace.TracerProvider
propagators propagation.TextMapPropagator
publicEndpointFn func(*http.Request) bool
publicEndpoint bool
filters []Filter
meterProvider metric.MeterProvider
}

type recordingResponseWriter struct {
writer http.ResponseWriter
written bool
status int
}

var rrwPool = &sync.Pool{
New: func() interface{} {
return &recordingResponseWriter{}
},
}

func getRRW(writer http.ResponseWriter) *recordingResponseWriter {
rrw := rrwPool.Get().(*recordingResponseWriter)
rrw.written = false
rrw.status = http.StatusOK
rrw.writer = httpsnoop.Wrap(writer, httpsnoop.Hooks{
Write: func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc {
return func(b []byte) (int, error) {
if !rrw.written {
rrw.written = true
}
return next(b)
}
},
WriteHeader: func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
return func(statusCode int) {
if !rrw.written {
rrw.written = true
rrw.status = statusCode
}
next(statusCode)
}
},
})
return rrw
}

func putRRW(rrw *recordingResponseWriter) {
rrw.writer = nil
rrwPool.Put(rrw)
}

// defaultSpanNameFunc just reuses the route name as the span name.
func defaultSpanNameFunc(routeName string, _ *http.Request) string { return routeName }

// ServeHTTP implements the http.Handler interface. It does the actual
// tracing of the request.
func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, f := range tw.filters {
if !f(r) {
// Simply pass through to the handler if a filter rejects the request
tw.handler.ServeHTTP(w, r)
return
}
}

ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
func (m middleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
routeStr := ""
route := mux.CurrentRoute(r)
route := mux.CurrentRoute(request)
if route != nil {
var err error
routeStr, err = route.GetPathTemplate()
Expand All @@ -141,34 +92,45 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}

opts := []trace.SpanStartOption{
trace.WithAttributes(semconvutil.HTTPServerRequest(tw.service, r)...),
trace.WithSpanKind(trace.SpanKindServer),
otelOpts := []otelhttp.Option{
otelhttp.WithTracerProvider(m.tracerProvider),
otelhttp.WithMeterProvider(m.meterProvider),
otelhttp.WithPropagators(m.propagators),
otelhttp.WithPublicEndpointFn(m.publicEndpointFn),
otelhttp.WithSpanNameFormatter(func(op string, r *http.Request) string {
return m.spanNameFormatter(routeStr, r)
}),
}

if tw.publicEndpoint || (tw.publicEndpointFn != nil && tw.publicEndpointFn(r.WithContext(ctx))) {
opts = append(opts, trace.WithNewRoot())
// Linking incoming span context if any for public endpoint.
if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s}))
}
if m.publicEndpoint {
otelOpts = append(otelOpts, otelhttp.WithPublicEndpoint())
}

if routeStr == "" {
routeStr = fmt.Sprintf("HTTP %s route not found", r.Method)
} else {
rAttr := semconv.HTTPRoute(routeStr)
opts = append(opts, trace.WithAttributes(rAttr))
for _, f := range m.filters {
otelOpts = append(otelOpts, otelhttp.WithFilter(otelhttp.Filter(f)))
}
traceOpts := []trace.SpanStartOption{
trace.WithAttributes(semconvutil.HTTPServerRequest(m.service, request)...),
trace.WithSpanKind(trace.SpanKindServer),
}
spanName := tw.spanNameFormatter(routeStr, r)
ctx, span := tw.tracer.Start(ctx, spanName, opts...)
defer span.End()
r2 := r.WithContext(ctx)
rrw := getRRW(w)
defer putRRW(rrw)
tw.handler.ServeHTTP(rrw.writer, r2)
if rrw.status > 0 {
span.SetAttributes(semconv.HTTPStatusCode(rrw.status))

if routeStr != "" {
rAttr := semconv.HTTPRoute(routeStr)
traceOpts = append(traceOpts, trace.WithAttributes(rAttr))
}
span.SetStatus(semconvutil.HTTPServerStatus(rrw.status))
otelOpts = append(otelOpts, otelhttp.WithSpanOptions(traceOpts...))

h := otelhttp.NewHandler(
m.handler,
m.service,
otelOpts...,
)
h.ServeHTTP(writer, request)
}

var _ http.Handler = middleware{}

// defaultSpanNameFunc just reuses the route name as the span name.
func defaultSpanNameFunc(routeName string, r *http.Request) string {
return routeName
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
Expand Down
Loading