mirror of
https://github.com/traefik/traefik.git
synced 2025-09-20 21:31:14 +02:00
Introduce trace verbosity config and produce less spans by default
This commit is contained in:
parent
77ef7fe490
commit
8c23eb6833
@ -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).
|
||||
|
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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` |
|
||||
|
@ -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:
|
||||
|
@ -84,7 +84,7 @@ additionalArguments:
|
||||
## Configuration Options
|
||||
|
||||
| Field | Description | Default | Required |
|
||||
|:-----------------|:--------|:--------|:---------|
|
||||
|:----------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------|:---------|
|
||||
| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.<br /> It also defines the protocol to use (TCP or UDP).<br /> If no protocol is specified, the default is TCP. The format is:`[host]:port[/tcp\|/udp] | - | Yes |
|
||||
| `asDefault` | Mark the `entryPoint` to be in the list of default `entryPoints`.<br /> `entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.<br /> More information [here](#asdefault). | false | No |
|
||||
| `forwardedHeaders.trustedIPs` | Set the IPs or CIDR from where Traefik trusts the forwarded headers information (`X-Forwarded-*`). | - | No |
|
||||
@ -105,9 +105,10 @@ additionalArguments:
|
||||
| `observability.accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No |
|
||||
| `observability.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. <br /> More information [here](#traceverbosity). | minimal | No |
|
||||
| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs. <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No |
|
||||
| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection. <br /> Every remote client address will be replaced (`trustedIPs`) won't have any effect). <br /> Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2. <br /> If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers. <br /> If the PROXY protocol header is passed, then the version is determined automatically.<br />We recommend to use this option only for tests purposes, not in production.<br /> More information [here](#proxyprotocol-and-load-balancers). | - | No |
|
||||
| `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option. <br /> It also allows the kernel to act like a load balancer to distribute incoming connections between entry points..<br /> More information [here](#reuseport). | false | No |
|
||||
| `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option. <br /> It also allows the kernel to act like a load balancer to distribute incoming connections between entry points.<br /> More information [here](#reuseport). | false | No |
|
||||
| `transport.`<br />`respondingTimeouts.`<br />`readTimeout` | Set the timeouts for incoming requests to the Traefik instance. This is the maximum duration for reading the entire request, including the body. Setting them has no effect for UDP `entryPoints`.<br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No |
|
||||
| `transport.`<br />`respondingTimeouts.`<br />`writeTimeout` | Maximum duration before timing out writes of the response. <br /> It covers the time from the end of the request header read to the end of the response write. <br /> If zero, no timeout exists. <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No |
|
||||
| `transport.`<br />`respondingTimeouts.`<br />`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself. <br /> If zero, no timeout exists <br />Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).<br />If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No |
|
||||
@ -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.
|
||||
|
@ -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,7 +69,8 @@ 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"
|
||||
]
|
||||
}
|
||||
```
|
||||
@ -74,7 +78,17 @@ labels:
|
||||
## 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 |
|
||||
| `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.
|
||||
|
@ -283,13 +283,16 @@ HTTP/3 configuration. (Default: ```false```)
|
||||
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
|
||||
|
||||
`--entrypoints.<name>.observability.accesslogs`:
|
||||
(Default: ```true```)
|
||||
Enables access-logs for this entryPoint. (Default: ```true```)
|
||||
|
||||
`--entrypoints.<name>.observability.metrics`:
|
||||
(Default: ```true```)
|
||||
Enables metrics for this entryPoint. (Default: ```true```)
|
||||
|
||||
`--entrypoints.<name>.observability.traceverbosity`:
|
||||
Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```)
|
||||
|
||||
`--entrypoints.<name>.observability.tracing`:
|
||||
(Default: ```true```)
|
||||
Enables tracing for this entryPoint. (Default: ```true```)
|
||||
|
||||
`--entrypoints.<name>.proxyprotocol`:
|
||||
Proxy-Protocol configuration. (Default: ```false```)
|
||||
|
@ -283,13 +283,16 @@ Subject alternative names.
|
||||
Default TLS options for the routers linked to the entry point.
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_ACCESSLOGS`:
|
||||
(Default: ```true```)
|
||||
Enables access-logs for this entryPoint. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_METRICS`:
|
||||
(Default: ```true```)
|
||||
Enables metrics for this entryPoint. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_TRACEVERBOSITY`:
|
||||
Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_TRACING`:
|
||||
(Default: ```true```)
|
||||
Enables tracing for this entryPoint. (Default: ```true```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`:
|
||||
Proxy-Protocol configuration. (Default: ```false```)
|
||||
|
@ -80,8 +80,9 @@
|
||||
timeout = "42s"
|
||||
[entryPoints.EntryPoint0.observability]
|
||||
accessLogs = true
|
||||
tracing = true
|
||||
metrics = true
|
||||
tracing = true
|
||||
traceVerbosity = "foobar"
|
||||
|
||||
[providers]
|
||||
providersThrottleDuration = "42s"
|
||||
|
@ -94,8 +94,9 @@ entryPoints:
|
||||
timeout: 42s
|
||||
observability:
|
||||
accessLogs: true
|
||||
tracing: true
|
||||
metrics: true
|
||||
tracing: true
|
||||
traceVerbosity: foobar
|
||||
providers:
|
||||
providersThrottleDuration: 42s
|
||||
docker:
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
21
integration/testdata/rawdata-consul.json
vendored
21
integration/testdata/rawdata-consul.json
vendored
@ -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": [
|
||||
{
|
||||
|
@ -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": [
|
||||
|
18
integration/testdata/rawdata-crd.json
vendored
18
integration/testdata/rawdata-crd.json
vendored
@ -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"
|
||||
|
21
integration/testdata/rawdata-etcd.json
vendored
21
integration/testdata/rawdata-etcd.json
vendored
@ -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": [
|
||||
{
|
||||
|
12
integration/testdata/rawdata-gateway.json
vendored
12
integration/testdata/rawdata-gateway.json
vendored
@ -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": [
|
||||
|
@ -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": [
|
||||
|
18
integration/testdata/rawdata-ingress.json
vendored
18
integration/testdata/rawdata-ingress.json
vendored
@ -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": [
|
||||
|
@ -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": [
|
||||
|
@ -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": [
|
||||
|
22
integration/testdata/rawdata-redis.json
vendored
22
integration/testdata/rawdata-redis.json
vendored
@ -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"
|
||||
|
21
integration/testdata/rawdata-zk.json
vendored
21
integration/testdata/rawdata-zk.json
vendored
@ -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": [
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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 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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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{}) {
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -61,6 +61,7 @@ func Test_parseRouterConfig(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -127,6 +127,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -243,8 +243,9 @@ 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,
|
||||
Tracing: ep.Observability.Tracing,
|
||||
TraceVerbosity: ep.Observability.TraceVerbosity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
// 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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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,7 +68,12 @@ 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 {
|
||||
if !observability.SemConvMetricsEnabled(req.Context()) ||
|
||||
t.semConvMetricRegistry == nil ||
|
||||
t.semConvMetricRegistry.HTTPClientRequestDuration() == nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
var attrs []attribute.KeyValue
|
||||
|
||||
if statusCode < 100 || statusCode >= 600 {
|
||||
@ -83,8 +88,8 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto)))
|
||||
attrs = append(attrs, semconv.ServerAddress(req.URL.Host))
|
||||
|
||||
_, port, err := net.SplitHostPort(req.URL.Host)
|
||||
if err != nil {
|
||||
_, port, splitErr := net.SplitHostPort(req.URL.Host)
|
||||
if splitErr != nil {
|
||||
switch req.URL.Scheme {
|
||||
case "http":
|
||||
attrs = append(attrs, semconv.ServerPort(80))
|
||||
@ -99,7 +104,6 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
|
||||
|
||||
t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
|
||||
}
|
||||
|
||||
return response, err
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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,6 +261,7 @@ 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 {
|
||||
@ -264,6 +270,7 @@ func applyDefaultObservabilityModel(cfg dynamic.Configuration) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
@ -524,6 +525,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -592,6 +594,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -625,6 +628,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -641,6 +645,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -654,6 +659,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -691,6 +697,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -733,6 +740,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
"websecure-test": {
|
||||
@ -743,6 +751,7 @@ func Test_applyModel(t *testing.T) {
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
// 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))
|
||||
}
|
||||
|
||||
if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware))
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,25 +204,10 @@ 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
|
||||
}
|
||||
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
|
||||
func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterInfo, routerName string) (http.Handler, error) {
|
||||
var qualifiedNames []string
|
||||
for _, name := range router.Middlewares {
|
||||
@ -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))
|
||||
}
|
||||
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
// 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
|
||||
}
|
||||
|
||||
// BuildDefaultHTTPRouter creates a default HTTP router.
|
||||
func BuildDefaultHTTPRouter() http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) {
|
||||
// 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, serviceName, accesslog.AddServiceFields)
|
||||
}
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, qualifiedSvcName, accesslog.AddServiceFields)
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() &&
|
||||
m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
|
||||
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
|
||||
metricsHandler := metricsMiddle.ServiceMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), qualifiedSvcName)
|
||||
metricsHandler = observability.WrapMiddleware(ctx, metricsHandler)
|
||||
|
||||
proxy, err = alice.New().
|
||||
Append(observability.WrapMiddleware(ctx, metricsHandler)).
|
||||
Append(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)
|
||||
|
||||
|
@ -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.
|
||||
@ -60,6 +61,7 @@ func WithObservability() func(*dynamic.Router) {
|
||||
AccessLogs: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
TraceVerbosity: types.MinimalVerbosity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"`
|
||||
|
72
pkg/types/tracing_test.go
Normal file
72
pkg/types/tracing_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTracingVerbosity_Allows(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
from TracingVerbosity
|
||||
to TracingVerbosity
|
||||
allows bool
|
||||
}{
|
||||
{
|
||||
desc: "minimal vs minimal",
|
||||
from: MinimalVerbosity,
|
||||
to: MinimalVerbosity,
|
||||
allows: true,
|
||||
},
|
||||
{
|
||||
desc: "minimal vs detailed",
|
||||
from: MinimalVerbosity,
|
||||
to: DetailedVerbosity,
|
||||
allows: false,
|
||||
},
|
||||
{
|
||||
desc: "detailed vs minimal",
|
||||
from: DetailedVerbosity,
|
||||
to: MinimalVerbosity,
|
||||
allows: true,
|
||||
},
|
||||
{
|
||||
desc: "detailed vs detailed",
|
||||
from: DetailedVerbosity,
|
||||
to: DetailedVerbosity,
|
||||
allows: true,
|
||||
},
|
||||
{
|
||||
desc: "unknown vs minimal",
|
||||
from: TracingVerbosity("unknown"),
|
||||
to: MinimalVerbosity,
|
||||
allows: true,
|
||||
},
|
||||
{
|
||||
desc: "unknown vs detailed",
|
||||
from: TracingVerbosity("unknown"),
|
||||
to: DetailedVerbosity,
|
||||
allows: false,
|
||||
},
|
||||
{
|
||||
desc: "minimal vs unknown",
|
||||
from: MinimalVerbosity,
|
||||
to: TracingVerbosity("unknown"),
|
||||
allows: false,
|
||||
},
|
||||
{
|
||||
desc: "detailed vs unknown",
|
||||
from: DetailedVerbosity,
|
||||
to: TracingVerbosity("unknown"),
|
||||
allows: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, test.allows, test.from.Allows(test.to))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user