diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 1274e5104..0ddcceb00 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -193,10 +193,13 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err tsProviders := initTailscaleProviders(staticConfiguration, &providerAggregator) - // Metrics + // Observability metricRegistries := registerMetricClients(staticConfiguration.Metrics) metricsRegistry := metrics.NewMultiRegistry(metricRegistries) + accessLog := setupAccessLog(staticConfiguration.AccessLog) + tracer, tracerCloser := setupTracing(staticConfiguration.Tracing) + observabilityMgr := middleware.NewObservabilityMgr(*staticConfiguration, metricsRegistry, accessLog, tracer, tracerCloser) // Entrypoints @@ -263,14 +266,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err roundTripperManager := service.NewRoundTripperManager(spiffeX509Source) dialerManager := tcp.NewDialerManager(spiffeX509Source) acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider) - managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler) + managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, roundTripperManager, acmeHTTPHandler) // Router factory - accessLog := setupAccessLog(staticConfiguration.AccessLog) - tracer, tracerCloser := setupTracing(staticConfiguration.Tracing) - chainBuilder := middleware.NewChainBuilder(metricsRegistry, accessLog, tracer) - routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry, dialerManager) + routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager) // Watcher @@ -351,7 +351,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err } }) - return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog, tracerCloser), nil + return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil } func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler { diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index b9f7c5102..08a45d741 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -693,3 +693,13 @@ Here are two possible transition strategies: This allows continued compatibility with the existing infrastructure. Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information. + +#### Internal Resources Observability (AccessLogs, Metrics and Tracing) + +In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default. +To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing. +Please take a look at the observability documentation for more information: + +- [AccessLogs](../observability/access-logs.md#addinternals) +- [Metrics](../observability/metrics/overview.md#addinternals) +- [AccessLogs](../observability/tracing/overview.md#addinternals) diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index dac71805f..beb114db1 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -26,6 +26,26 @@ accessLog: {} --accesslog=true ``` +### `addInternals` + +_Optional, Default="false"_ + +Enables accessLogs for internal resources. + +```yaml tab="File (YAML)" +accesslog: + addInternals: true +``` + +```toml tab="File (TOML)" +[accesslog] + addInternals = true +``` + +```bash tab="CLI" +--accesslog.addinternals +``` + ### `filePath` By default access logs are written to the standard output. diff --git a/docs/content/observability/metrics/overview.md b/docs/content/observability/metrics/overview.md index 1d6f04b15..f968e77e6 100644 --- a/docs/content/observability/metrics/overview.md +++ b/docs/content/observability/metrics/overview.md @@ -14,6 +14,28 @@ Traefik supports these metrics backends: Traefik Proxy hosts an official Grafana dashboard for both [on-premises](https://grafana.com/grafana/dashboards/17346) and [Kubernetes](https://grafana.com/grafana/dashboards/17347) deployments. +## Common Options + +### `addInternals` + +_Optional, Default="false"_ + +Enables metrics for internal resources. + +```yaml tab="File (YAML)" +metrics: + addInternals: true +``` + +```toml tab="File (TOML)" +[metrics] + addInternals = true +``` + +```bash tab="CLI" +--metrics.addinternals +``` + ## Global Metrics | Metric | Type | [Labels](#labels) | Description | diff --git a/docs/content/observability/overview.md b/docs/content/observability/overview.md new file mode 100644 index 000000000..2de429b4e --- /dev/null +++ b/docs/content/observability/overview.md @@ -0,0 +1,42 @@ +--- +title: "Traefik Observability Overview" +description: "Traefik provides Logs, Access Logs, Metrics and Tracing. Read the full documentation to get started." +--- + +# Overview + +Traefik's Observability system +{: .subtitle } + +## Logs + +Traefik logs informs about everything that happens within Traefik (startup, configuration, events, shutdown, and so on). + +Read the [Logs documentation](./logs.md) to learn how to configure it. + +## Access Logs + +Access logs are a key part of observability in Traefik. + +They are providing valuable insights about incoming traffic, and allow to monitor it. +The access logs record detailed information about each request received by Traefik, +including the source IP address, requested URL, response status code, and more. + +Read the [Access Logs documentation](./access-logs.md) to learn how to configure it. + +## Metrics + +Traefik offers a metrics feature that provides valuable insights about the performance and usage. +These metrics include the number of requests received, the requests duration, and more. + +Traefik supports these metrics systems: Prometheus, Datadog, InfluxDB 2.X, and StatsD. + +Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it. + +## Tracing + +The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure. + +Traefik supports these tracing with OpenTelemetry. + +Read the [Tracing documentation](./tracing/overview.md) to learn how to configure it. diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index 18b8c4f3e..52cbb483a 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -14,10 +14,8 @@ Traefik uses [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel" Please check our dedicated [OTel docs](./opentelemetry.md) to learn more. - ## Configuration - To enable the tracing: ```yaml tab="File (YAML)" @@ -34,6 +32,26 @@ tracing: {} ### Common Options +#### `addInternals` + +_Optional, Default="false"_ + +Enables tracing for internal resources. + +```yaml tab="File (YAML)" +tracing: + addInternals: true +``` + +```toml tab="File (TOML)" +[tracing] + addInternals = true +``` + +```bash tab="CLI" +--tracing.addinternals +``` + #### `serviceName` _Required, Default="traefik"_ diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index d2a5db058..a3bd3d391 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND `--accesslog`: Access log settings. (Default: ```false```) +`--accesslog.addinternals`: +Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```) + `--accesslog.bufferingsize`: Number of access log lines to process in a buffered way. (Default: ```0```) @@ -267,6 +270,9 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0 `--log.nocolor`: When using the 'common' format, disables the colorized output. (Default: ```false```) +`--metrics.addinternals`: +Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) + `--metrics.datadog`: Datadog metrics exporter type. (Default: ```false```) @@ -993,6 +999,9 @@ Defines the allowed SPIFFE trust domain. `--tracing`: OpenTracing configuration. (Default: ```false```) +`--tracing.addinternals`: +Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```) + `--tracing.globalattributes.`: Defines additional attributes (key:value) on all spans. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index cc813b154..79cc1797d 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND `TRAEFIK_ACCESSLOG`: Access log settings. (Default: ```false```) +`TRAEFIK_ACCESSLOG_ADDINTERNALS`: +Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```) + `TRAEFIK_ACCESSLOG_BUFFERINGSIZE`: Number of access log lines to process in a buffered way. (Default: ```0```) @@ -267,6 +270,9 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0 `TRAEFIK_LOG_NOCOLOR`: When using the 'common' format, disables the colorized output. (Default: ```false```) +`TRAEFIK_METRICS_ADDINTERNALS`: +Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) + `TRAEFIK_METRICS_DATADOG`: Datadog metrics exporter type. (Default: ```false```) @@ -993,6 +999,9 @@ Defines the allowed SPIFFE trust domain. `TRAEFIK_TRACING`: OpenTracing configuration. (Default: ```false```) +`TRAEFIK_TRACING_ADDINTERNALS`: +Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```) + `TRAEFIK_TRACING_GLOBALATTRIBUTES_`: Defines additional attributes (key:value) on all spans. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 79226cb70..e2a459ae6 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -277,6 +277,7 @@ disableDashboardAd = true [metrics] + addInternals = true [metrics.prometheus] buckets = [42.0, 42.0] addEntryPointsLabels = true @@ -351,6 +352,7 @@ filePath = "foobar" format = "foobar" bufferingSize = 42 + addInternals = true [accessLog.filters] statusCodes = ["foobar", "foobar"] retryAttempts = true @@ -369,6 +371,7 @@ [tracing] serviceName = "foobar" sampleRate = 42.0 + addInternals = true [tracing.headers] name0 = "foobar" name1 = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 45092911f..2779e0997 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -308,6 +308,7 @@ api: debug: true disableDashboardAd: true metrics: + addInternals: true prometheus: buckets: - 42 @@ -399,6 +400,7 @@ accessLog: name0: foobar name1: foobar bufferingSize: 42 + addInternals: true tracing: serviceName: foobar headers: @@ -408,6 +410,7 @@ tracing: name0: foobar name1: foobar sampleRate: 42 + addInternals: true otlp: grpc: endpoint: foobar diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 0b7ffaa2c..bbb3751ae 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -149,6 +149,7 @@ nav: - 'API': 'operations/api.md' - 'Ping': 'operations/ping.md' - 'Observability': + - 'Overview': 'observability/overview.md' - 'Logs': 'observability/logs.md' - 'Access Logs': 'observability/access-logs.md' - 'Metrics': diff --git a/integration/access_log_test.go b/integration/access_log_test.go index 8e2c9581f..b5e4ec1ef 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -61,7 +61,7 @@ func (s *AccessLogSuite) TestAccessLog() { ensureWorkingDirectoryIsClean() // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) defer func() { traefikLog, err := os.ReadFile(traefikTestLogFile) @@ -130,7 +130,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -194,7 +194,7 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -304,7 +304,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -410,7 +410,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -454,7 +454,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.waitForTraefik("server1") @@ -494,7 +494,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -534,7 +534,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -575,7 +575,7 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() { } // Start Traefik - s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) + s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml")) s.checkStatsForLogFile() @@ -603,6 +603,56 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() { s.checkNoOtherTraefikProblems() } +func (s *AccessLogSuite) TestAccessLogDisabledForInternals() { + ensureWorkingDirectoryIsClean() + + file := s.adaptFile("fixtures/access_log/access_log_ping.toml", struct{}{}) + + // Start Traefik. + s.traefikCmd(withConfigFile(file)) + + defer func() { + traefikLog, err := os.ReadFile(traefikTestLogFile) + require.NoError(s.T(), err) + log.Info().Msg(string(traefikLog)) + }() + + // waitForTraefik makes at least one call to the rawdata api endpoint, + // but the logs for this endpoint are ignored in checkAccessLogOutput. + s.waitForTraefik("customPing") + + s.checkStatsForLogFile() + + // Verify Traefik started OK. + s.checkTraefikStarted() + + // Make some requests on the internal ping router. + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/ping", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + + // Make some requests on the custom ping router. + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/ping", nil) + require.NoError(s.T(), err) + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody()) + require.NoError(s.T(), err) + + // Verify access.log output as expected. + count := s.checkAccessLogOutput() + + require.Equal(s.T(), 0, count) + + // Verify no other Traefik problems. + s.checkNoOtherTraefikProblems() +} + func (s *AccessLogSuite) checkNoOtherTraefikProblems() { traefikLog, err := os.ReadFile(traefikTestLogFile) require.NoError(s.T(), err) @@ -612,6 +662,8 @@ func (s *AccessLogSuite) checkNoOtherTraefikProblems() { } func (s *AccessLogSuite) checkAccessLogOutput() int { + s.T().Helper() + lines := s.extractLines() count := 0 for i, line := range lines { @@ -624,6 +676,8 @@ func (s *AccessLogSuite) checkAccessLogOutput() int { } func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int { + s.T().Helper() + lines := s.extractLines() count := 0 for i, line := range lines { @@ -641,6 +695,8 @@ func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue } func (s *AccessLogSuite) extractLines() []string { + s.T().Helper() + accessLog, err := os.ReadFile(traefikTestAccessLogFile) require.NoError(s.T(), err) @@ -656,6 +712,8 @@ func (s *AccessLogSuite) extractLines() []string { } func (s *AccessLogSuite) checkStatsForLogFile() { + s.T().Helper() + err := try.Do(1*time.Second, func() error { if _, errStat := os.Stat(traefikTestLogFile); errStat != nil { return fmt.Errorf("could not get stats for log file: %w", errStat) @@ -671,6 +729,8 @@ func ensureWorkingDirectoryIsClean() { } func (s *AccessLogSuite) checkTraefikStarted() []byte { + s.T().Helper() + traefikLog, err := os.ReadFile(traefikTestLogFile) require.NoError(s.T(), err) if len(traefikLog) > 0 { @@ -680,6 +740,8 @@ func (s *AccessLogSuite) checkTraefikStarted() []byte { } func (s *BaseSuite) CheckAccessLogFormat(line string, i int) { + s.T().Helper() + results, err := accesslog.ParseAccessLog(line) require.NoError(s.T(), err) assert.Len(s.T(), results, 14) @@ -692,6 +754,8 @@ func (s *BaseSuite) CheckAccessLogFormat(line string, i int) { } func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) { + s.T().Helper() + results, err := accesslog.ParseAccessLog(line) require.NoError(s.T(), err) assert.Len(s.T(), results, 14) diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log/access_log_base.toml similarity index 100% rename from integration/fixtures/access_log_config.toml rename to integration/fixtures/access_log/access_log_base.toml diff --git a/integration/fixtures/access_log/access_log_ping.toml b/integration/fixtures/access_log/access_log_ping.toml new file mode 100644 index 000000000..4e85b93bc --- /dev/null +++ b/integration/fixtures/access_log/access_log_ping.toml @@ -0,0 +1,30 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "ERROR" + filePath = "traefik.log" + +[accessLog] + filePath = "access.log" + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[api] + insecure = true + +[ping] + +[providers] + [providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## +[http.routers] + [http.routers.customPing] + entryPoints = ["web"] + rule = "PathPrefix(`/ping`)" + service = "ping@internal" diff --git a/integration/fixtures/throttling/simple.toml b/integration/fixtures/throttling/simple.toml index 668d4988b..cd98c44f1 100644 --- a/integration/fixtures/throttling/simple.toml +++ b/integration/fixtures/throttling/simple.toml @@ -19,5 +19,6 @@ insecure = true [metrics] + addInternals = true [metrics.prometheus] buckets = [0.1,0.3,1.2,5.0] diff --git a/integration/fixtures/tracing/simple-opentelemetry.toml b/integration/fixtures/tracing/simple-opentelemetry.toml index 77d779800..0599bb18d 100644 --- a/integration/fixtures/tracing/simple-opentelemetry.toml +++ b/integration/fixtures/tracing/simple-opentelemetry.toml @@ -9,6 +9,8 @@ [api] insecure = true +[ping] + [entryPoints] [entryPoints.web] address = ":8000" @@ -47,6 +49,10 @@ Service = "service3" Middlewares = ["retry", "basic-auth"] Rule = "Path(`/auth`)" + [http.routers.customPing] + entryPoints = ["web"] + rule = "PathPrefix(`/ping`)" + service = "ping@internal" [http.middlewares] [http.middlewares.retry.retry] diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index 28d72f8cd..269d17d69 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -57,7 +57,7 @@ func (s *LogRotationSuite) TearDownSuite() { func (s *LogRotationSuite) TestAccessLogRotation() { // Start Traefik - cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml")) + cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log/access_log_base.toml")) defer s.displayTraefikLogFile(traefikTestLogFile) // Verify Traefik started ok diff --git a/integration/simple_test.go b/integration/simple_test.go index d2c3be077..8e2f14ec1 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -287,6 +287,10 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint() { err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_")) require.NoError(s.T(), err) + + // No metrics for internals. + err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyNotContains("router=\"api@internal\"", "service=\"api@internal\"")) + require.NoError(s.T(), err) } func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService() { diff --git a/integration/tracing_test.go b/integration/tracing_test.go index c94f3c58b..ef92015bd 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -414,6 +414,67 @@ func (s *TracingSuite) TestOpentelemetryAuth() { s.checkTraceContent(contains) } +func (s *TracingSuite) TestNoInternals() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ + WhoamiIP: s.whoamiIP, + WhoamiPort: s.whoamiPort, + IP: s.otelCollectorIP, + IsHTTP: true, + }) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + err = try.GetRequest("http://127.0.0.1:8080/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/search") + require.NoError(s.T(), err) + + req := &http.Request{ + Method: http.MethodGet, + URL: baseURL, + } + // Wait for traces to be available. + time.Sleep(10 * time.Second) + resp, err := try.Response(req, 5*time.Second) + require.NoError(s.T(), err) + + out := &TraceResponse{} + content, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + err = json.Unmarshal(content, &out) + require.NoError(s.T(), err) + + s.NotEmptyf(len(out.Traces), "expected at least one trace") + + for _, t := range out.Traces { + baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/traces/" + t.TraceID) + require.NoError(s.T(), err) + + req := &http.Request{ + Method: http.MethodGet, + URL: baseURL, + } + + resp, err := try.Response(req, 5*time.Second) + require.NoError(s.T(), err) + + content, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + + require.NotContains(s.T(), content, "@internal") + } +} + func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) { s.T().Helper() diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index bb5778671..19d6ea24c 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -197,6 +197,7 @@ type Tracing struct { Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` + AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } @@ -218,7 +219,7 @@ type Providers struct { KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Rest *rest.Provider ` description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index 33e700697..1d84a1433 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -12,9 +12,10 @@ import ( // Muxer handles routing with rules. type Muxer struct { - routes routes - parser predicate.Parser - parserV2 predicate.Parser + routes routes + parser predicate.Parser + parserV2 predicate.Parser + defaultHandler http.Handler } // NewMuxer returns a new muxer instance. @@ -40,8 +41,9 @@ func NewMuxer() (*Muxer, error) { } return &Muxer{ - parser: parser, - parserV2: parserV2, + parser: parser, + parserV2: parserV2, + defaultHandler: http.NotFoundHandler(), }, nil } @@ -55,7 +57,12 @@ func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } - http.NotFoundHandler().ServeHTTP(rw, req) + m.defaultHandler.ServeHTTP(rw, req) +} + +// SetDefaultHandler sets the muxer default handler. +func (m *Muxer) SetDefaultHandler(handler http.Handler) { + m.defaultHandler = handler } // GetRulePriority computes the priority for a given rule. diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json index 97e678c85..d756ba779 100644 --- a/pkg/redactor/testdata/anonymized-static-config.json +++ b/pkg/redactor/testdata/anonymized-static-config.json @@ -407,4 +407,4 @@ } } } -} +} \ No newline at end of file diff --git a/pkg/server/middleware/chainbuilder.go b/pkg/server/middleware/chainbuilder.go deleted file mode 100644 index 980d7a2b5..000000000 --- a/pkg/server/middleware/chainbuilder.go +++ /dev/null @@ -1,63 +0,0 @@ -package middleware - -import ( - "context" - - "github.com/containous/alice" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/metrics" - "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" - tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" - "go.opentelemetry.io/otel/trace" -) - -// ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others. -type ChainBuilder struct { - metricsRegistry metrics.Registry - accessLoggerMiddleware *accesslog.Handler - tracer trace.Tracer -} - -// NewChainBuilder Creates a new ChainBuilder. -func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer) *ChainBuilder { - return &ChainBuilder{ - metricsRegistry: metricsRegistry, - accessLoggerMiddleware: accessLoggerMiddleware, - tracer: tracer, - } -} - -// Build a middleware chain by entry point. -func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.Chain { - chain := alice.New() - - if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) { - chain = chain.Append(capture.Wrap) - } - - if c.accessLoggerMiddleware != nil { - chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware)) - } - - if c.tracer != nil { - chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName)) - } - - if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() { - metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName) - chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)) - } - - return chain -} - -// Close accessLogger and tracer. -func (c *ChainBuilder) Close() { - if c.accessLoggerMiddleware != nil { - if err := c.accessLoggerMiddleware.Close(); err != nil { - log.Error().Err(err).Msg("Could not close the access log file") - } - } -} diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 655c1a047..3065b16ae 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -387,6 +387,9 @@ 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 tracing.WrapMiddleware(ctx, middleware), nil } diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go new file mode 100644 index 000000000..af65cf480 --- /dev/null +++ b/pkg/server/middleware/observability.go @@ -0,0 +1,140 @@ +package middleware + +import ( + "context" + "io" + "net/http" + "strings" + + "github.com/containous/alice" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/metrics" + "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" + tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" + "go.opentelemetry.io/otel/trace" +) + +// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement. +type ObservabilityMgr struct { + config static.Configuration + accessLoggerMiddleware *accesslog.Handler + metricsRegistry metrics.Registry + tracer trace.Tracer + tracerCloser io.Closer +} + +// NewObservabilityMgr creates a new ObservabilityMgr. +func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer, tracerCloser io.Closer) *ObservabilityMgr { + return &ObservabilityMgr{ + config: config, + metricsRegistry: metricsRegistry, + accessLoggerMiddleware: accessLoggerMiddleware, + tracer: tracer, + tracerCloser: tracerCloser, + } +} + +// BuildEPChain an observability middleware chain by entry point. +func (c *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string) alice.Chain { + chain := alice.New() + + if c == nil { + return chain + } + + if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) { + if c.ShouldAddAccessLogs(resourceName) || c.ShouldAddMetrics(resourceName) { + chain = chain.Append(capture.Wrap) + } + } + + if c.accessLoggerMiddleware != nil && c.ShouldAddAccessLogs(resourceName) { + chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware)) + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil + }) + } + + if c.tracer != nil && c.ShouldAddTracing(resourceName) { + chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName)) + } + + if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() && c.ShouldAddMetrics(resourceName) { + metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName) + + if c.tracer != nil && c.ShouldAddTracing(resourceName) { + chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)) + } else { + chain = chain.Append(metricsHandler) + } + } + + return chain +} + +// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource. +func (c *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool { + if c == nil { + return false + } + + return c.config.AccessLog != nil && (c.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal")) +} + +// ShouldAddMetrics returns whether the metrics should be enabled for the given resource. +func (c *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool { + if c == nil { + return false + } + + return c.config.Metrics != nil && (c.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal")) +} + +// ShouldAddTracing returns whether the tracing should be enabled for the given resource. +func (c *ObservabilityMgr) ShouldAddTracing(resourceName string) bool { + if c == nil { + return false + } + + return c.config.Tracing != nil && (c.config.Tracing.AddInternals || !strings.HasSuffix(resourceName, "@internal")) +} + +// MetricsRegistry is an accessor to the metrics registry. +func (c *ObservabilityMgr) MetricsRegistry() metrics.Registry { + if c == nil { + return nil + } + + return c.metricsRegistry +} + +// Close closes the accessLogger and tracer. +func (c *ObservabilityMgr) Close() { + if c == nil { + return + } + + if c.accessLoggerMiddleware != nil { + if err := c.accessLoggerMiddleware.Close(); err != nil { + log.Error().Err(err).Msg("Could not close the access log file") + } + } + + if c.tracerCloser != nil { + if err := c.tracerCloser.Close(); err != nil { + log.Error().Err(err).Msg("Could not close the tracer") + } + } +} + +func (c *ObservabilityMgr) RotateAccessLogs() error { + if c.accessLoggerMiddleware == nil { + return nil + } + + return c.accessLoggerMiddleware.Rotate() +} diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index bfa99cfb0..eae645383 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" @@ -35,21 +34,19 @@ type serviceManager interface { type Manager struct { routerHandlers map[string]http.Handler serviceManager serviceManager - metricsRegistry metrics.Registry + observabilityMgr *middleware.ObservabilityMgr middlewaresBuilder middlewareBuilder - chainBuilder *middleware.ChainBuilder conf *runtime.Configuration tlsManager *tls.Manager } // NewManager creates a new Manager. -func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager { +func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, observabilityMgr *middleware.ObservabilityMgr, tlsManager *tls.Manager) *Manager { return &Manager{ routerHandlers: make(map[string]http.Handler), serviceManager: serviceManager, - metricsRegistry: metricsRegistry, + observabilityMgr: observabilityMgr, middlewaresBuilder: middlewaresBuilder, - chainBuilder: chainBuilder, conf: conf, tlsManager: tlsManager, } @@ -73,49 +70,49 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() ctx := logger.WithContext(rootCtx) - handler, err := m.buildEntryPointHandler(ctx, routers) + handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers) if err != nil { logger.Error().Err(err).Send() continue } - handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil - }).Then(handler) - if err != nil { - logger.Error().Err(err).Send() - entryPointHandlers[entryPointName] = handler - } else { - entryPointHandlers[entryPointName] = handlerWithAccessLog - } + entryPointHandlers[entryPointName] = handler } + // Create default handlers. for _, entryPointName := range entryPoints { logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() ctx := logger.WithContext(rootCtx) handler, ok := entryPointHandlers[entryPointName] - if !ok || handler == nil { - handler = BuildDefaultHTTPRouter() + if ok || handler != nil { + continue } - handlerWithMiddlewares, err := m.chainBuilder.Build(ctx, entryPointName).Then(handler) + handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter()) if err != nil { logger.Error().Err(err).Send() continue } - entryPointHandlers[entryPointName] = handlerWithMiddlewares + entryPointHandlers[entryPointName] = handler } return entryPointHandlers } -func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.RouterInfo) (http.Handler, error) { +func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) { muxer, err := httpmuxer.NewMuxer() if err != nil { return nil, err } + defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "defaultHandler").Then(http.NotFoundHandler()) + if err != nil { + return nil, err + } + + muxer.SetDefaultHandler(defaultHandler) + for routerName, routerConfig := range configs { logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger() ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName)) @@ -131,6 +128,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string continue } + observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service) + handler, err = observabilityChain.Then(handler) + if err != nil { + routerConfig.AddError(err, true) + logger.Error().Err(err).Send() + continue + } + if err = muxer.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil { routerConfig.AddError(err, true) logger.Error().Err(err).Send() @@ -167,6 +172,12 @@ 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)) { + 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) @@ -200,10 +211,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn chain := alice.New() + if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() && + m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service)) { + 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)) { + return chain.Extend(*mHandler).Then(sHandler) + } + chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service))) - if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() { - metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, 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(tracing.WrapMiddleware(ctx, metricsHandler)) } diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 21e0de141..eb83ed25d 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -9,21 +9,15 @@ import ( "testing" "time" - "github.com/containous/alice" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/metrics" - "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" - "github.com/traefik/traefik/v3/pkg/middlewares/capture" "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/tls" - "github.com/traefik/traefik/v3/pkg/types" ) func TestRouterManager_Get(t *testing.T) { @@ -319,10 +313,9 @@ func TestRouterManager_Get(t *testing.T) { roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -341,126 +334,6 @@ func TestRouterManager_Get(t *testing.T) { } } -func TestAccessLog(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - - t.Cleanup(func() { server.Close() }) - - testCases := []struct { - desc string - routersConfig map[string]*dynamic.Router - serviceConfig map[string]*dynamic.Service - middlewaresConfig map[string]*dynamic.Middleware - entryPoints []string - expected string - }{ - { - desc: "apply routerName in accesslog (first match)", - routersConfig: map[string]*dynamic.Router{ - "foo": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`foo.bar`)", - }, - "bar": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`bar.foo`)", - }, - }, - serviceConfig: map[string]*dynamic.Service{ - "foo-service": { - LoadBalancer: &dynamic.ServersLoadBalancer{ - Servers: []dynamic.Server{ - { - URL: server.URL, - }, - }, - }, - }, - }, - entryPoints: []string{"web"}, - expected: "foo", - }, - { - desc: "apply routerName in accesslog (second match)", - routersConfig: map[string]*dynamic.Router{ - "foo": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`bar.foo`)", - }, - "bar": { - EntryPoints: []string{"web"}, - Service: "foo-service", - Rule: "Host(`foo.bar`)", - }, - }, - serviceConfig: map[string]*dynamic.Service{ - "foo-service": { - LoadBalancer: &dynamic.ServersLoadBalancer{ - Servers: []dynamic.Server{ - { - URL: server.URL, - }, - }, - }, - }, - }, - entryPoints: []string{"web"}, - expected: "bar", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - rtConf := runtime.NewConfig(dynamic.Configuration{ - HTTP: &dynamic.HTTPConfiguration{ - Services: test.serviceConfig, - Routers: test.routersConfig, - Middlewares: test.middlewaresConfig, - }, - }) - - roundTripperManager := service.NewRoundTripperManager(nil) - roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) - middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) - tlsManager := tls.NewManager() - - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) - - w := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) - - accesslogger, err := accesslog.NewHandler(&types.AccessLog{ - Format: "json", - }) - require.NoError(t, err) - - reqHost := requestdecorator.New(nil) - - chain := alice.New() - chain = chain.Append(capture.Wrap) - chain = chain.Append(accesslog.WrapHandler(accesslogger)) - handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP) - - data := accesslog.GetLogData(req) - require.NotNil(t, data) - - assert.Equal(t, test.expected, data.Core[accesslog.RouterName]) - })) - require.NoError(t, err) - - handler.ServeHTTP(w, req) - }) - } -} - func TestRuntimeConfiguration(t *testing.T) { testCases := []struct { desc string @@ -788,11 +661,10 @@ func TestRuntimeConfiguration(t *testing.T) { roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) @@ -866,10 +738,9 @@ func TestProviderOnMiddlewares(t *testing.T) { roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -935,10 +806,9 @@ func BenchmarkRouterServe(b *testing.B) { serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index d7a1bf68c..1ece7096a 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -6,7 +6,6 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/server/middleware" tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp" "github.com/traefik/traefik/v3/pkg/server/router" @@ -25,13 +24,12 @@ type RouterFactory struct { entryPointsTCP []string entryPointsUDP []string - managerFactory *service.ManagerFactory - metricsRegistry metrics.Registry + managerFactory *service.ManagerFactory pluginBuilder middleware.PluginsBuilder - chainBuilder *middleware.ChainBuilder - tlsManager *tls.Manager + observabilityMgr *middleware.ObservabilityMgr + tlsManager *tls.Manager dialerManager *tcp.DialerManager @@ -40,7 +38,7 @@ type RouterFactory struct { // NewRouterFactory creates a new RouterFactory. func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, - chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder, metricsRegistry metrics.Registry, dialerManager *tcp.DialerManager, + observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager, ) *RouterFactory { var entryPointsTCP, entryPointsUDP []string for name, cfg := range staticConfiguration.EntryPoints { @@ -58,14 +56,13 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * } return &RouterFactory{ - entryPointsTCP: entryPointsTCP, - entryPointsUDP: entryPointsUDP, - managerFactory: managerFactory, - metricsRegistry: metricsRegistry, - tlsManager: tlsManager, - chainBuilder: chainBuilder, - pluginBuilder: pluginBuilder, - dialerManager: dialerManager, + entryPointsTCP: entryPointsTCP, + entryPointsUDP: entryPointsUDP, + managerFactory: managerFactory, + observabilityMgr: observabilityMgr, + tlsManager: tlsManager, + pluginBuilder: pluginBuilder, + dialerManager: dialerManager, } } @@ -83,7 +80,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) - routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager) + routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index b935df89e..ebd923f62 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -9,7 +9,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/tcp" @@ -51,12 +50,12 @@ func TestReuseService(t *testing.T) { roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil) tlsManager := tls.NewManager() dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager) + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})) @@ -189,12 +188,13 @@ func TestServerResponseEmptyBackend(t *testing.T) { roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil) tlsManager := tls.NewManager() dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager) + observabiltyMgr := middleware.NewObservabilityMgr(staticConfig, nil, nil, nil, nil) + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, observabiltyMgr, nil, dialerManager) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)})) @@ -232,14 +232,12 @@ func TestInternalServices(t *testing.T) { roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil) + managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil) tlsManager := tls.NewManager() - voidRegistry := metrics.NewVoidRegistry() - dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(voidRegistry, nil, nil), nil, voidRegistry, dialerManager) + factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})) diff --git a/pkg/server/server.go b/pkg/server/server.go index 0f4379f04..9c96bdcc6 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -3,47 +3,39 @@ package server import ( "context" "errors" - "io" "os" "os/signal" "time" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/metrics" - "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/middleware" ) // Server is the reverse-proxy/load-balancer engine. type Server struct { - watcher *ConfigurationWatcher - tcpEntryPoints TCPEntryPoints - udpEntryPoints UDPEntryPoints - chainBuilder *middleware.ChainBuilder - - accessLoggerMiddleware *accesslog.Handler + watcher *ConfigurationWatcher + tcpEntryPoints TCPEntryPoints + udpEntryPoints UDPEntryPoints + observabilityMgr *middleware.ObservabilityMgr signals chan os.Signal stopChan chan bool routinesPool *safe.Pool - - tracerCloser io.Closer } // NewServer returns an initialized Server. -func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, tracerCloser io.Closer) *Server { +func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, observabilityMgr *middleware.ObservabilityMgr) *Server { srv := &Server{ - watcher: watcher, - tcpEntryPoints: entryPoints, - chainBuilder: chainBuilder, - accessLoggerMiddleware: accessLoggerMiddleware, - signals: make(chan os.Signal, 1), - stopChan: make(chan bool, 1), - routinesPool: routinesPool, - udpEntryPoints: entryPointsUDP, - tracerCloser: tracerCloser, + watcher: watcher, + tcpEntryPoints: entryPoints, + observabilityMgr: observabilityMgr, + signals: make(chan os.Signal, 1), + stopChan: make(chan bool, 1), + routinesPool: routinesPool, + udpEntryPoints: entryPointsUDP, } srv.configureSignals() @@ -105,13 +97,7 @@ func (s *Server) Close() { close(s.stopChan) - s.chainBuilder.Close() - - if s.tracerCloser != nil { - if err := s.tracerCloser.Close(); err != nil { - log.Error().Err(err).Msg("Could not close the tracer") - } - } + s.observabilityMgr.Close() cancel() } diff --git a/pkg/server/server_signals.go b/pkg/server/server_signals.go index 0987abb30..c582fce5c 100644 --- a/pkg/server/server_signals.go +++ b/pkg/server/server_signals.go @@ -24,10 +24,8 @@ func (s *Server) listenSignals(ctx context.Context) { if sig == syscall.SIGUSR1 { log.Info().Msgf("Closing and re-opening log files for rotation: %+v", sig) - if s.accessLoggerMiddleware != nil { - if err := s.accessLoggerMiddleware.Rotate(); err != nil { - log.Error().Err(err).Msg("Error rotating access log") - } + if err := s.observabilityMgr.RotateAccessLogs(); err != nil { + log.Error().Err(err).Msg("Error rotating access log") } } } diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go index 524ae01d9..abff2d5a6 100644 --- a/pkg/server/service/managerfactory.go +++ b/pkg/server/service/managerfactory.go @@ -10,11 +10,12 @@ import ( "github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/safe" + "github.com/traefik/traefik/v3/pkg/server/middleware" ) // ManagerFactory a factory of service manager. type ManagerFactory struct { - metricsRegistry metrics.Registry + observabilityMgr *middleware.ObservabilityMgr roundTripperManager *RoundTripperManager @@ -29,9 +30,9 @@ type ManagerFactory struct { } // NewManagerFactory creates a new ManagerFactory. -func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory { +func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, observabilityMgr *middleware.ObservabilityMgr, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory { factory := &ManagerFactory{ - metricsRegistry: metricsRegistry, + observabilityMgr: observabilityMgr, routinesPool: routinesPool, roundTripperManager: roundTripperManager, acmeHTTPHandler: acmeHTTPHandler, @@ -72,7 +73,7 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s // Build creates a service manager. func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers { - svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager) + svcManager := NewManager(configuration.Services, f.observabilityMgr, f.routinesPool, f.roundTripperManager) var apiHandler http.Handler if f.api != nil { diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 2d71791a8..b01d2327c 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -20,12 +20,12 @@ import ( "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/healthcheck" "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/cookie" + "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror" @@ -42,7 +42,7 @@ type RoundTripperGetter interface { // Manager The service manager. type Manager struct { routinePool *safe.Pool - metricsRegistry metrics.Registry + observabilityMgr *middleware.ObservabilityMgr bufferPool httputil.BufferPool roundTripperManager RoundTripperGetter @@ -53,10 +53,10 @@ type Manager struct { } // NewManager creates a new Manager. -func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { +func NewManager(configs map[string]*runtime.ServiceInfo, observabilityMgr *middleware.ObservabilityMgr, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { return &Manager{ routinePool: routinePool, - metricsRegistry: metricsRegistry, + observabilityMgr: observabilityMgr, bufferPool: newBufferPool(), roundTripperManager: roundTripperManager, services: make(map[string]http.Handler), @@ -302,12 +302,17 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName proxy := buildSingleHostProxy(target, passHostHeader, time.Duration(flushInterval), roundTripper, m.bufferPool) - 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) + // Prevents from enabling observability for internal resources. - if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() { - metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName) + if m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, serviceName)) { + 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) + } + + if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() && + m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, serviceName)) { + metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName) proxy, err = alice.New(). Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)). @@ -317,7 +322,9 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName } } - proxy = tracingMiddle.NewService(ctx, serviceName, proxy) + if m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, serviceName)) { + proxy = tracingMiddle.NewService(ctx, serviceName, proxy) + } lb.Add(proxyName, proxy, server.Weight) @@ -330,7 +337,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName if service.HealthCheck != nil { m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker( ctx, - m.metricsRegistry, + m.observabilityMgr.MetricsRegistry(), service.HealthCheck, lb, info, diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index 6bb382920..79c6cf5d3 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -42,6 +42,11 @@ func NewTracing(conf *static.Tracing) (trace.Tracer, io.Closer, error) { // TracerFromContext extracts the trace.Tracer from the given context. func TracerFromContext(ctx context.Context) trace.Tracer { + // Prevent picking trace.noopSpan tracer. + if !trace.SpanContextFromContext(ctx).IsValid() { + return nil + } + span := trace.SpanFromContext(ctx) if span != nil && span.TracerProvider() != nil { return span.TracerProvider().Tracer("github.com/traefik/traefik") diff --git a/pkg/types/logs.go b/pkg/types/logs.go index 657e56102..e0a50ec71 100644 --- a/pkg/types/logs.go +++ b/pkg/types/logs.go @@ -45,6 +45,7 @@ type AccessLog struct { Filters *AccessLogFilters `description:"Access log filters, used to keep only specific access logs." json:"filters,omitempty" toml:"filters,omitempty" yaml:"filters,omitempty" export:"true"` Fields *AccessLogFields `description:"AccessLogFields." json:"fields,omitempty" toml:"fields,omitempty" yaml:"fields,omitempty" export:"true"` BufferingSize int64 `description:"Number of access log lines to process in a buffered way." json:"bufferingSize,omitempty" toml:"bufferingSize,omitempty" yaml:"bufferingSize,omitempty" export:"true"` + AddInternals bool `description:"Enables access log for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` } // SetDefaults sets the default values. diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index 665333630..571666e23 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -10,6 +10,8 @@ import ( // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems. type Metrics struct { + AddInternals bool `description:"Enables metrics for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` + Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`