mirror of
https://github.com/containous/traefik.git
synced 2024-12-22 13:34:03 +03:00
Manage observability at entrypoint and router level
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
9588e51146
commit
b1934231ca
@ -68,6 +68,7 @@ metrics:
|
||||
```bash tab="CLI"
|
||||
--metrics.datadog.addEntryPointsLabels=true
|
||||
```
|
||||
|
||||
#### `addRoutersLabels`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
@ -5,16 +5,80 @@ description: "Traefik provides Logs, Access Logs, Metrics and Tracing. Read the
|
||||
|
||||
# Overview
|
||||
|
||||
Traefik's Observability system
|
||||
{: .subtitle }
|
||||
Traefik’s 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.
|
||||
|
||||
## 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).
|
||||
|
||||
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.
|
||||
|
||||
@ -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.
|
||||
|
||||
## Metrics
|
||||
### Metrics
|
||||
|
||||
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.
|
||||
@ -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.
|
||||
|
||||
## Tracing
|
||||
### Tracing
|
||||
|
||||
The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure.
|
||||
|
||||
|
@ -147,6 +147,9 @@
|
||||
- "traefik.http.middlewares.middleware25.stripprefixregex.regex=foobar, foobar"
|
||||
- "traefik.http.routers.router0.entrypoints=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.rule=foobar"
|
||||
- "traefik.http.routers.router0.rulesyntax=foobar"
|
||||
@ -160,6 +163,9 @@
|
||||
- "traefik.http.routers.router0.tls.options=foobar"
|
||||
- "traefik.http.routers.router1.entrypoints=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.rule=foobar"
|
||||
- "traefik.http.routers.router1.rulesyntax=foobar"
|
||||
|
@ -20,6 +20,10 @@
|
||||
[[http.routers.Router0.tls.domains]]
|
||||
main = "foobar"
|
||||
sans = ["foobar", "foobar"]
|
||||
[http.routers.Router0.observability]
|
||||
accessLogs = true
|
||||
tracing = true
|
||||
metrics = true
|
||||
[http.routers.Router1]
|
||||
entryPoints = ["foobar", "foobar"]
|
||||
middlewares = ["foobar", "foobar"]
|
||||
@ -38,6 +42,10 @@
|
||||
[[http.routers.Router1.tls.domains]]
|
||||
main = "foobar"
|
||||
sans = ["foobar", "foobar"]
|
||||
[http.routers.Router1.observability]
|
||||
accessLogs = true
|
||||
tracing = true
|
||||
metrics = true
|
||||
[http.services]
|
||||
[http.services.Service01]
|
||||
[http.services.Service01.failover]
|
||||
|
@ -25,6 +25,10 @@ http:
|
||||
sans:
|
||||
- foobar
|
||||
- foobar
|
||||
observability:
|
||||
accessLogs: true
|
||||
tracing: true
|
||||
metrics: true
|
||||
Router1:
|
||||
entryPoints:
|
||||
- foobar
|
||||
@ -48,6 +52,10 @@ http:
|
||||
sans:
|
||||
- foobar
|
||||
- foobar
|
||||
observability:
|
||||
accessLogs: true
|
||||
tracing: true
|
||||
metrics: true
|
||||
services:
|
||||
Service01:
|
||||
failover:
|
||||
|
@ -86,6 +86,18 @@ spec:
|
||||
- name
|
||||
type: object
|
||||
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:
|
||||
description: |-
|
||||
Priority defines the router's priority.
|
||||
|
@ -173,6 +173,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||
| `traefik/http/routers/Router0/entryPoints/1` | `foobar` |
|
||||
| `traefik/http/routers/Router0/middlewares/0` | `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/rule` | `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/middlewares/0` | `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/rule` | `foobar` |
|
||||
| `traefik/http/routers/Router1/ruleSyntax` | `foobar` |
|
||||
|
@ -86,6 +86,18 @@ spec:
|
||||
- name
|
||||
type: object
|
||||
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:
|
||||
description: |-
|
||||
Priority defines the router's priority.
|
||||
|
@ -264,6 +264,15 @@ HTTP/3 configuration. (Default: ```false```)
|
||||
`--entrypoints.<name>.http3.advertisedport`:
|
||||
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`:
|
||||
Proxy-Protocol configuration. (Default: ```false```)
|
||||
|
||||
|
@ -264,6 +264,15 @@ Subject alternative names.
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_OPTIONS`:
|
||||
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`:
|
||||
Proxy-Protocol configuration. (Default: ```false```)
|
||||
|
||||
|
@ -77,6 +77,10 @@
|
||||
advertisedPort = 42
|
||||
[entryPoints.EntryPoint0.udp]
|
||||
timeout = "42s"
|
||||
[entryPoints.EntryPoint0.observability]
|
||||
accessLogs = true
|
||||
tracing = true
|
||||
metrics = true
|
||||
|
||||
[providers]
|
||||
providersThrottleDuration = "42s"
|
||||
|
@ -91,6 +91,10 @@ entryPoints:
|
||||
advertisedPort: 42
|
||||
udp:
|
||||
timeout: 42s
|
||||
observability:
|
||||
accessLogs: true
|
||||
tracing: true
|
||||
metrics: true
|
||||
providers:
|
||||
providersThrottleDuration: 42s
|
||||
docker:
|
||||
|
@ -1237,8 +1237,6 @@ entryPoints:
|
||||
--entryPoints.foo.udp.timeout=10s
|
||||
```
|
||||
|
||||
{!traefik-for-business-applications.md!}
|
||||
|
||||
## Systemd Socket Activation
|
||||
|
||||
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"
|
||||
|
||||
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!}
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
??? 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`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
@ -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"
|
||||
```
|
||||
|
||||
??? 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`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
??? 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`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
@ -332,17 +332,21 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
||||
middlewares: # [5]
|
||||
- name: middleware1 # [6]
|
||||
namespace: default # [7]
|
||||
services: # [8]
|
||||
observability: # [8]
|
||||
accesslogs: true # [9]
|
||||
metrics: true # [10]
|
||||
tracing: true # [11]
|
||||
services: # [12]
|
||||
- kind: Service
|
||||
name: foo
|
||||
namespace: default
|
||||
passHostHeader: true
|
||||
port: 80 # [9]
|
||||
port: 80 # [13]
|
||||
responseForwarding:
|
||||
flushInterval: 1ms
|
||||
scheme: https
|
||||
serversTransport: transport # [10]
|
||||
healthCheck: # [11]
|
||||
serversTransport: transport # [14]
|
||||
healthCheck: # [15]
|
||||
path: /health
|
||||
interval: 15s
|
||||
sticky:
|
||||
@ -355,17 +359,17 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
||||
path: /foo
|
||||
strategy: RoundRobin
|
||||
weight: 10
|
||||
nativeLB: true # [12]
|
||||
nodePortLB: true # [13]
|
||||
tls: # [14]
|
||||
secretName: supersecret # [15]
|
||||
options: # [16]
|
||||
name: opt # [17]
|
||||
namespace: default # [18]
|
||||
certResolver: foo # [19]
|
||||
domains: # [20]
|
||||
- main: example.net # [21]
|
||||
sans: # [22]
|
||||
nativeLB: true # [16]
|
||||
nodePortLB: true # [17]
|
||||
tls: # [18]
|
||||
secretName: supersecret # [19]
|
||||
options: # [20]
|
||||
name: opt # [21]
|
||||
namespace: default # [22]
|
||||
certResolver: foo # [23]
|
||||
domains: # [24]
|
||||
- main: example.net # [25]
|
||||
sans: # [26]
|
||||
- a.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) |
|
||||
| [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. |
|
||||
| [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) |
|
||||
| [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. |
|
||||
| [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)). |
|
||||
| [11] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. |
|
||||
| [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. |
|
||||
| [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. |
|
||||
| [14] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
|
||||
| [15] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
|
||||
| [16] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [17] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [18] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
|
||||
| [19] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
|
||||
| [20] | `tls.domains` | List of [domains](../routers/index.md#domains) |
|
||||
| [21] | `domains[n].main` | Defines the main domain name |
|
||||
| [22] | `domains[n].sans` | List of SANs (alternative domains) |
|
||||
| [8] | `routes[n].observability` | Defines the route observability configuration. |
|
||||
| [9] | `observability.accesslogs` | Defines whether the route will produce [access-logs](../routers/index.md#accesslogs). |
|
||||
| [10] | `observability.metrics` | Defines whether the route will produce [metrics](../routers/index.md#metrics). |
|
||||
| [11] | `observability.tracing` | Defines whether the route will produce [traces](../routers/index.md#tracing). |
|
||||
| [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].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] | `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] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. |
|
||||
| [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] | `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] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
|
||||
| [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.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
|
||||
| [21] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
|
||||
| [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"
|
||||
|
||||
|
@ -288,6 +288,30 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||
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
|
||||
|
||||
??? info "`traefik.ingress.kubernetes.io/service.nativelb`"
|
||||
|
@ -95,6 +95,30 @@ A Story of key & values
|
||||
|---------------------------------------------|----------|
|
||||
| `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`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
??? 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`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
@ -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),
|
||||
but the router does not specify any service,
|
||||
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.
|
||||
|
||||
!!! info ""
|
||||
|
@ -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"
|
||||
```
|
||||
|
||||
??? 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`"
|
||||
|
||||
See [priority](../routers/index.md#priority) for more information.
|
||||
|
@ -877,6 +877,117 @@ The [supported `provider` table](../../https/acme.md#providers) indicates if the
|
||||
!!! warning "Double Wildcard Certificates"
|
||||
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
|
||||
|
||||
!!! warning "The character `@` is not authorized in the router name"
|
||||
|
@ -86,6 +86,18 @@ spec:
|
||||
- name
|
||||
type: object
|
||||
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:
|
||||
description: |-
|
||||
Priority defines the router's priority.
|
||||
|
@ -36,11 +36,12 @@ type HTTPConfiguration struct {
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Model is a set of default router's values.
|
||||
// Model holds model configuration.
|
||||
type Model struct {
|
||||
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"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" 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"`
|
||||
Observability RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
@ -57,14 +58,15 @@ type Service struct {
|
||||
|
||||
// Router holds the router configuration.
|
||||
type Router struct {
|
||||
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
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"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
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"`
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
@ -78,6 +80,15 @@ type RouterTLSConfig struct {
|
||||
|
||||
// +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.
|
||||
type Mirroring struct {
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
|
@ -1023,6 +1023,7 @@ func (in *Model) DeepCopyInto(out *Model) {
|
||||
*out = new(RouterTLSConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Observability.DeepCopyInto(&out.Observability)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1249,6 +1250,11 @@ func (in *Router) DeepCopyInto(out *Router) {
|
||||
*out = new(RouterTLSConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Observability != nil {
|
||||
in, out := &in.Observability, &out.Observability
|
||||
*out = new(RouterObservabilityConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -1262,6 +1268,37 @@ func (in *Router) DeepCopy() *Router {
|
||||
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.
|
||||
func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) {
|
||||
*out = *in
|
||||
|
@ -880,6 +880,11 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
"Router1": {
|
||||
EntryPoints: []string{
|
||||
@ -893,6 +898,11 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
@ -1405,17 +1415,23 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
|
||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",
|
||||
|
||||
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router0.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.Service": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.TLS": "true",
|
||||
"traefik.HTTP.Routers.Router1.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router1.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router1.Service": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router0.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.Service": "foobar",
|
||||
"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.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router1.Rule": "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.name1": "foobar",
|
||||
|
@ -23,6 +23,7 @@ type EntryPoint struct {
|
||||
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"`
|
||||
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
|
||||
@ -59,6 +60,8 @@ func (ep *EntryPoint) SetDefaults() {
|
||||
ep.HTTP.SetDefaults()
|
||||
ep.HTTP2 = &HTTP2Config{}
|
||||
ep.HTTP2.SetDefaults()
|
||||
ep.Observability = &ObservabilityConfig{}
|
||||
ep.Observability.SetDefaults()
|
||||
}
|
||||
|
||||
// HTTPConfig is the HTTP configuration of an entry point.
|
||||
@ -158,3 +161,17 @@ type UDPConfig struct {
|
||||
func (u *UDPConfig) SetDefaults() {
|
||||
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
|
||||
}
|
||||
|
@ -77,6 +77,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
||||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
},
|
||||
@ -122,6 +127,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
||||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
CertificatesResolvers: map[string]CertificateResolver{
|
||||
@ -178,6 +188,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
||||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
CertificatesResolvers: map[string]CertificateResolver{
|
||||
@ -238,6 +253,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
||||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
CertificatesResolvers: map[string]CertificateResolver{
|
||||
|
@ -119,6 +119,11 @@ func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanK
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
var labels []string
|
||||
|
@ -2,21 +2,15 @@ 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/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"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/noop"
|
||||
)
|
||||
@ -28,24 +22,19 @@ const (
|
||||
type entryPointTracing struct {
|
||||
tracer *tracing.Tracer
|
||||
|
||||
entryPoint string
|
||||
next http.Handler
|
||||
semConvMetricRegistry *metrics.SemConvMetricsRegistry
|
||||
entryPoint string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// WrapEntryPointHandler Wraps tracing to alice.Constructor.
|
||||
func WrapEntryPointHandler(ctx context.Context, tracer *tracing.Tracer, semConvMetricRegistry *metrics.SemConvMetricsRegistry, entryPointName string) alice.Constructor {
|
||||
// EntryPointHandler Wraps tracing to alice.Constructor.
|
||||
func EntryPointHandler(ctx context.Context, tracer *tracing.Tracer, entryPointName string) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
if tracer == nil {
|
||||
tracer = tracing.NewTracer(noop.Tracer{}, nil, nil, nil)
|
||||
}
|
||||
|
||||
return newEntryPoint(ctx, tracer, semConvMetricRegistry, entryPointName, next), nil
|
||||
return newEntryPoint(ctx, tracer, entryPointName, next), nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
if tracer == nil {
|
||||
@ -53,10 +42,9 @@ func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, semConvMetricReg
|
||||
}
|
||||
|
||||
return &entryPointTracing{
|
||||
entryPoint: entryPointName,
|
||||
tracer: tracer,
|
||||
semConvMetricRegistry: semConvMetricRegistry,
|
||||
next: next,
|
||||
entryPoint: entryPointName,
|
||||
tracer: tracer,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,23 +76,4 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
|
||||
end := time.Now()
|
||||
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...))
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,11 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/tracing"
|
||||
"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 TestEntryPointMiddleware_tracing(t *testing.T) {
|
||||
@ -77,7 +69,7 @@ func TestEntryPointMiddleware_tracing(t *testing.T) {
|
||||
|
||||
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)
|
||||
|
||||
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) {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/", http.NoBody)
|
||||
req = req.WithContext(
|
||||
@ -197,7 +94,7 @@ func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
|
||||
|
||||
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)
|
||||
|
||||
expectedSpanCtx := tracer.spans[0].SpanContext()
|
||||
|
@ -8,6 +8,11 @@ import (
|
||||
"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.
|
||||
func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) {
|
||||
if span := trace.SpanFromContext(ctx); span != nil {
|
||||
|
81
pkg/middlewares/observability/semconv.go
Normal file
81
pkg/middlewares/observability/semconv.go
Normal 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...))
|
||||
}
|
118
pkg/middlewares/observability/semconv_test.go
Normal file
118
pkg/middlewares/observability/semconv_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
@ -12,6 +12,10 @@ spec:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
observability:
|
||||
accessLogs: true
|
||||
tracing: true
|
||||
metrics: true
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
|
@ -114,12 +114,13 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
||||
}
|
||||
|
||||
r := &dynamic.Router{
|
||||
Middlewares: mds,
|
||||
Priority: route.Priority,
|
||||
RuleSyntax: route.Syntax,
|
||||
EntryPoints: ingressRoute.Spec.EntryPoints,
|
||||
Rule: route.Match,
|
||||
Service: serviceName,
|
||||
Middlewares: mds,
|
||||
Priority: route.Priority,
|
||||
RuleSyntax: route.Syntax,
|
||||
EntryPoints: ingressRoute.Spec.EntryPoints,
|
||||
Rule: route.Match,
|
||||
Service: serviceName,
|
||||
Observability: route.Observability,
|
||||
}
|
||||
|
||||
if ingressRoute.Spec.TLS != nil {
|
||||
|
@ -1688,6 +1688,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
@ -43,6 +43,9 @@ type Route struct {
|
||||
// Middlewares defines the list of references to Middleware resources.
|
||||
// More info: https://doc.traefik.io/traefik/v3.2/routing/providers/kubernetes-crd/#kind-middleware
|
||||
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.
|
||||
|
@ -1102,6 +1102,11 @@ func (in *Route) DeepCopyInto(out *Route) {
|
||||
*out = make([]MiddlewareRef, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Observability != nil {
|
||||
in, out := &in.Observability, &out.Observability
|
||||
*out = new(dynamic.RouterObservabilityConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,13 @@ type RouterConfig struct {
|
||||
|
||||
// RouterIng is the router's configuration from annotations.
|
||||
type RouterIng struct {
|
||||
PathMatcher string `json:"pathMatcher,omitempty"`
|
||||
EntryPoints []string `json:"entryPoints,omitempty"`
|
||||
Middlewares []string `json:"middlewares,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty"`
|
||||
TLS *dynamic.RouterTLSConfig `json:"tls,omitempty" label:"allowEmpty"`
|
||||
PathMatcher string `json:"pathMatcher,omitempty"`
|
||||
EntryPoints []string `json:"entryPoints,omitempty"`
|
||||
Middlewares []string `json:"middlewares,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty"`
|
||||
TLS *dynamic.RouterTLSConfig `json:"tls,omitempty" label:"allowEmpty"`
|
||||
Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty" label:"allowEmpty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
|
@ -18,20 +18,23 @@ func Test_parseRouterConfig(t *testing.T) {
|
||||
{
|
||||
desc: "router annotations",
|
||||
annotations: map[string]string{
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,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.options": "foobar",
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,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.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{
|
||||
Router: &RouterIng{
|
||||
@ -54,6 +57,11 @@ func Test_parseRouterConfig(t *testing.T) {
|
||||
},
|
||||
Options: "foobar",
|
||||
},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -182,35 +190,41 @@ func Test_convertAnnotations(t *testing.T) {
|
||||
{
|
||||
desc: "router annotations",
|
||||
annotations: map[string]string{
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,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.options": "foobar",
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,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.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{
|
||||
"traefik.foo": "bar",
|
||||
"traefik.router.pathmatcher": "foobar",
|
||||
"traefik.router.entrypoints": "foobar,foobar",
|
||||
"traefik.router.middlewares": "foobar,foobar",
|
||||
"traefik.router.priority": "42",
|
||||
"traefik.router.rulesyntax": "foobar",
|
||||
"traefik.router.tls": "true",
|
||||
"traefik.router.tls.certresolver": "foobar",
|
||||
"traefik.router.tls.domains[0].main": "foobar",
|
||||
"traefik.router.tls.domains[0].sans": "foobar,foobar",
|
||||
"traefik.router.tls.domains[1].main": "foobar",
|
||||
"traefik.router.tls.domains[1].sans": "foobar,foobar",
|
||||
"traefik.router.tls.options": "foobar",
|
||||
"traefik.foo": "bar",
|
||||
"traefik.router.pathmatcher": "foobar",
|
||||
"traefik.router.entrypoints": "foobar,foobar",
|
||||
"traefik.router.middlewares": "foobar,foobar",
|
||||
"traefik.router.priority": "42",
|
||||
"traefik.router.rulesyntax": "foobar",
|
||||
"traefik.router.tls": "true",
|
||||
"traefik.router.tls.certresolver": "foobar",
|
||||
"traefik.router.tls.domains[0].main": "foobar",
|
||||
"traefik.router.tls.domains[0].sans": "foobar,foobar",
|
||||
"traefik.router.tls.domains[1].main": "foobar",
|
||||
"traefik.router.tls.domains[1].sans": "foobar,foobar",
|
||||
"traefik.router.tls.options": "foobar",
|
||||
"traefik.router.observability.accessLogs": "true",
|
||||
"traefik.router.observability.metrics": "true",
|
||||
"traefik.router.observability.tracing": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -18,6 +18,9 @@ metadata:
|
||||
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.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:
|
||||
rules:
|
||||
|
@ -293,6 +293,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||
rt.EntryPoints = rtConfig.Router.EntryPoints
|
||||
rt.Middlewares = rtConfig.Router.Middlewares
|
||||
rt.TLS = rtConfig.Router.TLS
|
||||
rt.Observability = rtConfig.Router.Observability
|
||||
}
|
||||
|
||||
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.EntryPoints = rtConfig.Router.EntryPoints
|
||||
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
|
||||
|
@ -115,6 +115,11 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
},
|
||||
Options: "foobar",
|
||||
},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
|
@ -27,6 +27,11 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"observability": {
|
||||
"accessLogs": false,
|
||||
"tracing": false,
|
||||
"metrics": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +231,14 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
||||
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 {
|
||||
m.TLS = &dynamic.RouterTLSConfig{
|
||||
Options: ep.HTTP.TLS.Options,
|
||||
|
@ -184,6 +184,11 @@ func Test_createConfiguration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Observability: &static.ObservabilityConfig{
|
||||
AccessLogs: false,
|
||||
Tracing: false,
|
||||
Metrics: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -68,7 +68,7 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
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
|
||||
|
||||
if statusCode < 100 || statusCode >= 600 {
|
||||
|
@ -57,6 +57,11 @@ func init() {
|
||||
},
|
||||
},
|
||||
},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
|
@ -22,6 +22,11 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"observability": {
|
||||
"accessLogs": true,
|
||||
"tracing": true,
|
||||
"metrics": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -327,7 +332,8 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"observability": {}
|
||||
}
|
||||
},
|
||||
"serversTransports": {
|
||||
|
@ -22,6 +22,11 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"observability": {
|
||||
"accessLogs": true,
|
||||
"tracing": true,
|
||||
"metrics": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -330,7 +335,8 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"observability": {}
|
||||
}
|
||||
},
|
||||
"serversTransports": {
|
||||
|
@ -178,6 +178,22 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||
|
||||
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
|
||||
if len(eps) > 1 {
|
||||
rtName = epName + "-" + name
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
)
|
||||
|
||||
func pointer[T any](v T) *T { return &v }
|
||||
|
||||
func Test_mergeConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
@ -558,9 +560,10 @@ func Test_applyModel(t *testing.T) {
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"test": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{},
|
||||
},
|
||||
},
|
||||
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",
|
||||
input: dynamic.Configuration{
|
||||
@ -601,6 +658,11 @@ func Test_applyModel(t *testing.T) {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: nil,
|
||||
Tracing: nil,
|
||||
Metrics: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
@ -640,9 +702,10 @@ func Test_applyModel(t *testing.T) {
|
||||
EntryPoints: []string{"web"},
|
||||
},
|
||||
"websecure-test": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
|
@ -8,12 +8,13 @@ import (
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"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.
|
||||
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()
|
||||
|
||||
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.ShouldAddAccessLogs(resourceName) || o.ShouldAddMetrics(resourceName) {
|
||||
if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(capture.Wrap)
|
||||
}
|
||||
}
|
||||
|
||||
// As the Entry point observability middleware ensures that the tracing is added to the request and logger context,
|
||||
// it needs to be added before the access log middleware to ensure that the trace ID is logged.
|
||||
if (o.tracer != nil && o.ShouldAddTracing(resourceName)) || (o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName)) {
|
||||
chain = chain.Append(observability.WrapEntryPointHandler(ctx, o.tracer, o.semConvMetricRegistry, entryPointName))
|
||||
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
|
||||
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(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
})
|
||||
}
|
||||
|
||||
if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName) {
|
||||
metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName)
|
||||
// Semantic convention server metrics handler.
|
||||
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))
|
||||
} else {
|
||||
chain = chain.Append(metricsHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject context keys to control whether to produce metrics further downstream (services, round-tripper),
|
||||
// because the router configuration cannot be evaluated during build time for services.
|
||||
if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics {
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true)))
|
||||
}), nil
|
||||
})
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource.
|
||||
func (o *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool {
|
||||
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return o.config.AccessLog != nil && (o.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
if o.config.AccessLog == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.AccessLogs != nil && *observabilityConfig.AccessLogs
|
||||
}
|
||||
|
||||
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource.
|
||||
func (o *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool {
|
||||
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return o.config.Metrics != nil && (o.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
if o.config.Metrics == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.Metrics != nil && *observabilityConfig.Metrics
|
||||
}
|
||||
|
||||
// ShouldAddTracing returns whether the tracing should be enabled for the given resource.
|
||||
func (o *ObservabilityMgr) ShouldAddTracing(resourceName string) bool {
|
||||
// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
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.
|
||||
|
@ -91,12 +91,12 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
||||
continue
|
||||
}
|
||||
|
||||
handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter())
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
entryPointHandlers[entryPointName] = defaultHandler
|
||||
}
|
||||
|
||||
return entryPointHandlers
|
||||
@ -108,7 +108,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -137,7 +137,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
||||
continue
|
||||
}
|
||||
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service)
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability)
|
||||
handler, err = observabilityChain.Then(handler)
|
||||
if err != nil {
|
||||
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.
|
||||
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service)) {
|
||||
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service), routerConfig.Observability) {
|
||||
m.routerHandlers[routerName] = handler
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
@ -221,12 +221,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
||||
chain := alice.New()
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -356,7 +356,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, 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)
|
||||
if err != nil {
|
||||
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.
|
||||
|
||||
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.ServiceAddr, target.Host, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
// to re-target the request to it.
|
||||
// Those pieces of middleware can be configured on routes that expose a Traefik internal service.
|
||||
|
Loading…
Reference in New Issue
Block a user