Introduce trace verbosity config and produce less spans by default
This commit is contained in:
parent
77ef7fe490
commit
8c23eb6833
93 changed files with 1005 additions and 524 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/server/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration {
|
||||
|
|
@ -208,6 +209,10 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
cp.Observability.Tracing = m.Observability.Tracing
|
||||
}
|
||||
|
||||
if cp.Observability.TraceVerbosity == "" {
|
||||
cp.Observability.TraceVerbosity = m.Observability.TraceVerbosity
|
||||
}
|
||||
|
||||
rtName := name
|
||||
if len(eps) > 1 {
|
||||
rtName = epName + "-" + name
|
||||
|
|
@ -224,7 +229,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
cfg.HTTP.Routers = rts
|
||||
}
|
||||
|
||||
// Apply default observability model to HTTP routers.
|
||||
// Apply the default observability model to HTTP routers.
|
||||
applyDefaultObservabilityModel(cfg)
|
||||
|
||||
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
|
||||
|
|
@ -256,14 +261,16 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
// and make sure it is serialized and available in the API.
|
||||
// We could have introduced a "default" model, but it would have been more complex to manage for now.
|
||||
// This could be generalized in the future.
|
||||
// TODO: check if we can remove this and rely on the SetDefaults instead.
|
||||
func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
|
||||
if cfg.HTTP != nil {
|
||||
for _, router := range cfg.HTTP.Routers {
|
||||
if router.Observability == nil {
|
||||
router.Observability = &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
}
|
||||
|
||||
continue
|
||||
|
|
@ -273,12 +280,16 @@ func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
|
|||
router.Observability.AccessLogs = pointer(true)
|
||||
}
|
||||
|
||||
if router.Observability.Metrics == nil {
|
||||
router.Observability.Metrics = pointer(true)
|
||||
}
|
||||
|
||||
if router.Observability.Tracing == nil {
|
||||
router.Observability.Tracing = pointer(true)
|
||||
}
|
||||
|
||||
if router.Observability.Metrics == nil {
|
||||
router.Observability.Metrics = pointer(true)
|
||||
if router.Observability.TraceVerbosity == "" {
|
||||
router.Observability.TraceVerbosity = types.MinimalVerbosity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
func Test_mergeConfiguration(t *testing.T) {
|
||||
|
|
@ -521,9 +522,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Routers: map[string]*dynamic.Router{
|
||||
"test": {
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -589,9 +591,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -622,9 +625,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -638,9 +642,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -651,9 +656,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -688,9 +694,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -730,9 +737,10 @@ func Test_applyModel(t *testing.T) {
|
|||
"test": {
|
||||
EntryPoints: []string{"web"},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
"websecure-test": {
|
||||
|
|
@ -740,9 +748,10 @@ func Test_applyModel(t *testing.T) {
|
|||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -428,8 +428,5 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
|||
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
||||
}
|
||||
|
||||
// The tracing middleware is a NOOP if tracing is not setup on the middleware chain.
|
||||
// Hence, regarding internal resources' observability deactivation,
|
||||
// this would not enable tracing.
|
||||
return observability.WrapMiddleware(ctx, middleware), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -17,6 +16,7 @@ import (
|
|||
mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement.
|
||||
|
|
@ -42,111 +42,44 @@ func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Re
|
|||
}
|
||||
|
||||
// BuildEPChain an observability middleware chain by entry point.
|
||||
func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string, observabilityConfig *dynamic.RouterObservabilityConfig) alice.Chain {
|
||||
func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, internal bool, config dynamic.RouterObservabilityConfig) alice.Chain {
|
||||
chain := alice.New()
|
||||
|
||||
if o == nil {
|
||||
return chain
|
||||
}
|
||||
|
||||
if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) {
|
||||
if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(capture.Wrap)
|
||||
}
|
||||
// Injection of the observability variables in the request context.
|
||||
// This injection must be the first step in order for other observability middlewares to rely on it.
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return o.observabilityContextHandler(next, internal, config), nil
|
||||
})
|
||||
|
||||
// Capture middleware for accessLogs or metrics.
|
||||
if o.shouldAccessLog(internal, config) || o.shouldMeter(internal, config) || o.shouldMeterSemConv(internal, config) {
|
||||
chain = chain.Append(capture.Wrap)
|
||||
}
|
||||
|
||||
// As the Entry point observability middleware ensures that the tracing is added to the request and logger context,
|
||||
// it needs to be added before the access log middleware to ensure that the trace ID is logged.
|
||||
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
|
||||
}
|
||||
chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
|
||||
|
||||
if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware))
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
})
|
||||
}
|
||||
// Access log handlers.
|
||||
chain = chain.Append(o.accessLoggerMiddleware.AliceConstructor())
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
})
|
||||
|
||||
// Entrypoint metrics handler.
|
||||
metricsHandler := mmetrics.EntryPointMetricsHandler(ctx, o.metricsRegistry, entryPointName)
|
||||
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
|
||||
|
||||
// Semantic convention server metrics handler.
|
||||
if o.semConvMetricRegistry != nil && o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
|
||||
}
|
||||
|
||||
if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
metricsHandler := mmetrics.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName)
|
||||
|
||||
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
|
||||
} else {
|
||||
chain = chain.Append(metricsHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject context keys to control whether to produce metrics further downstream (services, round-tripper),
|
||||
// because the router configuration cannot be evaluated during build time for services.
|
||||
if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics {
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true)))
|
||||
}), nil
|
||||
})
|
||||
}
|
||||
chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.AccessLog == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
|
||||
}
|
||||
|
||||
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.Metrics == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
|
||||
}
|
||||
|
||||
// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.Tracing == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.Tracing.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
|
||||
}
|
||||
|
||||
// MetricsRegistry is an accessor to the metrics registry.
|
||||
func (o *ObservabilityMgr) MetricsRegistry() metrics.Registry {
|
||||
if o == nil {
|
||||
|
|
@ -191,3 +124,89 @@ func (o *ObservabilityMgr) RotateAccessLogs() error {
|
|||
|
||||
return o.accessLoggerMiddleware.Rotate()
|
||||
}
|
||||
|
||||
func (o *ObservabilityMgr) observabilityContextHandler(next http.Handler, internal bool, config dynamic.RouterObservabilityConfig) http.Handler {
|
||||
return observability.WithObservabilityHandler(next, observability.Observability{
|
||||
AccessLogsEnabled: o.shouldAccessLog(internal, config),
|
||||
MetricsEnabled: o.shouldMeter(internal, config),
|
||||
SemConvMetricsEnabled: o.shouldMeterSemConv(internal, config),
|
||||
TracingEnabled: o.shouldTrace(internal, config, types.MinimalVerbosity),
|
||||
DetailedTracingEnabled: o.shouldTrace(internal, config, types.DetailedVerbosity),
|
||||
})
|
||||
}
|
||||
|
||||
// shouldAccessLog returns whether the access logs should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) shouldAccessLog(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.AccessLog == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if internal && !o.config.AccessLog.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
|
||||
}
|
||||
|
||||
// shouldMeter returns whether the metrics should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) shouldMeter(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil || o.metricsRegistry == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !o.metricsRegistry.IsEpEnabled() && !o.metricsRegistry.IsRouterEnabled() && !o.metricsRegistry.IsSvcEnabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.Metrics == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if internal && !o.config.Metrics.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
|
||||
}
|
||||
|
||||
// shouldMeterSemConv returns whether the OTel semantic convention metrics should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) shouldMeterSemConv(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil || o.semConvMetricRegistry == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.Metrics == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if internal && !o.config.Metrics.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
|
||||
}
|
||||
|
||||
// shouldTrace returns whether the tracing should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) shouldTrace(internal bool, observabilityConfig dynamic.RouterObservabilityConfig, verbosity types.TracingVerbosity) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if o.config.Tracing == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if internal && !o.config.Tracing.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
if !observabilityConfig.TraceVerbosity.Allows(verbosity) {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/plugins"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const typeName = "Plugin"
|
||||
|
|
@ -55,6 +54,6 @@ func (s *traceablePlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
s.h.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (s *traceablePlugin) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return s.name, typeName, trace.SpanKindInternal
|
||||
func (s *traceablePlugin) GetTracingInformation() (string, string) {
|
||||
return s.name, typeName
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
|
|
@ -70,11 +71,22 @@ func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls
|
|||
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler {
|
||||
entryPointHandlers := make(map[string]http.Handler)
|
||||
|
||||
defaultObsConfig := dynamic.RouterObservabilityConfig{}
|
||||
defaultObsConfig.SetDefaults()
|
||||
|
||||
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
|
||||
logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
|
||||
ctx := logger.WithContext(rootCtx)
|
||||
|
||||
handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers)
|
||||
// TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration.
|
||||
// When the entry point has no observability configuration no model is produced,
|
||||
// and we need to create the default configuration is this case.
|
||||
epObsConfig := defaultObsConfig
|
||||
if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil {
|
||||
epObsConfig = model.Observability
|
||||
}
|
||||
|
||||
handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers, epObsConfig)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
|
|
@ -93,7 +105,15 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
|||
continue
|
||||
}
|
||||
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter())
|
||||
// TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration.
|
||||
// When the entry point has no observability configuration no model is produced,
|
||||
// and we need to create the default configuration is this case.
|
||||
epObsConfig := defaultObsConfig
|
||||
if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil {
|
||||
epObsConfig = model.Observability
|
||||
}
|
||||
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, epObsConfig).Then(http.NotFoundHandler())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
|
|
@ -104,10 +124,10 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
|||
return entryPointHandlers
|
||||
}
|
||||
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo, config dynamic.RouterObservabilityConfig) (http.Handler, error) {
|
||||
muxer := httpmuxer.NewMuxer(m.parser)
|
||||
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler())
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, config).Then(http.NotFoundHandler())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -136,7 +156,11 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
|||
continue
|
||||
}
|
||||
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability)
|
||||
if routerConfig.Observability != nil {
|
||||
config = *routerConfig.Observability
|
||||
}
|
||||
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctxRouter, entryPointName, strings.HasSuffix(routerConfig.Service, "@internal"), config)
|
||||
handler, err = observabilityChain.Then(handler)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
|
|
@ -180,22 +204,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Prevents from enabling observability for internal resources.
|
||||
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service), routerConfig.Observability) {
|
||||
m.routerHandlers[routerName] = handler
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
|
||||
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
|
||||
}).Then(handler)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Send()
|
||||
m.routerHandlers[routerName] = handler
|
||||
} else {
|
||||
m.routerHandlers[routerName] = handlerWithAccessLog
|
||||
}
|
||||
|
||||
m.routerHandlers[routerName] = handler
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
|
||||
|
|
@ -210,40 +219,29 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
|||
return nil, errors.New("the service is missing on the router")
|
||||
}
|
||||
|
||||
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
|
||||
qualifiedService := provider.GetQualifiedName(ctx, router.Service)
|
||||
|
||||
chain := alice.New()
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
|
||||
m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
|
||||
chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
|
||||
}
|
||||
|
||||
// Prevents from enabling tracing for internal resources.
|
||||
if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
}
|
||||
|
||||
chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() {
|
||||
metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))
|
||||
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
|
||||
}
|
||||
|
||||
if router.DefaultRule {
|
||||
chain = chain.Append(denyrouterrecursion.WrapHandler(routerName))
|
||||
}
|
||||
|
||||
// Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled.
|
||||
chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, qualifiedService))
|
||||
metricsHandler := metricsMiddle.RouterMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, qualifiedService)
|
||||
|
||||
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
|
||||
})
|
||||
|
||||
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
|
||||
|
||||
sHandler, err := m.serviceManager.BuildHTTP(ctx, qualifiedService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
}
|
||||
|
||||
// BuildDefaultHTTPRouter creates a default HTTP router.
|
||||
func BuildDefaultHTTPRouter() http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -929,7 +929,7 @@ func BenchmarkService(b *testing.B) {
|
|||
|
||||
type proxyBuilderMock struct{}
|
||||
|
||||
func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) {
|
||||
func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ func TestInternalServices(t *testing.T) {
|
|||
|
||||
type proxyBuilderMock struct{}
|
||||
|
||||
func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) {
|
||||
func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/middlewares/forwardedheaders"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/server/router"
|
||||
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
|
||||
"github.com/traefik/traefik/v3/pkg/server/service"
|
||||
"github.com/traefik/traefik/v3/pkg/tcp"
|
||||
|
|
@ -351,7 +350,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) {
|
|||
|
||||
httpHandler := rt.GetHTTPHandler()
|
||||
if httpHandler == nil {
|
||||
httpHandler = router.BuildDefaultHTTPRouter()
|
||||
httpHandler = http.NotFoundHandler()
|
||||
}
|
||||
|
||||
e.httpServer.Switcher.UpdateHandler(httpHandler)
|
||||
|
|
@ -360,7 +359,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) {
|
|||
|
||||
httpsHandler := rt.GetHTTPSHandler()
|
||||
if httpsHandler == nil {
|
||||
httpsHandler = router.BuildDefaultHTTPRouter()
|
||||
httpsHandler = http.NotFoundHandler()
|
||||
}
|
||||
|
||||
e.httpsServer.Switcher.UpdateHandler(httpsHandler)
|
||||
|
|
@ -591,7 +590,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
return nil, errors.New("max concurrent streams value must be greater than or equal to zero")
|
||||
}
|
||||
|
||||
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
|
||||
httpSwitcher := middlewares.NewHandlerSwitcher(http.NotFoundHandler())
|
||||
|
||||
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/healthcheck"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
|
||||
|
|
@ -37,7 +36,7 @@ import (
|
|||
|
||||
// ProxyBuilder builds reverse proxy handlers.
|
||||
type ProxyBuilder interface {
|
||||
Build(cfgName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error)
|
||||
Build(cfgName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error)
|
||||
Update(configs map[string]*dynamic.ServersTransport)
|
||||
}
|
||||
|
||||
|
|
@ -364,50 +363,32 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
|
||||
qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName)
|
||||
|
||||
shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil)
|
||||
proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, shouldObserve, passHostHeader, server.PreservePath, flushInterval)
|
||||
proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, passHostHeader, server.PreservePath, flushInterval)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
|
||||
}
|
||||
|
||||
// The retry wrapping must be done just before the proxy handler,
|
||||
// to make sure that the retry will not be triggered/disabled by
|
||||
// middlewares in the chain.
|
||||
proxy = retry.WrapHandler(proxy)
|
||||
|
||||
// Prevents from enabling observability for internal resources.
|
||||
// Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled.
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, qualifiedSvcName, accesslog.AddServiceFields)
|
||||
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) {
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
|
||||
metricsHandler := metricsMiddle.ServiceMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), qualifiedSvcName)
|
||||
metricsHandler = observability.WrapMiddleware(ctx, metricsHandler)
|
||||
|
||||
proxy, err = alice.New().
|
||||
Append(metricsHandler).
|
||||
Then(proxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error wrapping metrics handler: %w", err)
|
||||
}
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() &&
|
||||
m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
|
||||
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
|
||||
|
||||
proxy, err = alice.New().
|
||||
Append(observability.WrapMiddleware(ctx, metricsHandler)).
|
||||
Then(proxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error wrapping metrics handler: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) {
|
||||
proxy = observability.NewService(ctx, serviceName, proxy)
|
||||
}
|
||||
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
|
||||
// Some piece of middleware, like the ErrorPage, are relying on this serviceBuilder to get the handler for a given service,
|
||||
// to re-target the request to it.
|
||||
// Those pieces of middleware can be configured on routes that expose a Traefik internal service.
|
||||
// In such a case, observability for internals being optional, the capture probe could be absent from context (no wrap via the entrypoint).
|
||||
// But if the service targeted by this piece of middleware is not an internal one,
|
||||
// and requires observability, we still want the capture probe to be present in the request context.
|
||||
// Makes sure a capture probe is in the request context.
|
||||
proxy, _ = capture.Wrap(proxy)
|
||||
}
|
||||
proxy = observability.NewService(ctx, qualifiedSvcName, proxy)
|
||||
|
||||
lb.AddServer(server.URL, proxy, server)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue