Introduce trace verbosity config and produce less spans by default

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

View File

@ -319,3 +319,23 @@ and Traefik now keeps them encoded to avoid any ambiguity.
| `/foo/../bar` | PathPrefix(`/bar`) | Match | Match | | `/foo/../bar` | PathPrefix(`/bar`) | Match | Match |
| `/foo/%2E%2E/bar` | PathPrefix(`/foo`) | Match | No match | | `/foo/%2E%2E/bar` | PathPrefix(`/foo`) | Match | No match |
| `/foo/%2E%2E/bar` | PathPrefix(`/bar`) | No match | Match | | `/foo/%2E%2E/bar` | PathPrefix(`/bar`) | No match | Match |
## v3.5.0
### TraceVerbosity on Routers and Entrypoints
Starting with v3.5, a new `traceVerbosity` option is available for both entrypoints and routers.
This option allows you to control the level of detail for tracing spans.
Routers can override the value inherited from their entrypoint.
**Impact:**
- If you rely on tracing, review your configuration to explicitly set the desired verbosity level.
- Existing configurations will default to `minimal` unless overridden, which will result in fewer spans being generated than before.
Possible values are:
- `minimal`: produces a single server span and one client span for each request processed by a router.
- `detailed`: enables the creation of additional spans for each middleware executed for each request processed by a router.
See the updated documentation for [entrypoints](../reference/install-configuration/entrypoints.md) and [dynamic routers](../reference/dynamic-configuration/file.md#observability-options).

View File

@ -169,6 +169,7 @@
- "traefik.http.routers.router0.middlewares=foobar, foobar" - "traefik.http.routers.router0.middlewares=foobar, foobar"
- "traefik.http.routers.router0.observability.accesslogs=true" - "traefik.http.routers.router0.observability.accesslogs=true"
- "traefik.http.routers.router0.observability.metrics=true" - "traefik.http.routers.router0.observability.metrics=true"
- "traefik.http.routers.router0.observability.traceverbosity=foobar"
- "traefik.http.routers.router0.observability.tracing=true" - "traefik.http.routers.router0.observability.tracing=true"
- "traefik.http.routers.router0.priority=42" - "traefik.http.routers.router0.priority=42"
- "traefik.http.routers.router0.rule=foobar" - "traefik.http.routers.router0.rule=foobar"
@ -185,6 +186,7 @@
- "traefik.http.routers.router1.middlewares=foobar, foobar" - "traefik.http.routers.router1.middlewares=foobar, foobar"
- "traefik.http.routers.router1.observability.accesslogs=true" - "traefik.http.routers.router1.observability.accesslogs=true"
- "traefik.http.routers.router1.observability.metrics=true" - "traefik.http.routers.router1.observability.metrics=true"
- "traefik.http.routers.router1.observability.traceverbosity=foobar"
- "traefik.http.routers.router1.observability.tracing=true" - "traefik.http.routers.router1.observability.tracing=true"
- "traefik.http.routers.router1.priority=42" - "traefik.http.routers.router1.priority=42"
- "traefik.http.routers.router1.rule=foobar" - "traefik.http.routers.router1.rule=foobar"

View File

@ -22,8 +22,9 @@
sans = ["foobar", "foobar"] sans = ["foobar", "foobar"]
[http.routers.Router0.observability] [http.routers.Router0.observability]
accessLogs = true accessLogs = true
tracing = true
metrics = true metrics = true
tracing = true
traceVerbosity = "foobar"
[http.routers.Router1] [http.routers.Router1]
entryPoints = ["foobar", "foobar"] entryPoints = ["foobar", "foobar"]
middlewares = ["foobar", "foobar"] middlewares = ["foobar", "foobar"]
@ -44,8 +45,9 @@
sans = ["foobar", "foobar"] sans = ["foobar", "foobar"]
[http.routers.Router1.observability] [http.routers.Router1.observability]
accessLogs = true accessLogs = true
tracing = true
metrics = true metrics = true
tracing = true
traceVerbosity = "foobar"
[http.services] [http.services]
[http.services.Service01] [http.services.Service01]
[http.services.Service01.failover] [http.services.Service01.failover]

View File

@ -27,8 +27,9 @@ http:
- foobar - foobar
observability: observability:
accessLogs: true accessLogs: true
tracing: true
metrics: true metrics: true
tracing: true
traceVerbosity: foobar
Router1: Router1:
entryPoints: entryPoints:
- foobar - foobar
@ -54,8 +55,9 @@ http:
- foobar - foobar
observability: observability:
accessLogs: true accessLogs: true
tracing: true
metrics: true metrics: true
tracing: true
traceVerbosity: foobar
services: services:
Service01: Service01:
failover: failover:

View File

@ -92,10 +92,21 @@ spec:
More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability
properties: properties:
accessLogs: accessLogs:
description: AccessLogs enables access logs for this router.
type: boolean type: boolean
metrics: metrics:
description: Metrics enables metrics for this router.
type: boolean type: boolean
traceVerbosity:
default: minimal
description: TraceVerbosity defines the verbosity level
of the tracing for this router.
enum:
- minimal
- detailed
type: string
tracing: tracing:
description: Tracing enables tracing for this router.
type: boolean type: boolean
type: object type: object
priority: priority:

View File

@ -199,6 +199,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/routers/Router0/middlewares/1` | `foobar` | | `traefik/http/routers/Router0/middlewares/1` | `foobar` |
| `traefik/http/routers/Router0/observability/accessLogs` | `true` | | `traefik/http/routers/Router0/observability/accessLogs` | `true` |
| `traefik/http/routers/Router0/observability/metrics` | `true` | | `traefik/http/routers/Router0/observability/metrics` | `true` |
| `traefik/http/routers/Router0/observability/traceVerbosity` | `foobar` |
| `traefik/http/routers/Router0/observability/tracing` | `true` | | `traefik/http/routers/Router0/observability/tracing` | `true` |
| `traefik/http/routers/Router0/priority` | `42` | | `traefik/http/routers/Router0/priority` | `42` |
| `traefik/http/routers/Router0/rule` | `foobar` | | `traefik/http/routers/Router0/rule` | `foobar` |
@ -218,6 +219,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/routers/Router1/middlewares/1` | `foobar` | | `traefik/http/routers/Router1/middlewares/1` | `foobar` |
| `traefik/http/routers/Router1/observability/accessLogs` | `true` | | `traefik/http/routers/Router1/observability/accessLogs` | `true` |
| `traefik/http/routers/Router1/observability/metrics` | `true` | | `traefik/http/routers/Router1/observability/metrics` | `true` |
| `traefik/http/routers/Router1/observability/traceVerbosity` | `foobar` |
| `traefik/http/routers/Router1/observability/tracing` | `true` | | `traefik/http/routers/Router1/observability/tracing` | `true` |
| `traefik/http/routers/Router1/priority` | `42` | | `traefik/http/routers/Router1/priority` | `42` |
| `traefik/http/routers/Router1/rule` | `foobar` | | `traefik/http/routers/Router1/rule` | `foobar` |

View File

@ -92,10 +92,21 @@ spec:
More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability
properties: properties:
accessLogs: accessLogs:
description: AccessLogs enables access logs for this router.
type: boolean type: boolean
metrics: metrics:
description: Metrics enables metrics for this router.
type: boolean type: boolean
traceVerbosity:
default: minimal
description: TraceVerbosity defines the verbosity level
of the tracing for this router.
enum:
- minimal
- detailed
type: string
tracing: tracing:
description: Tracing enables tracing for this router.
type: boolean type: boolean
type: object type: object
priority: priority:

View File

@ -84,7 +84,7 @@ additionalArguments:
## Configuration Options ## Configuration Options
| Field | Description | Default | Required | | Field | Description | Default | Required |
|:-----------------|:--------|:--------|:---------| |:----------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------|:---------|
| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.<br /> It also defines the protocol to use (TCP or UDP).<br /> If no protocol is specified, the default is TCP. The format is:`[host]:port[/tcp\|/udp] | - | Yes | | `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.<br /> It also defines the protocol to use (TCP or UDP).<br /> If no protocol is specified, the default is TCP. The format is:`[host]:port[/tcp\|/udp] | - | Yes |
| `asDefault` | Mark the `entryPoint` to be in the list of default `entryPoints`.<br /> `entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.<br /> More information [here](#asdefault). | false | No | | `asDefault` | Mark the `entryPoint` to be in the list of default `entryPoints`.<br /> `entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.<br /> More information [here](#asdefault). | false | No |
| `forwardedHeaders.trustedIPs` | Set the IPs or CIDR from where Traefik trusts the forwarded headers information (`X-Forwarded-*`). | - | No | | `forwardedHeaders.trustedIPs` | Set the IPs or CIDR from where Traefik trusts the forwarded headers information (`X-Forwarded-*`). | - | No |
@ -92,11 +92,11 @@ additionalArguments:
| `http.redirections.`<br />`entryPoint.to` | The target element to enable (permanent) redirecting of all incoming requests on an entry point to another one. <br /> The target element can be an entry point name (ex: `websecure`), or a port (`:443`). | - | Yes | | `http.redirections.`<br />`entryPoint.to` | The target element to enable (permanent) redirecting of all incoming requests on an entry point to another one. <br /> The target element can be an entry point name (ex: `websecure`), or a port (`:443`). | - | Yes |
| `http.redirections.`<br />`entryPoint.scheme` | The target scheme to use for (permanent) redirection of all incoming requests. | https | No | | `http.redirections.`<br />`entryPoint.scheme` | The target scheme to use for (permanent) redirection of all incoming requests. | https | No |
| `http.redirections.`<br />`entryPoint.permanent` | Enable permanent redirecting of all incoming requests on an entry point to another one changing the scheme. <br /> The target element, it can be an entry point name (ex: `websecure`), or a port (`:443`). | false | No | | `http.redirections.`<br />`entryPoint.permanent` | Enable permanent redirecting of all incoming requests on an entry point to another one changing the scheme. <br /> The target element, it can be an entry point name (ex: `websecure`), or a port (`:443`). | false | No |
| `http.redirections.`<br />`entryPoint.priority` | Default priority applied to the routers attached to the `entryPoint`.| MaxInt32-1 (2147483646) | No | | `http.redirections.`<br />`entryPoint.priority` | Default priority applied to the routers attached to the `entryPoint`. | MaxInt32-1 (2147483646) | No |
| `http.encodeQuerySemicolons` | Enable query semicolons encoding. <br /> Use this option to avoid non-encoded semicolons to be interpreted as query parameter separators by Traefik. <br /> When using this option, the non-encoded semicolons characters in query will be transmitted encoded to the backend.<br /> More information [here](#encodequerysemicolons). | false | No | | `http.encodeQuerySemicolons` | Enable query semicolons encoding. <br /> Use this option to avoid non-encoded semicolons to be interpreted as query parameter separators by Traefik. <br /> When using this option, the non-encoded semicolons characters in query will be transmitted encoded to the backend.<br /> More information [here](#encodequerysemicolons). | false | No |
| `http.sanitizePath` | Defines whether to enable the request path sanitization.<br /> More information [here](#sanitizepath). | false | No | | `http.sanitizePath` | Defines whether to enable the request path sanitization.<br /> More information [here](#sanitizepath). | false | No |
| `http.middlewares` | Set the list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point. <br />More information [here](#httpmiddlewares). | - | No | | `http.middlewares` | Set the list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point. <br />More information [here](#httpmiddlewares). | - | No |
| `http.tls` | Enable TLS on every router attached to the `entryPoint`. <br /> If no certificate are set, a default self-signed certificate is generates by Traefik. <br /> We recommend to not use self signed certificates in production.| - | No | | `http.tls` | Enable TLS on every router attached to the `entryPoint`. <br /> If no certificate are set, a default self-signed certificate is generates by Traefik. <br /> We recommend to not use self signed certificates in production. | - | No |
| `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`. <br /> The TLS options can be overidden per router. <br /> More information in the [dedicated section](../../routing/providers/kubernetes-crd.md#kind-tlsoption). | - | No | | `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`. <br /> The TLS options can be overidden per router. <br /> More information in the [dedicated section](../../routing/providers/kubernetes-crd.md#kind-tlsoption). | - | No |
| `http.tls.certResolver` | Apply a certificate resolver on every router attached to the `entryPoint`. <br /> The TLS options can be overidden per router. <br /> More information in the [dedicated section](../install-configuration/tls/certificate-resolvers/overview.md). | - | No | | `http.tls.certResolver` | Apply a certificate resolver on every router attached to the `entryPoint`. <br /> The TLS options can be overidden per router. <br /> More information in the [dedicated section](../install-configuration/tls/certificate-resolvers/overview.md). | - | No |
| `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate. <br /> The value must be greater than zero. | 250 | No | | `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate. <br /> The value must be greater than zero. | 250 | No |
@ -105,9 +105,10 @@ additionalArguments:
| `observability.accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | | `observability.accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No |
| `observability.metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | | `observability.metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No |
| `observability.tracing` | Defines whether a router attached to this EntryPoint produces traces by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | | `observability.tracing` | Defines whether a router attached to this EntryPoint produces traces by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No |
| `observability.traceVerbosity` | Defines the tracing verbosity level for routers attached to this EntryPoint. Possible values: `minimal` (default), `detailed`. Routers can override this value in their own observability configuration. <br /> More information [here](#traceverbosity). | minimal | No |
| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs. <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No | | `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs. <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No |
| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection. <br /> Every remote client address will be replaced (`trustedIPs`) won't have any effect). <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br />We recommend to use this option only for tests purposes, not in production.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No | | `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection. <br /> Every remote client address will be replaced (`trustedIPs`) won't have any effect). <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br />We recommend to use this option only for tests purposes, not in production.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No |
| `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option. <br /> It also allows the kernel to act like a load balancer to distribute incoming connections between entry points..<br /> More information [here](#reuseport). | false | No | | `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option. <br /> It also allows the kernel to act like a load balancer to distribute incoming connections between entry points.<br /> More information [here](#reuseport). | false | No |
| `transport.`<br />`respondingTimeouts.`<br />`readTimeout` | Set the timeouts for incoming requests to the Traefik instance. This is the maximum duration for reading the entire request, including the body. Setting them has no effect for UDP `entryPoints`.<br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No | | `transport.`<br />`respondingTimeouts.`<br />`readTimeout` | Set the timeouts for incoming requests to the Traefik instance. This is the maximum duration for reading the entire request, including the body. Setting them has no effect for UDP `entryPoints`.<br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No |
| `transport.`<br />`respondingTimeouts.`<br />`writeTimeout` | Maximum duration before timing out writes of the response. <br /> It covers the time from the end of the request header read to the end of the response write. <br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No | | `transport.`<br />`respondingTimeouts.`<br />`writeTimeout` | Maximum duration before timing out writes of the response. <br /> It covers the time from the end of the request header read to the end of the response write. <br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No |
| `transport.`<br />`respondingTimeouts.`<br />`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself. <br /> If zero, no timeout exists <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No | | `transport.`<br />`respondingTimeouts.`<br />`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself. <br /> If zero, no timeout exists <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No |
@ -115,7 +116,7 @@ additionalArguments:
| `transport.`<br />`lifeCycle.`<br />`requestAcceptGraceTimeout` | Set the duration to keep accepting requests prior to initiating the graceful termination period (as defined by the `transportlifeCycle.graceTimeOut` option). <br /> This option is meant to give downstream load-balancers sufficient time to take Traefik out of rotation. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds | 0s (seconds) | No | | `transport.`<br />`lifeCycle.`<br />`requestAcceptGraceTimeout` | Set the duration to keep accepting requests prior to initiating the graceful termination period (as defined by the `transportlifeCycle.graceTimeOut` option). <br /> This option is meant to give downstream load-balancers sufficient time to take Traefik out of rotation. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds | 0s (seconds) | No |
| `transport.`<br />`keepAliveMaxRequests` | Set the maximum number of requests Traefik can handle before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). <br /> Zero means no limit. | 0 | No | | `transport.`<br />`keepAliveMaxRequests` | Set the maximum number of requests Traefik can handle before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). <br /> Zero means no limit. | 0 | No |
| `transport.`<br />`keepAliveMaxTime` | Set the maximum duration Traefik can handle requests before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). Zero means no limit. | 0s (seconds) | No | | `transport.`<br />`keepAliveMaxTime` | Set the maximum duration Traefik can handle requests before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). Zero means no limit. | 0s (seconds) | No |
| `udp.timeout` | Define how long to wait on an idle session before releasing the related resources. <br />The Timeout value must be greater than zero. | 3s (seconds)| No | | `udp.timeout` | Define how long to wait on an idle session before releasing the related resources. <br />The Timeout value must be greater than zero. | 3s (seconds) | No |
### asDefault ### asDefault
@ -271,3 +272,13 @@ Use the `reusePort` option with the other option `transport.lifeCycle.gracetimeo
to do to do
canary deployments against Traefik itself. Like upgrading Traefik version canary deployments against Traefik itself. Like upgrading Traefik version
or reloading the static configuration without any service downtime. or reloading the static configuration without any service downtime.
#### Trace Verbosity
`observability.traceVerbosity` defines the tracing verbosity level for routers attached to this EntryPoint.
Routers can override this value in their own observability configuration.
Possible values are:
- `minimal`: produces a single server span and one client span for each request processed by a router.
- `detailed`: enables the creation of additional spans for each middleware executed for each request processed by a router.

View File

@ -36,6 +36,7 @@ http:
metrics: false metrics: false
accessLogs: false accessLogs: false
tracing: false tracing: false
traceVerbosity: detailed
``` ```
```yaml tab="Structured (TOML)" ```yaml tab="Structured (TOML)"
@ -47,6 +48,7 @@ http:
metrics = false metrics = false
accessLogs = false accessLogs = false
tracing = false tracing = false
traceVerbosity = "detailed"
``` ```
```yaml tab="Labels" ```yaml tab="Labels"
@ -56,6 +58,7 @@ labels:
- "traefik.http.routers.my-router.observability.metrics=false" - "traefik.http.routers.my-router.observability.metrics=false"
- "traefik.http.routers.my-router.observability.accessLogs=false" - "traefik.http.routers.my-router.observability.accessLogs=false"
- "traefik.http.routers.my-router.observability.tracing=false" - "traefik.http.routers.my-router.observability.tracing=false"
- "traefik.http.routers.my-router.observability.traceVerbosity=detailed"
``` ```
```json tab="Tags" ```json tab="Tags"
@ -66,7 +69,8 @@ labels:
"traefik.http.routers.my-router.service=service-foo", "traefik.http.routers.my-router.service=service-foo",
"traefik.http.routers.my-router.observability.metrics=false", "traefik.http.routers.my-router.observability.metrics=false",
"traefik.http.routers.my-router.observability.accessLogs=false", "traefik.http.routers.my-router.observability.accessLogs=false",
"traefik.http.routers.my-router.observability.tracing=false" "traefik.http.routers.my-router.observability.tracing=false",
"traefik.http.routers.my-router.observability.traceVerbosity=detailed"
] ]
} }
``` ```
@ -74,7 +78,17 @@ labels:
## Configuration Options ## Configuration Options
| Field | Description | Default | Required | | Field | Description | Default | Required |
|:------|:------------|:--------|:---------| |:-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------|:---------|
| `accessLogs` | The `accessLogs` option controls whether the router will produce access-logs. | `true` | No | | `accessLogs` | The `accessLogs` option controls whether the router will produce access-logs. | `true` | No |
| `metrics` | The `metrics` option controls whether the router will produce metrics. | `true` | No | | `metrics` | The `metrics` option controls whether the router will produce metrics. | `true` | No |
| `tracing` | The `tracing` option controls whether the router will produce traces. | `true` | No | | `tracing` | The `tracing` option controls whether the router will produce traces. | `true` | No |
| `traceVerbosity` | The `traceVerbosity` option controls the tracing verbosity level for the router. Possible values: `minimal` (default), `detailed`. If not set, the value is inherited from the entryPoint. | `minimal` | No |
#### traceVerbosity
`observability.traceVerbosity` defines the tracing verbosity level for the router.
Possible values are:
- `minimal`: produces a single server span and one client span for each request processed by a router.
- `detailed`: enables the creation of additional spans for each middleware executed for each request processed by a router.

View File

@ -283,13 +283,16 @@ HTTP/3 configuration. (Default: ```false```)
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
`--entrypoints.<name>.observability.accesslogs`: `--entrypoints.<name>.observability.accesslogs`:
(Default: ```true```) Enables access-logs for this entryPoint. (Default: ```true```)
`--entrypoints.<name>.observability.metrics`: `--entrypoints.<name>.observability.metrics`:
(Default: ```true```) Enables metrics for this entryPoint. (Default: ```true```)
`--entrypoints.<name>.observability.traceverbosity`:
Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```)
`--entrypoints.<name>.observability.tracing`: `--entrypoints.<name>.observability.tracing`:
(Default: ```true```) Enables tracing for this entryPoint. (Default: ```true```)
`--entrypoints.<name>.proxyprotocol`: `--entrypoints.<name>.proxyprotocol`:
Proxy-Protocol configuration. (Default: ```false```) Proxy-Protocol configuration. (Default: ```false```)

View File

@ -283,13 +283,16 @@ Subject alternative names.
Default TLS options for the routers linked to the entry point. Default TLS options for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_ACCESSLOGS`: `TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_ACCESSLOGS`:
(Default: ```true```) Enables access-logs for this entryPoint. (Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_METRICS`: `TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_METRICS`:
(Default: ```true```) Enables metrics for this entryPoint. (Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_TRACEVERBOSITY`:
Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```)
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_TRACING`: `TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_TRACING`:
(Default: ```true```) Enables tracing for this entryPoint. (Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`: `TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`:
Proxy-Protocol configuration. (Default: ```false```) Proxy-Protocol configuration. (Default: ```false```)

View File

@ -80,8 +80,9 @@
timeout = "42s" timeout = "42s"
[entryPoints.EntryPoint0.observability] [entryPoints.EntryPoint0.observability]
accessLogs = true accessLogs = true
tracing = true
metrics = true metrics = true
tracing = true
traceVerbosity = "foobar"
[providers] [providers]
providersThrottleDuration = "42s" providersThrottleDuration = "42s"

View File

@ -94,8 +94,9 @@ entryPoints:
timeout: 42s timeout: 42s
observability: observability:
accessLogs: true accessLogs: true
tracing: true
metrics: true metrics: true
tracing: true
traceVerbosity: foobar
providers: providers:
providersThrottleDuration: 42s providersThrottleDuration: 42s
docker: docker:

View File

@ -648,25 +648,6 @@ func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
require.Equal(s.T(), 0, count) require.Equal(s.T(), 0, count)
// Make some requests on the custom ping router in error.
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8010/ping-error", nil)
require.NoError(s.T(), err)
req.Host = "ping-error.docker.local"
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.BodyContains("X-Forwarded-Host: ping-error.docker.local"))
require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.BodyContains("X-Forwarded-Host: ping-error.docker.local"))
require.NoError(s.T(), err)
// Here we verify that the remove of observability doesn't break the metrics for the error page service.
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/metrics", nil)
require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("service3"))
require.NoError(s.T(), err)
err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyNotContains("service=\"ping"))
require.NoError(s.T(), err)
// Verify no other Traefik problems. // Verify no other Traefik problems.
s.checkNoOtherTraefikProblems() s.checkNoOtherTraefikProblems()
} }

View File

@ -92,10 +92,21 @@ spec:
More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability
properties: properties:
accessLogs: accessLogs:
description: AccessLogs enables access logs for this router.
type: boolean type: boolean
metrics: metrics:
description: Metrics enables metrics for this router.
type: boolean type: boolean
traceVerbosity:
default: minimal
description: TraceVerbosity defines the verbosity level
of the tracing for this router.
enum:
- minimal
- detailed
type: string
tracing: tracing:
description: Tracing enables tracing for this router.
type: boolean type: boolean
type: object type: object
priority: priority:

View File

@ -14,6 +14,10 @@
[entryPoints] [entryPoints]
[entryPoints.web] [entryPoints.web]
address = ":8000" address = ":8000"
[entryPoints.web.observability]
traceVerbosity = "detailed"
[entryPoints.web-minimal]
address = ":8001"
# Adding metrics to confirm that there is no wrong interaction with tracing. # Adding metrics to confirm that there is no wrong interaction with tracing.
[metrics] [metrics]
@ -44,9 +48,13 @@
## dynamic configuration ## ## dynamic configuration ##
[http.routers] [http.routers]
[http.routers.routerBasicMinimal]
Service = "service0"
Rule = "Path(`/basic-minimal`)"
[http.routers.routerBasicMinimal.observability]
traceVerbosity = "minimal"
[http.routers.router0] [http.routers.router0]
Service = "service0" Service = "service0"
Middlewares = []
Rule = "Path(`/basic`)" Rule = "Path(`/basic`)"
[http.routers.router1] [http.routers.router1]
Service = "service1" Service = "service1"

View File

@ -101,13 +101,3 @@ services:
traefik.http.routers.ping.entryPoints: ping traefik.http.routers.ping.entryPoints: ping
traefik.http.routers.ping.rule: PathPrefix(`/ping`) traefik.http.routers.ping.rule: PathPrefix(`/ping`)
traefik.http.routers.ping.service: ping@internal traefik.http.routers.ping.service: ping@internal
traefik.http.routers.ping-error.entryPoints: ping
traefik.http.routers.ping-error.rule: PathPrefix(`/ping-error`)
traefik.http.routers.ping-error.middlewares: errors, basicauth
traefik.http.routers.ping-error.service: ping@internal
traefik.http.middlewares.basicauth.basicauth.users: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
traefik.http.middlewares.errors.errors.status: 401
traefik.http.middlewares.errors.errors.service: service3
traefik.http.middlewares.errors.errors.query: /
traefik.http.services.service3.loadbalancer.server.port: 80

View File

@ -14,8 +14,9 @@
"tls": {}, "tls": {},
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +50,9 @@
}, },
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -67,8 +69,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -89,8 +92,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -100,7 +104,13 @@
}, },
"middlewares": { "middlewares": {
"compressor@consul": { "compressor@consul": {
"compress": {}, "compress": {
"encodings": [
"gzip",
"br",
"zstd"
]
},
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"Router0@consul" "Router0@consul"
@ -173,6 +183,7 @@
"mirror@consul": { "mirror@consul": {
"mirroring": { "mirroring": {
"service": "simplesvc", "service": "simplesvc",
"mirrorBody": true,
"maxBodySize": -1, "maxBodySize": -1,
"mirrors": [ "mirrors": [
{ {

View File

@ -9,8 +9,9 @@
"priority": 18, "priority": 18,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -29,8 +30,9 @@
}, },
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [

View File

@ -9,8 +9,9 @@
"priority": 18, "priority": 18,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -29,8 +30,9 @@
}, },
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +51,9 @@
"priority": 46, "priority": 46,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -66,8 +69,9 @@
"priority": 38, "priority": 38,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -83,8 +87,9 @@
"priority": 50, "priority": 50,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -100,8 +105,9 @@
"priority": 35, "priority": 35,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"error": [ "error": [
"the service \"other-ns-wrr3@kubernetescrd\" does not exist" "the service \"other-ns-wrr3@kubernetescrd\" does not exist"

View File

@ -14,8 +14,9 @@
"tls": {}, "tls": {},
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +50,9 @@
}, },
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -67,8 +69,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -89,8 +92,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -100,7 +104,13 @@
}, },
"middlewares": { "middlewares": {
"compressor@etcd": { "compressor@etcd": {
"compress": {}, "compress": {
"encodings": [
"gzip",
"br",
"zstd"
]
},
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"Router0@etcd" "Router0@etcd"
@ -173,6 +183,7 @@
"mirror@etcd": { "mirror@etcd": {
"mirroring": { "mirroring": {
"service": "simplesvc", "service": "simplesvc",
"mirrorBody": true,
"maxBodySize": -1, "maxBodySize": -1,
"mirrors": [ "mirrors": [
{ {

View File

@ -10,8 +10,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -32,8 +33,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -50,8 +52,9 @@
"priority": 100008, "priority": 100008,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -69,8 +72,9 @@
"tls": {}, "tls": {},
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [

View File

@ -10,8 +10,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -32,8 +33,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +51,9 @@
"priority": 44, "priority": 44,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [

View File

@ -10,8 +10,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -32,8 +33,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +51,9 @@
"priority": 50, "priority": 50,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -66,8 +69,9 @@
"priority": 44, "priority": 44,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -83,8 +87,9 @@
"priority": 47, "priority": 47,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -100,8 +105,9 @@
"priority": 47, "priority": 47,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [

View File

@ -10,8 +10,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -32,8 +33,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [

View File

@ -10,8 +10,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -32,8 +33,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +51,9 @@
"priority": 47, "priority": 47,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [

View File

@ -14,8 +14,9 @@
"tls": {}, "tls": {},
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +50,9 @@
}, },
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -67,8 +69,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -89,8 +92,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -100,7 +104,13 @@
}, },
"middlewares": { "middlewares": {
"compressor@redis": { "compressor@redis": {
"compress": {}, "compress": {
"encodings": [
"gzip",
"br",
"zstd"
]
},
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"Router0@redis" "Router0@redis"
@ -173,6 +183,7 @@
"mirror@redis": { "mirror@redis": {
"mirroring": { "mirroring": {
"service": "simplesvc", "service": "simplesvc",
"mirrorBody": true,
"maxBodySize": -1, "maxBodySize": -1,
"mirrors": [ "mirrors": [
{ {
@ -244,6 +255,7 @@
"url": "http://10.0.1.3:8889" "url": "http://10.0.1.3:8889"
} }
], ],
"strategy": "wrr",
"passHostHeader": true, "passHostHeader": true,
"responseForwarding": { "responseForwarding": {
"flushInterval": "100ms" "flushInterval": "100ms"

View File

@ -14,8 +14,9 @@
"tls": {}, "tls": {},
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -49,8 +50,9 @@
}, },
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -67,8 +69,9 @@
"priority": 9223372036854775806, "priority": 9223372036854775806,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -89,8 +92,9 @@
"priority": 9223372036854775805, "priority": 9223372036854775805,
"observability": { "observability": {
"accessLogs": true, "accessLogs": true,
"metrics": true,
"tracing": true, "tracing": true,
"metrics": true "traceVerbosity": "minimal"
}, },
"status": "enabled", "status": "enabled",
"using": [ "using": [
@ -100,7 +104,13 @@
}, },
"middlewares": { "middlewares": {
"compressor@zookeeper": { "compressor@zookeeper": {
"compress": {}, "compress": {
"encodings": [
"gzip",
"br",
"zstd"
]
},
"status": "enabled", "status": "enabled",
"usedBy": [ "usedBy": [
"Router0@zookeeper" "Router0@zookeeper"
@ -173,6 +183,7 @@
"mirror@zookeeper": { "mirror@zookeeper": {
"mirroring": { "mirroring": {
"service": "simplesvc", "service": "simplesvc",
"mirrorBody": true,
"maxBodySize": -1, "maxBodySize": -1,
"mirrors": [ "mirrors": [
{ {

View File

@ -77,6 +77,104 @@ func (s *TracingSuite) TearDownTest() {
s.composeStop("tempo") s.composeStop("tempo")
} }
func (s *TracingSuite) TestOpenTelemetryBasic_HTTP_router_minimalVerbosity() {
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/basic-minimal", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
contains := []map[string]string{
{
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
"batches.0.scopeSpans.0.spans.0.name": "ReverseProxy",
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_CLIENT",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.protocol.version\").value.stringValue": "1.1",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.full\").value.stringValue": fmt.Sprintf("http://%s/basic-minimal", net.JoinHostPort(s.whoamiIP, "80")),
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.address\").value.stringValue": s.whoamiIP,
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.port\").value.intValue": "80",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.address\").value.stringValue": s.whoamiIP,
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.port\").value.intValue": "80",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
"batches.0.scopeSpans.0.spans.1.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"entry_point\").value.stringValue": "web",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.path\").value.stringValue": "/basic-minimal",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
},
}
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpenTelemetryBasic_HTTP_entrypoint_minimalVerbosity() {
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:8001/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
require.NoError(s.T(), err)
contains := []map[string]string{
{
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
"batches.0.scopeSpans.0.spans.0.name": "ReverseProxy",
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_CLIENT",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.protocol.version\").value.stringValue": "1.1",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.full\").value.stringValue": fmt.Sprintf("http://%s/basic", net.JoinHostPort(s.whoamiIP, "80")),
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.address\").value.stringValue": s.whoamiIP,
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.port\").value.intValue": "80",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.address\").value.stringValue": s.whoamiIP,
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.port\").value.intValue": "80",
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
"batches.0.scopeSpans.0.spans.1.name": "EntryPoint",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_SERVER",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"entry_point\").value.stringValue": "web-minimal",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.path\").value.stringValue": "/basic",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.query\").value.stringValue": "",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8001",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
},
}
s.checkTraceContent(contains)
}
func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() { func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
WhoamiIP: s.whoamiIP, WhoamiIP: s.whoamiIP,
@ -121,7 +219,7 @@ func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() {
"batches.0.scopeSpans.0.spans.3.name": "Router", "batches.0.scopeSpans.0.spans.3.name": "Router",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "router0@file", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router0@file",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.route\").value.stringValue": "Path(`/basic`)", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.route\").value.stringValue": "Path(`/basic`)",
"batches.0.scopeSpans.0.spans.4.name": "Metrics", "batches.0.scopeSpans.0.spans.4.name": "Metrics",
@ -189,7 +287,7 @@ func (s *TracingSuite) TestOpenTelemetryBasic_gRPC() {
"batches.0.scopeSpans.0.spans.3.name": "Router", "batches.0.scopeSpans.0.spans.3.name": "Router",
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "router0@file", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router0@file",
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.route\").value.stringValue": "Path(`/basic`)", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.route\").value.stringValue": "Path(`/basic`)",
"batches.0.scopeSpans.0.spans.4.name": "Metrics", "batches.0.scopeSpans.0.spans.4.name": "Metrics",
@ -251,7 +349,7 @@ func (s *TracingSuite) TestOpenTelemetryRateLimit() {
"batches.0.scopeSpans.0.spans.1.name": "Router", "batches.0.scopeSpans.0.spans.1.name": "Router",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router1@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
"batches.0.scopeSpans.0.spans.2.name": "Metrics", "batches.0.scopeSpans.0.spans.2.name": "Metrics",
@ -299,7 +397,7 @@ func (s *TracingSuite) TestOpenTelemetryRateLimit() {
"batches.0.scopeSpans.0.spans.4.name": "Router", "batches.0.scopeSpans.0.spans.4.name": "Router",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router1@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
"batches.0.scopeSpans.0.spans.5.name": "Metrics", "batches.0.scopeSpans.0.spans.5.name": "Metrics",
@ -423,7 +521,7 @@ func (s *TracingSuite) TestOpenTelemetryRetry() {
"batches.0.scopeSpans.0.spans.12.name": "Router", "batches.0.scopeSpans.0.spans.12.name": "Router",
"batches.0.scopeSpans.0.spans.12.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.12.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file", "batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file",
"batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.router.name\").value.stringValue": "router2@file", "batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router2@file",
"batches.0.scopeSpans.0.spans.13.name": "Metrics", "batches.0.scopeSpans.0.spans.13.name": "Metrics",
"batches.0.scopeSpans.0.spans.13.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.13.kind": "SPAN_KIND_INTERNAL",
@ -475,7 +573,7 @@ func (s *TracingSuite) TestOpenTelemetryAuth() {
"batches.0.scopeSpans.0.spans.1.name": "Router", "batches.0.scopeSpans.0.spans.1.name": "Router",
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router3@file",
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
"batches.0.scopeSpans.0.spans.2.name": "Metrics", "batches.0.scopeSpans.0.spans.2.name": "Metrics",
@ -532,7 +630,7 @@ func (s *TracingSuite) TestOpenTelemetryAuthWithRetry() {
"batches.0.scopeSpans.0.spans.2.name": "Router", "batches.0.scopeSpans.0.spans.2.name": "Router",
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service4@file", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service4@file",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "router4@file", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router4@file",
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"http.route\").value.stringValue": "Path(`/retry-auth`)", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"http.route\").value.stringValue": "Path(`/retry-auth`)",
"batches.0.scopeSpans.0.spans.3.name": "Metrics", "batches.0.scopeSpans.0.spans.3.name": "Metrics",
@ -601,7 +699,7 @@ func (s *TracingSuite) TestOpenTelemetrySafeURL() {
"batches.0.scopeSpans.0.spans.4.name": "Router", "batches.0.scopeSpans.0.spans.4.name": "Router",
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router3@file",
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
"batches.0.scopeSpans.0.spans.5.name": "Metrics", "batches.0.scopeSpans.0.spans.5.name": "Metrics",

View File

@ -88,9 +88,21 @@ type RouterTLSConfig struct {
// RouterObservabilityConfig holds the observability configuration for a router. // RouterObservabilityConfig holds the observability configuration for a router.
type RouterObservabilityConfig struct { type RouterObservabilityConfig struct {
// AccessLogs enables access logs for this router.
AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` // Metrics enables metrics for this router.
Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
// Tracing enables tracing for this router.
Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
// TraceVerbosity defines the verbosity level of the tracing for this router.
// +kubebuilder:validation:Enum=minimal;detailed
// +kubebuilder:default=minimal
TraceVerbosity types.TracingVerbosity `json:"traceVerbosity,omitempty" toml:"traceVerbosity,omitempty" yaml:"traceVerbosity,omitempty" export:"true"`
}
// SetDefaults Default values for a RouterObservabilityConfig.
func (r *RouterObservabilityConfig) SetDefaults() {
r.TraceVerbosity = types.MinimalVerbosity
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View File

@ -1335,13 +1335,13 @@ func (in *RouterObservabilityConfig) DeepCopyInto(out *RouterObservabilityConfig
*out = new(bool) *out = new(bool)
**out = **in **out = **in
} }
if in.Tracing != nil { if in.Metrics != nil {
in, out := &in.Tracing, &out.Tracing in, out := &in.Metrics, &out.Metrics
*out = new(bool) *out = new(bool)
**out = **in **out = **in
} }
if in.Metrics != nil { if in.Tracing != nil {
in, out := &in.Metrics, &out.Metrics in, out := &in.Tracing, &out.Tracing
*out = new(bool) *out = new(bool)
**out = **in **out = **in
} }

View File

@ -26,9 +26,10 @@ const (
type Configuration struct { type Configuration struct {
Routers map[string]*RouterInfo `json:"routers,omitempty"` Routers map[string]*RouterInfo `json:"routers,omitempty"`
Middlewares map[string]*MiddlewareInfo `json:"middlewares,omitempty"` Middlewares map[string]*MiddlewareInfo `json:"middlewares,omitempty"`
TCPMiddlewares map[string]*TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"`
Services map[string]*ServiceInfo `json:"services,omitempty"` Services map[string]*ServiceInfo `json:"services,omitempty"`
Models map[string]*dynamic.Model `json:"-"`
TCPRouters map[string]*TCPRouterInfo `json:"tcpRouters,omitempty"` TCPRouters map[string]*TCPRouterInfo `json:"tcpRouters,omitempty"`
TCPMiddlewares map[string]*TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"`
TCPServices map[string]*TCPServiceInfo `json:"tcpServices,omitempty"` TCPServices map[string]*TCPServiceInfo `json:"tcpServices,omitempty"`
UDPRouters map[string]*UDPRouterInfo `json:"udpRouters,omitempty"` UDPRouters map[string]*UDPRouterInfo `json:"udpRouters,omitempty"`
UDPServices map[string]*UDPServiceInfo `json:"udpServices,omitempty"` UDPServices map[string]*UDPServiceInfo `json:"udpServices,omitempty"`
@ -66,6 +67,8 @@ func NewConfig(conf dynamic.Configuration) *Configuration {
runtimeConfig.Middlewares[k] = &MiddlewareInfo{Middleware: v, Status: StatusEnabled} runtimeConfig.Middlewares[k] = &MiddlewareInfo{Middleware: v, Status: StatusEnabled}
} }
} }
runtimeConfig.Models = conf.HTTP.Models
} }
if conf.TCP != nil { if conf.TCP != nil {

View File

@ -165,15 +165,17 @@ func (u *UDPConfig) SetDefaults() {
// ObservabilityConfig holds the observability configuration for an entry point. // ObservabilityConfig holds the observability configuration for an entry point.
type ObservabilityConfig struct { type ObservabilityConfig struct {
AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` AccessLogs *bool `description:"Enables access-logs for this entryPoint." json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` Metrics *bool `description:"Enables metrics for this entryPoint." json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` Tracing *bool `description:"Enables tracing for this entryPoint." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
TraceVerbosity types.TracingVerbosity `description:"Defines the tracing verbosity level for this entryPoint." json:"traceVerbosity,omitempty" toml:"traceVerbosity,omitempty" yaml:"traceVerbosity,omitempty" export:"true"`
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.
func (o *ObservabilityConfig) SetDefaults() { func (o *ObservabilityConfig) SetDefaults() {
defaultValue := true defaultValue := true
o.AccessLogs = &defaultValue o.AccessLogs = &defaultValue
o.Tracing = &defaultValue
o.Metrics = &defaultValue o.Metrics = &defaultValue
o.Tracing = &defaultValue
o.TraceVerbosity = types.MinimalVerbosity
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,6 +61,7 @@ func Test_parseRouterConfig(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },

View File

@ -127,6 +127,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },

View File

@ -243,8 +243,9 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
if ep.Observability != nil { if ep.Observability != nil {
httpModel.Observability = dynamic.RouterObservabilityConfig{ httpModel.Observability = dynamic.RouterObservabilityConfig{
AccessLogs: ep.Observability.AccessLogs, AccessLogs: ep.Observability.AccessLogs,
Tracing: ep.Observability.Tracing,
Metrics: ep.Observability.Metrics, Metrics: ep.Observability.Metrics,
Tracing: ep.Observability.Tracing,
TraceVerbosity: ep.Observability.TraceVerbosity,
} }
} }

View File

@ -38,17 +38,15 @@ func NewProxyBuilder(transportManager TransportManager, semConvMetricsRegistry *
func (r *ProxyBuilder) Update(_ map[string]*dynamic.ServersTransport) {} func (r *ProxyBuilder) Update(_ map[string]*dynamic.ServersTransport) {}
// Build builds a new httputil.ReverseProxy with the given configuration. // Build builds a new httputil.ReverseProxy with the given configuration.
func (r *ProxyBuilder) Build(cfgName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) { func (r *ProxyBuilder) Build(cfgName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) {
roundTripper, err := r.transportManager.GetRoundTripper(cfgName) roundTripper, err := r.transportManager.GetRoundTripper(cfgName)
if err != nil { if err != nil {
return nil, fmt.Errorf("getting RoundTripper: %w", err) return nil, fmt.Errorf("getting RoundTripper: %w", err)
} }
if shouldObserve {
// Wrapping the roundTripper with the Tracing roundTripper, // Wrapping the roundTripper with the Tracing roundTripper,
// to handle the reverseProxy client span creation. // to create, if necessary, the reverseProxy client span and the semConv client metric.
roundTripper = newObservabilityRoundTripper(r.semConvMetricsRegistry, roundTripper) roundTripper = newObservabilityRoundTripper(r.semConvMetricsRegistry, roundTripper)
}
return buildSingleHostProxy(targetURL, passHostHeader, preservePath, flushInterval, roundTripper, r.bufferPool), nil return buildSingleHostProxy(targetURL, passHostHeader, preservePath, flushInterval, roundTripper, r.bufferPool), nil
} }

View File

@ -23,7 +23,7 @@ func TestEscapedPath(t *testing.T) {
roundTrippers: map[string]http.RoundTripper{"default": &http.Transport{}}, roundTrippers: map[string]http.RoundTripper{"default": &http.Transport{}},
} }
p, err := NewProxyBuilder(transportManager, nil).Build("default", testhelpers.MustParseURL(srv.URL), false, true, false, 0) p, err := NewProxyBuilder(transportManager, nil).Build("default", testhelpers.MustParseURL(srv.URL), true, false, 0)
require.NoError(t, err) require.NoError(t, err)
proxy := httptest.NewServer(http.HandlerFunc(p.ServeHTTP)) proxy := httptest.NewServer(http.HandlerFunc(p.ServeHTTP))

View File

@ -35,7 +35,7 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
var span trace.Span var span trace.Span
var tracingCtx context.Context var tracingCtx context.Context
var tracer *tracing.Tracer var tracer *tracing.Tracer
if tracer = tracing.TracerFromContext(req.Context()); tracer != nil { if tracer = tracing.TracerFromContext(req.Context()); tracer != nil && observability.TracingEnabled(req.Context()) {
tracingCtx, span = tracer.Start(req.Context(), "ReverseProxy", trace.WithSpanKind(trace.SpanKindClient)) tracingCtx, span = tracer.Start(req.Context(), "ReverseProxy", trace.WithSpanKind(trace.SpanKindClient))
defer span.End() defer span.End()
@ -68,7 +68,12 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
span.End(trace.WithTimestamp(end)) span.End(trace.WithTimestamp(end))
} }
if req.Context().Value(observability.DisableMetricsKey) == nil && t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil { if !observability.SemConvMetricsEnabled(req.Context()) ||
t.semConvMetricRegistry == nil ||
t.semConvMetricRegistry.HTTPClientRequestDuration() == nil {
return response, err
}
var attrs []attribute.KeyValue var attrs []attribute.KeyValue
if statusCode < 100 || statusCode >= 600 { if statusCode < 100 || statusCode >= 600 {
@ -83,8 +88,8 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto))) attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto)))
attrs = append(attrs, semconv.ServerAddress(req.URL.Host)) attrs = append(attrs, semconv.ServerAddress(req.URL.Host))
_, port, err := net.SplitHostPort(req.URL.Host) _, port, splitErr := net.SplitHostPort(req.URL.Host)
if err != nil { if splitErr != nil {
switch req.URL.Scheme { switch req.URL.Scheme {
case "http": case "http":
attrs = append(attrs, semconv.ServerPort(80)) attrs = append(attrs, semconv.ServerPort(80))
@ -99,7 +104,6 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto"))) attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...)) t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
}
return response, err return response, err
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types" ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdkmetric "go.opentelemetry.io/otel/sdk/metric"
@ -77,6 +78,11 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) {
req.Header.Set("User-Agent", "rt-test") req.Header.Set("User-Agent", "rt-test")
req.Header.Set("X-Forwarded-Proto", "http") req.Header.Set("X-Forwarded-Proto", "http")
// Injection of the observability variables in the request context.
req = req.WithContext(observability.WithObservability(req.Context(), observability.Observability{
SemConvMetricsEnabled: true,
}))
ort := newObservabilityRoundTripper(semConvMetricRegistry, mockRoundTripper{statusCode: test.statusCode}) ort := newObservabilityRoundTripper(semConvMetricRegistry, mockRoundTripper{statusCode: test.statusCode})
_, err = ort.RoundTrip(req) _, err = ort.RoundTrip(req)
require.NoError(t, err) require.NoError(t, err)

View File

@ -301,7 +301,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
}, },
} }
p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), false, true, false, 0) p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), true, false, 0)
require.NoError(t, err) require.NoError(t, err)
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
req.URL = testhelpers.MustParseURL(srv.URL) req.URL = testhelpers.MustParseURL(srv.URL)
@ -357,7 +357,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
}, },
} }
p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), false, true, false, 0) p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), true, false, 0)
require.NoError(t, err) require.NoError(t, err)
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path // keep the original path path := req.URL.Path // keep the original path
@ -618,7 +618,7 @@ func createProxyWithForwarder(t *testing.T, uri string, transport http.RoundTrip
roundTrippers: map[string]http.RoundTripper{"fwd": transport}, roundTrippers: map[string]http.RoundTripper{"fwd": transport},
} }
p, err := NewProxyBuilder(transportManager, nil).Build("fwd", u, false, true, false, 0) p, err := NewProxyBuilder(transportManager, nil).Build("fwd", u, true, false, 0)
require.NoError(t, err) require.NoError(t, err)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View File

@ -45,7 +45,7 @@ func (b *SmartBuilder) Update(newConfigs map[string]*dynamic.ServersTransport) {
} }
// Build builds an HTTP proxy for the given URL using the ServersTransport with the given name. // Build builds an HTTP proxy for the given URL using the ServersTransport with the given name.
func (b *SmartBuilder) Build(configName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) { func (b *SmartBuilder) Build(configName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) {
serversTransport, err := b.transportManager.Get(configName) serversTransport, err := b.transportManager.Get(configName)
if err != nil { if err != nil {
return nil, fmt.Errorf("getting ServersTransport: %w", err) return nil, fmt.Errorf("getting ServersTransport: %w", err)
@ -55,7 +55,7 @@ func (b *SmartBuilder) Build(configName string, targetURL *url.URL, shouldObserv
// For the https scheme we cannot guess if the backend communication will use HTTP2, // For the https scheme we cannot guess if the backend communication will use HTTP2,
// thus we check if HTTP/2 is disabled to use the fast proxy implementation when this is possible. // thus we check if HTTP/2 is disabled to use the fast proxy implementation when this is possible.
if targetURL.Scheme == "h2c" || (targetURL.Scheme == "https" && !serversTransport.DisableHTTP2) { if targetURL.Scheme == "h2c" || (targetURL.Scheme == "https" && !serversTransport.DisableHTTP2) {
return b.proxyBuilder.Build(configName, targetURL, shouldObserve, passHostHeader, preservePath, flushInterval) return b.proxyBuilder.Build(configName, targetURL, passHostHeader, preservePath, flushInterval)
} }
return b.fastProxyBuilder.Build(configName, targetURL, passHostHeader, preservePath) return b.fastProxyBuilder.Build(configName, targetURL, passHostHeader, preservePath)
} }

View File

@ -101,7 +101,7 @@ func TestSmartBuilder_Build(t *testing.T) {
httpProxyBuilder := httputil.NewProxyBuilder(transportManager, nil) httpProxyBuilder := httputil.NewProxyBuilder(transportManager, nil)
proxyBuilder := NewSmartBuilder(transportManager, httpProxyBuilder, test.fastProxyConfig) proxyBuilder := NewSmartBuilder(transportManager, httpProxyBuilder, test.fastProxyConfig)
proxyHandler, err := proxyBuilder.Build("test", targetURL, false, false, false, time.Second) proxyHandler, err := proxyBuilder.Build("test", targetURL, false, false, time.Second)
require.NoError(t, err) require.NoError(t, err)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()

View File

@ -10,6 +10,7 @@ import (
"github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/server/provider"
"github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
) )
func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration { func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration {
@ -208,6 +209,10 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cp.Observability.Tracing = m.Observability.Tracing cp.Observability.Tracing = m.Observability.Tracing
} }
if cp.Observability.TraceVerbosity == "" {
cp.Observability.TraceVerbosity = m.Observability.TraceVerbosity
}
rtName := name rtName := name
if len(eps) > 1 { if len(eps) > 1 {
rtName = epName + "-" + name rtName = epName + "-" + name
@ -224,7 +229,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cfg.HTTP.Routers = rts cfg.HTTP.Routers = rts
} }
// Apply default observability model to HTTP routers. // Apply the default observability model to HTTP routers.
applyDefaultObservabilityModel(cfg) applyDefaultObservabilityModel(cfg)
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 { if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
@ -256,6 +261,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
// and make sure it is serialized and available in the API. // and make sure it is serialized and available in the API.
// We could have introduced a "default" model, but it would have been more complex to manage for now. // We could have introduced a "default" model, but it would have been more complex to manage for now.
// This could be generalized in the future. // This could be generalized in the future.
// TODO: check if we can remove this and rely on the SetDefaults instead.
func applyDefaultObservabilityModel(cfg dynamic.Configuration) { func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
if cfg.HTTP != nil { if cfg.HTTP != nil {
for _, router := range cfg.HTTP.Routers { for _, router := range cfg.HTTP.Routers {
@ -264,6 +270,7 @@ func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
} }
continue continue
@ -273,12 +280,16 @@ func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
router.Observability.AccessLogs = pointer(true) router.Observability.AccessLogs = pointer(true)
} }
if router.Observability.Metrics == nil {
router.Observability.Metrics = pointer(true)
}
if router.Observability.Tracing == nil { if router.Observability.Tracing == nil {
router.Observability.Tracing = pointer(true) router.Observability.Tracing = pointer(true)
} }
if router.Observability.Metrics == nil { if router.Observability.TraceVerbosity == "" {
router.Observability.Metrics = pointer(true) router.Observability.TraceVerbosity = types.MinimalVerbosity
} }
} }
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
) )
func Test_mergeConfiguration(t *testing.T) { func Test_mergeConfiguration(t *testing.T) {
@ -524,6 +525,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },
@ -592,6 +594,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },
@ -625,6 +628,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },
@ -641,6 +645,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },
@ -654,6 +659,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },
@ -691,6 +697,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },
@ -733,6 +740,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
"websecure-test": { "websecure-test": {
@ -743,6 +751,7 @@ func Test_applyModel(t *testing.T) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
}, },
}, },
}, },

View File

@ -428,8 +428,5 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName) return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
} }
// The tracing middleware is a NOOP if tracing is not setup on the middleware chain.
// Hence, regarding internal resources' observability deactivation,
// this would not enable tracing.
return observability.WrapMiddleware(ctx, middleware), nil return observability.WrapMiddleware(ctx, middleware), nil
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"io" "io"
"net/http" "net/http"
"strings"
"github.com/containous/alice" "github.com/containous/alice"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -17,6 +16,7 @@ import (
mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics" mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/types"
) )
// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement. // ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement.
@ -42,111 +42,44 @@ func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Re
} }
// BuildEPChain an observability middleware chain by entry point. // BuildEPChain an observability middleware chain by entry point.
func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string, observabilityConfig *dynamic.RouterObservabilityConfig) alice.Chain { func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, internal bool, config dynamic.RouterObservabilityConfig) alice.Chain {
chain := alice.New() chain := alice.New()
if o == nil { if o == nil {
return chain return chain
} }
if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) { // Injection of the observability variables in the request context.
if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) { // This injection must be the first step in order for other observability middlewares to rely on it.
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return o.observabilityContextHandler(next, internal, config), nil
})
// Capture middleware for accessLogs or metrics.
if o.shouldAccessLog(internal, config) || o.shouldMeter(internal, config) || o.shouldMeterSemConv(internal, config) {
chain = chain.Append(capture.Wrap) chain = chain.Append(capture.Wrap)
} }
}
// As the Entry point observability middleware ensures that the tracing is added to the request and logger context, // As the Entry point observability middleware ensures that the tracing is added to the request and logger context,
// it needs to be added before the access log middleware to ensure that the trace ID is logged. // it needs to be added before the access log middleware to ensure that the trace ID is logged.
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName)) chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
}
if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) { // Access log handlers.
chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware)) chain = chain.Append(o.accessLoggerMiddleware.AliceConstructor())
chain = chain.Append(func(next http.Handler) (http.Handler, error) { chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
}) })
}
// Entrypoint metrics handler.
metricsHandler := mmetrics.EntryPointMetricsHandler(ctx, o.metricsRegistry, entryPointName)
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
// Semantic convention server metrics handler. // Semantic convention server metrics handler.
if o.semConvMetricRegistry != nil && o.ShouldAddMetrics(resourceName, observabilityConfig) {
chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry)) chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
}
if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName, observabilityConfig) {
metricsHandler := mmetrics.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName)
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
} else {
chain = chain.Append(metricsHandler)
}
}
// Inject context keys to control whether to produce metrics further downstream (services, round-tripper),
// because the router configuration cannot be evaluated during build time for services.
if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics {
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true)))
}), nil
})
}
return chain return chain
} }
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
if o == nil {
return false
}
if o.config.AccessLog == nil {
return false
}
if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals {
return false
}
return observabilityConfig == nil || observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
}
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config.
func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
if o == nil {
return false
}
if o.config.Metrics == nil {
return false
}
if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals {
return false
}
return observabilityConfig == nil || observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
}
// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
if o == nil {
return false
}
if o.config.Tracing == nil {
return false
}
if strings.HasSuffix(serviceName, "@internal") && !o.config.Tracing.AddInternals {
return false
}
return observabilityConfig == nil || observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
}
// MetricsRegistry is an accessor to the metrics registry. // MetricsRegistry is an accessor to the metrics registry.
func (o *ObservabilityMgr) MetricsRegistry() metrics.Registry { func (o *ObservabilityMgr) MetricsRegistry() metrics.Registry {
if o == nil { if o == nil {
@ -191,3 +124,89 @@ func (o *ObservabilityMgr) RotateAccessLogs() error {
return o.accessLoggerMiddleware.Rotate() return o.accessLoggerMiddleware.Rotate()
} }
func (o *ObservabilityMgr) observabilityContextHandler(next http.Handler, internal bool, config dynamic.RouterObservabilityConfig) http.Handler {
return observability.WithObservabilityHandler(next, observability.Observability{
AccessLogsEnabled: o.shouldAccessLog(internal, config),
MetricsEnabled: o.shouldMeter(internal, config),
SemConvMetricsEnabled: o.shouldMeterSemConv(internal, config),
TracingEnabled: o.shouldTrace(internal, config, types.MinimalVerbosity),
DetailedTracingEnabled: o.shouldTrace(internal, config, types.DetailedVerbosity),
})
}
// shouldAccessLog returns whether the access logs should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) shouldAccessLog(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
if o == nil {
return false
}
if o.config.AccessLog == nil {
return false
}
if internal && !o.config.AccessLog.AddInternals {
return false
}
return observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
}
// shouldMeter returns whether the metrics should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) shouldMeter(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
if o == nil || o.metricsRegistry == nil {
return false
}
if !o.metricsRegistry.IsEpEnabled() && !o.metricsRegistry.IsRouterEnabled() && !o.metricsRegistry.IsSvcEnabled() {
return false
}
if o.config.Metrics == nil {
return false
}
if internal && !o.config.Metrics.AddInternals {
return false
}
return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
}
// shouldMeterSemConv returns whether the OTel semantic convention metrics should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) shouldMeterSemConv(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
if o == nil || o.semConvMetricRegistry == nil {
return false
}
if o.config.Metrics == nil {
return false
}
if internal && !o.config.Metrics.AddInternals {
return false
}
return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
}
// shouldTrace returns whether the tracing should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) shouldTrace(internal bool, observabilityConfig dynamic.RouterObservabilityConfig, verbosity types.TracingVerbosity) bool {
if o == nil {
return false
}
if o.config.Tracing == nil {
return false
}
if internal && !o.config.Tracing.AddInternals {
return false
}
if !observabilityConfig.TraceVerbosity.Allows(verbosity) {
return false
}
return observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
}

View File

@ -7,7 +7,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/plugins" "github.com/traefik/traefik/v3/pkg/plugins"
"go.opentelemetry.io/otel/trace"
) )
const typeName = "Plugin" const typeName = "Plugin"
@ -55,6 +54,6 @@ func (s *traceablePlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
s.h.ServeHTTP(rw, req) s.h.ServeHTTP(rw, req)
} }
func (s *traceablePlugin) GetTracingInformation() (string, string, trace.SpanKind) { func (s *traceablePlugin) GetTracingInformation() (string, string) {
return s.name, typeName, trace.SpanKindInternal return s.name, typeName
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/containous/alice" "github.com/containous/alice"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
@ -70,11 +71,22 @@ func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler { func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler {
entryPointHandlers := make(map[string]http.Handler) entryPointHandlers := make(map[string]http.Handler)
defaultObsConfig := dynamic.RouterObservabilityConfig{}
defaultObsConfig.SetDefaults()
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) { for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
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, entryPointName, routers) // TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration.
// When the entry point has no observability configuration no model is produced,
// and we need to create the default configuration is this case.
epObsConfig := defaultObsConfig
if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil {
epObsConfig = model.Observability
}
handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers, epObsConfig)
if err != nil { if err != nil {
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
continue continue
@ -93,7 +105,15 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
continue continue
} }
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter()) // TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration.
// When the entry point has no observability configuration no model is produced,
// and we need to create the default configuration is this case.
epObsConfig := defaultObsConfig
if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil {
epObsConfig = model.Observability
}
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, epObsConfig).Then(http.NotFoundHandler())
if err != nil { if err != nil {
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
continue continue
@ -104,10 +124,10 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
return entryPointHandlers return entryPointHandlers
} }
func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) { func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo, config dynamic.RouterObservabilityConfig) (http.Handler, error) {
muxer := httpmuxer.NewMuxer(m.parser) muxer := httpmuxer.NewMuxer(m.parser)
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler()) defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, config).Then(http.NotFoundHandler())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -136,7 +156,11 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
continue continue
} }
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability) if routerConfig.Observability != nil {
config = *routerConfig.Observability
}
observabilityChain := m.observabilityMgr.BuildEPChain(ctxRouter, entryPointName, strings.HasSuffix(routerConfig.Service, "@internal"), config)
handler, err = observabilityChain.Then(handler) handler, err = observabilityChain.Then(handler)
if err != nil { if err != nil {
routerConfig.AddError(err, true) routerConfig.AddError(err, true)
@ -180,22 +204,7 @@ 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), routerConfig.Observability) {
m.routerHandlers[routerName] = handler m.routerHandlers[routerName] = handler
return m.routerHandlers[routerName], nil
}
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
}).Then(handler)
if err != nil {
log.Ctx(ctx).Error().Err(err).Send()
m.routerHandlers[routerName] = handler
} else {
m.routerHandlers[routerName] = handlerWithAccessLog
}
return m.routerHandlers[routerName], nil return m.routerHandlers[routerName], nil
} }
@ -210,40 +219,29 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
return nil, errors.New("the service is missing on the router") return nil, errors.New("the service is missing on the router")
} }
sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service) qualifiedService := provider.GetQualifiedName(ctx, router.Service)
if err != nil {
return nil, err
}
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
chain := alice.New() chain := alice.New()
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
}
// Prevents from enabling tracing for internal resources.
if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
return chain.Extend(*mHandler).Then(sHandler)
}
chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() {
metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
}
if router.DefaultRule { if router.DefaultRule {
chain = chain.Append(denyrouterrecursion.WrapHandler(routerName)) chain = chain.Append(denyrouterrecursion.WrapHandler(routerName))
} }
// Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled.
chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, qualifiedService))
metricsHandler := metricsMiddle.RouterMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, qualifiedService)
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
})
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
sHandler, err := m.serviceManager.BuildHTTP(ctx, qualifiedService)
if err != nil {
return nil, err
}
return chain.Extend(*mHandler).Then(sHandler) return chain.Extend(*mHandler).Then(sHandler)
} }
// BuildDefaultHTTPRouter creates a default HTTP router.
func BuildDefaultHTTPRouter() http.Handler {
return http.NotFoundHandler()
}

View File

@ -929,7 +929,7 @@ func BenchmarkService(b *testing.B) {
type proxyBuilderMock struct{} type proxyBuilderMock struct{}
func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) { func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) {
return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil
} }

View File

@ -258,7 +258,7 @@ func TestInternalServices(t *testing.T) {
type proxyBuilderMock struct{} type proxyBuilderMock struct{}
func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) { func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) {
return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil
} }

View File

@ -29,7 +29,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/forwardedheaders" "github.com/traefik/traefik/v3/pkg/middlewares/forwardedheaders"
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
"github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/server/router"
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp" tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
"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"
@ -351,7 +350,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) {
httpHandler := rt.GetHTTPHandler() httpHandler := rt.GetHTTPHandler()
if httpHandler == nil { if httpHandler == nil {
httpHandler = router.BuildDefaultHTTPRouter() httpHandler = http.NotFoundHandler()
} }
e.httpServer.Switcher.UpdateHandler(httpHandler) e.httpServer.Switcher.UpdateHandler(httpHandler)
@ -360,7 +359,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) {
httpsHandler := rt.GetHTTPSHandler() httpsHandler := rt.GetHTTPSHandler()
if httpsHandler == nil { if httpsHandler == nil {
httpsHandler = router.BuildDefaultHTTPRouter() httpsHandler = http.NotFoundHandler()
} }
e.httpsServer.Switcher.UpdateHandler(httpsHandler) e.httpsServer.Switcher.UpdateHandler(httpsHandler)
@ -591,7 +590,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
return nil, errors.New("max concurrent streams value must be greater than or equal to zero") return nil, errors.New("max concurrent streams value must be greater than or equal to zero")
} }
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter()) httpSwitcher := middlewares.NewHandlerSwitcher(http.NotFoundHandler())
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher) next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
if err != nil { if err != nil {

View File

@ -19,7 +19,6 @@ import (
"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/middlewares/accesslog" "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" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/retry" "github.com/traefik/traefik/v3/pkg/middlewares/retry"
@ -37,7 +36,7 @@ import (
// ProxyBuilder builds reverse proxy handlers. // ProxyBuilder builds reverse proxy handlers.
type ProxyBuilder interface { type ProxyBuilder interface {
Build(cfgName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) Build(cfgName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error)
Update(configs map[string]*dynamic.ServersTransport) Update(configs map[string]*dynamic.ServersTransport)
} }
@ -364,50 +363,32 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName) qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName)
shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, passHostHeader, server.PreservePath, flushInterval)
proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, shouldObserve, passHostHeader, server.PreservePath, flushInterval)
if err != nil { if err != nil {
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err) return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
} }
// The retry wrapping must be done just before the proxy handler, // The retry wrapping must be done just before the proxy handler,
// to make sure that the retry will not be triggered/disabled by // to make sure that the retry will not be triggered/disabled by
// middlewares in the chain. // middlewares in the chain.
proxy = retry.WrapHandler(proxy) proxy = retry.WrapHandler(proxy)
// Prevents from enabling observability for internal resources. // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled.
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) {
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, qualifiedSvcName, accesslog.AddServiceFields)
}
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() && metricsHandler := metricsMiddle.ServiceMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), qualifiedSvcName)
m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) { metricsHandler = observability.WrapMiddleware(ctx, metricsHandler)
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
proxy, err = alice.New(). proxy, err = alice.New().
Append(observability.WrapMiddleware(ctx, metricsHandler)). Append(metricsHandler).
Then(proxy) Then(proxy)
if err != nil { if err != nil {
return nil, fmt.Errorf("error wrapping metrics handler: %w", err) return nil, fmt.Errorf("error wrapping metrics handler: %w", err)
} }
}
if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) { proxy = observability.NewService(ctx, qualifiedSvcName, proxy)
proxy = observability.NewService(ctx, serviceName, proxy)
}
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
// Some piece of middleware, like the ErrorPage, are relying on this serviceBuilder to get the handler for a given service,
// to re-target the request to it.
// Those pieces of middleware can be configured on routes that expose a Traefik internal service.
// In such a case, observability for internals being optional, the capture probe could be absent from context (no wrap via the entrypoint).
// But if the service targeted by this piece of middleware is not an internal one,
// and requires observability, we still want the capture probe to be present in the request context.
// Makes sure a capture probe is in the request context.
proxy, _ = capture.Wrap(proxy)
}
lb.AddServer(server.URL, proxy, server) lb.AddServer(server.URL, proxy, server)

View File

@ -2,6 +2,7 @@ package testhelpers
import ( import (
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/types"
) )
// BuildConfiguration is a helper to create a configuration. // BuildConfiguration is a helper to create a configuration.
@ -60,6 +61,7 @@ func WithObservability() func(*dynamic.Router) {
AccessLogs: pointer(true), AccessLogs: pointer(true),
Metrics: pointer(true), Metrics: pointer(true),
Tracing: pointer(true), Tracing: pointer(true),
TraceVerbosity: types.MinimalVerbosity,
} }
} }
} }

View File

@ -23,6 +23,22 @@ import (
"google.golang.org/grpc/encoding/gzip" "google.golang.org/grpc/encoding/gzip"
) )
type TracingVerbosity string
const (
MinimalVerbosity TracingVerbosity = "minimal"
DetailedVerbosity TracingVerbosity = "detailed"
)
func (v TracingVerbosity) Allows(verbosity TracingVerbosity) bool {
switch v {
case DetailedVerbosity:
return verbosity == DetailedVerbosity || verbosity == MinimalVerbosity
default:
return verbosity == MinimalVerbosity
}
}
// OTelTracing provides configuration settings for the open-telemetry tracer. // OTelTracing provides configuration settings for the open-telemetry tracer.
type OTelTracing struct { type OTelTracing struct {
GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`

72
pkg/types/tracing_test.go Normal file
View File

@ -0,0 +1,72 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestTracingVerbosity_Allows(t *testing.T) {
tests := []struct {
desc string
from TracingVerbosity
to TracingVerbosity
allows bool
}{
{
desc: "minimal vs minimal",
from: MinimalVerbosity,
to: MinimalVerbosity,
allows: true,
},
{
desc: "minimal vs detailed",
from: MinimalVerbosity,
to: DetailedVerbosity,
allows: false,
},
{
desc: "detailed vs minimal",
from: DetailedVerbosity,
to: MinimalVerbosity,
allows: true,
},
{
desc: "detailed vs detailed",
from: DetailedVerbosity,
to: DetailedVerbosity,
allows: true,
},
{
desc: "unknown vs minimal",
from: TracingVerbosity("unknown"),
to: MinimalVerbosity,
allows: true,
},
{
desc: "unknown vs detailed",
from: TracingVerbosity("unknown"),
to: DetailedVerbosity,
allows: false,
},
{
desc: "minimal vs unknown",
from: MinimalVerbosity,
to: TracingVerbosity("unknown"),
allows: false,
},
{
desc: "detailed vs unknown",
from: DetailedVerbosity,
to: TracingVerbosity("unknown"),
allows: false,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
require.Equal(t, test.allows, test.from.Allows(test.to))
})
}
}