1
0
Fork 0

Introduce trace verbosity config and produce less spans by default

This commit is contained in:
Romain 2025-07-18 15:32:05 +02:00 committed by GitHub
parent 77ef7fe490
commit 8c23eb6833
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 1005 additions and 524 deletions

View file

@ -21,6 +21,7 @@ import (
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/contrib/bridges/otellogrus"
@ -69,11 +70,16 @@ type Handler struct {
wg sync.WaitGroup
}
// WrapHandler Wraps access log handler into an Alice Constructor.
func WrapHandler(handler *Handler) alice.Constructor {
// AliceConstructor returns an alice.Constructor that wraps the Handler (conditionally) in a middleware chain.
func (h *Handler) AliceConstructor() alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
handler.ServeHTTP(rw, req, next)
if h == nil {
next.ServeHTTP(rw, req)
return
}
h.ServeHTTP(rw, req, next)
}), nil
}
}
@ -196,6 +202,12 @@ func GetLogData(req *http.Request) *LogData {
}
func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.Handler) {
if !observability.AccessLogsEnabled(req.Context()) {
next.ServeHTTP(rw, req)
return
}
now := time.Now().UTC()
core := CoreLogData{

View file

@ -7,6 +7,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCommonLogFormatter_Format(t *testing.T) {
@ -82,8 +83,9 @@ func TestCommonLogFormatter_Format(t *testing.T) {
},
}
// Set timezone to Etc/GMT+9 to have a constant behavior
t.Setenv("TZ", "Etc/GMT+9")
var err error
time.Local, err = time.LoadLocation("Etc/GMT+9")
require.NoError(t, err)
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {

View file

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/otel/attribute"
@ -105,7 +106,15 @@ func TestOTelAccessLog(t *testing.T) {
chain := alice.New()
chain = chain.Append(capture.Wrap)
chain = chain.Append(WrapHandler(logHandler))
// Injection of the observability variables in the request context.
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return observability.WithObservabilityHandler(next, observability.Observability{
AccessLogsEnabled: true,
}), nil
})
chain = chain.Append(logHandler.AliceConstructor())
handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
@ -138,7 +147,15 @@ func TestLogRotation(t *testing.T) {
chain := alice.New()
chain = chain.Append(capture.Wrap)
chain = chain.Append(WrapHandler(logHandler))
// Injection of the observability variables in the request context.
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return observability.WithObservabilityHandler(next, observability.Observability{
AccessLogsEnabled: true,
}), nil
})
chain = chain.Append(logHandler.AliceConstructor())
handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
@ -290,7 +307,15 @@ func TestLoggerHeaderFields(t *testing.T) {
chain := alice.New()
chain = chain.Append(capture.Wrap)
chain = chain.Append(WrapHandler(logger))
// Injection of the observability variables in the request context.
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return observability.WithObservabilityHandler(next, observability.Observability{
AccessLogsEnabled: true,
}), nil
})
chain = chain.Append(logger.AliceConstructor())
handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
@ -998,7 +1023,15 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS, tracing b
chain := alice.New()
chain = chain.Append(capture.Wrap)
chain = chain.Append(WrapHandler(logger))
// Injection of the observability variables in the request context.
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return observability.WithObservabilityHandler(next, observability.Observability{
AccessLogsEnabled: true,
}), nil
})
chain = chain.Append(logger.AliceConstructor())
handler, err := chain.Then(http.HandlerFunc(logWriterTestHandlerFunc))
require.NoError(t, err)
@ -1085,7 +1118,15 @@ func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) {
}), nil
})
chain = chain.Append(capture.Wrap)
chain = chain.Append(WrapHandler(logger))
// Injection of the observability variables in the request context.
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return observability.WithObservabilityHandler(next, observability.Observability{
AccessLogsEnabled: true,
}), nil
})
chain = chain.Append(logger.AliceConstructor())
service := NewFieldHandler(http.HandlerFunc(streamBackend), ServiceURL, "http://stream", nil)
service = NewFieldHandler(service, ServiceAddr, "127.0.0.1", nil)

View file

@ -7,7 +7,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const (
@ -39,8 +38,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name
return result, nil
}
func (a *addPrefix) GetTracingInformation() (string, string, trace.SpanKind) {
return a.name, typeName, trace.SpanKindInternal
func (a *addPrefix) GetTracingInformation() (string, string) {
return a.name, typeName
}
func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/singleflight"
)
@ -61,8 +60,8 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
return ba, nil
}
func (b *basicAuth) GetTracingInformation() (string, string, trace.SpanKind) {
return b.name, typeNameBasic, trace.SpanKindInternal
func (b *basicAuth) GetTracingInformation() (string, string) {
return b.name, typeNameBasic
}
func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
const (
@ -54,8 +53,8 @@ func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.Digest
return da, nil
}
func (d *digestAuth) GetTracingInformation() (string, string, trace.SpanKind) {
return d.name, typeNameDigest, trace.SpanKindInternal
func (d *digestAuth) GetTracingInformation() (string, string) {
return d.name, typeNameDigest
}
func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -131,8 +131,8 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
return fa, nil
}
func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) {
return fa.name, typeNameForward, trace.SpanKindInternal
func (fa *forwardAuth) GetTracingInformation() (string, string) {
return fa.name, typeNameForward
}
func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@ -180,7 +180,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
var forwardSpan trace.Span
var tracer *tracing.Tracer
if tracer = tracing.TracerFromContext(req.Context()); tracer != nil {
if tracer = tracing.TracerFromContext(req.Context()); tracer != nil && observability.TracingEnabled(req.Context()) {
var tracingCtx context.Context
tracingCtx, forwardSpan = tracer.Start(req.Context(), "AuthRequest", trace.WithSpanKind(trace.SpanKindClient))
defer forwardSpan.End()

View file

@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/proxy/httputil"
"github.com/traefik/traefik/v3/pkg/testhelpers"
"github.com/traefik/traefik/v3/pkg/tracing"
@ -756,6 +757,10 @@ func TestForwardAuthTracing(t *testing.T) {
next, err := NewForward(t.Context(), next, auth, "authTest")
require.NoError(t, err)
next = observability.WithObservabilityHandler(next, observability.Observability{
TracingEnabled: true,
})
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
req.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "forward-test")

View file

@ -9,7 +9,6 @@ import (
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares"
oxybuffer "github.com/vulcand/oxy/v2/buffer"
"go.opentelemetry.io/otel/trace"
)
const (
@ -48,8 +47,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name
}, nil
}
func (b *buffer) GetTracingInformation() (string, string, trace.SpanKind) {
return b.name, typeName, trace.SpanKindInternal
func (b *buffer) GetTracingInformation() (string, string) {
return b.name, typeName
}
func (b *buffer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/vulcand/oxy/v2/cbreaker"
"go.opentelemetry.io/otel/trace"
)
const typeName = "CircuitBreaker"
@ -68,8 +67,8 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
}, nil
}
func (c *circuitBreaker) GetTracingInformation() (string, string, trace.SpanKind) {
return c.name, typeName, trace.SpanKindInternal
func (c *circuitBreaker) GetTracingInformation() (string, string) {
return c.name, typeName
}
func (c *circuitBreaker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -13,7 +13,6 @@ import (
"github.com/klauspost/compress/zstd"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const typeName = "Compress"
@ -181,8 +180,8 @@ func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.R
}
}
func (c *compress) GetTracingInformation() (string, string, trace.SpanKind) {
return c.name, typeName, trace.SpanKindInternal
func (c *compress) GetTracingInformation() (string, string) {
return c.name, typeName
}
func (c *compress) newGzipHandler() (http.Handler, error) {

View file

@ -15,7 +15,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/vulcand/oxy/v2/utils"
"go.opentelemetry.io/otel/trace"
)
// Compile time validation that the response recorder implements http interfaces correctly.
@ -83,8 +82,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
}, nil
}
func (c *customErrors) GetTracingInformation() (string, string, trace.SpanKind) {
return c.name, typeName, trace.SpanKindInternal
func (c *customErrors) GetTracingInformation() (string, string) {
return c.name, typeName
}
func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -6,7 +6,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const requestHeaderModifierTypeName = "RequestHeaderModifier"
@ -35,8 +34,8 @@ func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dyn
}
}
func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, requestHeaderModifierTypeName, trace.SpanKindUnspecified
func (r *requestHeaderModifier) GetTracingInformation() (string, string) {
return r.name, requestHeaderModifierTypeName
}
func (r *requestHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -6,7 +6,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const responseHeaderModifierTypeName = "ResponseHeaderModifier"
@ -35,8 +34,8 @@ func NewResponseHeaderModifier(ctx context.Context, next http.Handler, config dy
}
}
func (r *responseHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, responseHeaderModifierTypeName, trace.SpanKindUnspecified
func (r *responseHeaderModifier) GetTracingInformation() (string, string) {
return r.name, responseHeaderModifierTypeName
}
func (r *responseHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -10,7 +10,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const typeName = "RequestRedirect"
@ -52,8 +51,8 @@ func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.Req
}, nil
}
func (r redirect) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, typeName, trace.SpanKindInternal
func (r redirect) GetTracingInformation() (string, string) {
return r.name, typeName
}
func (r redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -8,7 +8,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const (
@ -38,8 +37,8 @@ func NewURLRewrite(ctx context.Context, next http.Handler, conf dynamic.URLRewri
}
}
func (u urlRewrite) GetTracingInformation() (string, string, trace.SpanKind) {
return u.name, typeName, trace.SpanKindInternal
func (u urlRewrite) GetTracingInformation() (string, string) {
return u.name, typeName
}
func (u urlRewrite) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -8,7 +8,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const (
@ -58,8 +57,8 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
}, nil
}
func (h *headers) GetTracingInformation() (string, string, trace.SpanKind) {
return h.name, typeName, trace.SpanKindInternal
func (h *headers) GetTracingInformation() (string, string) {
return h.name, typeName
}
func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -13,7 +13,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"go.opentelemetry.io/otel/trace"
)
func TestNew_withoutOptions(t *testing.T) {
@ -107,11 +106,10 @@ func Test_headers_getTracingInformation(t *testing.T) {
name: "testing",
}
name, typeName, spanKind := mid.GetTracingInformation()
name, typeName := mid.GetTracingInformation()
assert.Equal(t, "testing", name)
assert.Equal(t, "Headers", typeName)
assert.Equal(t, trace.SpanKindInternal, spanKind)
}
// This test is an adapted version of net/http/httputil.Test1xxResponses test.

View file

@ -10,7 +10,6 @@ import (
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/vulcand/oxy/v2/connlimit"
"go.opentelemetry.io/otel/trace"
)
const (
@ -53,8 +52,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, nam
return &inFlightReq{handler: handler, name: name}, nil
}
func (i *inFlightReq) GetTracingInformation() (string, string, trace.SpanKind) {
return i.name, typeName, trace.SpanKindInternal
func (i *inFlightReq) GetTracingInformation() (string, string) {
return i.name, typeName
}
func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -11,7 +11,6 @@ import (
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
const (
@ -65,8 +64,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam
}, nil
}
func (al *ipAllowLister) GetTracingInformation() (string, string, trace.SpanKind) {
return al.name, typeName, trace.SpanKindInternal
func (al *ipAllowLister) GetTracingInformation() (string, string) {
return al.name, typeName
}
func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -11,7 +11,6 @@ import (
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
const (
@ -55,8 +54,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPWhiteList, nam
}, nil
}
func (wl *ipWhiteLister) GetTracingInformation() (string, string, trace.SpanKind) {
return wl.name, typeName, trace.SpanKindInternal
func (wl *ipWhiteLister) GetTracingInformation() (string, string) {
return wl.name, typeName
}
func (wl *ipWhiteLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -18,7 +18,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/codes"
)
@ -93,33 +92,45 @@ func NewServiceMiddleware(ctx context.Context, next http.Handler, registry metri
}
}
// WrapEntryPointHandler Wraps metrics entrypoint to alice.Constructor.
func WrapEntryPointHandler(ctx context.Context, registry metrics.Registry, entryPointName string) alice.Constructor {
// EntryPointMetricsHandler returns the metrics entrypoint handler.
func EntryPointMetricsHandler(ctx context.Context, registry metrics.Registry, entryPointName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
if registry == nil || !registry.IsEpEnabled() {
return next, nil
}
return NewEntryPointMiddleware(ctx, next, registry, entryPointName), nil
}
}
// WrapRouterHandler Wraps metrics router to alice.Constructor.
func WrapRouterHandler(ctx context.Context, registry metrics.Registry, routerName string, serviceName string) alice.Constructor {
// RouterMetricsHandler returns the metrics router handler.
func RouterMetricsHandler(ctx context.Context, registry metrics.Registry, routerName string, serviceName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
if registry == nil || !registry.IsRouterEnabled() {
return next, nil
}
return NewRouterMiddleware(ctx, next, registry, routerName, serviceName), nil
}
}
// WrapServiceHandler Wraps metrics service to alice.Constructor.
func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor {
// ServiceMetricsHandler returns the metrics service handler.
func ServiceMetricsHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
if registry == nil || !registry.IsSvcEnabled() {
return next, nil
}
return NewServiceMiddleware(ctx, next, registry, serviceName), nil
}
}
func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanKind) {
return m.name, typeName, trace.SpanKindInternal
func (m *metricsMiddleware) GetTracingInformation() (string, string) {
return m.name, typeName
}
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if val := req.Context().Value(observability.DisableMetricsKey); val != nil {
if !observability.MetricsEnabled(req.Context()) {
m.next.ServeHTTP(rw, req)
return
}

View file

@ -48,11 +48,17 @@ func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, entryPointName s
}
func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if e.tracer == nil || !TracingEnabled(req.Context()) {
e.next.ServeHTTP(rw, req)
return
}
tracingCtx := tracing.ExtractCarrierIntoContext(req.Context(), req.Header)
start := time.Now()
tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start))
// Associate the request context with the logger.
// This allows the logger to be aware of the tracing context and log accordingly (TraceID, SpanID, etc.).
logger := log.Ctx(tracingCtx).With().Ctx(tracingCtx).Logger()
loggerCtx := logger.WithContext(tracingCtx)

View file

@ -14,7 +14,7 @@ import (
// Traceable embeds tracing information.
type Traceable interface {
GetTracingInformation() (name string, typeName string, spanKind trace.SpanKind)
GetTracingInformation() (name string, typeName string)
}
// WrapMiddleware adds traceability to an alice.Constructor.
@ -29,21 +29,20 @@ func WrapMiddleware(ctx context.Context, constructor alice.Constructor) alice.Co
}
if traceableHandler, ok := handler.(Traceable); ok {
name, typeName, spanKind := traceableHandler.GetTracingInformation()
name, typeName := traceableHandler.GetTracingInformation()
log.Ctx(ctx).Debug().Str(logs.MiddlewareName, name).Msg("Adding tracing to middleware")
return NewMiddleware(handler, name, typeName, spanKind), nil
return NewMiddleware(handler, name, typeName), nil
}
return handler, nil
}
}
// NewMiddleware returns a http.Handler struct.
func NewMiddleware(next http.Handler, name string, typeName string, spanKind trace.SpanKind) http.Handler {
func NewMiddleware(next http.Handler, name string, typeName string) http.Handler {
return &middlewareTracing{
next: next,
name: name,
typeName: typeName,
spanKind: spanKind,
}
}
@ -52,12 +51,11 @@ type middlewareTracing struct {
next http.Handler
name string
typeName string
spanKind trace.SpanKind
}
func (w *middlewareTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(w.spanKind))
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) {
tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()
req = req.WithContext(tracingCtx)

View file

@ -3,6 +3,7 @@ package observability
import (
"context"
"fmt"
"net/http"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
@ -10,8 +11,58 @@ import (
type contextKey int
// DisableMetricsKey is a context key used to disable the metrics.
const DisableMetricsKey contextKey = iota
const observabilityKey contextKey = iota
type Observability struct {
AccessLogsEnabled bool
MetricsEnabled bool
SemConvMetricsEnabled bool
TracingEnabled bool
DetailedTracingEnabled bool
}
// WithObservabilityHandler sets the observability state in the context for the next handler.
// This is also used for testing purposes to control whether access logs are enabled or not.
func WithObservabilityHandler(next http.Handler, obs Observability) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
next.ServeHTTP(rw, req.WithContext(WithObservability(req.Context(), obs)))
})
}
// WithObservability injects the observability state into the context.
func WithObservability(ctx context.Context, obs Observability) context.Context {
return context.WithValue(ctx, observabilityKey, obs)
}
// AccessLogsEnabled returns whether access-logs are enabled.
func AccessLogsEnabled(ctx context.Context) bool {
obs, ok := ctx.Value(observabilityKey).(Observability)
return ok && obs.AccessLogsEnabled
}
// MetricsEnabled returns whether metrics are enabled.
func MetricsEnabled(ctx context.Context) bool {
obs, ok := ctx.Value(observabilityKey).(Observability)
return ok && obs.MetricsEnabled
}
// SemConvMetricsEnabled returns whether metrics are enabled.
func SemConvMetricsEnabled(ctx context.Context) bool {
obs, ok := ctx.Value(observabilityKey).(Observability)
return ok && obs.SemConvMetricsEnabled
}
// TracingEnabled returns whether tracing is enabled.
func TracingEnabled(ctx context.Context) bool {
obs, ok := ctx.Value(observabilityKey).(Observability)
return ok && obs.TracingEnabled
}
// DetailedTracingEnabled returns whether detailed tracing is enabled.
func DetailedTracingEnabled(ctx context.Context) bool {
obs, ok := ctx.Value(observabilityKey).(Observability)
return ok && obs.DetailedTracingEnabled
}
// SetStatusErrorf flags the span as in error and log an event.
func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) {

View file

@ -45,7 +45,7 @@ func newRouter(ctx context.Context, router, routerRule, service string, next htt
}
func (f *routerTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) {
tracingCtx, span := tracer.Start(req.Context(), "Router", trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()

View file

@ -46,7 +46,7 @@ func newServerMetricsSemConv(ctx context.Context, semConvMetricRegistry *metrics
}
func (e *semConvServerMetrics) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if e.semConvMetricRegistry == nil || e.semConvMetricRegistry.HTTPServerRequestDuration() == nil {
if e.semConvMetricRegistry == nil || e.semConvMetricRegistry.HTTPServerRequestDuration() == nil || !SemConvMetricsEnabled(req.Context()) {
e.next.ServeHTTP(rw, req)
return
}

View file

@ -83,6 +83,11 @@ func TestSemConvServerMetrics(t *testing.T) {
handler, err = capture.Wrap(handler)
require.NoError(t, err)
// Injection of the observability variables in the request context.
handler = WithObservabilityHandler(handler, Observability{
SemConvMetricsEnabled: true,
})
handler.ServeHTTP(rw, req)
got := metricdata.ResourceMetrics{}

View file

@ -32,7 +32,7 @@ func NewService(ctx context.Context, service string, next http.Handler) http.Han
}
func (t *serviceTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) {
tracingCtx, span := tracer.Start(req.Context(), "Service", trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()

View file

@ -14,7 +14,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const typeName = "PassClientTLSCert"
@ -139,8 +138,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer
}, nil
}
func (p *passTLSClientCert) GetTracingInformation() (string, string, trace.SpanKind) {
return p.name, typeName, trace.SpanKindInternal
func (p *passTLSClientCert) GetTracingInformation() (string, string) {
return p.name, typeName
}
func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -14,7 +14,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/vulcand/oxy/v2/utils"
"go.opentelemetry.io/otel/trace"
"golang.org/x/time/rate"
)
@ -127,8 +126,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
}, nil
}
func (rl *rateLimiter) GetTracingInformation() (string, string, trace.SpanKind) {
return rl.name, typeName, trace.SpanKindInternal
func (rl *rateLimiter) GetTracingInformation() (string, string) {
return rl.name, typeName
}
func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -6,7 +6,6 @@ import (
"regexp"
"github.com/vulcand/oxy/v2/utils"
"go.opentelemetry.io/otel/trace"
)
const (
@ -46,8 +45,8 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, r
}, nil
}
func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, typeName, trace.SpanKindInternal
func (r *redirect) GetTracingInformation() (string, string) {
return r.name, typeName
}
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -8,7 +8,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
const (
@ -35,8 +34,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePath, nam
}, nil
}
func (r *replacePath) GetTracingInformation() (string, string, trace.SpanKind) {
return r.name, typeName, trace.SpanKindInternal
func (r *replacePath) GetTracingInformation() (string, string) {
return r.name, typeName
}
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/replacepath"
"go.opentelemetry.io/otel/trace"
)
const typeName = "ReplacePathRegex"
@ -42,8 +41,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePathRegex
}, nil
}
func (rp *replacePathRegex) GetTracingInformation() (string, string, trace.SpanKind) {
return rp.name, typeName, trace.SpanKindInternal
func (rp *replacePathRegex) GetTracingInformation() (string, string) {
return rp.name, typeName
}
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -14,6 +14,7 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
@ -124,7 +125,7 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
var currentSpan trace.Span
operation := func() error {
if tracer != nil {
if tracer != nil && observability.DetailedTracingEnabled(req.Context()) {
if currentSpan != nil {
currentSpan.End()
}

View file

@ -7,7 +7,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace"
)
const (
@ -45,8 +44,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, nam
}, nil
}
func (s *stripPrefix) GetTracingInformation() (string, string, trace.SpanKind) {
return s.name, typeName, trace.SpanKindUnspecified
func (s *stripPrefix) GetTracingInformation() (string, string) {
return s.name, typeName
}
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {

View file

@ -9,7 +9,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/stripprefix"
"go.opentelemetry.io/otel/trace"
)
const (
@ -43,8 +42,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefixRegex
return &stripPrefix, nil
}
func (s *stripPrefixRegex) GetTracingInformation() (string, string, trace.SpanKind) {
return s.name, typeName, trace.SpanKindInternal
func (s *stripPrefixRegex) GetTracingInformation() (string, string) {
return s.name, typeName
}
func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {