1
0
mirror of https://github.com/containous/traefik.git synced 2025-01-03 01:17:53 +03:00

Manage observability at entrypoint and router level

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Romain 2024-12-12 09:52:07 +01:00 committed by GitHub
parent 9588e51146
commit b1934231ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1216 additions and 303 deletions

View File

@ -68,6 +68,7 @@ metrics:
```bash tab="CLI" ```bash tab="CLI"
--metrics.datadog.addEntryPointsLabels=true --metrics.datadog.addEntryPointsLabels=true
``` ```
#### `addRoutersLabels` #### `addRoutersLabels`
_Optional, Default=false_ _Optional, Default=false_

View File

@ -5,16 +5,80 @@ description: "Traefik provides Logs, Access Logs, Metrics and Tracing. Read the
# Overview # Overview
Traefik's Observability system Traefiks observability features include logs, access logs, metrics, and tracing. You can configure these options globally or at more specific levels, such as per router or per entry point.
{: .subtitle }
## Logs ## Configuration Example
Enable access logs, metrics, and tracing globally
```yaml tab="File (YAML)"
accessLog: {}
metrics:
otlp: {}
tracing: {}
```
```yaml tab="File (TOML)"
[accessLog]
[metrics]
[metrics.otlp]
[tracing]
```
```bash tab="CLI"
--accesslog=true
--metrics.otlp=true
--tracing=true
```
You can disable access logs, metrics, and tracing for a specific entrypoint attached to a router:
```yaml tab="File (YAML)"
# Static Configuration
entryPoints:
EntryPoint0:
address: ':8000/udp'
observability:
accessLogs: false
tracing: false
metrics: false
```
```toml tab="File (TOML)"
# Static Configuration
[entryPoints.EntryPoint0]
address = ":8000/udp"
[entryPoints.EntryPoint0.observability]
accessLogs = false
tracing = false
metrics = false
```
```bash tab="CLI"
# Static Configuration
--entryPoints.EntryPoint0.address=:8000/udp
--entryPoints.EntryPoint0.observability.accessLogs=false
--entryPoints.EntryPoint0.observability.metrics=false
--entryPoints.EntryPoint0.observability.tracing=false
```
!!!note "Default Behavior"
A router with its own observability configuration will override the global default.
## Configuration Options
### Logs
Traefik logs informs about everything that happens within Traefik (startup, configuration, events, shutdown, and so on). Traefik logs informs about everything that happens within Traefik (startup, configuration, events, shutdown, and so on).
Read the [Logs documentation](./logs.md) to learn how to configure it. Read the [Logs documentation](./logs.md) to learn how to configure it.
## Access Logs ### Access Logs
Access logs are a key part of observability in Traefik. Access logs are a key part of observability in Traefik.
@ -24,7 +88,7 @@ including the source IP address, requested URL, response status code, and more.
Read the [Access Logs documentation](./access-logs.md) to learn how to configure it. Read the [Access Logs documentation](./access-logs.md) to learn how to configure it.
## Metrics ### Metrics
Traefik offers a metrics feature that provides valuable insights about the performance and usage. Traefik offers a metrics feature that provides valuable insights about the performance and usage.
These metrics include the number of requests received, the requests duration, and more. These metrics include the number of requests received, the requests duration, and more.
@ -33,7 +97,7 @@ On top of supporting metrics in the OpenTelemetry format, Traefik supports the f
Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it. Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it.
## Tracing ### Tracing
The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure. The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure.

View File

@ -147,6 +147,9 @@
- "traefik.http.middlewares.middleware25.stripprefixregex.regex=foobar, foobar" - "traefik.http.middlewares.middleware25.stripprefixregex.regex=foobar, foobar"
- "traefik.http.routers.router0.entrypoints=foobar, foobar" - "traefik.http.routers.router0.entrypoints=foobar, foobar"
- "traefik.http.routers.router0.middlewares=foobar, foobar" - "traefik.http.routers.router0.middlewares=foobar, foobar"
- "traefik.http.routers.router0.observability.accesslogs=true"
- "traefik.http.routers.router0.observability.metrics=true"
- "traefik.http.routers.router0.observability.tracing=true"
- "traefik.http.routers.router0.priority=42" - "traefik.http.routers.router0.priority=42"
- "traefik.http.routers.router0.rule=foobar" - "traefik.http.routers.router0.rule=foobar"
- "traefik.http.routers.router0.rulesyntax=foobar" - "traefik.http.routers.router0.rulesyntax=foobar"
@ -160,6 +163,9 @@
- "traefik.http.routers.router0.tls.options=foobar" - "traefik.http.routers.router0.tls.options=foobar"
- "traefik.http.routers.router1.entrypoints=foobar, foobar" - "traefik.http.routers.router1.entrypoints=foobar, foobar"
- "traefik.http.routers.router1.middlewares=foobar, foobar" - "traefik.http.routers.router1.middlewares=foobar, foobar"
- "traefik.http.routers.router1.observability.accesslogs=true"
- "traefik.http.routers.router1.observability.metrics=true"
- "traefik.http.routers.router1.observability.tracing=true"
- "traefik.http.routers.router1.priority=42" - "traefik.http.routers.router1.priority=42"
- "traefik.http.routers.router1.rule=foobar" - "traefik.http.routers.router1.rule=foobar"
- "traefik.http.routers.router1.rulesyntax=foobar" - "traefik.http.routers.router1.rulesyntax=foobar"

View File

@ -20,6 +20,10 @@
[[http.routers.Router0.tls.domains]] [[http.routers.Router0.tls.domains]]
main = "foobar" main = "foobar"
sans = ["foobar", "foobar"] sans = ["foobar", "foobar"]
[http.routers.Router0.observability]
accessLogs = true
tracing = true
metrics = true
[http.routers.Router1] [http.routers.Router1]
entryPoints = ["foobar", "foobar"] entryPoints = ["foobar", "foobar"]
middlewares = ["foobar", "foobar"] middlewares = ["foobar", "foobar"]
@ -38,6 +42,10 @@
[[http.routers.Router1.tls.domains]] [[http.routers.Router1.tls.domains]]
main = "foobar" main = "foobar"
sans = ["foobar", "foobar"] sans = ["foobar", "foobar"]
[http.routers.Router1.observability]
accessLogs = true
tracing = true
metrics = true
[http.services] [http.services]
[http.services.Service01] [http.services.Service01]
[http.services.Service01.failover] [http.services.Service01.failover]

View File

@ -25,6 +25,10 @@ http:
sans: sans:
- foobar - foobar
- foobar - foobar
observability:
accessLogs: true
tracing: true
metrics: true
Router1: Router1:
entryPoints: entryPoints:
- foobar - foobar
@ -48,6 +52,10 @@ http:
sans: sans:
- foobar - foobar
- foobar - foobar
observability:
accessLogs: true
tracing: true
metrics: true
services: services:
Service01: Service01:
failover: failover:

View File

@ -86,6 +86,18 @@ spec:
- name - name
type: object type: object
type: array type: array
observability:
description: |-
Observability defines the observability configuration for a router.
More info: https://doc.traefik.io/traefik/v3.2/routing/routers/#observability
properties:
accessLogs:
type: boolean
metrics:
type: boolean
tracing:
type: boolean
type: object
priority: priority:
description: |- description: |-
Priority defines the router's priority. Priority defines the router's priority.

View File

@ -173,6 +173,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/routers/Router0/entryPoints/1` | `foobar` | | `traefik/http/routers/Router0/entryPoints/1` | `foobar` |
| `traefik/http/routers/Router0/middlewares/0` | `foobar` | | `traefik/http/routers/Router0/middlewares/0` | `foobar` |
| `traefik/http/routers/Router0/middlewares/1` | `foobar` | | `traefik/http/routers/Router0/middlewares/1` | `foobar` |
| `traefik/http/routers/Router0/observability/accessLogs` | `true` |
| `traefik/http/routers/Router0/observability/metrics` | `true` |
| `traefik/http/routers/Router0/observability/tracing` | `true` |
| `traefik/http/routers/Router0/priority` | `42` | | `traefik/http/routers/Router0/priority` | `42` |
| `traefik/http/routers/Router0/rule` | `foobar` | | `traefik/http/routers/Router0/rule` | `foobar` |
| `traefik/http/routers/Router0/ruleSyntax` | `foobar` | | `traefik/http/routers/Router0/ruleSyntax` | `foobar` |
@ -189,6 +192,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/routers/Router1/entryPoints/1` | `foobar` | | `traefik/http/routers/Router1/entryPoints/1` | `foobar` |
| `traefik/http/routers/Router1/middlewares/0` | `foobar` | | `traefik/http/routers/Router1/middlewares/0` | `foobar` |
| `traefik/http/routers/Router1/middlewares/1` | `foobar` | | `traefik/http/routers/Router1/middlewares/1` | `foobar` |
| `traefik/http/routers/Router1/observability/accessLogs` | `true` |
| `traefik/http/routers/Router1/observability/metrics` | `true` |
| `traefik/http/routers/Router1/observability/tracing` | `true` |
| `traefik/http/routers/Router1/priority` | `42` | | `traefik/http/routers/Router1/priority` | `42` |
| `traefik/http/routers/Router1/rule` | `foobar` | | `traefik/http/routers/Router1/rule` | `foobar` |
| `traefik/http/routers/Router1/ruleSyntax` | `foobar` | | `traefik/http/routers/Router1/ruleSyntax` | `foobar` |

View File

@ -86,6 +86,18 @@ spec:
- name - name
type: object type: object
type: array type: array
observability:
description: |-
Observability defines the observability configuration for a router.
More info: https://doc.traefik.io/traefik/v3.2/routing/routers/#observability
properties:
accessLogs:
type: boolean
metrics:
type: boolean
tracing:
type: boolean
type: object
priority: priority:
description: |- description: |-
Priority defines the router's priority. Priority defines the router's priority.

View File

@ -264,6 +264,15 @@ HTTP/3 configuration. (Default: ```false```)
`--entrypoints.<name>.http3.advertisedport`: `--entrypoints.<name>.http3.advertisedport`:
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
`--entrypoints.<name>.observability.accesslogs`:
(Default: ```true```)
`--entrypoints.<name>.observability.metrics`:
(Default: ```true```)
`--entrypoints.<name>.observability.tracing`:
(Default: ```true```)
`--entrypoints.<name>.proxyprotocol`: `--entrypoints.<name>.proxyprotocol`:
Proxy-Protocol configuration. (Default: ```false```) Proxy-Protocol configuration. (Default: ```false```)

View File

@ -264,6 +264,15 @@ Subject alternative names.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_OPTIONS`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_OPTIONS`:
Default TLS options for the routers linked to the entry point. Default TLS options for the routers linked to the entry point.
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_ACCESSLOGS`:
(Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_METRICS`:
(Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_OBSERVABILITY_TRACING`:
(Default: ```true```)
`TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`: `TRAEFIK_ENTRYPOINTS_<NAME>_PROXYPROTOCOL`:
Proxy-Protocol configuration. (Default: ```false```) Proxy-Protocol configuration. (Default: ```false```)

View File

@ -77,6 +77,10 @@
advertisedPort = 42 advertisedPort = 42
[entryPoints.EntryPoint0.udp] [entryPoints.EntryPoint0.udp]
timeout = "42s" timeout = "42s"
[entryPoints.EntryPoint0.observability]
accessLogs = true
tracing = true
metrics = true
[providers] [providers]
providersThrottleDuration = "42s" providersThrottleDuration = "42s"

View File

@ -91,6 +91,10 @@ entryPoints:
advertisedPort: 42 advertisedPort: 42
udp: udp:
timeout: 42s timeout: 42s
observability:
accessLogs: true
tracing: true
metrics: true
providers: providers:
providersThrottleDuration: 42s providersThrottleDuration: 42s
docker: docker:

View File

@ -1237,8 +1237,6 @@ entryPoints:
--entryPoints.foo.udp.timeout=10s --entryPoints.foo.udp.timeout=10s
``` ```
{!traefik-for-business-applications.md!}
## Systemd Socket Activation ## Systemd Socket Activation
Traefik supports [systemd socket activation](https://www.freedesktop.org/software/systemd/man/latest/systemd-socket-activate.html). Traefik supports [systemd socket activation](https://www.freedesktop.org/software/systemd/man/latest/systemd-socket-activate.html).
@ -1260,3 +1258,105 @@ systemd-socket-activate -l 80 -l 443 --fdname web:websecure ./traefik --entrypo
!!! warning "Docker Support" !!! warning "Docker Support"
Socket activation is not supported by Docker but works with Podman containers. Socket activation is not supported by Docker but works with Podman containers.
## Observability Options
This section is dedicated to options to control observability for an EntryPoint.
!!! info "Note that you must first enable access-logs, tracing, and/or metrics."
!!! warning "AddInternals option"
By default, and for any type of signals (access-logs, metrics and tracing),
Traefik disables observability for internal resources.
The observability options described below cannot interfere with the `AddInternals` ones,
and will be ignored.
For instance, if a router exposes the `api@internal` service and `metrics.AddInternals` is false,
it will never produces metrics, even if the EntryPoint observability configuration enables metrics.
### AccessLogs
_Optional, Default=true_
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.
```yaml tab="File (YAML)"
entryPoints:
foo:
address: ':8000/udp'
observability:
accessLogs: false
```
```toml tab="File (TOML)"
[entryPoints.foo]
address = ":8000/udp"
[entryPoints.foo.observability]
accessLogs = false
```
```bash tab="CLI"
--entryPoints.foo.address=:8000/udp
--entryPoints.foo.observability.accessLogs=false
```
### Metrics
_Optional, Default=true_
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.
```yaml tab="File (YAML)"
entryPoints:
foo:
address: ':8000/udp'
observability:
metrics: false
```
```toml tab="File (TOML)"
[entryPoints.foo]
address = ":8000/udp"
[entryPoints.foo.observability]
metrics = false
```
```bash tab="CLI"
--entryPoints.foo.address=:8000/udp
--entryPoints.foo.observability.metrics=false
```
### Tracing
_Optional, Default=true_
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.
```yaml tab="File (YAML)"
entryPoints:
foo:
address: ':8000/udp'
observability:
tracing: false
```
```toml tab="File (TOML)"
[entryPoints.foo]
address = ":8000/udp"
[entryPoints.foo.observability]
tracing = false
```
```bash tab="CLI"
--entryPoints.foo.address=:8000/udp
--entryPoints.foo.observability.tracing=false
```
{!traefik-for-business-applications.md!}

View File

@ -111,6 +111,30 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m
traefik.http.routers.myrouter.tls.options=foobar traefik.http.routers.myrouter.tls.options=foobar
``` ```
??? info "`traefik.http.routers.<router_name>.observability.accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
```yaml
traefik.http.routers.myrouter.observability.accesslogs=true
```
??? info "`traefik.http.routers.<router_name>.observability.metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
```yaml
traefik.http.routers.myrouter.observability.metrics=true
```
??? info "`traefik.http.routers.<router_name>.observability.tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
```yaml
traefik.http.routers.myrouter.observability.tracing=true
```
??? info "`traefik.http.routers.<router_name>.priority`" ??? info "`traefik.http.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority) for more information. See [priority](../routers/index.md#priority) for more information.

View File

@ -224,6 +224,30 @@ For example, to change the rule, you could add the label ```traefik.http.routers
- "traefik.http.routers.myrouter.tls.options=foobar" - "traefik.http.routers.myrouter.tls.options=foobar"
``` ```
??? info "`traefik.http.routers.<router_name>.observability.accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
```yaml
- "traefik.http.routers.myrouter.observability.accesslogs=true"
```
??? info "`traefik.http.routers.<router_name>.observability.metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
```yaml
- "traefik.http.routers.myrouter.observability.metrics=true"
```
??? info "`traefik.http.routers.<router_name>.observability.tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
```yaml
- "traefik.http.routers.myrouter.observability.tracing=true"
```
??? info "`traefik.http.routers.<router_name>.priority`" ??? info "`traefik.http.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority) for more information. See [priority](../routers/index.md#priority) for more information.

View File

@ -111,6 +111,30 @@ For example, to change the rule, you could add the label ```traefik.http.routers
traefik.http.routers.myrouter.tls.options=foobar traefik.http.routers.myrouter.tls.options=foobar
``` ```
??? info "`traefik.http.routers.<router_name>.observability.accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
```yaml
traefik.http.routers.myrouter.observability.accesslogs=true
```
??? info "`traefik.http.routers.<router_name>.observability.metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
```yaml
traefik.http.routers.myrouter.observability.metrics=true
```
??? info "`traefik.http.routers.<router_name>.observability.tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
```yaml
traefik.http.routers.myrouter.observability.tracing=true
```
??? info "`traefik.http.routers.<router_name>.priority`" ??? info "`traefik.http.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority) for more information. See [priority](../routers/index.md#priority) for more information.

View File

@ -332,17 +332,21 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
middlewares: # [5] middlewares: # [5]
- name: middleware1 # [6] - name: middleware1 # [6]
namespace: default # [7] namespace: default # [7]
services: # [8] observability: # [8]
accesslogs: true # [9]
metrics: true # [10]
tracing: true # [11]
services: # [12]
- kind: Service - kind: Service
name: foo name: foo
namespace: default namespace: default
passHostHeader: true passHostHeader: true
port: 80 # [9] port: 80 # [13]
responseForwarding: responseForwarding:
flushInterval: 1ms flushInterval: 1ms
scheme: https scheme: https
serversTransport: transport # [10] serversTransport: transport # [14]
healthCheck: # [11] healthCheck: # [15]
path: /health path: /health
interval: 15s interval: 15s
sticky: sticky:
@ -355,17 +359,17 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
path: /foo path: /foo
strategy: RoundRobin strategy: RoundRobin
weight: 10 weight: 10
nativeLB: true # [12] nativeLB: true # [16]
nodePortLB: true # [13] nodePortLB: true # [17]
tls: # [14] tls: # [18]
secretName: supersecret # [15] secretName: supersecret # [19]
options: # [16] options: # [20]
name: opt # [17] name: opt # [21]
namespace: default # [18] namespace: default # [22]
certResolver: foo # [19] certResolver: foo # [23]
domains: # [20] domains: # [24]
- main: example.net # [21] - main: example.net # [25]
sans: # [22] sans: # [26]
- a.example.net - a.example.net
- b.example.net - b.example.net
``` ```
@ -379,21 +383,25 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) | | [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name | | [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace. It can be omitted when the Middleware is in the IngressRoute namespace. | | [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace. It can be omitted when the Middleware is in the IngressRoute namespace. |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) | | [8] | `routes[n].observability` | Defines the route observability configuration. |
| [9] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [9] | `observability.accesslogs` | Defines whether the route will produce [access-logs](../routers/index.md#accesslogs). |
| [10] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). | | [10] | `observability.metrics` | Defines whether the route will produce [metrics](../routers/index.md#metrics). |
| [11] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. | | [11] | `observability.tracing` | Defines whether the route will produce [traces](../routers/index.md#tracing). |
| [12] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. | | [12] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
| [13] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. | | [13] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. |
| [14] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration | | [14] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). |
| [15] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) | | [15] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. |
| [16] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) | | [16] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. |
| [17] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name | | [17] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. |
| [18] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace | | [18] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [19] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) | | [19] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [20] | `tls.domains` | List of [domains](../routers/index.md#domains) | | [20] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [21] | `domains[n].main` | Defines the main domain name | | [21] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [22] | `domains[n].sans` | List of SANs (alternative domains) | | [22] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [23] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [24] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [25] | `domains[n].main` | Defines the main domain name |
| [26] | `domains[n].sans` | List of SANs (alternative domains) |
??? example "Declaring an IngressRoute" ??? example "Declaring an IngressRoute"

View File

@ -288,6 +288,30 @@ which in turn will create the resulting routers, services, handlers, etc.
traefik.ingress.kubernetes.io/router.tls.options: foobar@file traefik.ingress.kubernetes.io/router.tls.options: foobar@file
``` ```
??? info "`traefik.ingress.kubernetes.io/router.observability.accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
```yaml
traefik.ingress.kubernetes.io/router.observability.accesslogs: true
```
??? info "`traefik.ingress.kubernetes.io/router.observability.metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
```yaml
traefik.ingress.kubernetes.io/router.observability.metrics: true
```
??? info "`traefik.ingress.kubernetes.io/router.observability.tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
```yaml
traefik.ingress.kubernetes.io/router.observability.tracing: true
```
#### On Service #### On Service
??? info "`traefik.ingress.kubernetes.io/service.nativelb`" ??? info "`traefik.ingress.kubernetes.io/service.nativelb`"

View File

@ -95,6 +95,30 @@ A Story of key & values
|---------------------------------------------|----------| |---------------------------------------------|----------|
| `traefik/http/routers/myrouter/tls/options` | `foobar` | | `traefik/http/routers/myrouter/tls/options` | `foobar` |
??? info "`traefik/http/routers/<router_name>/observability/accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
| Key (Path) | Value |
|----------------------------------------------------------|--------|
| `traefik/http/routers/myrouter/observability/accesslogs` | `true` |
??? info "`traefik/http/routers/<router_name>/observability/metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
| Key (Path) | Value |
|-------------------------------------------------------|--------|
| `traefik/http/routers/myrouter/observability/metrics` | `true` |
??? info "`traefik/http/routers/<router_name>/observability/tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
| Key (Path) | Value |
|-------------------------------------------------------|--------|
| `traefik/http/routers/myrouter/observability/tracing` | `true` |
??? info "`traefik/http/routers/<router_name>/priority`" ??? info "`traefik/http/routers/<router_name>/priority`"
See [priority](../routers/index.md#priority) for more information. See [priority](../routers/index.md#priority) for more information.

View File

@ -111,6 +111,30 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m
traefik.http.routers.myrouter.tls.options=foobar traefik.http.routers.myrouter.tls.options=foobar
``` ```
??? info "`traefik.http.routers.<router_name>.observability.accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
```yaml
traefik.http.routers.myrouter.observability.accesslogs=true
```
??? info "`traefik.http.routers.<router_name>.observability.metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
```yaml
traefik.http.routers.myrouter.observability.metrics=true
```
??? info "`traefik.http.routers.<router_name>.observability.tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
```yaml
traefik.http.routers.myrouter.observability.tracing=true
```
??? info "`traefik.http.routers.<router_name>.priority`" ??? info "`traefik.http.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority) for more information. See [priority](../routers/index.md#priority) for more information.

View File

@ -7,7 +7,8 @@ There are, however, exceptions when using label-based configurations:
and a label defines a service (e.g. implicitly through a loadbalancer server port value), and a label defines a service (e.g. implicitly through a loadbalancer server port value),
but the router does not specify any service, but the router does not specify any service,
then that service is automatically assigned to the router. then that service is automatically assigned to the router.
1. If a label defines a router (e.g. through a router Rule) but no service is defined,
2. If a label defines a router (e.g. through a router Rule) but no service is defined,
then a service is automatically created and assigned to the router. then a service is automatically created and assigned to the router.
!!! info "" !!! info ""

View File

@ -235,6 +235,30 @@ For example, to change the rule, you could add the label ```traefik.http.routers
- "traefik.http.routers.myrouter.tls.options=foobar" - "traefik.http.routers.myrouter.tls.options=foobar"
``` ```
??? info "`traefik.http.routers.<router_name>.observability.accesslogs`"
See accesslogs [option](../routers/index.md#accesslogs) for more information.
```yaml
- "traefik.http.routers.myrouter.observability.accesslogs=true"
```
??? info "`traefik.http.routers.<router_name>.observability.metrics`"
See metrics [option](../routers/index.md#metrics) for more information.
```yaml
- "traefik.http.routers.myrouter.observability.metrics=true"
```
??? info "`traefik.http.routers.<router_name>.observability.tracing`"
See tracing [option](../routers/index.md#tracing) for more information.
```yaml
- "traefik.http.routers.myrouter.observability.tracing=true"
```
??? info "`traefik.http.routers.<router_name>.priority`" ??? info "`traefik.http.routers.<router_name>.priority`"
See [priority](../routers/index.md#priority) for more information. See [priority](../routers/index.md#priority) for more information.

View File

@ -877,6 +877,117 @@ The [supported `provider` table](../../https/acme.md#providers) indicates if the
!!! warning "Double Wildcard Certificates" !!! warning "Double Wildcard Certificates"
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`). It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
### Observability
The Observability section defines a per router behavior regarding access-logs, metrics or tracing.
The default router observability configuration is inherited from the attached EntryPoints and can be configured with the observability [options](../../routing/entrypoints.md#observability-options).
However, a router defining its own observability configuration will opt-out from these defaults.
!!! info "Note that to enable router-level observability, you must first enable access-logs, tracing, and/or metrics."
!!! warning "AddInternals option"
By default, and for any type of signals (access-logs, metrics and tracing),
Traefik disables observability for internal resources.
The observability options described below cannot interfere with the `AddInternals` ones,
and will be ignored.
For instance, if a router exposes the `api@internal` service and `metrics.AddInternals` is false,
it will never produces metrics, even if the router observability configuration enables metrics.
#### `accessLogs`
_Optional_
The `accessLogs` option controls whether the router will produce access-logs.
??? example "Disable access-logs for a router using the [File Provider](../../providers/file.md)"
```yaml tab="YAML"
## Dynamic configuration
http:
routers:
my-router:
rule: "Path(`/foo`)"
service: service-foo
observability:
accessLogs: false
```
```toml tab="TOML"
## Dynamic configuration
[http.routers]
[http.routers.my-router]
rule = "Path(`/foo`)"
service = "service-foo"
[http.routers.my-router.observability]
accessLogs = false
```
#### `metrics`
_Optional_
The `metrics` option controls whether the router will produce metrics.
!!! warning "Metrics layers"
When metrics layers are not enabled with the `addEntryPointsLabels`, `addRoutersLabels` and/or `addServicesLabels` options,
enabling metrics for a router will not enable them.
??? example "Disable metrics for a router using the [File Provider](../../providers/file.md)"
```yaml tab="YAML"
## Dynamic configuration
http:
routers:
my-router:
rule: "Path(`/foo`)"
service: service-foo
observability:
metrics: false
```
```toml tab="TOML"
## Dynamic configuration
[http.routers]
[http.routers.my-router]
rule = "Path(`/foo`)"
service = "service-foo"
[http.routers.my-router.observability]
metrics = false
```
#### `tracing`
_Optional_
The `tracing` option controls whether the router will produce traces.
??? example "Disable tracing for a router using the [File Provider](../../providers/file.md)"
```yaml tab="YAML"
## Dynamic configuration
http:
routers:
my-router:
rule: "Path(`/foo`)"
service: service-foo
observability:
tracing: false
```
```toml tab="TOML"
## Dynamic configuration
[http.routers]
[http.routers.my-router]
rule = "Path(`/foo`)"
service = "service-foo"
[http.routers.my-router.observability]
tracing = false
```
## Configuring TCP Routers ## Configuring TCP Routers
!!! warning "The character `@` is not authorized in the router name" !!! warning "The character `@` is not authorized in the router name"

View File

@ -86,6 +86,18 @@ spec:
- name - name
type: object type: object
type: array type: array
observability:
description: |-
Observability defines the observability configuration for a router.
More info: https://doc.traefik.io/traefik/v3.2/routing/routers/#observability
properties:
accessLogs:
type: boolean
metrics:
type: boolean
tracing:
type: boolean
type: object
priority: priority:
description: |- description: |-
Priority defines the router's priority. Priority defines the router's priority.

View File

@ -36,10 +36,11 @@ type HTTPConfiguration struct {
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// Model is a set of default router's values. // Model holds model configuration.
type Model struct { type Model struct {
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
Observability RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
} }
@ -64,6 +65,7 @@ type Router struct {
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"` RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"` Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
Observability *RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
} }
@ -78,6 +80,15 @@ type RouterTLSConfig struct {
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// RouterObservabilityConfig holds the observability configuration for a router.
type RouterObservabilityConfig 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"`
}
// +k8s:deepcopy-gen=true
// Mirroring holds the Mirroring configuration. // Mirroring holds the Mirroring configuration.
type Mirroring struct { type Mirroring struct {
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"` Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`

View File

@ -1023,6 +1023,7 @@ func (in *Model) DeepCopyInto(out *Model) {
*out = new(RouterTLSConfig) *out = new(RouterTLSConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
in.Observability.DeepCopyInto(&out.Observability)
return return
} }
@ -1249,6 +1250,11 @@ func (in *Router) DeepCopyInto(out *Router) {
*out = new(RouterTLSConfig) *out = new(RouterTLSConfig)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.Observability != nil {
in, out := &in.Observability, &out.Observability
*out = new(RouterObservabilityConfig)
(*in).DeepCopyInto(*out)
}
return return
} }
@ -1262,6 +1268,37 @@ func (in *Router) DeepCopy() *Router {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouterObservabilityConfig) DeepCopyInto(out *RouterObservabilityConfig) {
*out = *in
if in.AccessLogs != nil {
in, out := &in.AccessLogs, &out.AccessLogs
*out = new(bool)
**out = **in
}
if in.Tracing != nil {
in, out := &in.Tracing, &out.Tracing
*out = new(bool)
**out = **in
}
if in.Metrics != nil {
in, out := &in.Metrics, &out.Metrics
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouterObservabilityConfig.
func (in *RouterObservabilityConfig) DeepCopy() *RouterObservabilityConfig {
if in == nil {
return nil
}
out := new(RouterObservabilityConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) { func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) {
*out = *in *out = *in

View File

@ -880,6 +880,11 @@ func TestEncodeConfiguration(t *testing.T) {
Rule: "foobar", Rule: "foobar",
Priority: 42, Priority: 42,
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
}, },
"Router1": { "Router1": {
EntryPoints: []string{ EntryPoints: []string{
@ -893,6 +898,11 @@ func TestEncodeConfiguration(t *testing.T) {
Service: "foobar", Service: "foobar",
Rule: "foobar", Rule: "foobar",
Priority: 42, Priority: 42,
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
}, },
}, },
Middlewares: map[string]*dynamic.Middleware{ Middlewares: map[string]*dynamic.Middleware{
@ -1411,11 +1421,17 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Routers.Router0.Rule": "foobar", "traefik.HTTP.Routers.Router0.Rule": "foobar",
"traefik.HTTP.Routers.Router0.Service": "foobar", "traefik.HTTP.Routers.Router0.Service": "foobar",
"traefik.HTTP.Routers.Router0.TLS": "true", "traefik.HTTP.Routers.Router0.TLS": "true",
"traefik.HTTP.Routers.Router0.Observability.AccessLogs": "true",
"traefik.HTTP.Routers.Router0.Observability.Tracing": "true",
"traefik.HTTP.Routers.Router0.Observability.Metrics": "true",
"traefik.HTTP.Routers.Router1.EntryPoints": "foobar, fiibar", "traefik.HTTP.Routers.Router1.EntryPoints": "foobar, fiibar",
"traefik.HTTP.Routers.Router1.Middlewares": "foobar, fiibar", "traefik.HTTP.Routers.Router1.Middlewares": "foobar, fiibar",
"traefik.HTTP.Routers.Router1.Priority": "42", "traefik.HTTP.Routers.Router1.Priority": "42",
"traefik.HTTP.Routers.Router1.Rule": "foobar", "traefik.HTTP.Routers.Router1.Rule": "foobar",
"traefik.HTTP.Routers.Router1.Service": "foobar", "traefik.HTTP.Routers.Router1.Service": "foobar",
"traefik.HTTP.Routers.Router1.Observability.AccessLogs": "true",
"traefik.HTTP.Routers.Router1.Observability.Tracing": "true",
"traefik.HTTP.Routers.Router1.Observability.Metrics": "true",
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar",

View File

@ -23,6 +23,7 @@ type EntryPoint struct {
HTTP2 *HTTP2Config `description:"HTTP/2 configuration." json:"http2,omitempty" toml:"http2,omitempty" yaml:"http2,omitempty" export:"true"` HTTP2 *HTTP2Config `description:"HTTP/2 configuration." json:"http2,omitempty" toml:"http2,omitempty" yaml:"http2,omitempty" export:"true"`
HTTP3 *HTTP3Config `description:"HTTP/3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HTTP3 *HTTP3Config `description:"HTTP/3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"` UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
Observability *ObservabilityConfig `description:"Observability configuration." json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
} }
// GetAddress strips any potential protocol part of the address field of the // GetAddress strips any potential protocol part of the address field of the
@ -59,6 +60,8 @@ func (ep *EntryPoint) SetDefaults() {
ep.HTTP.SetDefaults() ep.HTTP.SetDefaults()
ep.HTTP2 = &HTTP2Config{} ep.HTTP2 = &HTTP2Config{}
ep.HTTP2.SetDefaults() ep.HTTP2.SetDefaults()
ep.Observability = &ObservabilityConfig{}
ep.Observability.SetDefaults()
} }
// HTTPConfig is the HTTP configuration of an entry point. // HTTPConfig is the HTTP configuration of an entry point.
@ -158,3 +161,17 @@ type UDPConfig struct {
func (u *UDPConfig) SetDefaults() { func (u *UDPConfig) SetDefaults() {
u.Timeout = ptypes.Duration(DefaultUDPTimeout) u.Timeout = ptypes.Duration(DefaultUDPTimeout)
} }
// 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"`
}
// SetDefaults sets the default values.
func (o *ObservabilityConfig) SetDefaults() {
o.AccessLogs = true
o.Tracing = true
o.Metrics = true
}

View File

@ -77,6 +77,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{ UDP: &UDPConfig{
Timeout: 3000000000, Timeout: 3000000000,
}, },
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}}, }},
Providers: &Providers{}, Providers: &Providers{},
}, },
@ -122,6 +127,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{ UDP: &UDPConfig{
Timeout: 3000000000, Timeout: 3000000000,
}, },
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}}, }},
Providers: &Providers{}, Providers: &Providers{},
CertificatesResolvers: map[string]CertificateResolver{ CertificatesResolvers: map[string]CertificateResolver{
@ -178,6 +188,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{ UDP: &UDPConfig{
Timeout: 3000000000, Timeout: 3000000000,
}, },
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}}, }},
Providers: &Providers{}, Providers: &Providers{},
CertificatesResolvers: map[string]CertificateResolver{ CertificatesResolvers: map[string]CertificateResolver{
@ -238,6 +253,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
UDP: &UDPConfig{ UDP: &UDPConfig{
Timeout: 3000000000, Timeout: 3000000000,
}, },
Observability: &ObservabilityConfig{
AccessLogs: true,
Tracing: true,
Metrics: true,
},
}}, }},
Providers: &Providers{}, Providers: &Providers{},
CertificatesResolvers: map[string]CertificateResolver{ CertificatesResolvers: map[string]CertificateResolver{

View File

@ -119,6 +119,11 @@ func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanK
} }
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if val := req.Context().Value(observability.DisableMetricsKey); val != nil {
m.next.ServeHTTP(rw, req)
return
}
proto := getRequestProtocol(req) proto := getRequestProtocol(req)
var labels []string var labels []string

View File

@ -2,21 +2,15 @@ package observability
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"strconv"
"strings"
"time" "time"
"github.com/containous/alice" "github.com/containous/alice"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop" "go.opentelemetry.io/otel/trace/noop"
) )
@ -30,22 +24,17 @@ type entryPointTracing struct {
entryPoint string entryPoint string
next http.Handler next http.Handler
semConvMetricRegistry *metrics.SemConvMetricsRegistry
} }
// WrapEntryPointHandler Wraps tracing to alice.Constructor. // EntryPointHandler Wraps tracing to alice.Constructor.
func WrapEntryPointHandler(ctx context.Context, tracer *tracing.Tracer, semConvMetricRegistry *metrics.SemConvMetricsRegistry, entryPointName string) alice.Constructor { func EntryPointHandler(ctx context.Context, tracer *tracing.Tracer, entryPointName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) { return func(next http.Handler) (http.Handler, error) {
if tracer == nil { return newEntryPoint(ctx, tracer, entryPointName, next), nil
tracer = tracing.NewTracer(noop.Tracer{}, nil, nil, nil)
}
return newEntryPoint(ctx, tracer, semConvMetricRegistry, entryPointName, next), nil
} }
} }
// newEntryPoint creates a new tracing middleware for incoming requests. // newEntryPoint creates a new tracing middleware for incoming requests.
func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, semConvMetricRegistry *metrics.SemConvMetricsRegistry, entryPointName string, next http.Handler) http.Handler { func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, entryPointName string, next http.Handler) http.Handler {
middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware") middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware")
if tracer == nil { if tracer == nil {
@ -55,7 +44,6 @@ func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, semConvMetricReg
return &entryPointTracing{ return &entryPointTracing{
entryPoint: entryPointName, entryPoint: entryPointName,
tracer: tracer, tracer: tracer,
semConvMetricRegistry: semConvMetricRegistry,
next: next, next: next,
} }
} }
@ -88,23 +76,4 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request)
end := time.Now() end := time.Now()
span.End(trace.WithTimestamp(end)) span.End(trace.WithTimestamp(end))
if e.semConvMetricRegistry != nil && e.semConvMetricRegistry.HTTPServerRequestDuration() != nil {
var attrs []attribute.KeyValue
if recorder.Status() < 100 || recorder.Status() >= 600 {
attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code ; %d", recorder.Status())))
} else if recorder.Status() >= 400 {
attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(recorder.Status())))
}
attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method))
attrs = append(attrs, semconv.HTTPResponseStatusCode(recorder.Status()))
attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto)))
attrs = append(attrs, semconv.NetworkProtocolVersion(Proto(req.Proto)))
attrs = append(attrs, semconv.ServerAddress(req.Host))
attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
e.semConvMetricRegistry.HTTPServerRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
}
} }

View File

@ -5,19 +5,11 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"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/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
) )
func TestEntryPointMiddleware_tracing(t *testing.T) { func TestEntryPointMiddleware_tracing(t *testing.T) {
@ -77,7 +69,7 @@ func TestEntryPointMiddleware_tracing(t *testing.T) {
tracer := &mockTracer{} tracer := &mockTracer{}
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), nil, test.entryPoint, next) handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next)
handler.ServeHTTP(rw, req) handler.ServeHTTP(rw, req)
for _, span := range tracer.spans { for _, span := range tracer.spans {
@ -88,101 +80,6 @@ func TestEntryPointMiddleware_tracing(t *testing.T) {
} }
} }
func TestEntryPointMiddleware_metrics(t *testing.T) {
tests := []struct {
desc string
statusCode int
wantAttributes attribute.Set
}{
{
desc: "not found status",
statusCode: http.StatusNotFound,
wantAttributes: attribute.NewSet(
attribute.Key("error.type").String("404"),
attribute.Key("http.request.method").String("GET"),
attribute.Key("http.response.status_code").Int(404),
attribute.Key("network.protocol.name").String("http/1.1"),
attribute.Key("network.protocol.version").String("1.1"),
attribute.Key("server.address").String("www.test.com"),
attribute.Key("url.scheme").String("http"),
),
},
{
desc: "created status",
statusCode: http.StatusCreated,
wantAttributes: attribute.NewSet(
attribute.Key("http.request.method").String("GET"),
attribute.Key("http.response.status_code").Int(201),
attribute.Key("network.protocol.name").String("http/1.1"),
attribute.Key("network.protocol.version").String("1.1"),
attribute.Key("server.address").String("www.test.com"),
attribute.Key("url.scheme").String("http"),
),
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var cfg types.OTLP
(&cfg).SetDefaults()
cfg.AddRoutersLabels = true
cfg.PushInterval = ptypes.Duration(10 * time.Millisecond)
rdr := sdkmetric.NewManualReader()
meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(rdr))
// force the meter provider with manual reader to collect metrics for the test.
metrics.SetMeterProvider(meterProvider)
semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg)
require.NoError(t, err)
require.NotNil(t, semConvMetricRegistry)
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
rw := httptest.NewRecorder()
req.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "entrypoint-test")
req.Header.Set("X-Forwarded-Proto", "http")
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(test.statusCode)
})
handler := newEntryPoint(context.Background(), nil, semConvMetricRegistry, "test", next)
handler.ServeHTTP(rw, req)
got := metricdata.ResourceMetrics{}
err = rdr.Collect(context.Background(), &got)
require.NoError(t, err)
require.Len(t, got.ScopeMetrics, 1)
expected := metricdata.Metrics{
Name: "http.server.request.duration",
Description: "Duration of HTTP server requests.",
Unit: "s",
Data: metricdata.Histogram[float64]{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: test.wantAttributes,
Count: 1,
Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10},
BucketCounts: []uint64{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Min: metricdata.NewExtrema[float64](1),
Max: metricdata.NewExtrema[float64](1),
Sum: 1,
},
},
Temporality: metricdata.CumulativeTemporality,
},
}
metricdatatest.AssertEqual[metricdata.Metrics](t, expected, got.ScopeMetrics[0].Metrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
})
}
}
func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) { func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/", http.NoBody) req := httptest.NewRequest(http.MethodGet, "http://www.test.com/", http.NoBody)
req = req.WithContext( req = req.WithContext(
@ -197,7 +94,7 @@ func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
tracer := &mockTracer{} tracer := &mockTracer{}
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{}, []string{}, []string{}), nil, "test", next) handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{}, []string{}, []string{}), "test", next)
handler.ServeHTTP(httptest.NewRecorder(), req) handler.ServeHTTP(httptest.NewRecorder(), req)
expectedSpanCtx := tracer.spans[0].SpanContext() expectedSpanCtx := tracer.spans[0].SpanContext()

View File

@ -8,6 +8,11 @@ import (
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
type contextKey int
// DisableMetricsKey is a context key used to disable the metrics.
const DisableMetricsKey contextKey = iota
// SetStatusErrorf flags the span as in error and log an event. // SetStatusErrorf flags the span as in error and log an event.
func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) { func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) {
if span := trace.SpanFromContext(ctx); span != nil { if span := trace.SpanFromContext(ctx); span != nil {

View File

@ -0,0 +1,81 @@
package observability
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/containous/alice"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
const (
semConvServerMetricsTypeName = "SemConvServerMetrics"
)
type semConvServerMetrics struct {
next http.Handler
semConvMetricRegistry *metrics.SemConvMetricsRegistry
}
// SemConvServerMetricsHandler return the alice.Constructor for semantic conventions servers metrics.
func SemConvServerMetricsHandler(ctx context.Context, semConvMetricRegistry *metrics.SemConvMetricsRegistry) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
return newServerMetricsSemConv(ctx, semConvMetricRegistry, next), nil
}
}
// newServerMetricsSemConv creates a new semConv server metrics middleware for incoming requests.
func newServerMetricsSemConv(ctx context.Context, semConvMetricRegistry *metrics.SemConvMetricsRegistry, next http.Handler) http.Handler {
middlewares.GetLogger(ctx, "tracing", semConvServerMetricsTypeName).Debug().Msg("Creating middleware")
return &semConvServerMetrics{
semConvMetricRegistry: semConvMetricRegistry,
next: next,
}
}
func (e *semConvServerMetrics) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if e.semConvMetricRegistry == nil || e.semConvMetricRegistry.HTTPServerRequestDuration() == nil {
e.next.ServeHTTP(rw, req)
return
}
start := time.Now()
e.next.ServeHTTP(rw, req)
end := time.Now()
ctx := req.Context()
capt, err := capture.FromContext(ctx)
if err != nil {
log.Ctx(ctx).Error().Err(err).Str(logs.MiddlewareType, semConvServerMetricsTypeName).Msg("Could not get Capture")
return
}
var attrs []attribute.KeyValue
if capt.StatusCode() < 100 || capt.StatusCode() >= 600 {
attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code ; %d", capt.StatusCode())))
} else if capt.StatusCode() >= 400 {
attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(capt.StatusCode())))
}
attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method))
attrs = append(attrs, semconv.HTTPResponseStatusCode(capt.StatusCode()))
attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto)))
attrs = append(attrs, semconv.NetworkProtocolVersion(Proto(req.Proto)))
attrs = append(attrs, semconv.ServerAddress(req.Host))
attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
e.semConvMetricRegistry.HTTPServerRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
}

View File

@ -0,0 +1,118 @@
package observability
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"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/capture"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)
func TestSemConvServerMetrics(t *testing.T) {
tests := []struct {
desc string
statusCode int
wantAttributes attribute.Set
}{
{
desc: "not found status",
statusCode: http.StatusNotFound,
wantAttributes: attribute.NewSet(
attribute.Key("error.type").String("404"),
attribute.Key("http.request.method").String("GET"),
attribute.Key("http.response.status_code").Int(404),
attribute.Key("network.protocol.name").String("http/1.1"),
attribute.Key("network.protocol.version").String("1.1"),
attribute.Key("server.address").String("www.test.com"),
attribute.Key("url.scheme").String("http"),
),
},
{
desc: "created status",
statusCode: http.StatusCreated,
wantAttributes: attribute.NewSet(
attribute.Key("http.request.method").String("GET"),
attribute.Key("http.response.status_code").Int(201),
attribute.Key("network.protocol.name").String("http/1.1"),
attribute.Key("network.protocol.version").String("1.1"),
attribute.Key("server.address").String("www.test.com"),
attribute.Key("url.scheme").String("http"),
),
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var cfg types.OTLP
(&cfg).SetDefaults()
cfg.AddRoutersLabels = true
cfg.PushInterval = ptypes.Duration(10 * time.Millisecond)
rdr := sdkmetric.NewManualReader()
meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(rdr))
// force the meter provider with manual reader to collect metrics for the test.
metrics.SetMeterProvider(meterProvider)
semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg)
require.NoError(t, err)
require.NotNil(t, semConvMetricRegistry)
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
rw := httptest.NewRecorder()
req.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "entrypoint-test")
req.Header.Set("X-Forwarded-Proto", "http")
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(test.statusCode)
})
handler := newServerMetricsSemConv(context.Background(), semConvMetricRegistry, next)
handler, err = capture.Wrap(handler)
require.NoError(t, err)
handler.ServeHTTP(rw, req)
got := metricdata.ResourceMetrics{}
err = rdr.Collect(context.Background(), &got)
require.NoError(t, err)
require.Len(t, got.ScopeMetrics, 1)
expected := metricdata.Metrics{
Name: "http.server.request.duration",
Description: "Duration of HTTP server requests.",
Unit: "s",
Data: metricdata.Histogram[float64]{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: test.wantAttributes,
Count: 1,
Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10},
BucketCounts: []uint64{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Min: metricdata.NewExtrema[float64](1),
Max: metricdata.NewExtrema[float64](1),
Sum: 1,
},
},
Temporality: metricdata.CumulativeTemporality,
},
}
metricdatatest.AssertEqual[metricdata.Metrics](t, expected, got.ScopeMetrics[0].Metrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
})
}
}

View File

@ -12,6 +12,10 @@ spec:
- match: Host(`foo.com`) && PathPrefix(`/bar`) - match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule kind: Rule
priority: 12 priority: 12
observability:
accessLogs: true
tracing: true
metrics: true
services: services:
- name: whoami - name: whoami
port: 80 port: 80

View File

@ -120,6 +120,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
EntryPoints: ingressRoute.Spec.EntryPoints, EntryPoints: ingressRoute.Spec.EntryPoints,
Rule: route.Match, Rule: route.Match,
Service: serviceName, Service: serviceName,
Observability: route.Observability,
} }
if ingressRoute.Spec.TLS != nil { if ingressRoute.Spec.TLS != nil {

View File

@ -1688,6 +1688,11 @@ func TestLoadIngressRoutes(t *testing.T) {
Service: "default-test-route-6b204d94623b3df4370c", Service: "default-test-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12, Priority: 12,
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
}, },
}, },
Middlewares: map[string]*dynamic.Middleware{}, Middlewares: map[string]*dynamic.Middleware{},

View File

@ -43,6 +43,9 @@ type Route struct {
// Middlewares defines the list of references to Middleware resources. // Middlewares defines the list of references to Middleware resources.
// More info: https://doc.traefik.io/traefik/v3.2/routing/providers/kubernetes-crd/#kind-middleware // More info: https://doc.traefik.io/traefik/v3.2/routing/providers/kubernetes-crd/#kind-middleware
Middlewares []MiddlewareRef `json:"middlewares,omitempty"` Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
// Observability defines the observability configuration for a router.
// More info: https://doc.traefik.io/traefik/v3.2/routing/routers/#observability
Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty"`
} }
// TLS holds the TLS configuration. // TLS holds the TLS configuration.

View File

@ -1102,6 +1102,11 @@ func (in *Route) DeepCopyInto(out *Route) {
*out = make([]MiddlewareRef, len(*in)) *out = make([]MiddlewareRef, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.Observability != nil {
in, out := &in.Observability, &out.Observability
*out = new(dynamic.RouterObservabilityConfig)
(*in).DeepCopyInto(*out)
}
return return
} }

View File

@ -28,6 +28,7 @@ type RouterIng struct {
Priority int `json:"priority,omitempty"` Priority int `json:"priority,omitempty"`
RuleSyntax string `json:"ruleSyntax,omitempty"` RuleSyntax string `json:"ruleSyntax,omitempty"`
TLS *dynamic.RouterTLSConfig `json:"tls,omitempty" label:"allowEmpty"` TLS *dynamic.RouterTLSConfig `json:"tls,omitempty" label:"allowEmpty"`
Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty" label:"allowEmpty"`
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.

View File

@ -32,6 +32,9 @@ func Test_parseRouterConfig(t *testing.T) {
"traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar", "traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar",
"traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar", "traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar",
"traefik.ingress.kubernetes.io/router.tls.options": "foobar", "traefik.ingress.kubernetes.io/router.tls.options": "foobar",
"traefik.ingress.kubernetes.io/router.observability.accessLogs": "true",
"traefik.ingress.kubernetes.io/router.observability.metrics": "true",
"traefik.ingress.kubernetes.io/router.observability.tracing": "true",
}, },
expected: &RouterConfig{ expected: &RouterConfig{
Router: &RouterIng{ Router: &RouterIng{
@ -54,6 +57,11 @@ func Test_parseRouterConfig(t *testing.T) {
}, },
Options: "foobar", Options: "foobar",
}, },
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
}, },
}, },
}, },
@ -196,6 +204,9 @@ func Test_convertAnnotations(t *testing.T) {
"traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar", "traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar",
"traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar", "traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar",
"traefik.ingress.kubernetes.io/router.tls.options": "foobar", "traefik.ingress.kubernetes.io/router.tls.options": "foobar",
"traefik.ingress.kubernetes.io/router.observability.accessLogs": "true",
"traefik.ingress.kubernetes.io/router.observability.metrics": "true",
"traefik.ingress.kubernetes.io/router.observability.tracing": "true",
}, },
expected: map[string]string{ expected: map[string]string{
"traefik.foo": "bar", "traefik.foo": "bar",
@ -211,6 +222,9 @@ func Test_convertAnnotations(t *testing.T) {
"traefik.router.tls.domains[1].main": "foobar", "traefik.router.tls.domains[1].main": "foobar",
"traefik.router.tls.domains[1].sans": "foobar,foobar", "traefik.router.tls.domains[1].sans": "foobar,foobar",
"traefik.router.tls.options": "foobar", "traefik.router.tls.options": "foobar",
"traefik.router.observability.accessLogs": "true",
"traefik.router.observability.metrics": "true",
"traefik.router.observability.tracing": "true",
}, },
}, },
{ {

View File

@ -18,6 +18,9 @@ metadata:
traefik.ingress.kubernetes.io/router.tls.domains.1.main: example.com traefik.ingress.kubernetes.io/router.tls.domains.1.main: example.com
traefik.ingress.kubernetes.io/router.tls.domains.1.sans: one.example.com,two.example.com traefik.ingress.kubernetes.io/router.tls.domains.1.sans: one.example.com,two.example.com
traefik.ingress.kubernetes.io/router.tls.options: foobar traefik.ingress.kubernetes.io/router.tls.options: foobar
traefik.ingress.kubernetes.io/router.observability.accesslogs: "true"
traefik.ingress.kubernetes.io/router.observability.metrics: "true"
traefik.ingress.kubernetes.io/router.observability.tracing: "true"
spec: spec:
rules: rules:

View File

@ -293,6 +293,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
rt.EntryPoints = rtConfig.Router.EntryPoints rt.EntryPoints = rtConfig.Router.EntryPoints
rt.Middlewares = rtConfig.Router.Middlewares rt.Middlewares = rtConfig.Router.Middlewares
rt.TLS = rtConfig.Router.TLS rt.TLS = rtConfig.Router.TLS
rt.Observability = rtConfig.Router.Observability
} }
p.applyRouterTransform(ctxIngress, rt, ingress) p.applyRouterTransform(ctxIngress, rt, ingress)
@ -619,10 +620,8 @@ func (p *Provider) loadRouter(rule netv1.IngressRule, pa netv1.HTTPIngressPath,
rt.Priority = rtConfig.Router.Priority rt.Priority = rtConfig.Router.Priority
rt.EntryPoints = rtConfig.Router.EntryPoints rt.EntryPoints = rtConfig.Router.EntryPoints
rt.Middlewares = rtConfig.Router.Middlewares rt.Middlewares = rtConfig.Router.Middlewares
if rtConfig.Router.TLS != nil {
rt.TLS = rtConfig.Router.TLS rt.TLS = rtConfig.Router.TLS
} rt.Observability = rtConfig.Router.Observability
} }
var rules []string var rules []string

View File

@ -115,6 +115,11 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
Options: "foobar", Options: "foobar",
}, },
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
}, },
}, },
Services: map[string]*dynamic.Service{ Services: map[string]*dynamic.Service{

View File

@ -27,6 +27,11 @@
] ]
} }
] ]
},
"observability": {
"accessLogs": false,
"tracing": false,
"metrics": false
} }
} }
} }

View File

@ -231,6 +231,14 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
Middlewares: ep.HTTP.Middlewares, Middlewares: ep.HTTP.Middlewares,
} }
if ep.Observability != nil {
m.Observability = dynamic.RouterObservabilityConfig{
AccessLogs: &ep.Observability.AccessLogs,
Tracing: &ep.Observability.Tracing,
Metrics: &ep.Observability.Metrics,
}
}
if ep.HTTP.TLS != nil { if ep.HTTP.TLS != nil {
m.TLS = &dynamic.RouterTLSConfig{ m.TLS = &dynamic.RouterTLSConfig{
Options: ep.HTTP.TLS.Options, Options: ep.HTTP.TLS.Options,

View File

@ -184,6 +184,11 @@ func Test_createConfiguration(t *testing.T) {
}, },
}, },
}, },
Observability: &static.ObservabilityConfig{
AccessLogs: false,
Tracing: false,
Metrics: false,
},
}, },
}, },
}, },

View File

@ -68,7 +68,7 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
span.End(trace.WithTimestamp(end)) span.End(trace.WithTimestamp(end))
} }
if t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil { if req.Context().Value(observability.DisableMetricsKey) == nil && t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil {
var attrs []attribute.KeyValue var attrs []attribute.KeyValue
if statusCode < 100 || statusCode >= 600 { if statusCode < 100 || statusCode >= 600 {

View File

@ -57,6 +57,11 @@ func init() {
}, },
}, },
}, },
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
}, },
}, },
Services: map[string]*dynamic.Service{ Services: map[string]*dynamic.Service{

View File

@ -22,6 +22,11 @@
] ]
} }
] ]
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
} }
} }
}, },
@ -327,7 +332,8 @@
] ]
} }
] ]
} },
"observability": {}
} }
}, },
"serversTransports": { "serversTransports": {

View File

@ -22,6 +22,11 @@
] ]
} }
] ]
},
"observability": {
"accessLogs": true,
"tracing": true,
"metrics": true
} }
} }
}, },
@ -330,7 +335,8 @@
] ]
} }
] ]
} },
"observability": {}
} }
}, },
"serversTransports": { "serversTransports": {

View File

@ -178,6 +178,22 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
cp.Middlewares = append(m.Middlewares, cp.Middlewares...) cp.Middlewares = append(m.Middlewares, cp.Middlewares...)
if cp.Observability == nil {
cp.Observability = &dynamic.RouterObservabilityConfig{}
}
if cp.Observability.AccessLogs == nil {
cp.Observability.AccessLogs = m.Observability.AccessLogs
}
if cp.Observability.Tracing == nil {
cp.Observability.Tracing = m.Observability.Tracing
}
if cp.Observability.Metrics == nil {
cp.Observability.Metrics = m.Observability.Metrics
}
rtName := name rtName := name
if len(eps) > 1 { if len(eps) > 1 {
rtName = epName + "-" + name rtName = epName + "-" + name

View File

@ -9,6 +9,8 @@ import (
"github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/tls"
) )
func pointer[T any](v T) *T { return &v }
func Test_mergeConfiguration(t *testing.T) { func Test_mergeConfiguration(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -561,6 +563,7 @@ func Test_applyModel(t *testing.T) {
EntryPoints: []string{"websecure"}, EntryPoints: []string{"websecure"},
Middlewares: []string{"test"}, Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{},
}, },
}, },
Middlewares: make(map[string]*dynamic.Middleware), Middlewares: make(map[string]*dynamic.Middleware),
@ -574,6 +577,60 @@ func Test_applyModel(t *testing.T) {
}, },
}, },
}, },
{
desc: "with model, one entry point with observability",
input: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
},
},
},
},
expected: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"test": {
EntryPoints: []string{"websecure"},
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
},
},
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: map[string]*dynamic.Model{
"websecure@internal": {
Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{},
Observability: dynamic.RouterObservabilityConfig{
AccessLogs: pointer(true),
Tracing: pointer(true),
Metrics: pointer(true),
},
},
},
},
},
},
{ {
desc: "with model, one entry point, and router with tls", desc: "with model, one entry point, and router with tls",
input: dynamic.Configuration{ input: dynamic.Configuration{
@ -601,6 +658,11 @@ func Test_applyModel(t *testing.T) {
EntryPoints: []string{"websecure"}, EntryPoints: []string{"websecure"},
Middlewares: []string{"test"}, Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"}, TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
Observability: &dynamic.RouterObservabilityConfig{
AccessLogs: nil,
Tracing: nil,
Metrics: nil,
},
}, },
}, },
Middlewares: make(map[string]*dynamic.Middleware), Middlewares: make(map[string]*dynamic.Middleware),
@ -643,6 +705,7 @@ func Test_applyModel(t *testing.T) {
EntryPoints: []string{"websecure"}, EntryPoints: []string{"websecure"},
Middlewares: []string{"test"}, Middlewares: []string{"test"},
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
Observability: &dynamic.RouterObservabilityConfig{},
}, },
}, },
Middlewares: make(map[string]*dynamic.Middleware), Middlewares: make(map[string]*dynamic.Middleware),

View File

@ -8,12 +8,13 @@ import (
"github.com/containous/alice" "github.com/containous/alice"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/config/static"
"github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/middlewares/capture" "github.com/traefik/traefik/v3/pkg/middlewares/capture"
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/tracing"
) )
@ -41,7 +42,7 @@ func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Re
} }
// BuildEPChain an observability middleware chain by entry point. // BuildEPChain an observability middleware chain by entry point.
func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string) alice.Chain { func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string, observabilityConfig *dynamic.RouterObservabilityConfig) alice.Chain {
chain := alice.New() chain := alice.New()
if o == nil { if o == nil {
@ -49,62 +50,101 @@ func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName stri
} }
if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) { if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) {
if o.ShouldAddAccessLogs(resourceName) || o.ShouldAddMetrics(resourceName) { if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) {
chain = chain.Append(capture.Wrap) chain = chain.Append(capture.Wrap)
} }
} }
// As the Entry point observability middleware ensures that the tracing is added to the request and logger context, // As the Entry point observability middleware ensures that the tracing is added to the request and logger context,
// it needs to be added before the access log middleware to ensure that the trace ID is logged. // it needs to be added before the access log middleware to ensure that the trace ID is logged.
if (o.tracer != nil && o.ShouldAddTracing(resourceName)) || (o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName)) { if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
chain = chain.Append(observability.WrapEntryPointHandler(ctx, o.tracer, o.semConvMetricRegistry, entryPointName)) chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
} }
if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName) { if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) {
chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware)) chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware))
chain = chain.Append(func(next http.Handler) (http.Handler, error) { chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
}) })
} }
if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName) { // Semantic convention server metrics handler.
metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName) if o.semConvMetricRegistry != nil && o.ShouldAddMetrics(resourceName, observabilityConfig) {
chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
}
if o.tracer != nil && o.ShouldAddTracing(resourceName) { 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)) chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
} else { } else {
chain = chain.Append(metricsHandler) chain = chain.Append(metricsHandler)
} }
} }
// Inject context keys to control whether to produce metrics further downstream (services, round-tripper),
// because the router configuration cannot be evaluated during build time for services.
if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics {
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true)))
}), nil
})
}
return chain return chain
} }
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource. // ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config.
func (o *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool { func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
if o == nil { if o == nil {
return false return false
} }
return o.config.AccessLog != nil && (o.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal")) if o.config.AccessLog == nil {
return false
} }
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource. if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals {
func (o *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool { 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 { if o == nil {
return false return false
} }
return o.config.Metrics != nil && (o.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal")) if o.config.Metrics == nil {
return false
} }
// ShouldAddTracing returns whether the tracing should be enabled for the given resource. if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals {
func (o *ObservabilityMgr) ShouldAddTracing(resourceName string) bool { 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 { if o == nil {
return false return false
} }
return o.config.Tracing != nil && (o.config.Tracing.AddInternals || !strings.HasSuffix(resourceName, "@internal")) if o.config.Tracing == nil {
return false
}
if strings.HasSuffix(serviceName, "@internal") && !o.config.Tracing.AddInternals {
return false
}
return observabilityConfig == nil || observabilityConfig.Tracing != nil && *observabilityConfig.Tracing
} }
// MetricsRegistry is an accessor to the metrics registry. // MetricsRegistry is an accessor to the metrics registry.

View File

@ -91,12 +91,12 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
continue continue
} }
handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter()) defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter())
if err != nil { if err != nil {
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
continue continue
} }
entryPointHandlers[entryPointName] = handler entryPointHandlers[entryPointName] = defaultHandler
} }
return entryPointHandlers return entryPointHandlers
@ -108,7 +108,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
return nil, err return nil, err
} }
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "defaultHandler").Then(http.NotFoundHandler()) defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -137,7 +137,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
continue continue
} }
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service) observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability)
handler, err = observabilityChain.Then(handler) handler, err = observabilityChain.Then(handler)
if err != nil { if err != nil {
routerConfig.AddError(err, true) routerConfig.AddError(err, true)
@ -182,7 +182,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
} }
// Prevents from enabling observability for internal resources. // Prevents from enabling observability for internal resources.
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service)) { if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service), routerConfig.Observability) {
m.routerHandlers[routerName] = handler m.routerHandlers[routerName] = handler
return m.routerHandlers[routerName], nil return m.routerHandlers[routerName], nil
} }
@ -221,12 +221,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
chain := alice.New() chain := alice.New()
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() && if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service)) { 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))) chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
} }
// Prevents from enabling tracing for internal resources. // Prevents from enabling tracing for internal resources.
if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service)) { if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
return chain.Extend(*mHandler).Then(sHandler) return chain.Extend(*mHandler).Then(sHandler)
} }

View File

@ -356,7 +356,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName) qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName)
shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName) 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, shouldObserve, passHostHeader, server.PreservePath, flushInterval)
if err != nil { if err != nil {
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err) return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
@ -364,14 +364,14 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
// Prevents from enabling observability for internal resources. // Prevents from enabling observability for internal resources.
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName) { if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) {
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields) proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
} }
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() && if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() &&
m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName) { m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName) metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
proxy, err = alice.New(). proxy, err = alice.New().
@ -382,11 +382,11 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
} }
} }
if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName) { if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) {
proxy = observability.NewService(ctx, serviceName, proxy) proxy = observability.NewService(ctx, serviceName, proxy)
} }
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName) { 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, // 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. // to re-target the request to it.
// Those pieces of middleware can be configured on routes that expose a Traefik internal service. // Those pieces of middleware can be configured on routes that expose a Traefik internal service.