Remove observability for internal resources

This commit is contained in:
Romain 2024-01-30 16:28:05 +01:00 committed by GitHub
parent d02be003ab
commit 8b77f0c2dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 594 additions and 317 deletions

View file

@ -193,10 +193,13 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
tsProviders := initTailscaleProviders(staticConfiguration, &providerAggregator) tsProviders := initTailscaleProviders(staticConfiguration, &providerAggregator)
// Metrics // Observability
metricRegistries := registerMetricClients(staticConfiguration.Metrics) metricRegistries := registerMetricClients(staticConfiguration.Metrics)
metricsRegistry := metrics.NewMultiRegistry(metricRegistries) metricsRegistry := metrics.NewMultiRegistry(metricRegistries)
accessLog := setupAccessLog(staticConfiguration.AccessLog)
tracer, tracerCloser := setupTracing(staticConfiguration.Tracing)
observabilityMgr := middleware.NewObservabilityMgr(*staticConfiguration, metricsRegistry, accessLog, tracer, tracerCloser)
// Entrypoints // Entrypoints
@ -263,14 +266,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
roundTripperManager := service.NewRoundTripperManager(spiffeX509Source) roundTripperManager := service.NewRoundTripperManager(spiffeX509Source)
dialerManager := tcp.NewDialerManager(spiffeX509Source) dialerManager := tcp.NewDialerManager(spiffeX509Source)
acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider) acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider)
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler) managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, roundTripperManager, acmeHTTPHandler)
// Router factory // Router factory
accessLog := setupAccessLog(staticConfiguration.AccessLog)
tracer, tracerCloser := setupTracing(staticConfiguration.Tracing) routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager)
chainBuilder := middleware.NewChainBuilder(metricsRegistry, accessLog, tracer)
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry, dialerManager)
// Watcher // 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 { func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler {

View file

@ -693,3 +693,13 @@ Here are two possible transition strategies:
This allows continued compatibility with the existing infrastructure. This allows continued compatibility with the existing infrastructure.
Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information. 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)

View file

@ -26,6 +26,26 @@ accessLog: {}
--accesslog=true --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` ### `filePath`
By default access logs are written to the standard output. By default access logs are written to the standard output.

View file

@ -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. 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 ## Global Metrics
| Metric | Type | [Labels](#labels) | Description | | Metric | Type | [Labels](#labels) | Description |

View file

@ -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.

View file

@ -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. Please check our dedicated [OTel docs](./opentelemetry.md) to learn more.
## Configuration ## Configuration
To enable the tracing: To enable the tracing:
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
@ -34,6 +32,26 @@ tracing: {}
### Common Options ### 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` #### `serviceName`
_Required, Default="traefik"_ _Required, Default="traefik"_

View file

@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
`--accesslog`: `--accesslog`:
Access log settings. (Default: ```false```) Access log settings. (Default: ```false```)
`--accesslog.addinternals`:
Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```)
`--accesslog.bufferingsize`: `--accesslog.bufferingsize`:
Number of access log lines to process in a buffered way. (Default: ```0```) 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`: `--log.nocolor`:
When using the 'common' format, disables the colorized output. (Default: ```false```) 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`: `--metrics.datadog`:
Datadog metrics exporter type. (Default: ```false```) Datadog metrics exporter type. (Default: ```false```)
@ -993,6 +999,9 @@ Defines the allowed SPIFFE trust domain.
`--tracing`: `--tracing`:
OpenTracing configuration. (Default: ```false```) OpenTracing configuration. (Default: ```false```)
`--tracing.addinternals`:
Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```)
`--tracing.globalattributes.<name>`: `--tracing.globalattributes.<name>`:
Defines additional attributes (key:value) on all spans. Defines additional attributes (key:value) on all spans.

View file

@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
`TRAEFIK_ACCESSLOG`: `TRAEFIK_ACCESSLOG`:
Access log settings. (Default: ```false```) Access log settings. (Default: ```false```)
`TRAEFIK_ACCESSLOG_ADDINTERNALS`:
Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```)
`TRAEFIK_ACCESSLOG_BUFFERINGSIZE`: `TRAEFIK_ACCESSLOG_BUFFERINGSIZE`:
Number of access log lines to process in a buffered way. (Default: ```0```) 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`: `TRAEFIK_LOG_NOCOLOR`:
When using the 'common' format, disables the colorized output. (Default: ```false```) 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`: `TRAEFIK_METRICS_DATADOG`:
Datadog metrics exporter type. (Default: ```false```) Datadog metrics exporter type. (Default: ```false```)
@ -993,6 +999,9 @@ Defines the allowed SPIFFE trust domain.
`TRAEFIK_TRACING`: `TRAEFIK_TRACING`:
OpenTracing configuration. (Default: ```false```) OpenTracing configuration. (Default: ```false```)
`TRAEFIK_TRACING_ADDINTERNALS`:
Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```)
`TRAEFIK_TRACING_GLOBALATTRIBUTES_<NAME>`: `TRAEFIK_TRACING_GLOBALATTRIBUTES_<NAME>`:
Defines additional attributes (key:value) on all spans. Defines additional attributes (key:value) on all spans.

View file

@ -277,6 +277,7 @@
disableDashboardAd = true disableDashboardAd = true
[metrics] [metrics]
addInternals = true
[metrics.prometheus] [metrics.prometheus]
buckets = [42.0, 42.0] buckets = [42.0, 42.0]
addEntryPointsLabels = true addEntryPointsLabels = true
@ -351,6 +352,7 @@
filePath = "foobar" filePath = "foobar"
format = "foobar" format = "foobar"
bufferingSize = 42 bufferingSize = 42
addInternals = true
[accessLog.filters] [accessLog.filters]
statusCodes = ["foobar", "foobar"] statusCodes = ["foobar", "foobar"]
retryAttempts = true retryAttempts = true
@ -369,6 +371,7 @@
[tracing] [tracing]
serviceName = "foobar" serviceName = "foobar"
sampleRate = 42.0 sampleRate = 42.0
addInternals = true
[tracing.headers] [tracing.headers]
name0 = "foobar" name0 = "foobar"
name1 = "foobar" name1 = "foobar"

View file

@ -308,6 +308,7 @@ api:
debug: true debug: true
disableDashboardAd: true disableDashboardAd: true
metrics: metrics:
addInternals: true
prometheus: prometheus:
buckets: buckets:
- 42 - 42
@ -399,6 +400,7 @@ accessLog:
name0: foobar name0: foobar
name1: foobar name1: foobar
bufferingSize: 42 bufferingSize: 42
addInternals: true
tracing: tracing:
serviceName: foobar serviceName: foobar
headers: headers:
@ -408,6 +410,7 @@ tracing:
name0: foobar name0: foobar
name1: foobar name1: foobar
sampleRate: 42 sampleRate: 42
addInternals: true
otlp: otlp:
grpc: grpc:
endpoint: foobar endpoint: foobar

View file

@ -149,6 +149,7 @@ nav:
- 'API': 'operations/api.md' - 'API': 'operations/api.md'
- 'Ping': 'operations/ping.md' - 'Ping': 'operations/ping.md'
- 'Observability': - 'Observability':
- 'Overview': 'observability/overview.md'
- 'Logs': 'observability/logs.md' - 'Logs': 'observability/logs.md'
- 'Access Logs': 'observability/access-logs.md' - 'Access Logs': 'observability/access-logs.md'
- 'Metrics': - 'Metrics':

View file

@ -61,7 +61,7 @@ func (s *AccessLogSuite) TestAccessLog() {
ensureWorkingDirectoryIsClean() ensureWorkingDirectoryIsClean()
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
defer func() { defer func() {
traefikLog, err := os.ReadFile(traefikTestLogFile) traefikLog, err := os.ReadFile(traefikTestLogFile)
@ -130,7 +130,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -194,7 +194,7 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -304,7 +304,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -410,7 +410,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -454,7 +454,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.waitForTraefik("server1") s.waitForTraefik("server1")
@ -494,7 +494,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -534,7 +534,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -575,7 +575,7 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
} }
// Start Traefik // Start Traefik
s.traefikCmd(withConfigFile("fixtures/access_log_config.toml")) s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
s.checkStatsForLogFile() s.checkStatsForLogFile()
@ -603,6 +603,56 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
s.checkNoOtherTraefikProblems() 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() { func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
traefikLog, err := os.ReadFile(traefikTestLogFile) traefikLog, err := os.ReadFile(traefikTestLogFile)
require.NoError(s.T(), err) require.NoError(s.T(), err)
@ -612,6 +662,8 @@ func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
} }
func (s *AccessLogSuite) checkAccessLogOutput() int { func (s *AccessLogSuite) checkAccessLogOutput() int {
s.T().Helper()
lines := s.extractLines() lines := s.extractLines()
count := 0 count := 0
for i, line := range lines { for i, line := range lines {
@ -624,6 +676,8 @@ func (s *AccessLogSuite) checkAccessLogOutput() int {
} }
func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int { func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int {
s.T().Helper()
lines := s.extractLines() lines := s.extractLines()
count := 0 count := 0
for i, line := range lines { for i, line := range lines {
@ -641,6 +695,8 @@ func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue
} }
func (s *AccessLogSuite) extractLines() []string { func (s *AccessLogSuite) extractLines() []string {
s.T().Helper()
accessLog, err := os.ReadFile(traefikTestAccessLogFile) accessLog, err := os.ReadFile(traefikTestAccessLogFile)
require.NoError(s.T(), err) require.NoError(s.T(), err)
@ -656,6 +712,8 @@ func (s *AccessLogSuite) extractLines() []string {
} }
func (s *AccessLogSuite) checkStatsForLogFile() { func (s *AccessLogSuite) checkStatsForLogFile() {
s.T().Helper()
err := try.Do(1*time.Second, func() error { err := try.Do(1*time.Second, func() error {
if _, errStat := os.Stat(traefikTestLogFile); errStat != nil { if _, errStat := os.Stat(traefikTestLogFile); errStat != nil {
return fmt.Errorf("could not get stats for log file: %w", errStat) return fmt.Errorf("could not get stats for log file: %w", errStat)
@ -671,6 +729,8 @@ func ensureWorkingDirectoryIsClean() {
} }
func (s *AccessLogSuite) checkTraefikStarted() []byte { func (s *AccessLogSuite) checkTraefikStarted() []byte {
s.T().Helper()
traefikLog, err := os.ReadFile(traefikTestLogFile) traefikLog, err := os.ReadFile(traefikTestLogFile)
require.NoError(s.T(), err) require.NoError(s.T(), err)
if len(traefikLog) > 0 { if len(traefikLog) > 0 {
@ -680,6 +740,8 @@ func (s *AccessLogSuite) checkTraefikStarted() []byte {
} }
func (s *BaseSuite) CheckAccessLogFormat(line string, i int) { func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
s.T().Helper()
results, err := accesslog.ParseAccessLog(line) results, err := accesslog.ParseAccessLog(line)
require.NoError(s.T(), err) require.NoError(s.T(), err)
assert.Len(s.T(), results, 14) 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) { func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) {
s.T().Helper()
results, err := accesslog.ParseAccessLog(line) results, err := accesslog.ParseAccessLog(line)
require.NoError(s.T(), err) require.NoError(s.T(), err)
assert.Len(s.T(), results, 14) assert.Len(s.T(), results, 14)

View file

@ -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"

View file

@ -19,5 +19,6 @@
insecure = true insecure = true
[metrics] [metrics]
addInternals = true
[metrics.prometheus] [metrics.prometheus]
buckets = [0.1,0.3,1.2,5.0] buckets = [0.1,0.3,1.2,5.0]

View file

@ -9,6 +9,8 @@
[api] [api]
insecure = true insecure = true
[ping]
[entryPoints] [entryPoints]
[entryPoints.web] [entryPoints.web]
address = ":8000" address = ":8000"
@ -47,6 +49,10 @@
Service = "service3" Service = "service3"
Middlewares = ["retry", "basic-auth"] Middlewares = ["retry", "basic-auth"]
Rule = "Path(`/auth`)" Rule = "Path(`/auth`)"
[http.routers.customPing]
entryPoints = ["web"]
rule = "PathPrefix(`/ping`)"
service = "ping@internal"
[http.middlewares] [http.middlewares]
[http.middlewares.retry.retry] [http.middlewares.retry.retry]

View file

@ -57,7 +57,7 @@ func (s *LogRotationSuite) TearDownSuite() {
func (s *LogRotationSuite) TestAccessLogRotation() { func (s *LogRotationSuite) TestAccessLogRotation() {
// Start Traefik // 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) defer s.displayTraefikLogFile(traefikTestLogFile)
// Verify Traefik started ok // Verify Traefik started ok

View file

@ -287,6 +287,10 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint() {
err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_")) err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_"))
require.NoError(s.T(), err) 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() { func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService() {

View file

@ -414,6 +414,67 @@ func (s *TracingSuite) TestOpentelemetryAuth() {
s.checkTraceContent(contains) 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) { func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) {
s.T().Helper() s.T().Helper()

View file

@ -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"` 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"` 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"` 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"` 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"` 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"` 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"` 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"` 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"` 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"` 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"`

View file

@ -12,9 +12,10 @@ import (
// Muxer handles routing with rules. // Muxer handles routing with rules.
type Muxer struct { type Muxer struct {
routes routes routes routes
parser predicate.Parser parser predicate.Parser
parserV2 predicate.Parser parserV2 predicate.Parser
defaultHandler http.Handler
} }
// NewMuxer returns a new muxer instance. // NewMuxer returns a new muxer instance.
@ -40,8 +41,9 @@ func NewMuxer() (*Muxer, error) {
} }
return &Muxer{ return &Muxer{
parser: parser, parser: parser,
parserV2: parserV2, parserV2: parserV2,
defaultHandler: http.NotFoundHandler(),
}, nil }, 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. // GetRulePriority computes the priority for a given rule.

View file

@ -407,4 +407,4 @@
} }
} }
} }
} }

View file

@ -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")
}
}
}

View file

@ -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) 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 return tracing.WrapMiddleware(ctx, middleware), nil
} }

View file

@ -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()
}

View file

@ -10,7 +10,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/logs" "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/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion" "github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion"
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
@ -35,21 +34,19 @@ type serviceManager interface {
type Manager struct { type Manager struct {
routerHandlers map[string]http.Handler routerHandlers map[string]http.Handler
serviceManager serviceManager serviceManager serviceManager
metricsRegistry metrics.Registry observabilityMgr *middleware.ObservabilityMgr
middlewaresBuilder middlewareBuilder middlewaresBuilder middlewareBuilder
chainBuilder *middleware.ChainBuilder
conf *runtime.Configuration conf *runtime.Configuration
tlsManager *tls.Manager tlsManager *tls.Manager
} }
// NewManager creates a new 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{ return &Manager{
routerHandlers: make(map[string]http.Handler), routerHandlers: make(map[string]http.Handler),
serviceManager: serviceManager, serviceManager: serviceManager,
metricsRegistry: metricsRegistry, observabilityMgr: observabilityMgr,
middlewaresBuilder: middlewaresBuilder, middlewaresBuilder: middlewaresBuilder,
chainBuilder: chainBuilder,
conf: conf, conf: conf,
tlsManager: tlsManager, 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() logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
ctx := logger.WithContext(rootCtx) ctx := logger.WithContext(rootCtx)
handler, err := m.buildEntryPointHandler(ctx, routers) handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers)
if err != nil { if err != nil {
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
continue continue
} }
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { entryPointHandlers[entryPointName] = handler
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
}
} }
// Create default handlers.
for _, entryPointName := range entryPoints { for _, entryPointName := range entryPoints {
logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
ctx := logger.WithContext(rootCtx) ctx := logger.WithContext(rootCtx)
handler, ok := entryPointHandlers[entryPointName] handler, ok := entryPointHandlers[entryPointName]
if !ok || handler == nil { if ok || handler != nil {
handler = BuildDefaultHTTPRouter() continue
} }
handlerWithMiddlewares, err := m.chainBuilder.Build(ctx, entryPointName).Then(handler) handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter())
if err != nil { if err != nil {
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
continue continue
} }
entryPointHandlers[entryPointName] = handlerWithMiddlewares entryPointHandlers[entryPointName] = handler
} }
return entryPointHandlers 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() muxer, err := httpmuxer.NewMuxer()
if err != nil { if err != nil {
return nil, err 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 { for routerName, routerConfig := range configs {
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger() logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName)) ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
@ -131,6 +128,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
continue 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 { if err = muxer.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
routerConfig.AddError(err, true) routerConfig.AddError(err, true)
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
@ -167,6 +172,12 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
return nil, err 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) { handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
}).Then(handler) }).Then(handler)
@ -200,10 +211,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
chain := alice.New() 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))) chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() { if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() {
metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service)) metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))
chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler)) chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler))
} }

View file

@ -9,21 +9,15 @@ import (
"testing" "testing"
"time" "time"
"github.com/containous/alice"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types" ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/runtime" "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/middlewares/requestdecorator"
"github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/middleware"
"github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/server/service"
"github.com/traefik/traefik/v3/pkg/testhelpers" "github.com/traefik/traefik/v3/pkg/testhelpers"
"github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
) )
func TestRouterManager_Get(t *testing.T) { func TestRouterManager_Get(t *testing.T) {
@ -319,10 +313,9 @@ func TestRouterManager_Get(t *testing.T) {
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager() 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) 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) { func TestRuntimeConfiguration(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -788,11 +661,10 @@ func TestRuntimeConfiguration(t *testing.T) {
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager() tlsManager := tls.NewManager()
tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) 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, false)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, true) _ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
@ -866,10 +738,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager() 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) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -935,10 +806,9 @@ func BenchmarkRouterServe(b *testing.B) {
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager() 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) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)

View file

@ -6,7 +6,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/config/static" "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/middleware"
tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp" tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp"
"github.com/traefik/traefik/v3/pkg/server/router" "github.com/traefik/traefik/v3/pkg/server/router"
@ -25,13 +24,12 @@ type RouterFactory struct {
entryPointsTCP []string entryPointsTCP []string
entryPointsUDP []string entryPointsUDP []string
managerFactory *service.ManagerFactory managerFactory *service.ManagerFactory
metricsRegistry metrics.Registry
pluginBuilder middleware.PluginsBuilder pluginBuilder middleware.PluginsBuilder
chainBuilder *middleware.ChainBuilder observabilityMgr *middleware.ObservabilityMgr
tlsManager *tls.Manager tlsManager *tls.Manager
dialerManager *tcp.DialerManager dialerManager *tcp.DialerManager
@ -40,7 +38,7 @@ type RouterFactory struct {
// NewRouterFactory creates a new RouterFactory. // NewRouterFactory creates a new RouterFactory.
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, 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 { ) *RouterFactory {
var entryPointsTCP, entryPointsUDP []string var entryPointsTCP, entryPointsUDP []string
for name, cfg := range staticConfiguration.EntryPoints { for name, cfg := range staticConfiguration.EntryPoints {
@ -58,14 +56,13 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
} }
return &RouterFactory{ return &RouterFactory{
entryPointsTCP: entryPointsTCP, entryPointsTCP: entryPointsTCP,
entryPointsUDP: entryPointsUDP, entryPointsUDP: entryPointsUDP,
managerFactory: managerFactory, managerFactory: managerFactory,
metricsRegistry: metricsRegistry, observabilityMgr: observabilityMgr,
tlsManager: tlsManager, tlsManager: tlsManager,
chainBuilder: chainBuilder, pluginBuilder: pluginBuilder,
pluginBuilder: pluginBuilder, dialerManager: dialerManager,
dialerManager: dialerManager,
} }
} }
@ -83,7 +80,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) 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) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)

View file

@ -9,7 +9,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/config/static" "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/middleware"
"github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/server/service"
"github.com/traefik/traefik/v3/pkg/tcp" "github.com/traefik/traefik/v3/pkg/tcp"
@ -51,12 +50,12 @@ func TestReuseService(t *testing.T) {
roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager := service.NewRoundTripperManager(nil)
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) 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() tlsManager := tls.NewManager()
dialerManager := tcp.NewDialerManager(nil) dialerManager := tcp.NewDialerManager(nil)
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) 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})) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
@ -189,12 +188,13 @@ func TestServerResponseEmptyBackend(t *testing.T) {
roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager := service.NewRoundTripperManager(nil)
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) 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() tlsManager := tls.NewManager()
dialerManager := tcp.NewDialerManager(nil) dialerManager := tcp.NewDialerManager(nil)
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) 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)})) 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 := service.NewRoundTripperManager(nil)
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) 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() tlsManager := tls.NewManager()
voidRegistry := metrics.NewVoidRegistry()
dialerManager := tcp.NewDialerManager(nil) dialerManager := tcp.NewDialerManager(nil)
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) 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})) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))

View file

@ -3,47 +3,39 @@ package server
import ( import (
"context" "context"
"errors" "errors"
"io"
"os" "os"
"os/signal" "os/signal"
"time" "time"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/metrics" "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/safe"
"github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/middleware"
) )
// Server is the reverse-proxy/load-balancer engine. // Server is the reverse-proxy/load-balancer engine.
type Server struct { type Server struct {
watcher *ConfigurationWatcher watcher *ConfigurationWatcher
tcpEntryPoints TCPEntryPoints tcpEntryPoints TCPEntryPoints
udpEntryPoints UDPEntryPoints udpEntryPoints UDPEntryPoints
chainBuilder *middleware.ChainBuilder observabilityMgr *middleware.ObservabilityMgr
accessLoggerMiddleware *accesslog.Handler
signals chan os.Signal signals chan os.Signal
stopChan chan bool stopChan chan bool
routinesPool *safe.Pool routinesPool *safe.Pool
tracerCloser io.Closer
} }
// NewServer returns an initialized Server. // 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{ srv := &Server{
watcher: watcher, watcher: watcher,
tcpEntryPoints: entryPoints, tcpEntryPoints: entryPoints,
chainBuilder: chainBuilder, observabilityMgr: observabilityMgr,
accessLoggerMiddleware: accessLoggerMiddleware, signals: make(chan os.Signal, 1),
signals: make(chan os.Signal, 1), stopChan: make(chan bool, 1),
stopChan: make(chan bool, 1), routinesPool: routinesPool,
routinesPool: routinesPool, udpEntryPoints: entryPointsUDP,
udpEntryPoints: entryPointsUDP,
tracerCloser: tracerCloser,
} }
srv.configureSignals() srv.configureSignals()
@ -105,13 +97,7 @@ func (s *Server) Close() {
close(s.stopChan) close(s.stopChan)
s.chainBuilder.Close() s.observabilityMgr.Close()
if s.tracerCloser != nil {
if err := s.tracerCloser.Close(); err != nil {
log.Error().Err(err).Msg("Could not close the tracer")
}
}
cancel() cancel()
} }

View file

@ -24,10 +24,8 @@ func (s *Server) listenSignals(ctx context.Context) {
if sig == syscall.SIGUSR1 { if sig == syscall.SIGUSR1 {
log.Info().Msgf("Closing and re-opening log files for rotation: %+v", sig) log.Info().Msgf("Closing and re-opening log files for rotation: %+v", sig)
if s.accessLoggerMiddleware != nil { if err := s.observabilityMgr.RotateAccessLogs(); err != nil {
if err := s.accessLoggerMiddleware.Rotate(); err != nil { log.Error().Err(err).Msg("Error rotating access log")
log.Error().Err(err).Msg("Error rotating access log")
}
} }
} }
} }

View file

@ -10,11 +10,12 @@ import (
"github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/config/static"
"github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/server/middleware"
) )
// ManagerFactory a factory of service manager. // ManagerFactory a factory of service manager.
type ManagerFactory struct { type ManagerFactory struct {
metricsRegistry metrics.Registry observabilityMgr *middleware.ObservabilityMgr
roundTripperManager *RoundTripperManager roundTripperManager *RoundTripperManager
@ -29,9 +30,9 @@ type ManagerFactory struct {
} }
// NewManagerFactory creates a new ManagerFactory. // 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{ factory := &ManagerFactory{
metricsRegistry: metricsRegistry, observabilityMgr: observabilityMgr,
routinesPool: routinesPool, routinesPool: routinesPool,
roundTripperManager: roundTripperManager, roundTripperManager: roundTripperManager,
acmeHTTPHandler: acmeHTTPHandler, acmeHTTPHandler: acmeHTTPHandler,
@ -72,7 +73,7 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
// Build creates a service manager. // Build creates a service manager.
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers { 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 var apiHandler http.Handler
if f.api != nil { if f.api != nil {

View file

@ -20,12 +20,12 @@ import (
"github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/healthcheck" "github.com/traefik/traefik/v3/pkg/healthcheck"
"github.com/traefik/traefik/v3/pkg/logs" "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/accesslog"
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing" tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
"github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/server/cookie" "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/provider"
"github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover"
"github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror"
@ -42,7 +42,7 @@ type RoundTripperGetter interface {
// Manager The service manager. // Manager The service manager.
type Manager struct { type Manager struct {
routinePool *safe.Pool routinePool *safe.Pool
metricsRegistry metrics.Registry observabilityMgr *middleware.ObservabilityMgr
bufferPool httputil.BufferPool bufferPool httputil.BufferPool
roundTripperManager RoundTripperGetter roundTripperManager RoundTripperGetter
@ -53,10 +53,10 @@ type Manager struct {
} }
// NewManager creates a new Manager. // 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{ return &Manager{
routinePool: routinePool, routinePool: routinePool,
metricsRegistry: metricsRegistry, observabilityMgr: observabilityMgr,
bufferPool: newBufferPool(), bufferPool: newBufferPool(),
roundTripperManager: roundTripperManager, roundTripperManager: roundTripperManager,
services: make(map[string]http.Handler), 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 := buildSingleHostProxy(target, passHostHeader, time.Duration(flushInterval), roundTripper, m.bufferPool)
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) // Prevents from enabling observability for internal resources.
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() { if m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, serviceName)) {
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, 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(). proxy, err = alice.New().
Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)). 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) lb.Add(proxyName, proxy, server.Weight)
@ -330,7 +337,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
if service.HealthCheck != nil { if service.HealthCheck != nil {
m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker( m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker(
ctx, ctx,
m.metricsRegistry, m.observabilityMgr.MetricsRegistry(),
service.HealthCheck, service.HealthCheck,
lb, lb,
info, info,

View file

@ -42,6 +42,11 @@ func NewTracing(conf *static.Tracing) (trace.Tracer, io.Closer, error) {
// TracerFromContext extracts the trace.Tracer from the given context. // TracerFromContext extracts the trace.Tracer from the given context.
func TracerFromContext(ctx context.Context) trace.Tracer { func TracerFromContext(ctx context.Context) trace.Tracer {
// Prevent picking trace.noopSpan tracer.
if !trace.SpanContextFromContext(ctx).IsValid() {
return nil
}
span := trace.SpanFromContext(ctx) span := trace.SpanFromContext(ctx)
if span != nil && span.TracerProvider() != nil { if span != nil && span.TracerProvider() != nil {
return span.TracerProvider().Tracer("github.com/traefik/traefik") return span.TracerProvider().Tracer("github.com/traefik/traefik")

View file

@ -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"` 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"` 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"` 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. // SetDefaults sets the default values.

View file

@ -10,6 +10,8 @@ import (
// Metrics provides options to expose and send Traefik metrics to different third party monitoring systems. // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems.
type Metrics struct { 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"` 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"` 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"` StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`