diff --git a/docs/content/migration/v3.md b/docs/content/migration/v3.md
index 6581b5f399..e3cbe8a9b5 100644
--- a/docs/content/migration/v3.md
+++ b/docs/content/migration/v3.md
@@ -319,3 +319,23 @@ and Traefik now keeps them encoded to avoid any ambiguity.
| `/foo/../bar` | PathPrefix(`/bar`) | Match | Match |
| `/foo/%2E%2E/bar` | PathPrefix(`/foo`) | Match | No 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).
diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml
index b2af3def6a..3399c27626 100644
--- a/docs/content/reference/dynamic-configuration/docker-labels.yml
+++ b/docs/content/reference/dynamic-configuration/docker-labels.yml
@@ -169,6 +169,7 @@
- "traefik.http.routers.router0.middlewares=foobar, foobar"
- "traefik.http.routers.router0.observability.accesslogs=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.priority=42"
- "traefik.http.routers.router0.rule=foobar"
@@ -185,6 +186,7 @@
- "traefik.http.routers.router1.middlewares=foobar, foobar"
- "traefik.http.routers.router1.observability.accesslogs=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.priority=42"
- "traefik.http.routers.router1.rule=foobar"
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index 202815c392..b7f30c6496 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -22,8 +22,9 @@
sans = ["foobar", "foobar"]
[http.routers.Router0.observability]
accessLogs = true
- tracing = true
metrics = true
+ tracing = true
+ traceVerbosity = "foobar"
[http.routers.Router1]
entryPoints = ["foobar", "foobar"]
middlewares = ["foobar", "foobar"]
@@ -44,8 +45,9 @@
sans = ["foobar", "foobar"]
[http.routers.Router1.observability]
accessLogs = true
- tracing = true
metrics = true
+ tracing = true
+ traceVerbosity = "foobar"
[http.services]
[http.services.Service01]
[http.services.Service01.failover]
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index e8210ef858..2ac73fd854 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -27,8 +27,9 @@ http:
- foobar
observability:
accessLogs: true
- tracing: true
metrics: true
+ tracing: true
+ traceVerbosity: foobar
Router1:
entryPoints:
- foobar
@@ -54,8 +55,9 @@ http:
- foobar
observability:
accessLogs: true
- tracing: true
metrics: true
+ tracing: true
+ traceVerbosity: foobar
services:
Service01:
failover:
diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
index d3f71b5f94..22a08a7d71 100644
--- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
+++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
@@ -92,10 +92,21 @@ spec:
More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability
properties:
accessLogs:
+ description: AccessLogs enables access logs for this router.
type: boolean
metrics:
+ description: Metrics enables metrics for this router.
type: boolean
+ traceVerbosity:
+ default: minimal
+ description: TraceVerbosity defines the verbosity level
+ of the tracing for this router.
+ enum:
+ - minimal
+ - detailed
+ type: string
tracing:
+ description: Tracing enables tracing for this router.
type: boolean
type: object
priority:
diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md
index 733b1bdb5f..a7c1b217f2 100644
--- a/docs/content/reference/dynamic-configuration/kv-ref.md
+++ b/docs/content/reference/dynamic-configuration/kv-ref.md
@@ -199,6 +199,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/routers/Router0/middlewares/1` | `foobar` |
| `traefik/http/routers/Router0/observability/accessLogs` | `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/priority` | `42` |
| `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/observability/accessLogs` | `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/priority` | `42` |
| `traefik/http/routers/Router1/rule` | `foobar` |
diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml
index 0a5d6749a8..46cc789769 100644
--- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml
+++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml
@@ -92,10 +92,21 @@ spec:
More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability
properties:
accessLogs:
+ description: AccessLogs enables access logs for this router.
type: boolean
metrics:
+ description: Metrics enables metrics for this router.
type: boolean
+ traceVerbosity:
+ default: minimal
+ description: TraceVerbosity defines the verbosity level
+ of the tracing for this router.
+ enum:
+ - minimal
+ - detailed
+ type: string
tracing:
+ description: Tracing enables tracing for this router.
type: boolean
type: object
priority:
diff --git a/docs/content/reference/install-configuration/entrypoints.md b/docs/content/reference/install-configuration/entrypoints.md
index 9c0aab31cc..2ae13200e1 100644
--- a/docs/content/reference/install-configuration/entrypoints.md
+++ b/docs/content/reference/install-configuration/entrypoints.md
@@ -83,39 +83,40 @@ additionalArguments:
## Configuration Options
-| Field | Description | Default | Required |
-|:-----------------|:--------|:--------|:---------|
-| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.
It also defines the protocol to use (TCP or UDP).
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`.
`entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.
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.insecure` | Set the insecure mode to always trust the forwarded headers information (`X-Forwarded-*`).
We recommend to use this option only for tests purposes, not in production. | false | No |
-| `http.redirections.`
`entryPoint.to` | The target element to enable (permanent) redirecting of all incoming requests on an entry point to another one.
The target element can be an entry point name (ex: `websecure`), or a port (`:443`). | - | Yes |
-| `http.redirections.`
`entryPoint.scheme` | The target scheme to use for (permanent) redirection of all incoming requests. | https | No |
-| `http.redirections.`
`entryPoint.permanent` | Enable permanent redirecting of all incoming requests on an entry point to another one changing the scheme.
The target element, it can be an entry point name (ex: `websecure`), or a port (`:443`). | false | No |
-| `http.redirections.`
`entryPoint.priority` | Default priority applied to the routers attached to the `entryPoint`.| MaxInt32-1 (2147483646) | No |
-| `http.encodeQuerySemicolons` | Enable query semicolons encoding.
Use this option to avoid non-encoded semicolons to be interpreted as query parameter separators by Traefik.
When using this option, the non-encoded semicolons characters in query will be transmitted encoded to the backend.
More information [here](#encodequerysemicolons). | false | No |
-| `http.sanitizePath` | Defines whether to enable the request path sanitization.
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.
More information [here](#httpmiddlewares). | - | No |
-| `http.tls` | Enable TLS on every router attached to the `entryPoint`.
If no certificate are set, a default self-signed certificate is generates by Traefik.
We recommend to not use self signed certificates in production.| - | No |
-| `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
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`.
The TLS options can be overidden per router.
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.
The value must be greater than zero. | 250 | No |
-| `http3` | Enable HTTP/3 protocol on the `entryPoint`.
HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.
More information [here](#http3. | - | No |
-| `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority.
It defaults to the entryPoint's address port.
It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | 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.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 |
-| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs.
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
More information [here](#proxyprotocol-and-load-balancers). | - | No |
-| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection.
Every remote client address will be replaced (`trustedIPs`) won't have any effect).
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
We recommend to use this option only for tests purposes, not in production.
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.
It also allows the kernel to act like a load balancer to distribute incoming connections between entry points..
More information [here](#reuseport). | false | No |
-| `transport.`
`respondingTimeouts.`
`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`.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No |
-| `transport.`
`respondingTimeouts.`
`writeTimeout` | Maximum duration before timing out writes of the response.
It covers the time from the end of the request header read to the end of the response write.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No |
-| `transport.`
`respondingTimeouts.`
`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself.
If zero, no timeout exists
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No |
-| `transport.`
`lifeCycle.`
`graceTimeOut` | Set the duration to give active requests a chance to finish before Traefik stops.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds
In this time frame no new requests are accepted. | 10s (seconds) | No |
-| `transport.`
`lifeCycle.`
`requestAcceptGraceTimeout` | Set the duration to keep accepting requests prior to initiating the graceful termination period (as defined by the `transportlifeCycle.graceTimeOut` option).
This option is meant to give downstream load-balancers sufficient time to take Traefik out of rotation.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 0s (seconds) | No |
-| `transport.`
`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).
Zero means no limit. | 0 | No |
-| `transport.`
`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.
The Timeout value must be greater than zero. | 3s (seconds)| No |
+| Field | Description | Default | Required |
+|:----------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------|:---------|
+| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.
It also defines the protocol to use (TCP or UDP).
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`.
`entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.
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.insecure` | Set the insecure mode to always trust the forwarded headers information (`X-Forwarded-*`).
We recommend to use this option only for tests purposes, not in production. | false | No |
+| `http.redirections.`
`entryPoint.to` | The target element to enable (permanent) redirecting of all incoming requests on an entry point to another one.
The target element can be an entry point name (ex: `websecure`), or a port (`:443`). | - | Yes |
+| `http.redirections.`
`entryPoint.scheme` | The target scheme to use for (permanent) redirection of all incoming requests. | https | No |
+| `http.redirections.`
`entryPoint.permanent` | Enable permanent redirecting of all incoming requests on an entry point to another one changing the scheme.
The target element, it can be an entry point name (ex: `websecure`), or a port (`:443`). | false | No |
+| `http.redirections.`
`entryPoint.priority` | Default priority applied to the routers attached to the `entryPoint`. | MaxInt32-1 (2147483646) | No |
+| `http.encodeQuerySemicolons` | Enable query semicolons encoding.
Use this option to avoid non-encoded semicolons to be interpreted as query parameter separators by Traefik.
When using this option, the non-encoded semicolons characters in query will be transmitted encoded to the backend.
More information [here](#encodequerysemicolons). | false | No |
+| `http.sanitizePath` | Defines whether to enable the request path sanitization.
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.
More information [here](#httpmiddlewares). | - | No |
+| `http.tls` | Enable TLS on every router attached to the `entryPoint`.
If no certificate are set, a default self-signed certificate is generates by Traefik.
We recommend to not use self signed certificates in production. | - | No |
+| `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
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`.
The TLS options can be overidden per router.
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.
The value must be greater than zero. | 250 | No |
+| `http3` | Enable HTTP/3 protocol on the `entryPoint`.
HTTP/3 requires a TCP `entryPoint`. as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. In most scenarios, this `entryPoint` is the same as the one used for TLS traffic.
More information [here](#http3. | - | No |
+| `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority.
It defaults to the entryPoint's address port.
It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | 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.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.
More information [here](#traceverbosity). | minimal | No |
+| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs.
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
More information [here](#proxyprotocol-and-load-balancers). | - | No |
+| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection.
Every remote client address will be replaced (`trustedIPs`) won't have any effect).
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
We recommend to use this option only for tests purposes, not in production.
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.
It also allows the kernel to act like a load balancer to distribute incoming connections between entry points.
More information [here](#reuseport). | false | No |
+| `transport.`
`respondingTimeouts.`
`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`.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No |
+| `transport.`
`respondingTimeouts.`
`writeTimeout` | Maximum duration before timing out writes of the response.
It covers the time from the end of the request header read to the end of the response write.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No |
+| `transport.`
`respondingTimeouts.`
`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself.
If zero, no timeout exists
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No |
+| `transport.`
`lifeCycle.`
`graceTimeOut` | Set the duration to give active requests a chance to finish before Traefik stops.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds
In this time frame no new requests are accepted. | 10s (seconds) | No |
+| `transport.`
`lifeCycle.`
`requestAcceptGraceTimeout` | Set the duration to keep accepting requests prior to initiating the graceful termination period (as defined by the `transportlifeCycle.graceTimeOut` option).
This option is meant to give downstream load-balancers sufficient time to take Traefik out of rotation.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 0s (seconds) | No |
+| `transport.`
`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).
Zero means no limit. | 0 | No |
+| `transport.`
`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.
The Timeout value must be greater than zero. | 3s (seconds) | No |
### asDefault
@@ -271,3 +272,13 @@ Use the `reusePort` option with the other option `transport.lifeCycle.gracetimeo
to do
canary deployments against Traefik itself. Like upgrading Traefik version
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.
diff --git a/docs/content/reference/routing-configuration/http/router/observability.md b/docs/content/reference/routing-configuration/http/router/observability.md
index ecadcaeed9..4c9229f1cc 100644
--- a/docs/content/reference/routing-configuration/http/router/observability.md
+++ b/docs/content/reference/routing-configuration/http/router/observability.md
@@ -36,6 +36,7 @@ http:
metrics: false
accessLogs: false
tracing: false
+ traceVerbosity: detailed
```
```yaml tab="Structured (TOML)"
@@ -47,6 +48,7 @@ http:
metrics = false
accessLogs = false
tracing = false
+ traceVerbosity = "detailed"
```
```yaml tab="Labels"
@@ -56,6 +58,7 @@ labels:
- "traefik.http.routers.my-router.observability.metrics=false"
- "traefik.http.routers.my-router.observability.accessLogs=false"
- "traefik.http.routers.my-router.observability.tracing=false"
+ - "traefik.http.routers.my-router.observability.traceVerbosity=detailed"
```
```json tab="Tags"
@@ -66,15 +69,26 @@ labels:
"traefik.http.routers.my-router.service=service-foo",
"traefik.http.routers.my-router.observability.metrics=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"
]
}
```
## Configuration Options
-| Field | Description | Default | Required |
-|:------|:------------|:--------|:---------|
-| `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 |
-| `tracing` | The `tracing` option controls whether the router will produce traces. | `true` | No |
+| Field | Description | Default | Required |
+|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------|:---------|
+| `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 |
+| `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.
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index cfc3779350..839f389881 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -283,13 +283,16 @@ HTTP/3 configuration. (Default: ```false```)
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
`--entrypoints..observability.accesslogs`:
- (Default: ```true```)
+Enables access-logs for this entryPoint. (Default: ```true```)
`--entrypoints..observability.metrics`:
- (Default: ```true```)
+Enables metrics for this entryPoint. (Default: ```true```)
+
+`--entrypoints..observability.traceverbosity`:
+Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```)
`--entrypoints..observability.tracing`:
- (Default: ```true```)
+Enables tracing for this entryPoint. (Default: ```true```)
`--entrypoints..proxyprotocol`:
Proxy-Protocol configuration. (Default: ```false```)
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 7a3f59fcbf..f98a205b18 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -283,13 +283,16 @@ Subject alternative names.
Default TLS options for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS__OBSERVABILITY_ACCESSLOGS`:
- (Default: ```true```)
+Enables access-logs for this entryPoint. (Default: ```true```)
`TRAEFIK_ENTRYPOINTS__OBSERVABILITY_METRICS`:
- (Default: ```true```)
+Enables metrics for this entryPoint. (Default: ```true```)
+
+`TRAEFIK_ENTRYPOINTS__OBSERVABILITY_TRACEVERBOSITY`:
+Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```)
`TRAEFIK_ENTRYPOINTS__OBSERVABILITY_TRACING`:
- (Default: ```true```)
+Enables tracing for this entryPoint. (Default: ```true```)
`TRAEFIK_ENTRYPOINTS__PROXYPROTOCOL`:
Proxy-Protocol configuration. (Default: ```false```)
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index 515a556871..c9746ce47a 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -80,8 +80,9 @@
timeout = "42s"
[entryPoints.EntryPoint0.observability]
accessLogs = true
- tracing = true
metrics = true
+ tracing = true
+ traceVerbosity = "foobar"
[providers]
providersThrottleDuration = "42s"
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index f364786111..c6ad473f54 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -94,8 +94,9 @@ entryPoints:
timeout: 42s
observability:
accessLogs: true
- tracing: true
metrics: true
+ tracing: true
+ traceVerbosity: foobar
providers:
providersThrottleDuration: 42s
docker:
diff --git a/integration/access_log_test.go b/integration/access_log_test.go
index e1c4003e9d..2f480b5240 100644
--- a/integration/access_log_test.go
+++ b/integration/access_log_test.go
@@ -648,25 +648,6 @@ func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
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.
s.checkNoOtherTraefikProblems()
}
diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml
index d3f71b5f94..22a08a7d71 100644
--- a/integration/fixtures/k8s/01-traefik-crd.yml
+++ b/integration/fixtures/k8s/01-traefik-crd.yml
@@ -92,10 +92,21 @@ spec:
More info: https://doc.traefik.io/traefik/v3.5/routing/routers/#observability
properties:
accessLogs:
+ description: AccessLogs enables access logs for this router.
type: boolean
metrics:
+ description: Metrics enables metrics for this router.
type: boolean
+ traceVerbosity:
+ default: minimal
+ description: TraceVerbosity defines the verbosity level
+ of the tracing for this router.
+ enum:
+ - minimal
+ - detailed
+ type: string
tracing:
+ description: Tracing enables tracing for this router.
type: boolean
type: object
priority:
diff --git a/integration/fixtures/tracing/simple-opentelemetry.toml b/integration/fixtures/tracing/simple-opentelemetry.toml
index a8643ebd71..61aaebfa8b 100644
--- a/integration/fixtures/tracing/simple-opentelemetry.toml
+++ b/integration/fixtures/tracing/simple-opentelemetry.toml
@@ -14,6 +14,10 @@
[entryPoints]
[entryPoints.web]
address = ":8000"
+ [entryPoints.web.observability]
+ traceVerbosity = "detailed"
+ [entryPoints.web-minimal]
+ address = ":8001"
# Adding metrics to confirm that there is no wrong interaction with tracing.
[metrics]
@@ -44,9 +48,13 @@
## dynamic configuration ##
[http.routers]
+ [http.routers.routerBasicMinimal]
+ Service = "service0"
+ Rule = "Path(`/basic-minimal`)"
+ [http.routers.routerBasicMinimal.observability]
+ traceVerbosity = "minimal"
[http.routers.router0]
Service = "service0"
- Middlewares = []
Rule = "Path(`/basic`)"
[http.routers.router1]
Service = "service1"
diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml
index a5e4f5d441..1447f4cba5 100644
--- a/integration/resources/compose/access_log.yml
+++ b/integration/resources/compose/access_log.yml
@@ -101,13 +101,3 @@ services:
traefik.http.routers.ping.entryPoints: ping
traefik.http.routers.ping.rule: PathPrefix(`/ping`)
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
diff --git a/integration/testdata/rawdata-consul.json b/integration/testdata/rawdata-consul.json
index 90806c2ac4..9b27203a7e 100644
--- a/integration/testdata/rawdata-consul.json
+++ b/integration/testdata/rawdata-consul.json
@@ -14,8 +14,9 @@
"tls": {},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +50,9 @@
},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -67,8 +69,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -89,8 +92,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -100,7 +104,13 @@
},
"middlewares": {
"compressor@consul": {
- "compress": {},
+ "compress": {
+ "encodings": [
+ "gzip",
+ "br",
+ "zstd"
+ ]
+ },
"status": "enabled",
"usedBy": [
"Router0@consul"
@@ -173,6 +183,7 @@
"mirror@consul": {
"mirroring": {
"service": "simplesvc",
+ "mirrorBody": true,
"maxBodySize": -1,
"mirrors": [
{
diff --git a/integration/testdata/rawdata-crd-label-selector.json b/integration/testdata/rawdata-crd-label-selector.json
index 8ff23a25aa..88219a627d 100644
--- a/integration/testdata/rawdata-crd-label-selector.json
+++ b/integration/testdata/rawdata-crd-label-selector.json
@@ -9,8 +9,9 @@
"priority": 18,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -29,8 +30,9 @@
},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json
index 334b6c698d..9f1b2d33c7 100644
--- a/integration/testdata/rawdata-crd.json
+++ b/integration/testdata/rawdata-crd.json
@@ -9,8 +9,9 @@
"priority": 18,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -29,8 +30,9 @@
},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +51,9 @@
"priority": 46,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -66,8 +69,9 @@
"priority": 38,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -83,8 +87,9 @@
"priority": 50,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -100,8 +105,9 @@
"priority": 35,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"error": [
"the service \"other-ns-wrr3@kubernetescrd\" does not exist"
diff --git a/integration/testdata/rawdata-etcd.json b/integration/testdata/rawdata-etcd.json
index f03d1b67e5..360488433e 100644
--- a/integration/testdata/rawdata-etcd.json
+++ b/integration/testdata/rawdata-etcd.json
@@ -14,8 +14,9 @@
"tls": {},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +50,9 @@
},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -67,8 +69,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -89,8 +92,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -100,7 +104,13 @@
},
"middlewares": {
"compressor@etcd": {
- "compress": {},
+ "compress": {
+ "encodings": [
+ "gzip",
+ "br",
+ "zstd"
+ ]
+ },
"status": "enabled",
"usedBy": [
"Router0@etcd"
@@ -173,6 +183,7 @@
"mirror@etcd": {
"mirroring": {
"service": "simplesvc",
+ "mirrorBody": true,
"maxBodySize": -1,
"mirrors": [
{
diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json
index b6206c1216..8a6e1bc8c5 100644
--- a/integration/testdata/rawdata-gateway.json
+++ b/integration/testdata/rawdata-gateway.json
@@ -10,8 +10,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -32,8 +33,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -50,8 +52,9 @@
"priority": 100008,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -69,8 +72,9 @@
"tls": {},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
diff --git a/integration/testdata/rawdata-ingress-label-selector.json b/integration/testdata/rawdata-ingress-label-selector.json
index 23305a8474..a4081171c1 100644
--- a/integration/testdata/rawdata-ingress-label-selector.json
+++ b/integration/testdata/rawdata-ingress-label-selector.json
@@ -10,8 +10,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -32,8 +33,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +51,9 @@
"priority": 44,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json
index ba2ad706b2..a317099fb8 100644
--- a/integration/testdata/rawdata-ingress.json
+++ b/integration/testdata/rawdata-ingress.json
@@ -10,8 +10,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -32,8 +33,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +51,9 @@
"priority": 50,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -66,8 +69,9 @@
"priority": 44,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -83,8 +87,9 @@
"priority": 47,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -100,8 +105,9 @@
"priority": 47,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
diff --git a/integration/testdata/rawdata-ingressclass-disabled.json b/integration/testdata/rawdata-ingressclass-disabled.json
index a263692823..5f56a981f3 100644
--- a/integration/testdata/rawdata-ingressclass-disabled.json
+++ b/integration/testdata/rawdata-ingressclass-disabled.json
@@ -10,8 +10,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -32,8 +33,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
diff --git a/integration/testdata/rawdata-ingressclass.json b/integration/testdata/rawdata-ingressclass.json
index 05649bfa17..c860c8862d 100644
--- a/integration/testdata/rawdata-ingressclass.json
+++ b/integration/testdata/rawdata-ingressclass.json
@@ -10,8 +10,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -32,8 +33,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +51,9 @@
"priority": 47,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
diff --git a/integration/testdata/rawdata-redis.json b/integration/testdata/rawdata-redis.json
index 381d92df52..84e0949476 100644
--- a/integration/testdata/rawdata-redis.json
+++ b/integration/testdata/rawdata-redis.json
@@ -14,8 +14,9 @@
"tls": {},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +50,9 @@
},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -67,8 +69,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -89,8 +92,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -100,7 +104,13 @@
},
"middlewares": {
"compressor@redis": {
- "compress": {},
+ "compress": {
+ "encodings": [
+ "gzip",
+ "br",
+ "zstd"
+ ]
+ },
"status": "enabled",
"usedBy": [
"Router0@redis"
@@ -173,6 +183,7 @@
"mirror@redis": {
"mirroring": {
"service": "simplesvc",
+ "mirrorBody": true,
"maxBodySize": -1,
"mirrors": [
{
@@ -244,6 +255,7 @@
"url": "http://10.0.1.3:8889"
}
],
+ "strategy": "wrr",
"passHostHeader": true,
"responseForwarding": {
"flushInterval": "100ms"
diff --git a/integration/testdata/rawdata-zk.json b/integration/testdata/rawdata-zk.json
index 265afbdbc0..3401d4c32e 100644
--- a/integration/testdata/rawdata-zk.json
+++ b/integration/testdata/rawdata-zk.json
@@ -14,8 +14,9 @@
"tls": {},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -49,8 +50,9 @@
},
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -67,8 +69,9 @@
"priority": 9223372036854775806,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -89,8 +92,9 @@
"priority": 9223372036854775805,
"observability": {
"accessLogs": true,
+ "metrics": true,
"tracing": true,
- "metrics": true
+ "traceVerbosity": "minimal"
},
"status": "enabled",
"using": [
@@ -100,7 +104,13 @@
},
"middlewares": {
"compressor@zookeeper": {
- "compress": {},
+ "compress": {
+ "encodings": [
+ "gzip",
+ "br",
+ "zstd"
+ ]
+ },
"status": "enabled",
"usedBy": [
"Router0@zookeeper"
@@ -173,6 +183,7 @@
"mirror@zookeeper": {
"mirroring": {
"service": "simplesvc",
+ "mirrorBody": true,
"maxBodySize": -1,
"mirrors": [
{
diff --git a/integration/tracing_test.go b/integration/tracing_test.go
index d44ed0d0b8..9b9fa0fafe 100644
--- a/integration/tracing_test.go
+++ b/integration/tracing_test.go
@@ -77,6 +77,104 @@ func (s *TracingSuite) TearDownTest() {
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() {
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.5.name": "Metrics",
diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go
index a867c882b4..ef6a896f16 100644
--- a/pkg/config/dynamic/http_config.go
+++ b/pkg/config/dynamic/http_config.go
@@ -88,9 +88,21 @@ type RouterTLSConfig struct {
// RouterObservabilityConfig holds the observability configuration for a router.
type RouterObservabilityConfig struct {
+ // AccessLogs enables access logs for this router.
AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
- Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
- Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
+ // Metrics enables metrics for this router.
+ 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
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index 9cf39ba40b..97f8907e08 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -1335,13 +1335,13 @@ func (in *RouterObservabilityConfig) DeepCopyInto(out *RouterObservabilityConfig
*out = new(bool)
**out = **in
}
- if in.Tracing != nil {
- in, out := &in.Tracing, &out.Tracing
+ if in.Metrics != nil {
+ in, out := &in.Metrics, &out.Metrics
*out = new(bool)
**out = **in
}
- if in.Metrics != nil {
- in, out := &in.Metrics, &out.Metrics
+ if in.Tracing != nil {
+ in, out := &in.Tracing, &out.Tracing
*out = new(bool)
**out = **in
}
diff --git a/pkg/config/runtime/runtime.go b/pkg/config/runtime/runtime.go
index d67233887d..4034fa0774 100644
--- a/pkg/config/runtime/runtime.go
+++ b/pkg/config/runtime/runtime.go
@@ -26,9 +26,10 @@ const (
type Configuration struct {
Routers map[string]*RouterInfo `json:"routers,omitempty"`
Middlewares map[string]*MiddlewareInfo `json:"middlewares,omitempty"`
- TCPMiddlewares map[string]*TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"`
Services map[string]*ServiceInfo `json:"services,omitempty"`
+ Models map[string]*dynamic.Model `json:"-"`
TCPRouters map[string]*TCPRouterInfo `json:"tcpRouters,omitempty"`
+ TCPMiddlewares map[string]*TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"`
TCPServices map[string]*TCPServiceInfo `json:"tcpServices,omitempty"`
UDPRouters map[string]*UDPRouterInfo `json:"udpRouters,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.Models = conf.HTTP.Models
}
if conf.TCP != nil {
diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go
index d98cdb1b31..3ac50e0484 100644
--- a/pkg/config/static/entrypoints.go
+++ b/pkg/config/static/entrypoints.go
@@ -165,15 +165,17 @@ func (u *UDPConfig) SetDefaults() {
// ObservabilityConfig holds the observability configuration for an entry point.
type ObservabilityConfig struct {
- AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
- Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
- Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
+ AccessLogs *bool `description:"Enables access-logs for this entryPoint." json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
+ Metrics *bool `description:"Enables metrics for this entryPoint." 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.
func (o *ObservabilityConfig) SetDefaults() {
defaultValue := true
o.AccessLogs = &defaultValue
- o.Tracing = &defaultValue
o.Metrics = &defaultValue
+ o.Tracing = &defaultValue
+ o.TraceVerbosity = types.MinimalVerbosity
}
diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go
index 6ec8ea4c91..2465561957 100644
--- a/pkg/middlewares/accesslog/logger.go
+++ b/pkg/middlewares/accesslog/logger.go
@@ -21,6 +21,7 @@ import (
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
+ "github.com/traefik/traefik/v3/pkg/middlewares/observability"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/contrib/bridges/otellogrus"
@@ -69,11 +70,16 @@ type Handler struct {
wg sync.WaitGroup
}
-// WrapHandler Wraps access log handler into an Alice Constructor.
-func WrapHandler(handler *Handler) alice.Constructor {
+// AliceConstructor returns an alice.Constructor that wraps the Handler (conditionally) in a middleware chain.
+func (h *Handler) AliceConstructor() alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
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
}
}
@@ -196,6 +202,12 @@ func GetLogData(req *http.Request) *LogData {
}
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()
core := CoreLogData{
diff --git a/pkg/middlewares/accesslog/logger_formatters_test.go b/pkg/middlewares/accesslog/logger_formatters_test.go
index 028e3dbf7b..7b00bacbaa 100644
--- a/pkg/middlewares/accesslog/logger_formatters_test.go
+++ b/pkg/middlewares/accesslog/logger_formatters_test.go
@@ -7,6 +7,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
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
- t.Setenv("TZ", "Etc/GMT+9")
+ var err error
+ time.Local, err = time.LoadLocation("Etc/GMT+9")
+ require.NoError(t, err)
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go
index 4daf1a5a57..7babf81215 100644
--- a/pkg/middlewares/accesslog/logger_test.go
+++ b/pkg/middlewares/accesslog/logger_test.go
@@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
+ "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/otel/attribute"
@@ -105,7 +106,15 @@ func TestOTelAccessLog(t *testing.T) {
chain := alice.New()
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) {
rw.WriteHeader(http.StatusOK)
}))
@@ -138,7 +147,15 @@ func TestLogRotation(t *testing.T) {
chain := alice.New()
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) {
rw.WriteHeader(http.StatusOK)
}))
@@ -290,7 +307,15 @@ func TestLoggerHeaderFields(t *testing.T) {
chain := alice.New()
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) {
rw.WriteHeader(http.StatusOK)
}))
@@ -998,7 +1023,15 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS, tracing b
chain := alice.New()
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))
require.NoError(t, err)
@@ -1085,7 +1118,15 @@ func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) {
}), nil
})
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(service, ServiceAddr, "127.0.0.1", nil)
diff --git a/pkg/middlewares/addprefix/add_prefix.go b/pkg/middlewares/addprefix/add_prefix.go
index 46f8d98d09..b698cc9ce5 100644
--- a/pkg/middlewares/addprefix/add_prefix.go
+++ b/pkg/middlewares/addprefix/add_prefix.go
@@ -7,7 +7,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -39,8 +38,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name
return result, nil
}
-func (a *addPrefix) GetTracingInformation() (string, string, trace.SpanKind) {
- return a.name, typeName, trace.SpanKindInternal
+func (a *addPrefix) GetTracingInformation() (string, string) {
+ return a.name, typeName
}
func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/auth/basic_auth.go b/pkg/middlewares/auth/basic_auth.go
index e6c175bcbe..863c968f31 100644
--- a/pkg/middlewares/auth/basic_auth.go
+++ b/pkg/middlewares/auth/basic_auth.go
@@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
- "go.opentelemetry.io/otel/trace"
"golang.org/x/sync/singleflight"
)
@@ -61,8 +60,8 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
return ba, nil
}
-func (b *basicAuth) GetTracingInformation() (string, string, trace.SpanKind) {
- return b.name, typeNameBasic, trace.SpanKindInternal
+func (b *basicAuth) GetTracingInformation() (string, string) {
+ return b.name, typeNameBasic
}
func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/auth/digest_auth.go b/pkg/middlewares/auth/digest_auth.go
index 25c22865ff..af64a134ba 100644
--- a/pkg/middlewares/auth/digest_auth.go
+++ b/pkg/middlewares/auth/digest_auth.go
@@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -54,8 +53,8 @@ func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.Digest
return da, nil
}
-func (d *digestAuth) GetTracingInformation() (string, string, trace.SpanKind) {
- return d.name, typeNameDigest, trace.SpanKindInternal
+func (d *digestAuth) GetTracingInformation() (string, string) {
+ return d.name, typeNameDigest
}
func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go
index 8520e23570..d30d33e2a1 100644
--- a/pkg/middlewares/auth/forward.go
+++ b/pkg/middlewares/auth/forward.go
@@ -131,8 +131,8 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
return fa, nil
}
-func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) {
- return fa.name, typeNameForward, trace.SpanKindInternal
+func (fa *forwardAuth) GetTracingInformation() (string, string) {
+ return fa.name, typeNameForward
}
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 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
tracingCtx, forwardSpan = tracer.Start(req.Context(), "AuthRequest", trace.WithSpanKind(trace.SpanKindClient))
defer forwardSpan.End()
diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go
index 2bd062370c..d3cdebd36a 100644
--- a/pkg/middlewares/auth/forward_test.go
+++ b/pkg/middlewares/auth/forward_test.go
@@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/testhelpers"
"github.com/traefik/traefik/v3/pkg/tracing"
@@ -756,6 +757,10 @@ func TestForwardAuthTracing(t *testing.T) {
next, err := NewForward(t.Context(), next, auth, "authTest")
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.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "forward-test")
diff --git a/pkg/middlewares/buffering/buffering.go b/pkg/middlewares/buffering/buffering.go
index ec9100a98f..cb0fa34140 100644
--- a/pkg/middlewares/buffering/buffering.go
+++ b/pkg/middlewares/buffering/buffering.go
@@ -9,7 +9,6 @@ import (
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares"
oxybuffer "github.com/vulcand/oxy/v2/buffer"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -48,8 +47,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name
}, nil
}
-func (b *buffer) GetTracingInformation() (string, string, trace.SpanKind) {
- return b.name, typeName, trace.SpanKindInternal
+func (b *buffer) GetTracingInformation() (string, string) {
+ return b.name, typeName
}
func (b *buffer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/circuitbreaker/circuit_breaker.go b/pkg/middlewares/circuitbreaker/circuit_breaker.go
index ceaa93de41..c9b3546e3a 100644
--- a/pkg/middlewares/circuitbreaker/circuit_breaker.go
+++ b/pkg/middlewares/circuitbreaker/circuit_breaker.go
@@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/vulcand/oxy/v2/cbreaker"
- "go.opentelemetry.io/otel/trace"
)
const typeName = "CircuitBreaker"
@@ -68,8 +67,8 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
}, nil
}
-func (c *circuitBreaker) GetTracingInformation() (string, string, trace.SpanKind) {
- return c.name, typeName, trace.SpanKindInternal
+func (c *circuitBreaker) GetTracingInformation() (string, string) {
+ return c.name, typeName
}
func (c *circuitBreaker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/compress/compress.go b/pkg/middlewares/compress/compress.go
index 0bb3788d2b..4ab312cc27 100644
--- a/pkg/middlewares/compress/compress.go
+++ b/pkg/middlewares/compress/compress.go
@@ -13,7 +13,6 @@ import (
"github.com/klauspost/compress/zstd"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
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) {
- return c.name, typeName, trace.SpanKindInternal
+func (c *compress) GetTracingInformation() (string, string) {
+ return c.name, typeName
}
func (c *compress) newGzipHandler() (http.Handler, error) {
diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go
index 3cd866a74c..d9c95ea6d0 100644
--- a/pkg/middlewares/customerrors/custom_errors.go
+++ b/pkg/middlewares/customerrors/custom_errors.go
@@ -15,7 +15,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/vulcand/oxy/v2/utils"
- "go.opentelemetry.io/otel/trace"
)
// 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
}
-func (c *customErrors) GetTracingInformation() (string, string, trace.SpanKind) {
- return c.name, typeName, trace.SpanKindInternal
+func (c *customErrors) GetTracingInformation() (string, string) {
+ return c.name, typeName
}
func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go
index 94d43211a9..8808f3e7ef 100644
--- a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go
+++ b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go
@@ -6,7 +6,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
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) {
- return r.name, requestHeaderModifierTypeName, trace.SpanKindUnspecified
+func (r *requestHeaderModifier) GetTracingInformation() (string, string) {
+ return r.name, requestHeaderModifierTypeName
}
func (r *requestHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go
index 2d55b686b5..e47d23758e 100644
--- a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go
+++ b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go
@@ -6,7 +6,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
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) {
- return r.name, responseHeaderModifierTypeName, trace.SpanKindUnspecified
+func (r *responseHeaderModifier) GetTracingInformation() (string, string) {
+ return r.name, responseHeaderModifierTypeName
}
func (r *responseHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/gatewayapi/redirect/request_redirect.go b/pkg/middlewares/gatewayapi/redirect/request_redirect.go
index e2d32e518a..1305a23724 100644
--- a/pkg/middlewares/gatewayapi/redirect/request_redirect.go
+++ b/pkg/middlewares/gatewayapi/redirect/request_redirect.go
@@ -10,7 +10,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
const typeName = "RequestRedirect"
@@ -52,8 +51,8 @@ func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.Req
}, nil
}
-func (r redirect) GetTracingInformation() (string, string, trace.SpanKind) {
- return r.name, typeName, trace.SpanKindInternal
+func (r redirect) GetTracingInformation() (string, string) {
+ return r.name, typeName
}
func (r redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
index 2960a55838..bcddb81375 100644
--- a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
+++ b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
@@ -8,7 +8,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -38,8 +37,8 @@ func NewURLRewrite(ctx context.Context, next http.Handler, conf dynamic.URLRewri
}
}
-func (u urlRewrite) GetTracingInformation() (string, string, trace.SpanKind) {
- return u.name, typeName, trace.SpanKindInternal
+func (u urlRewrite) GetTracingInformation() (string, string) {
+ return u.name, typeName
}
func (u urlRewrite) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go
index 861d1066de..cba8b6401a 100644
--- a/pkg/middlewares/headers/headers.go
+++ b/pkg/middlewares/headers/headers.go
@@ -8,7 +8,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -58,8 +57,8 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
}, nil
}
-func (h *headers) GetTracingInformation() (string, string, trace.SpanKind) {
- return h.name, typeName, trace.SpanKindInternal
+func (h *headers) GetTracingInformation() (string, string) {
+ return h.name, typeName
}
func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go
index 6ce7383cfa..df71e68964 100644
--- a/pkg/middlewares/headers/headers_test.go
+++ b/pkg/middlewares/headers/headers_test.go
@@ -13,7 +13,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
- "go.opentelemetry.io/otel/trace"
)
func TestNew_withoutOptions(t *testing.T) {
@@ -107,11 +106,10 @@ func Test_headers_getTracingInformation(t *testing.T) {
name: "testing",
}
- name, typeName, spanKind := mid.GetTracingInformation()
+ name, typeName := mid.GetTracingInformation()
assert.Equal(t, "testing", name)
assert.Equal(t, "Headers", typeName)
- assert.Equal(t, trace.SpanKindInternal, spanKind)
}
// This test is an adapted version of net/http/httputil.Test1xxResponses test.
diff --git a/pkg/middlewares/inflightreq/inflight_req.go b/pkg/middlewares/inflightreq/inflight_req.go
index c051936071..65a7457d37 100644
--- a/pkg/middlewares/inflightreq/inflight_req.go
+++ b/pkg/middlewares/inflightreq/inflight_req.go
@@ -10,7 +10,6 @@ import (
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/vulcand/oxy/v2/connlimit"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -53,8 +52,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, nam
return &inFlightReq{handler: handler, name: name}, nil
}
-func (i *inFlightReq) GetTracingInformation() (string, string, trace.SpanKind) {
- return i.name, typeName, trace.SpanKindInternal
+func (i *inFlightReq) GetTracingInformation() (string, string) {
+ return i.name, typeName
}
func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/ipallowlist/ip_allowlist.go b/pkg/middlewares/ipallowlist/ip_allowlist.go
index 46f17c74c9..6d9cf0232d 100644
--- a/pkg/middlewares/ipallowlist/ip_allowlist.go
+++ b/pkg/middlewares/ipallowlist/ip_allowlist.go
@@ -11,7 +11,6 @@ import (
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -65,8 +64,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam
}, nil
}
-func (al *ipAllowLister) GetTracingInformation() (string, string, trace.SpanKind) {
- return al.name, typeName, trace.SpanKindInternal
+func (al *ipAllowLister) GetTracingInformation() (string, string) {
+ return al.name, typeName
}
func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist.go b/pkg/middlewares/ipwhitelist/ip_whitelist.go
index d8e3dadb33..7458b4de69 100644
--- a/pkg/middlewares/ipwhitelist/ip_whitelist.go
+++ b/pkg/middlewares/ipwhitelist/ip_whitelist.go
@@ -11,7 +11,6 @@ import (
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -55,8 +54,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPWhiteList, nam
}, nil
}
-func (wl *ipWhiteLister) GetTracingInformation() (string, string, trace.SpanKind) {
- return wl.name, typeName, trace.SpanKindInternal
+func (wl *ipWhiteLister) GetTracingInformation() (string, string) {
+ return wl.name, typeName
}
func (wl *ipWhiteLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/metrics/metrics.go b/pkg/middlewares/metrics/metrics.go
index e8a1c6dbab..f7eb3b8ff8 100644
--- a/pkg/middlewares/metrics/metrics.go
+++ b/pkg/middlewares/metrics/metrics.go
@@ -18,7 +18,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
- "go.opentelemetry.io/otel/trace"
"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.
-func WrapEntryPointHandler(ctx context.Context, registry metrics.Registry, entryPointName string) alice.Constructor {
+// EntryPointMetricsHandler returns the metrics entrypoint handler.
+func EntryPointMetricsHandler(ctx context.Context, registry metrics.Registry, entryPointName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
+ if registry == nil || !registry.IsEpEnabled() {
+ return next, nil
+ }
+
return NewEntryPointMiddleware(ctx, next, registry, entryPointName), nil
}
}
-// WrapRouterHandler Wraps metrics router to alice.Constructor.
-func WrapRouterHandler(ctx context.Context, registry metrics.Registry, routerName string, serviceName string) alice.Constructor {
+// RouterMetricsHandler returns the metrics router handler.
+func RouterMetricsHandler(ctx context.Context, registry metrics.Registry, routerName string, serviceName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
+ if registry == nil || !registry.IsRouterEnabled() {
+ return next, nil
+ }
+
return NewRouterMiddleware(ctx, next, registry, routerName, serviceName), nil
}
}
-// WrapServiceHandler Wraps metrics service to alice.Constructor.
-func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor {
+// ServiceMetricsHandler returns the metrics service handler.
+func ServiceMetricsHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
+ if registry == nil || !registry.IsSvcEnabled() {
+ return next, nil
+ }
+
return NewServiceMiddleware(ctx, next, registry, serviceName), nil
}
}
-func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanKind) {
- return m.name, typeName, trace.SpanKindInternal
+func (m *metricsMiddleware) GetTracingInformation() (string, string) {
+ return m.name, typeName
}
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)
return
}
diff --git a/pkg/middlewares/observability/entrypoint.go b/pkg/middlewares/observability/entrypoint.go
index 64b89b9213..e9c77c160b 100644
--- a/pkg/middlewares/observability/entrypoint.go
+++ b/pkg/middlewares/observability/entrypoint.go
@@ -48,11 +48,17 @@ func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, entryPointName s
}
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)
start := time.Now()
tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start))
// 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()
loggerCtx := logger.WithContext(tracingCtx)
diff --git a/pkg/middlewares/observability/middleware.go b/pkg/middlewares/observability/middleware.go
index 51e4b6d509..b44175d40f 100644
--- a/pkg/middlewares/observability/middleware.go
+++ b/pkg/middlewares/observability/middleware.go
@@ -14,7 +14,7 @@ import (
// Traceable embeds tracing information.
type Traceable interface {
- GetTracingInformation() (name string, typeName string, spanKind trace.SpanKind)
+ GetTracingInformation() (name string, typeName string)
}
// 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 {
- name, typeName, spanKind := traceableHandler.GetTracingInformation()
+ name, typeName := traceableHandler.GetTracingInformation()
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
}
}
// 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{
next: next,
name: name,
typeName: typeName,
- spanKind: spanKind,
}
}
@@ -52,12 +51,11 @@ type middlewareTracing struct {
next http.Handler
name string
typeName string
- spanKind trace.SpanKind
}
func (w *middlewareTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
- if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
- tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(w.spanKind))
+ if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) {
+ tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()
req = req.WithContext(tracingCtx)
diff --git a/pkg/middlewares/observability/observability.go b/pkg/middlewares/observability/observability.go
index 1ee9f3b99f..1490a1e8e4 100644
--- a/pkg/middlewares/observability/observability.go
+++ b/pkg/middlewares/observability/observability.go
@@ -3,6 +3,7 @@ package observability
import (
"context"
"fmt"
+ "net/http"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
@@ -10,8 +11,58 @@ import (
type contextKey int
-// DisableMetricsKey is a context key used to disable the metrics.
-const DisableMetricsKey contextKey = iota
+const observabilityKey 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.
func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) {
diff --git a/pkg/middlewares/observability/router.go b/pkg/middlewares/observability/router.go
index 5339726ffd..41740443c8 100644
--- a/pkg/middlewares/observability/router.go
+++ b/pkg/middlewares/observability/router.go
@@ -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) {
- 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))
defer span.End()
diff --git a/pkg/middlewares/observability/semconv.go b/pkg/middlewares/observability/semconv.go
index 51f4480b59..d23363b4a0 100644
--- a/pkg/middlewares/observability/semconv.go
+++ b/pkg/middlewares/observability/semconv.go
@@ -46,7 +46,7 @@ func newServerMetricsSemConv(ctx context.Context, semConvMetricRegistry *metrics
}
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)
return
}
diff --git a/pkg/middlewares/observability/semconv_test.go b/pkg/middlewares/observability/semconv_test.go
index 40d3faf626..06a39e70b6 100644
--- a/pkg/middlewares/observability/semconv_test.go
+++ b/pkg/middlewares/observability/semconv_test.go
@@ -83,6 +83,11 @@ func TestSemConvServerMetrics(t *testing.T) {
handler, err = capture.Wrap(handler)
require.NoError(t, err)
+ // Injection of the observability variables in the request context.
+ handler = WithObservabilityHandler(handler, Observability{
+ SemConvMetricsEnabled: true,
+ })
+
handler.ServeHTTP(rw, req)
got := metricdata.ResourceMetrics{}
diff --git a/pkg/middlewares/observability/service.go b/pkg/middlewares/observability/service.go
index cacd3ef1bf..c914c09cc0 100644
--- a/pkg/middlewares/observability/service.go
+++ b/pkg/middlewares/observability/service.go
@@ -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) {
- 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))
defer span.End()
diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go
index cadc375af0..6f892a7793 100644
--- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go
+++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go
@@ -14,7 +14,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
const typeName = "PassClientTLSCert"
@@ -139,8 +138,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer
}, nil
}
-func (p *passTLSClientCert) GetTracingInformation() (string, string, trace.SpanKind) {
- return p.name, typeName, trace.SpanKindInternal
+func (p *passTLSClientCert) GetTracingInformation() (string, string) {
+ return p.name, typeName
}
func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go
index 043974d472..fcc553e150 100755
--- a/pkg/middlewares/ratelimiter/rate_limiter.go
+++ b/pkg/middlewares/ratelimiter/rate_limiter.go
@@ -14,7 +14,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/vulcand/oxy/v2/utils"
- "go.opentelemetry.io/otel/trace"
"golang.org/x/time/rate"
)
@@ -127,8 +126,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
}, nil
}
-func (rl *rateLimiter) GetTracingInformation() (string, string, trace.SpanKind) {
- return rl.name, typeName, trace.SpanKindInternal
+func (rl *rateLimiter) GetTracingInformation() (string, string) {
+ return rl.name, typeName
}
func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/redirect/redirect.go b/pkg/middlewares/redirect/redirect.go
index 25ca6a2ac9..d8499d1aaa 100644
--- a/pkg/middlewares/redirect/redirect.go
+++ b/pkg/middlewares/redirect/redirect.go
@@ -6,7 +6,6 @@ import (
"regexp"
"github.com/vulcand/oxy/v2/utils"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -46,8 +45,8 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, r
}, nil
}
-func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) {
- return r.name, typeName, trace.SpanKindInternal
+func (r *redirect) GetTracingInformation() (string, string) {
+ return r.name, typeName
}
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/replacepath/replace_path.go b/pkg/middlewares/replacepath/replace_path.go
index 9c8e404d49..d211b83dd9 100644
--- a/pkg/middlewares/replacepath/replace_path.go
+++ b/pkg/middlewares/replacepath/replace_path.go
@@ -8,7 +8,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -35,8 +34,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePath, nam
}, nil
}
-func (r *replacePath) GetTracingInformation() (string, string, trace.SpanKind) {
- return r.name, typeName, trace.SpanKindInternal
+func (r *replacePath) GetTracingInformation() (string, string) {
+ return r.name, typeName
}
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/replacepathregex/replace_path_regex.go b/pkg/middlewares/replacepathregex/replace_path_regex.go
index f04e9d9a06..42cd79404b 100644
--- a/pkg/middlewares/replacepathregex/replace_path_regex.go
+++ b/pkg/middlewares/replacepathregex/replace_path_regex.go
@@ -12,7 +12,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/replacepath"
- "go.opentelemetry.io/otel/trace"
)
const typeName = "ReplacePathRegex"
@@ -42,8 +41,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePathRegex
}, nil
}
-func (rp *replacePathRegex) GetTracingInformation() (string, string, trace.SpanKind) {
- return rp.name, typeName, trace.SpanKindInternal
+func (rp *replacePathRegex) GetTracingInformation() (string, string) {
+ return rp.name, typeName
}
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/retry/retry.go b/pkg/middlewares/retry/retry.go
index 068030777e..20263d897c 100644
--- a/pkg/middlewares/retry/retry.go
+++ b/pkg/middlewares/retry/retry.go
@@ -14,6 +14,7 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
+ "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
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
operation := func() error {
- if tracer != nil {
+ if tracer != nil && observability.DetailedTracingEnabled(req.Context()) {
if currentSpan != nil {
currentSpan.End()
}
diff --git a/pkg/middlewares/stripprefix/strip_prefix.go b/pkg/middlewares/stripprefix/strip_prefix.go
index 8483f51002..1632814ce9 100644
--- a/pkg/middlewares/stripprefix/strip_prefix.go
+++ b/pkg/middlewares/stripprefix/strip_prefix.go
@@ -7,7 +7,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -45,8 +44,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, nam
}, nil
}
-func (s *stripPrefix) GetTracingInformation() (string, string, trace.SpanKind) {
- return s.name, typeName, trace.SpanKindUnspecified
+func (s *stripPrefix) GetTracingInformation() (string, string) {
+ return s.name, typeName
}
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go
index 71de1ad281..a38752659c 100644
--- a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go
+++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go
@@ -9,7 +9,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/stripprefix"
- "go.opentelemetry.io/otel/trace"
)
const (
@@ -43,8 +42,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefixRegex
return &stripPrefix, nil
}
-func (s *stripPrefixRegex) GetTracingInformation() (string, string, trace.SpanKind) {
- return s.name, typeName, trace.SpanKindInternal
+func (s *stripPrefixRegex) GetTracingInformation() (string, string) {
+ return s.name, typeName
}
func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
diff --git a/pkg/provider/kubernetes/ingress/annotations_test.go b/pkg/provider/kubernetes/ingress/annotations_test.go
index bda4048897..6879f2ae14 100644
--- a/pkg/provider/kubernetes/ingress/annotations_test.go
+++ b/pkg/provider/kubernetes/ingress/annotations_test.go
@@ -58,9 +58,10 @@ func Test_parseRouterConfig(t *testing.T) {
Options: "foobar",
},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Tracing: pointer(true),
- Metrics: pointer(true),
+ AccessLogs: pointer(true),
+ Tracing: pointer(true),
+ Metrics: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go
index 6cf0113a47..acd7400a46 100644
--- a/pkg/provider/kubernetes/ingress/kubernetes_test.go
+++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go
@@ -124,9 +124,10 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
Options: "foobar",
},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Tracing: pointer(true),
- Metrics: pointer(true),
+ AccessLogs: pointer(true),
+ Tracing: pointer(true),
+ Metrics: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go
index 22efe451df..544c1a2c55 100644
--- a/pkg/provider/traefik/internal.go
+++ b/pkg/provider/traefik/internal.go
@@ -242,9 +242,10 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
if ep.Observability != nil {
httpModel.Observability = dynamic.RouterObservabilityConfig{
- AccessLogs: ep.Observability.AccessLogs,
- Tracing: ep.Observability.Tracing,
- Metrics: ep.Observability.Metrics,
+ AccessLogs: ep.Observability.AccessLogs,
+ Metrics: ep.Observability.Metrics,
+ Tracing: ep.Observability.Tracing,
+ TraceVerbosity: ep.Observability.TraceVerbosity,
}
}
diff --git a/pkg/proxy/httputil/builder.go b/pkg/proxy/httputil/builder.go
index 64360517ae..cb591c8dba 100644
--- a/pkg/proxy/httputil/builder.go
+++ b/pkg/proxy/httputil/builder.go
@@ -38,17 +38,15 @@ func NewProxyBuilder(transportManager TransportManager, semConvMetricsRegistry *
func (r *ProxyBuilder) Update(_ map[string]*dynamic.ServersTransport) {}
// 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)
if err != nil {
return nil, fmt.Errorf("getting RoundTripper: %w", err)
}
- if shouldObserve {
- // Wrapping the roundTripper with the Tracing roundTripper,
- // to handle the reverseProxy client span creation.
- roundTripper = newObservabilityRoundTripper(r.semConvMetricsRegistry, roundTripper)
- }
+ // Wrapping the roundTripper with the Tracing roundTripper,
+ // to create, if necessary, the reverseProxy client span and the semConv client metric.
+ roundTripper = newObservabilityRoundTripper(r.semConvMetricsRegistry, roundTripper)
return buildSingleHostProxy(targetURL, passHostHeader, preservePath, flushInterval, roundTripper, r.bufferPool), nil
}
diff --git a/pkg/proxy/httputil/builder_test.go b/pkg/proxy/httputil/builder_test.go
index f7ff939024..e45a871b68 100644
--- a/pkg/proxy/httputil/builder_test.go
+++ b/pkg/proxy/httputil/builder_test.go
@@ -23,7 +23,7 @@ func TestEscapedPath(t *testing.T) {
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)
proxy := httptest.NewServer(http.HandlerFunc(p.ServeHTTP))
diff --git a/pkg/proxy/httputil/observability.go b/pkg/proxy/httputil/observability.go
index 8fa3382e39..1a8d7b1f16 100644
--- a/pkg/proxy/httputil/observability.go
+++ b/pkg/proxy/httputil/observability.go
@@ -35,7 +35,7 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
var span trace.Span
var tracingCtx context.Context
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))
defer span.End()
@@ -68,38 +68,42 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
span.End(trace.WithTimestamp(end))
}
- if req.Context().Value(observability.DisableMetricsKey) == nil && t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil {
- var attrs []attribute.KeyValue
-
- if statusCode < 100 || statusCode >= 600 {
- attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code %d", statusCode)))
- } else if statusCode >= 400 {
- attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(statusCode)))
- }
-
- attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method))
- attrs = append(attrs, semconv.HTTPResponseStatusCode(statusCode))
- attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto)))
- attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto)))
- attrs = append(attrs, semconv.ServerAddress(req.URL.Host))
-
- _, port, err := net.SplitHostPort(req.URL.Host)
- if err != nil {
- switch req.URL.Scheme {
- case "http":
- attrs = append(attrs, semconv.ServerPort(80))
- case "https":
- attrs = append(attrs, semconv.ServerPort(443))
- }
- } else {
- intPort, _ := strconv.Atoi(port)
- attrs = append(attrs, semconv.ServerPort(intPort))
- }
-
- attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
-
- t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
+ if !observability.SemConvMetricsEnabled(req.Context()) ||
+ t.semConvMetricRegistry == nil ||
+ t.semConvMetricRegistry.HTTPClientRequestDuration() == nil {
+ return response, err
}
+ var attrs []attribute.KeyValue
+
+ if statusCode < 100 || statusCode >= 600 {
+ attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code %d", statusCode)))
+ } else if statusCode >= 400 {
+ attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(statusCode)))
+ }
+
+ attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method))
+ attrs = append(attrs, semconv.HTTPResponseStatusCode(statusCode))
+ attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto)))
+ attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto)))
+ attrs = append(attrs, semconv.ServerAddress(req.URL.Host))
+
+ _, port, splitErr := net.SplitHostPort(req.URL.Host)
+ if splitErr != nil {
+ switch req.URL.Scheme {
+ case "http":
+ attrs = append(attrs, semconv.ServerPort(80))
+ case "https":
+ attrs = append(attrs, semconv.ServerPort(443))
+ }
+ } else {
+ intPort, _ := strconv.Atoi(port)
+ attrs = append(attrs, semconv.ServerPort(intPort))
+ }
+
+ attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
+
+ t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
+
return response, err
}
diff --git a/pkg/proxy/httputil/observability_test.go b/pkg/proxy/httputil/observability_test.go
index 67d585a8fd..02a4f084f4 100644
--- a/pkg/proxy/httputil/observability_test.go
+++ b/pkg/proxy/httputil/observability_test.go
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/metrics"
+ "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/otel/attribute"
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("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})
_, err = ort.RoundTrip(req)
require.NoError(t, err)
diff --git a/pkg/proxy/httputil/proxy_websocket_test.go b/pkg/proxy/httputil/proxy_websocket_test.go
index 48296f955b..7abfc39f6a 100644
--- a/pkg/proxy/httputil/proxy_websocket_test.go
+++ b/pkg/proxy/httputil/proxy_websocket_test.go
@@ -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)
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
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)
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
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},
}
- 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)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
diff --git a/pkg/proxy/smart_builder.go b/pkg/proxy/smart_builder.go
index 08b247c53f..ad9d14d568 100644
--- a/pkg/proxy/smart_builder.go
+++ b/pkg/proxy/smart_builder.go
@@ -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.
-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)
if err != nil {
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,
// 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) {
- 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)
}
diff --git a/pkg/proxy/smart_builder_test.go b/pkg/proxy/smart_builder_test.go
index c03bd19f37..d1c29ddd8c 100644
--- a/pkg/proxy/smart_builder_test.go
+++ b/pkg/proxy/smart_builder_test.go
@@ -101,7 +101,7 @@ func TestSmartBuilder_Build(t *testing.T) {
httpProxyBuilder := httputil.NewProxyBuilder(transportManager, nil)
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)
rw := httptest.NewRecorder()
diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go
index 2a0c1bd4fe..9c35b00ac8 100644
--- a/pkg/server/aggregator.go
+++ b/pkg/server/aggregator.go
@@ -10,6 +10,7 @@ import (
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/server/provider"
"github.com/traefik/traefik/v3/pkg/tls"
+ "github.com/traefik/traefik/v3/pkg/types"
)
func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration {
@@ -208,6 +209,10 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cp.Observability.Tracing = m.Observability.Tracing
}
+ if cp.Observability.TraceVerbosity == "" {
+ cp.Observability.TraceVerbosity = m.Observability.TraceVerbosity
+ }
+
rtName := name
if len(eps) > 1 {
rtName = epName + "-" + name
@@ -224,7 +229,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cfg.HTTP.Routers = rts
}
- // Apply default observability model to HTTP routers.
+ // Apply the default observability model to HTTP routers.
applyDefaultObservabilityModel(cfg)
if cfg.TCP == nil || len(cfg.TCP.Models) == 0 {
@@ -256,14 +261,16 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
// and make sure it is serialized and available in the API.
// We could have introduced a "default" model, but it would have been more complex to manage for now.
// This could be generalized in the future.
+// TODO: check if we can remove this and rely on the SetDefaults instead.
func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
if cfg.HTTP != nil {
for _, router := range cfg.HTTP.Routers {
if router.Observability == nil {
router.Observability = &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
}
continue
@@ -273,12 +280,16 @@ func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
router.Observability.AccessLogs = pointer(true)
}
+ if router.Observability.Metrics == nil {
+ router.Observability.Metrics = pointer(true)
+ }
+
if router.Observability.Tracing == nil {
router.Observability.Tracing = pointer(true)
}
- if router.Observability.Metrics == nil {
- router.Observability.Metrics = pointer(true)
+ if router.Observability.TraceVerbosity == "" {
+ router.Observability.TraceVerbosity = types.MinimalVerbosity
}
}
}
diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go
index 182815a395..4c202fdf85 100644
--- a/pkg/server/aggregator_test.go
+++ b/pkg/server/aggregator_test.go
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/tls"
+ "github.com/traefik/traefik/v3/pkg/types"
)
func Test_mergeConfiguration(t *testing.T) {
@@ -521,9 +522,10 @@ func Test_applyModel(t *testing.T) {
Routers: map[string]*dynamic.Router{
"test": {
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
@@ -589,9 +591,10 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
@@ -622,9 +625,10 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Tracing: pointer(true),
- Metrics: pointer(true),
+ AccessLogs: pointer(true),
+ Tracing: pointer(true),
+ Metrics: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
@@ -638,9 +642,10 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Tracing: pointer(true),
- Metrics: pointer(true),
+ AccessLogs: pointer(true),
+ Tracing: pointer(true),
+ Metrics: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
@@ -651,9 +656,10 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Tracing: pointer(true),
- Metrics: pointer(true),
+ AccessLogs: pointer(true),
+ Tracing: pointer(true),
+ Metrics: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
@@ -688,9 +694,10 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
@@ -730,9 +737,10 @@ func Test_applyModel(t *testing.T) {
"test": {
EntryPoints: []string{"web"},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
"websecure-test": {
@@ -740,9 +748,10 @@ func Test_applyModel(t *testing.T) {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
},
},
},
diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go
index 9eebfd31c9..624a3c9c95 100644
--- a/pkg/server/middleware/middlewares.go
+++ b/pkg/server/middleware/middlewares.go
@@ -428,8 +428,5 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
}
- // The tracing middleware is a NOOP if tracing is not setup on the middleware chain.
- // Hence, regarding internal resources' observability deactivation,
- // this would not enable tracing.
return observability.WrapMiddleware(ctx, middleware), nil
}
diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go
index d279be9028..82152c7fcd 100644
--- a/pkg/server/middleware/observability.go
+++ b/pkg/server/middleware/observability.go
@@ -4,7 +4,6 @@ import (
"context"
"io"
"net/http"
- "strings"
"github.com/containous/alice"
"github.com/rs/zerolog/log"
@@ -17,6 +16,7 @@ import (
mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/tracing"
+ "github.com/traefik/traefik/v3/pkg/types"
)
// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement.
@@ -42,111 +42,44 @@ func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Re
}
// BuildEPChain an observability middleware chain by entry point.
-func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string, observabilityConfig *dynamic.RouterObservabilityConfig) alice.Chain {
+func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, internal bool, config dynamic.RouterObservabilityConfig) alice.Chain {
chain := alice.New()
if o == nil {
return chain
}
- if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) {
- if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) {
- chain = chain.Append(capture.Wrap)
- }
+ // Injection of the observability variables in the request context.
+ // This injection must be the first step in order for other observability middlewares to rely on it.
+ chain = chain.Append(func(next http.Handler) (http.Handler, error) {
+ return o.observabilityContextHandler(next, internal, config), nil
+ })
+
+ // Capture middleware for accessLogs or metrics.
+ if o.shouldAccessLog(internal, config) || o.shouldMeter(internal, config) || o.shouldMeterSemConv(internal, config) {
+ chain = chain.Append(capture.Wrap)
}
// As the Entry point observability middleware ensures that the tracing is added to the request and logger context,
// it needs to be added before the access log middleware to ensure that the trace ID is logged.
- if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
- chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
- }
+ chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
- if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) {
- chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware))
- chain = chain.Append(func(next http.Handler) (http.Handler, error) {
- return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
- })
- }
+ // Access log handlers.
+ chain = chain.Append(o.accessLoggerMiddleware.AliceConstructor())
+ chain = chain.Append(func(next http.Handler) (http.Handler, error) {
+ return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
+ })
+
+ // Entrypoint metrics handler.
+ metricsHandler := mmetrics.EntryPointMetricsHandler(ctx, o.metricsRegistry, entryPointName)
+ chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
// Semantic convention server metrics handler.
- if o.semConvMetricRegistry != nil && o.ShouldAddMetrics(resourceName, observabilityConfig) {
- chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
- }
-
- if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName, observabilityConfig) {
- metricsHandler := mmetrics.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName)
-
- if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
- chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
- } else {
- chain = chain.Append(metricsHandler)
- }
- }
-
- // Inject context keys to control whether to produce metrics further downstream (services, round-tripper),
- // because the router configuration cannot be evaluated during build time for services.
- if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics {
- chain = chain.Append(func(next http.Handler) (http.Handler, error) {
- return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
- next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true)))
- }), nil
- })
- }
+ chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
return chain
}
-// ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config.
-func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
- if o == nil {
- return false
- }
-
- if o.config.AccessLog == nil {
- return false
- }
-
- if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals {
- return false
- }
-
- return observabilityConfig == nil || observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
-}
-
-// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config.
-func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
- if o == nil {
- return false
- }
-
- if o.config.Metrics == nil {
- return false
- }
-
- if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals {
- return false
- }
-
- return observabilityConfig == nil || observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
-}
-
-// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config.
-func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
- if o == nil {
- return false
- }
-
- if o.config.Tracing == nil {
- return false
- }
-
- if strings.HasSuffix(serviceName, "@internal") && !o.config.Tracing.AddInternals {
- return false
- }
-
- return observabilityConfig == nil || observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
-}
-
// MetricsRegistry is an accessor to the metrics registry.
func (o *ObservabilityMgr) MetricsRegistry() metrics.Registry {
if o == nil {
@@ -191,3 +124,89 @@ func (o *ObservabilityMgr) RotateAccessLogs() error {
return o.accessLoggerMiddleware.Rotate()
}
+
+func (o *ObservabilityMgr) observabilityContextHandler(next http.Handler, internal bool, config dynamic.RouterObservabilityConfig) http.Handler {
+ return observability.WithObservabilityHandler(next, observability.Observability{
+ AccessLogsEnabled: o.shouldAccessLog(internal, config),
+ MetricsEnabled: o.shouldMeter(internal, config),
+ SemConvMetricsEnabled: o.shouldMeterSemConv(internal, config),
+ TracingEnabled: o.shouldTrace(internal, config, types.MinimalVerbosity),
+ DetailedTracingEnabled: o.shouldTrace(internal, config, types.DetailedVerbosity),
+ })
+}
+
+// shouldAccessLog returns whether the access logs should be enabled for the given serviceName and the observability config.
+func (o *ObservabilityMgr) shouldAccessLog(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
+ if o == nil {
+ return false
+ }
+
+ if o.config.AccessLog == nil {
+ return false
+ }
+
+ if internal && !o.config.AccessLog.AddInternals {
+ return false
+ }
+
+ return observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs
+}
+
+// shouldMeter returns whether the metrics should be enabled for the given serviceName and the observability config.
+func (o *ObservabilityMgr) shouldMeter(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
+ if o == nil || o.metricsRegistry == nil {
+ return false
+ }
+
+ if !o.metricsRegistry.IsEpEnabled() && !o.metricsRegistry.IsRouterEnabled() && !o.metricsRegistry.IsSvcEnabled() {
+ return false
+ }
+
+ if o.config.Metrics == nil {
+ return false
+ }
+
+ if internal && !o.config.Metrics.AddInternals {
+ return false
+ }
+
+ return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
+}
+
+// shouldMeterSemConv returns whether the OTel semantic convention metrics should be enabled for the given serviceName and the observability config.
+func (o *ObservabilityMgr) shouldMeterSemConv(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool {
+ if o == nil || o.semConvMetricRegistry == nil {
+ return false
+ }
+
+ if o.config.Metrics == nil {
+ return false
+ }
+
+ if internal && !o.config.Metrics.AddInternals {
+ return false
+ }
+
+ return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics
+}
+
+// shouldTrace returns whether the tracing should be enabled for the given serviceName and the observability config.
+func (o *ObservabilityMgr) shouldTrace(internal bool, observabilityConfig dynamic.RouterObservabilityConfig, verbosity types.TracingVerbosity) bool {
+ if o == nil {
+ return false
+ }
+
+ if o.config.Tracing == nil {
+ return false
+ }
+
+ if internal && !o.config.Tracing.AddInternals {
+ return false
+ }
+
+ if !observabilityConfig.TraceVerbosity.Allows(verbosity) {
+ return false
+ }
+
+ return observabilityConfig.Tracing == nil || *observabilityConfig.Tracing
+}
diff --git a/pkg/server/middleware/plugins.go b/pkg/server/middleware/plugins.go
index eacf8fba1c..0529ca1901 100644
--- a/pkg/server/middleware/plugins.go
+++ b/pkg/server/middleware/plugins.go
@@ -7,7 +7,6 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/plugins"
- "go.opentelemetry.io/otel/trace"
)
const typeName = "Plugin"
@@ -55,6 +54,6 @@ func (s *traceablePlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
s.h.ServeHTTP(rw, req)
}
-func (s *traceablePlugin) GetTracingInformation() (string, string, trace.SpanKind) {
- return s.name, typeName, trace.SpanKindInternal
+func (s *traceablePlugin) GetTracingInformation() (string, string) {
+ return s.name, typeName
}
diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go
index 3abd239e55..44f6950d7f 100644
--- a/pkg/server/router/router.go
+++ b/pkg/server/router/router.go
@@ -10,6 +10,7 @@ import (
"github.com/containous/alice"
"github.com/rs/zerolog/log"
+ "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
@@ -70,11 +71,22 @@ func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler {
entryPointHandlers := make(map[string]http.Handler)
+ defaultObsConfig := dynamic.RouterObservabilityConfig{}
+ defaultObsConfig.SetDefaults()
+
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
ctx := logger.WithContext(rootCtx)
- handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers)
+ // TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration.
+ // When the entry point has no observability configuration no model is produced,
+ // and we need to create the default configuration is this case.
+ epObsConfig := defaultObsConfig
+ if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil {
+ epObsConfig = model.Observability
+ }
+
+ handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers, epObsConfig)
if err != nil {
logger.Error().Err(err).Send()
continue
@@ -93,7 +105,15 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
continue
}
- defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter())
+ // TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration.
+ // When the entry point has no observability configuration no model is produced,
+ // and we need to create the default configuration is this case.
+ epObsConfig := defaultObsConfig
+ if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil {
+ epObsConfig = model.Observability
+ }
+
+ defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, epObsConfig).Then(http.NotFoundHandler())
if err != nil {
logger.Error().Err(err).Send()
continue
@@ -104,10 +124,10 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
return entryPointHandlers
}
-func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
+func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo, config dynamic.RouterObservabilityConfig) (http.Handler, error) {
muxer := httpmuxer.NewMuxer(m.parser)
- defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler())
+ defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, config).Then(http.NotFoundHandler())
if err != nil {
return nil, err
}
@@ -136,7 +156,11 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
continue
}
- observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability)
+ if routerConfig.Observability != nil {
+ config = *routerConfig.Observability
+ }
+
+ observabilityChain := m.observabilityMgr.BuildEPChain(ctxRouter, entryPointName, strings.HasSuffix(routerConfig.Service, "@internal"), config)
handler, err = observabilityChain.Then(handler)
if err != nil {
routerConfig.AddError(err, true)
@@ -180,22 +204,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
return nil, err
}
- // Prevents from enabling observability for internal resources.
- if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service), routerConfig.Observability) {
- m.routerHandlers[routerName] = handler
- return m.routerHandlers[routerName], nil
- }
-
- handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
- return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
- }).Then(handler)
- if err != nil {
- log.Ctx(ctx).Error().Err(err).Send()
- m.routerHandlers[routerName] = handler
- } else {
- m.routerHandlers[routerName] = handlerWithAccessLog
- }
-
+ m.routerHandlers[routerName] = handler
return m.routerHandlers[routerName], nil
}
@@ -210,40 +219,29 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
return nil, errors.New("the service is missing on the router")
}
- sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service)
- if err != nil {
- return nil, err
- }
-
- mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
+ qualifiedService := provider.GetQualifiedName(ctx, router.Service)
chain := alice.New()
- if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
- m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
- chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
- }
-
- // Prevents from enabling tracing for internal resources.
- if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
- return chain.Extend(*mHandler).Then(sHandler)
- }
-
- chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
-
- if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() {
- metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))
- chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
- }
-
if router.DefaultRule {
chain = chain.Append(denyrouterrecursion.WrapHandler(routerName))
}
+ // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled.
+ chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, qualifiedService))
+ metricsHandler := metricsMiddle.RouterMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, qualifiedService)
+
+ chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
+ chain = chain.Append(func(next http.Handler) (http.Handler, error) {
+ return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
+ })
+
+ mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
+
+ sHandler, err := m.serviceManager.BuildHTTP(ctx, qualifiedService)
+ if err != nil {
+ return nil, err
+ }
+
return chain.Extend(*mHandler).Then(sHandler)
}
-
-// BuildDefaultHTTPRouter creates a default HTTP router.
-func BuildDefaultHTTPRouter() http.Handler {
- return http.NotFoundHandler()
-}
diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go
index 50811be7c0..1458b2e99e 100644
--- a/pkg/server/router/router_test.go
+++ b/pkg/server/router/router_test.go
@@ -929,7 +929,7 @@ func BenchmarkService(b *testing.B) {
type proxyBuilderMock struct{}
-func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) {
+func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) {
return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil
}
diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go
index 8a536890ff..ba77f5e318 100644
--- a/pkg/server/routerfactory_test.go
+++ b/pkg/server/routerfactory_test.go
@@ -258,7 +258,7 @@ func TestInternalServices(t *testing.T) {
type proxyBuilderMock struct{}
-func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) {
+func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) {
return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil
}
diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go
index ea8ea6e481..41db759a77 100644
--- a/pkg/server/server_entrypoint_tcp.go
+++ b/pkg/server/server_entrypoint_tcp.go
@@ -29,7 +29,6 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/forwardedheaders"
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
"github.com/traefik/traefik/v3/pkg/safe"
- "github.com/traefik/traefik/v3/pkg/server/router"
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
"github.com/traefik/traefik/v3/pkg/server/service"
"github.com/traefik/traefik/v3/pkg/tcp"
@@ -351,7 +350,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) {
httpHandler := rt.GetHTTPHandler()
if httpHandler == nil {
- httpHandler = router.BuildDefaultHTTPRouter()
+ httpHandler = http.NotFoundHandler()
}
e.httpServer.Switcher.UpdateHandler(httpHandler)
@@ -360,7 +359,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) {
httpsHandler := rt.GetHTTPSHandler()
if httpsHandler == nil {
- httpsHandler = router.BuildDefaultHTTPRouter()
+ httpsHandler = http.NotFoundHandler()
}
e.httpsServer.Switcher.UpdateHandler(httpsHandler)
@@ -591,7 +590,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
return nil, errors.New("max concurrent streams value must be greater than or equal to zero")
}
- httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
+ httpSwitcher := middlewares.NewHandlerSwitcher(http.NotFoundHandler())
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
if err != nil {
diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go
index 82cc96e361..b1bc6d8e9f 100644
--- a/pkg/server/service/service.go
+++ b/pkg/server/service/service.go
@@ -19,7 +19,6 @@ import (
"github.com/traefik/traefik/v3/pkg/healthcheck"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
- "github.com/traefik/traefik/v3/pkg/middlewares/capture"
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
@@ -37,7 +36,7 @@ import (
// ProxyBuilder builds reverse proxy handlers.
type ProxyBuilder interface {
- Build(cfgName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error)
+ Build(cfgName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error)
Update(configs map[string]*dynamic.ServersTransport)
}
@@ -364,50 +363,32 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName)
- shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil)
- proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, shouldObserve, passHostHeader, server.PreservePath, flushInterval)
+ proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, passHostHeader, server.PreservePath, flushInterval)
if err != nil {
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
}
+
// The retry wrapping must be done just before the proxy handler,
// to make sure that the retry will not be triggered/disabled by
// middlewares in the chain.
proxy = retry.WrapHandler(proxy)
- // Prevents from enabling observability for internal resources.
+ // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled.
+ proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
+ proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
+ proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, qualifiedSvcName, accesslog.AddServiceFields)
- if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) {
- proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
- proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
- proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
+ metricsHandler := metricsMiddle.ServiceMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), qualifiedSvcName)
+ metricsHandler = observability.WrapMiddleware(ctx, metricsHandler)
+
+ proxy, err = alice.New().
+ Append(metricsHandler).
+ Then(proxy)
+ if err != nil {
+ return nil, fmt.Errorf("error wrapping metrics handler: %w", err)
}
- if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() &&
- m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
- metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
-
- proxy, err = alice.New().
- Append(observability.WrapMiddleware(ctx, metricsHandler)).
- Then(proxy)
- if err != nil {
- return nil, fmt.Errorf("error wrapping metrics handler: %w", err)
- }
- }
-
- if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) {
- proxy = observability.NewService(ctx, serviceName, proxy)
- }
-
- if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
- // Some piece of middleware, like the ErrorPage, are relying on this serviceBuilder to get the handler for a given service,
- // to re-target the request to it.
- // Those pieces of middleware can be configured on routes that expose a Traefik internal service.
- // In such a case, observability for internals being optional, the capture probe could be absent from context (no wrap via the entrypoint).
- // But if the service targeted by this piece of middleware is not an internal one,
- // and requires observability, we still want the capture probe to be present in the request context.
- // Makes sure a capture probe is in the request context.
- proxy, _ = capture.Wrap(proxy)
- }
+ proxy = observability.NewService(ctx, qualifiedSvcName, proxy)
lb.AddServer(server.URL, proxy, server)
diff --git a/pkg/testhelpers/config.go b/pkg/testhelpers/config.go
index ee3dbc9757..8ab73b6b65 100644
--- a/pkg/testhelpers/config.go
+++ b/pkg/testhelpers/config.go
@@ -2,6 +2,7 @@ package testhelpers
import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
+ "github.com/traefik/traefik/v3/pkg/types"
)
// BuildConfiguration is a helper to create a configuration.
@@ -57,9 +58,10 @@ func WithServiceName(serviceName string) func(*dynamic.Router) {
func WithObservability() func(*dynamic.Router) {
return func(r *dynamic.Router) {
r.Observability = &dynamic.RouterObservabilityConfig{
- AccessLogs: pointer(true),
- Metrics: pointer(true),
- Tracing: pointer(true),
+ AccessLogs: pointer(true),
+ Metrics: pointer(true),
+ Tracing: pointer(true),
+ TraceVerbosity: types.MinimalVerbosity,
}
}
}
diff --git a/pkg/types/tracing.go b/pkg/types/tracing.go
index c3d83deec7..0ee66ea3c5 100644
--- a/pkg/types/tracing.go
+++ b/pkg/types/tracing.go
@@ -23,6 +23,22 @@ import (
"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.
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"`
diff --git a/pkg/types/tracing_test.go b/pkg/types/tracing_test.go
new file mode 100644
index 0000000000..77f7648713
--- /dev/null
+++ b/pkg/types/tracing_test.go
@@ -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))
+ })
+ }
+}