diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index f443abe37..59c75bfe4 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -60,8 +60,6 @@ func (ep *EntryPoint) SetDefaults() { ep.HTTP.SetDefaults() ep.HTTP2 = &HTTP2Config{} ep.HTTP2.SetDefaults() - ep.Observability = &ObservabilityConfig{} - ep.Observability.SetDefaults() } // HTTPConfig is the HTTP configuration of an entry point. @@ -164,14 +162,15 @@ func (u *UDPConfig) SetDefaults() { // ObservabilityConfig holds the observability configuration for an entry point. type ObservabilityConfig struct { - AccessLogs bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` - Tracing bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` - Metrics bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` + AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` + Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` + Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` } // SetDefaults sets the default values. func (o *ObservabilityConfig) SetDefaults() { - o.AccessLogs = true - o.Tracing = true - o.Metrics = true + defaultValue := true + o.AccessLogs = &defaultValue + o.Tracing = &defaultValue + o.Metrics = &defaultValue } diff --git a/pkg/config/static/static_config_test.go b/pkg/config/static/static_config_test.go index 78633bc2f..67c643a32 100644 --- a/pkg/config/static/static_config_test.go +++ b/pkg/config/static/static_config_test.go @@ -77,11 +77,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { UDP: &UDPConfig{ Timeout: 3000000000, }, - Observability: &ObservabilityConfig{ - AccessLogs: true, - Tracing: true, - Metrics: true, - }, }}, Providers: &Providers{}, }, @@ -127,11 +122,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { UDP: &UDPConfig{ Timeout: 3000000000, }, - Observability: &ObservabilityConfig{ - AccessLogs: true, - Tracing: true, - Metrics: true, - }, }}, Providers: &Providers{}, CertificatesResolvers: map[string]CertificateResolver{ @@ -188,11 +178,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { UDP: &UDPConfig{ Timeout: 3000000000, }, - Observability: &ObservabilityConfig{ - AccessLogs: true, - Tracing: true, - Metrics: true, - }, }}, Providers: &Providers{}, CertificatesResolvers: map[string]CertificateResolver{ @@ -253,11 +238,6 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) { UDP: &UDPConfig{ Timeout: 3000000000, }, - Observability: &ObservabilityConfig{ - AccessLogs: true, - Tracing: true, - Metrics: true, - }, }}, Providers: &Providers{}, CertificatesResolvers: map[string]CertificateResolver{ diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 47f822321..f1490a41b 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -240,9 +240,9 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) { if ep.Observability != nil { httpModel.Observability = dynamic.RouterObservabilityConfig{ - AccessLogs: &ep.Observability.AccessLogs, - Tracing: &ep.Observability.Tracing, - Metrics: &ep.Observability.Metrics, + AccessLogs: ep.Observability.AccessLogs, + Tracing: ep.Observability.Tracing, + Metrics: ep.Observability.Metrics, } } diff --git a/pkg/provider/traefik/internal_test.go b/pkg/provider/traefik/internal_test.go index ed7197f82..3ff89a727 100644 --- a/pkg/provider/traefik/internal_test.go +++ b/pkg/provider/traefik/internal_test.go @@ -18,6 +18,8 @@ import ( var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures") +func pointer[T any](v T) *T { return &v } + func Test_createConfiguration(t *testing.T) { testCases := []struct { desc string @@ -185,9 +187,9 @@ func Test_createConfiguration(t *testing.T) { }, }, Observability: &static.ObservabilityConfig{ - AccessLogs: false, - Tracing: false, - Metrics: false, + AccessLogs: pointer(false), + Tracing: pointer(false), + Metrics: pointer(false), }, }, }, diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 72934d469..3cd97f206 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -191,14 +191,14 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { cp.Observability.AccessLogs = m.Observability.AccessLogs } - if cp.Observability.Tracing == nil { - cp.Observability.Tracing = m.Observability.Tracing - } - if cp.Observability.Metrics == nil { cp.Observability.Metrics = m.Observability.Metrics } + if cp.Observability.Tracing == nil { + cp.Observability.Tracing = m.Observability.Tracing + } + rtName := name if len(eps) > 1 { rtName = epName + "-" + name @@ -215,6 +215,9 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { cfg.HTTP.Routers = rts } + // Apply default observability model to HTTP routers. + applyDefaultObservabilityModel(cfg) + if cfg.TCP == nil || len(cfg.TCP.Models) == 0 { return cfg } @@ -238,3 +241,38 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { return cfg } + +// applyDefaultObservabilityModel applies the default observability model to the configuration. +// This function is used to ensure that the observability configuration is set for all routers, +// 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. +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), + } + + continue + } + + if router.Observability.AccessLogs == nil { + router.Observability.AccessLogs = pointer(true) + } + + if router.Observability.Tracing == nil { + router.Observability.Tracing = pointer(true) + } + + if router.Observability.Metrics == nil { + router.Observability.Metrics = pointer(true) + } + } + } +} + +func pointer[T any](v T) *T { return &v } diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index 50a400d2a..182815a39 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -9,8 +9,6 @@ import ( "github.com/traefik/traefik/v3/pkg/tls" ) -func pointer[T any](v T) *T { return &v } - func Test_mergeConfiguration(t *testing.T) { testCases := []struct { desc string @@ -508,6 +506,33 @@ func Test_applyModel(t *testing.T) { }, }, }, + { + desc: "without model, one router", + input: dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{"test": {}}, + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + Models: make(map[string]*dynamic.Model), + }, + }, + expected: dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "test": { + Observability: &dynamic.RouterObservabilityConfig{ + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + }, + }, + }, + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + Models: make(map[string]*dynamic.Model), + }, + }, + }, { desc: "with model, not used", input: dynamic.Configuration{ @@ -560,10 +585,14 @@ func Test_applyModel(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ "test": { - EntryPoints: []string{"websecure"}, - Middlewares: []string{"test"}, - TLS: &dynamic.RouterTLSConfig{}, - Observability: &dynamic.RouterObservabilityConfig{}, + EntryPoints: []string{"websecure"}, + Middlewares: []string{"test"}, + TLS: &dynamic.RouterTLSConfig{}, + Observability: &dynamic.RouterObservabilityConfig{ + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + }, }, }, Middlewares: make(map[string]*dynamic.Middleware), @@ -659,9 +688,9 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{CertResolver: "router"}, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: nil, - Tracing: nil, - Metrics: nil, + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), }, }, }, @@ -700,12 +729,21 @@ func Test_applyModel(t *testing.T) { Routers: map[string]*dynamic.Router{ "test": { EntryPoints: []string{"web"}, + Observability: &dynamic.RouterObservabilityConfig{ + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + }, }, "websecure-test": { - EntryPoints: []string{"websecure"}, - Middlewares: []string{"test"}, - TLS: &dynamic.RouterTLSConfig{}, - Observability: &dynamic.RouterObservabilityConfig{}, + EntryPoints: []string{"websecure"}, + Middlewares: []string{"test"}, + TLS: &dynamic.RouterTLSConfig{}, + Observability: &dynamic.RouterObservabilityConfig{ + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + }, }, }, Middlewares: make(map[string]*dynamic.Middleware), diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index 7c4ad735f..7baf233d0 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -84,7 +84,8 @@ func TestNewConfigurationWatcher(t *testing.T) { th.WithRouters( th.WithRouter("test@mock", th.WithEntryPoints("e"), - th.WithServiceName("scv"))), + th.WithServiceName("scv"), + th.WithObservability())), th.WithMiddlewares(), th.WithLoadBalancerServices(), ), @@ -175,7 +176,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) { expectedConfig := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithMiddlewares(), ), @@ -200,7 +201,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) { expectedConfig3 := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), th.WithLoadBalancerServices(th.WithService("bar-config3@mock")), th.WithMiddlewares(), ), @@ -447,7 +448,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithMiddlewares(), ), @@ -538,7 +539,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), + th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability())), th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithMiddlewares(), ), @@ -674,7 +675,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( - th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))), + th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"), th.WithObservability())), th.WithLoadBalancerServices(th.WithService("final@mock")), th.WithMiddlewares(), ), @@ -738,8 +739,8 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { expected := dynamic.Configuration{ HTTP: th.BuildConfiguration( th.WithRouters( - th.WithRouter("foo@mock", th.WithEntryPoints("ep")), - th.WithRouter("foo@mock2", th.WithEntryPoints("ep")), + th.WithRouter("foo@mock", th.WithEntryPoints("ep"), th.WithObservability()), + th.WithRouter("foo@mock2", th.WithEntryPoints("ep"), th.WithObservability()), ), th.WithLoadBalancerServices( th.WithService("bar@mock"), diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go index 566d8522c..d279be902 100644 --- a/pkg/server/middleware/observability.go +++ b/pkg/server/middleware/observability.go @@ -110,7 +110,7 @@ func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observability return false } - return observabilityConfig == nil || observabilityConfig.AccessLogs != nil && *observabilityConfig.AccessLogs + return observabilityConfig == nil || observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs } // ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config. @@ -127,7 +127,7 @@ func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityCon return false } - return observabilityConfig == nil || observabilityConfig.Metrics != nil && *observabilityConfig.Metrics + return observabilityConfig == nil || observabilityConfig.Metrics == nil || *observabilityConfig.Metrics } // ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config. @@ -144,7 +144,7 @@ func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityCon return false } - return observabilityConfig == nil || observabilityConfig.Tracing != nil && *observabilityConfig.Tracing + return observabilityConfig == nil || observabilityConfig.Tracing == nil || *observabilityConfig.Tracing } // MetricsRegistry is an accessor to the metrics registry. diff --git a/pkg/testhelpers/config.go b/pkg/testhelpers/config.go index 3f379f6bd..2bbea1956 100644 --- a/pkg/testhelpers/config.go +++ b/pkg/testhelpers/config.go @@ -53,6 +53,17 @@ func WithServiceName(serviceName string) func(*dynamic.Router) { } } +// WithObservability is a helper to create a configuration. +func WithObservability() func(*dynamic.Router) { + return func(r *dynamic.Router) { + r.Observability = &dynamic.RouterObservabilityConfig{ + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + } + } +} + // WithLoadBalancerServices is a helper to create a configuration. func WithLoadBalancerServices(opts ...func(service *dynamic.ServersLoadBalancer) string) func(*dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) { @@ -149,3 +160,5 @@ func WithSticky(cookieName string) func(*dynamic.ServersLoadBalancer) { } } } + +func pointer[T any](v T) *T { return &v }