diff --git a/docs/content/migration/v3.md b/docs/content/migration/v3.md
index 6581b5f39..e3cbe8a9b 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 b2af3def6..3399c2762 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 202815c39..b7f30c649 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 e8210ef85..2ac73fd85 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 d3f71b5f9..22a08a7d7 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 733b1bdb5..a7c1b217f 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 0a5d6749a..46cc78976 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 9c0aab31c..2ae13200e 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 ecadcaeed..4c9229f1c 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 cfc377935..839f38988 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 7a3f59fcb..f98a205b1 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 515a55687..c9746ce47 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 f36478611..c6ad473f5 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 e1c4003e9..2f480b524 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 d3f71b5f9..22a08a7d7 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 a8643ebd7..61aaebfa8 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 a5e4f5d44..1447f4cba 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 90806c2ac..9b27203a7 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 8ff23a25a..88219a627 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 334b6c698..9f1b2d33c 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 f03d1b67e..360488433 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 b6206c121..8a6e1bc8c 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 23305a847..a4081171c 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 ba2ad706b..a317099fb 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 a26369282..5f56a981f 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 05649bfa1..c860c8862 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 381d92df5..84e094947 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 265afbdbc..3401d4c32 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 d44ed0d0b..9b9fa0faf 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 a867c882b..ef6a896f1 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 9cf39ba40..97f8907e0 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 d67233887..4034fa077 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 d98cdb1b3..3ac50e048 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 6ec8ea4c9..246556195 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 028e3dbf7..7b00bacba 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 4daf1a5a5..7babf8121 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 46f8d98d0..b698cc9ce 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 e6c175bcb..863c968f3 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 25c22865f..af64a134b 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 8520e2357..d30d33e2a 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 2bd062370..d3cdebd36 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 ec9100a98..cb0fa3414 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 ceaa93de4..c9b3546e3 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 0bb3788d2..4ab312cc2 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 3cd866a74..d9c95ea6d 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 94d43211a..8808f3e7e 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 2d55b686b..e47d23758 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 e2d32e518..1305a2372 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 2960a5583..bcddb8137 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 861d1066d..cba8b6401 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 6ce7383cf..df71e6896 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 c05193607..65a7457d3 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 46f17c74c..6d9cf0232 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 d8e3dadb3..7458b4de6 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 e8a1c6dba..f7eb3b8ff 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 64b89b921..e9c77c160 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 51e4b6d50..b44175d40 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 1ee9f3b99..1490a1e8e 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 5339726ff..41740443c 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 51f4480b5..d23363b4a 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 40d3faf62..06a39e70b 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 cacd3ef1b..c914c09cc 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 cadc375af..6f892a779 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 043974d47..fcc553e15 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 25ca6a2ac..d8499d1aa 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 9c8e404d4..d211b83dd 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 f04e9d9a0..42cd79404 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 068030777..20263d897 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 8483f5100..1632814ce 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 71de1ad28..a38752659 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 bda404889..6879f2ae1 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 6cf0113a4..acd7400a4 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 22efe451d..544c1a2c5 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 64360517a..cb591c8db 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 f7ff93902..e45a871b6 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 8fa3382e3..1a8d7b1f1 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 67d585a8f..02a4f084f 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 48296f955..7abfc39f6 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 08b247c53..ad9d14d56 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 c03bd19f3..d1c29ddd8 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 2a0c1bd4f..9c35b00ac 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 182815a39..4c202fdf8 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 9eebfd31c..624a3c9c9 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 d279be902..82152c7fc 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 eacf8fba1..0529ca190 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 3abd239e5..44f6950d7 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 50811be7c..1458b2e99 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 8a536890f..ba77f5e31 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 ea8ea6e48..41db759a7 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 82cc96e36..b1bc6d8e9 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 ee3dbc975..8ab73b6b6 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 c3d83deec..0ee66ea3c 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 000000000..77f764871
--- /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))
+ })
+ }
+}