From 826a2b74aabb6fd808efbd91a47183a550f4ff12 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 6 Dec 2024 14:50:04 +0100 Subject: [PATCH] OpenTelemetry Logs and Access Logs Co-authored-by: Kevin Pollet --- cmd/traefik/logger.go | 21 +- cmd/traefik/traefik.go | 4 +- docs/content/migration/v3.md | 6 + docs/content/observability/access-logs.md | 399 +++++++++++++++++- docs/content/observability/logs.md | 397 +++++++++++++++++ .../observability/metrics/opentelemetry.md | 14 +- .../observability/tracing/opentelemetry.md | 14 +- .../content/observability/tracing/overview.md | 12 +- .../reference/static-configuration/cli-ref.md | 117 ++++- .../reference/static-configuration/env-ref.md | 117 ++++- .../reference/static-configuration/file.toml | 57 ++- .../reference/static-configuration/file.yaml | 57 ++- go.mod | 27 +- go.sum | 63 +-- pkg/cli/deprecation.go | 25 +- pkg/config/static/static_config.go | 41 +- pkg/logs/otel.go | 120 ++++++ pkg/logs/otel_test.go | 197 +++++++++ pkg/metrics/{opentelemetry.go => otel.go} | 4 +- .../{opentelemetry_test.go => otel_test.go} | 2 +- pkg/middlewares/accesslog/logger.go | 21 +- pkg/middlewares/accesslog/logger_test.go | 72 ++++ pkg/middlewares/observability/entrypoint.go | 7 +- pkg/redactor/redactor_config_test.go | 54 ++- .../testdata/anonymized-static-config.json | 35 +- pkg/server/middleware/observability.go | 10 +- .../opentelemetry/opentelemetry_test.go | 324 -------------- pkg/tracing/tracing.go | 4 +- pkg/tracing/tracing_test.go | 324 ++++++++++++++ pkg/types/logs.go | 147 ++++++- pkg/types/metrics.go | 33 +- pkg/types/otel.go | 26 ++ .../opentelemetry.go => types/tracing.go} | 23 +- 33 files changed, 2298 insertions(+), 476 deletions(-) create mode 100644 pkg/logs/otel.go create mode 100644 pkg/logs/otel_test.go rename pkg/metrics/{opentelemetry.go => otel.go} (99%) rename pkg/metrics/{opentelemetry_test.go => otel_test.go} (99%) delete mode 100644 pkg/tracing/opentelemetry/opentelemetry_test.go create mode 100644 pkg/types/otel.go rename pkg/{tracing/opentelemetry/opentelemetry.go => types/tracing.go} (80%) diff --git a/cmd/traefik/logger.go b/cmd/traefik/logger.go index 8ba6ac6826..58e664d795 100644 --- a/cmd/traefik/logger.go +++ b/cmd/traefik/logger.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io" stdlog "log" "os" @@ -20,12 +21,13 @@ func init() { zerolog.SetGlobalLevel(zerolog.ErrorLevel) } -func setupLogger(staticConfiguration *static.Configuration) { +func setupLogger(staticConfiguration *static.Configuration) error { // configure log format w := getLogWriter(staticConfiguration) // configure log level logLevel := getLogLevel(staticConfiguration) + zerolog.SetGlobalLevel(logLevel) // create logger logCtx := zerolog.New(w).With().Timestamp() @@ -34,8 +36,16 @@ func setupLogger(staticConfiguration *static.Configuration) { } log.Logger = logCtx.Logger().Level(logLevel) + + if staticConfiguration.Log != nil && staticConfiguration.Log.OTLP != nil { + var err error + log.Logger, err = logs.SetupOTelLogger(log.Logger, staticConfiguration.Log.OTLP) + if err != nil { + return fmt.Errorf("setting up OpenTelemetry logger: %w", err) + } + } + zerolog.DefaultContextLogger = &log.Logger - zerolog.SetGlobalLevel(logLevel) // Global logrus replacement (related to lib like go-rancher-metadata, docker, etc.) logrus.StandardLogger().Out = logs.NoLevel(log.Logger, zerolog.DebugLevel) @@ -43,11 +53,16 @@ func setupLogger(staticConfiguration *static.Configuration) { // configure default standard log. stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags) stdlog.SetOutput(logs.NoLevel(log.Logger, zerolog.DebugLevel)) + + return nil } func getLogWriter(staticConfiguration *static.Configuration) io.Writer { - var w io.Writer = os.Stdout + if staticConfiguration.Log != nil && staticConfiguration.Log.OTLP != nil { + return io.Discard + } + var w io.Writer = os.Stdout if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 { _, _ = os.OpenFile(staticConfiguration.Log.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) w = &lumberjack.Logger{ diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 96953fb0a4..45df4fbf2c 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -90,7 +90,9 @@ Complete documentation is available at https://traefik.io`, } func runCmd(staticConfiguration *static.Configuration) error { - setupLogger(staticConfiguration) + if err := setupLogger(staticConfiguration); err != nil { + return fmt.Errorf("setting up logger: %w", err) + } http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment diff --git a/docs/content/migration/v3.md b/docs/content/migration/v3.md index c72296e7bd..939cb5f73c 100644 --- a/docs/content/migration/v3.md +++ b/docs/content/migration/v3.md @@ -167,3 +167,9 @@ Please refer to the Forwarded headers [documentation](../routing/entrypoints.md# In `v3.3`, the `acme.dnsChallenge.delaybeforecheck` and `acme.dnsChallenge.disablepropagationcheck` options of the ACME certificate resolver are deprecated, please use respectively `acme.dnsChallenge.propagation.delayBeforeCheck` and `acme.dnsChallenge.propagation.disableAllChecks` options instead. + +### Tracing Global Attributes + +In `v3.3`, the `tracing.globalAttributes` option has been deprecated, please use the `tracing.resourceAttributes` option instead. +The `tracing.globalAttributes` option is misleading as its name does not reflect the operation of adding resource attributes to be sent to the collector, +and will be removed in the next major version. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 8a24603fa2..6f659a9c16 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -30,7 +30,7 @@ accessLog: {} _Optional, Default="false"_ -Enables accessLogs for internal resources (e.g.: `ping@internal`). +Enables access logs for internal resources (e.g.: `ping@internal`). ```yaml tab="File (YAML)" accesslog: @@ -306,4 +306,401 @@ services: - /var/run/docker.sock:/var/run/docker.sock ``` +## OpenTelemetry + +To enable the OpenTelemetry Logger for access logs: + +```yaml tab="File (YAML)" +accesslog: + otlp: {} +``` + +```toml tab="File (TOML)" +[accesslog.otlp] +``` + +```bash tab="CLI" +--accesslog.otlp=true +``` + +!!! info "Default protocol" + + The OpenTelemetry Logger exporter will export access logs to the collector using HTTPS by default to https://localhost:4318/v1/logs, see the [gRPC Section](#grpc-configuration) to use gRPC. + +### HTTP configuration + +_Optional_ + +This instructs the exporter to send access logs to the OpenTelemetry Collector using HTTP. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: {} +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http] +``` + +```bash tab="CLI" +--accesslog.otlp.http=true +``` + +#### `endpoint` + +_Optional, Default="`https://localhost:4318/v1/logs`", Format="`://:`"_ + +URL of the OpenTelemetry Collector to send access logs to. + +!!! info "Insecure mode" + + To disable TLS, use `http://` instead of `https://` in the `endpoint` configuration. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: + endpoint: https://collector:4318/v1/logs +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http] + endpoint = "https://collector:4318/v1/logs" +``` + +```bash tab="CLI" +--accesslog.otlp.http.endpoint=https://collector:4318/v1/logs +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with access logs by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--accesslog.otlp.http.headers.foo=bar --accesslog.otlp.http.headers.baz=buz +``` + +#### `tls` + +_Optional_ + +Defines the Client TLS configuration used by the exporter to send access logs to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--accesslog.otlp.http.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--accesslog.otlp.http.tls.cert=path/to/foo.cert +--accesslog.otlp.http.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--accesslog.otlp.http.tls.cert=path/to/foo.cert +--accesslog.otlp.http.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +accesslog: + otlp: + http: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[accesslog.otlp.http.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--accesslog.otlp.http.tls.insecureSkipVerify=true +``` + +### gRPC configuration + +_Optional_ + +This instructs the exporter to send access logs to the OpenTelemetry Collector using gRPC. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: {} +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc] +``` + +```bash tab="CLI" +--accesslog.otlp.grpc=true +``` + +#### `endpoint` + +_Required, Default="localhost:4317", Format="`:`"_ + +Address of the OpenTelemetry Collector to send access logs to. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + endpoint: localhost:4317 +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc] + endpoint = "localhost:4317" +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.endpoint=localhost:4317 +``` + +#### `insecure` + +_Optional, Default=false_ + +Allows exporter to send access logs to the OpenTelemetry Collector without using a secured protocol. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + insecure: true +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc] + insecure = true +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.insecure=true +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with access logs by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.headers.foo=bar --accesslog.otlp.grpc.headers.baz=buz +``` + +#### `tls` + +_Optional_ + +Defines the Client TLS configuration used by the exporter to send access logs to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.tls.cert=path/to/foo.cert +--accesslog.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.tls.cert=path/to/foo.cert +--accesslog.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +accesslog: + otlp: + grpc: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[accesslog.otlp.grpc.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--accesslog.otlp.grpc.tls.insecureSkipVerify=true +``` + {!traefik-for-business-applications.md!} diff --git a/docs/content/observability/logs.md b/docs/content/observability/logs.md index 4ee168e66e..8b774c406f 100644 --- a/docs/content/observability/logs.md +++ b/docs/content/observability/logs.md @@ -181,4 +181,401 @@ log: --log.compress=true ``` +## OpenTelemetry + +To enable the OpenTelemetry Logger for logs: + +```yaml tab="File (YAML)" +log: + otlp: {} +``` + +```toml tab="File (TOML)" +[log.otlp] +``` + +```bash tab="CLI" +--log.otlp=true +``` + +!!! info "Default protocol" + + The OpenTelemetry Logger exporter will export logs to the collector using HTTPS by default to https://localhost:4318/v1/logs, see the [gRPC Section](#grpc-configuration) to use gRPC. + +### HTTP configuration + +_Optional_ + +This instructs the exporter to send logs to the OpenTelemetry Collector using HTTP. + +```yaml tab="File (YAML)" +log: + otlp: + http: {} +``` + +```toml tab="File (TOML)" +[log.otlp.http] +``` + +```bash tab="CLI" +--log.otlp.http=true +``` + +#### `endpoint` + +_Optional, Default="`https://localhost:4318/v1/logs`", Format="`://:`"_ + +URL of the OpenTelemetry Collector to send logs to. + +!!! info "Insecure mode" + + To disable TLS, use `http://` instead of `https://` in the `endpoint` configuration. + +```yaml tab="File (YAML)" +log: + otlp: + http: + endpoint: https://collector:4318/v1/logs +``` + +```toml tab="File (TOML)" +[log.otlp.http] + endpoint = "https://collector:4318/v1/logs" +``` + +```bash tab="CLI" +--log.otlp.http.endpoint=https://collector:4318/v1/logs +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with logs by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +log: + otlp: + http: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[log.otlp.http.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--log.otlp.http.headers.foo=bar --log.otlp.http.headers.baz=buz +``` + +#### `tls` + +_Optional_ + +Defines the Client TLS configuration used by the exporter to send logs to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +log: + otlp: + http: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[log.otlp.http.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--log.otlp.http.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +log: + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[log.otlp.http.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--log.otlp.http.tls.cert=path/to/foo.cert +--log.otlp.http.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +log: + otlp: + http: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[log.otlp.http.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--log.otlp.http.tls.cert=path/to/foo.cert +--log.otlp.http.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +log: + otlp: + http: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[log.otlp.http.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--log.otlp.http.tls.insecureSkipVerify=true +``` + +### gRPC configuration + +_Optional_ + +This instructs the exporter to send logs to the OpenTelemetry Collector using gRPC. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: {} +``` + +```toml tab="File (TOML)" +[log.otlp.grpc] +``` + +```bash tab="CLI" +--log.otlp.grpc=true +``` + +#### `endpoint` + +_Required, Default="localhost:4317", Format="`:`"_ + +Address of the OpenTelemetry Collector to send logs to. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + endpoint: localhost:4317 +``` + +```toml tab="File (TOML)" +[log.otlp.grpc] + endpoint = "localhost:4317" +``` + +```bash tab="CLI" +--log.otlp.grpc.endpoint=localhost:4317 +``` + +#### `insecure` + +_Optional, Default=false_ + +Allows exporter to send logs to the OpenTelemetry Collector without using a secured protocol. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + insecure: true +``` + +```toml tab="File (TOML)" +[log.otlp.grpc] + insecure = true +``` + +```bash tab="CLI" +--log.otlp.grpc.insecure=true +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with logs by the exporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[log.otlp.grpc.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--log.otlp.grpc.headers.foo=bar --log.otlp.grpc.headers.baz=buz +``` + +#### `tls` + +_Optional_ + +Defines the Client TLS configuration used by the exporter to send logs to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[log.otlp.grpc.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--log.otlp.grpc.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[log.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--log.otlp.grpc.tls.cert=path/to/foo.cert +--log.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[log.otlp.grpc.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--log.otlp.grpc.tls.cert=path/to/foo.cert +--log.otlp.grpc.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +log: + otlp: + grpc: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[log.otlp.grpc.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--log.otlp.grpc.tls.insecureSkipVerify=true +``` + {!traefik-for-business-applications.md!} diff --git a/docs/content/observability/metrics/opentelemetry.md b/docs/content/observability/metrics/opentelemetry.md index e71e5ec63b..0bcb965878 100644 --- a/docs/content/observability/metrics/opentelemetry.md +++ b/docs/content/observability/metrics/opentelemetry.md @@ -23,7 +23,7 @@ metrics: !!! info "Default protocol" - The OpenTelemetry exporter will export metrics to the collector using HTTP by default to https://localhost:4318/v1/metrics, see the [gRPC Section](#grpc-configuration) to use gRPC. + The OpenTelemetry exporter will export metrics to the collector using HTTPS by default to https://localhost:4318/v1/metrics, see the [gRPC Section](#grpc-configuration) to use gRPC. #### `addEntryPointsLabels` @@ -184,25 +184,29 @@ metrics: #### `endpoint` -_Required, Default="http://localhost:4318/v1/metrics", Format="`://:`"_ +_Optional, Default="https://localhost:4318/v1/metrics", Format="`://:`"_ URL of the OpenTelemetry Collector to send metrics to. +!!! info "Insecure mode" + + To disable TLS, use `http://` instead of `https://` in the `endpoint` configuration. + ```yaml tab="File (YAML)" metrics: otlp: http: - endpoint: http://localhost:4318/v1/metrics + endpoint: https://collector:4318/v1/metrics ``` ```toml tab="File (TOML)" [metrics] [metrics.otlp.http] - endpoint = "http://localhost:4318/v1/metrics" + endpoint = "https://collector:4318/v1/metrics" ``` ```bash tab="CLI" ---metrics.otlp.http.endpoint=http://localhost:4318/v1/metrics +--metrics.otlp.http.endpoint=https://collector:4318/v1/metrics ``` #### `headers` diff --git a/docs/content/observability/tracing/opentelemetry.md b/docs/content/observability/tracing/opentelemetry.md index 3844882c3b..921ce8399e 100644 --- a/docs/content/observability/tracing/opentelemetry.md +++ b/docs/content/observability/tracing/opentelemetry.md @@ -25,7 +25,7 @@ tracing: !!! info "Default protocol" - The OpenTelemetry trace exporter will export traces to the collector using HTTP by default to https://localhost:4318/v1/traces, see the [gRPC Section](#grpc-configuration) to use gRPC. + The OpenTelemetry trace exporter will export traces to the collector using HTTPS by default to https://localhost:4318/v1/traces, see the [gRPC Section](#grpc-configuration) to use gRPC. !!! info "Trace sampling" @@ -72,25 +72,29 @@ tracing: #### `endpoint` -_Required, Default="http://localhost:4318/v1/traces", Format="`://:`"_ +_Optional, Default="https://localhost:4318/v1/traces", Format="`://:`"_ URL of the OpenTelemetry Collector to send spans to. +!!! info "Insecure mode" + + To disable TLS, use `http://` instead of `https://` in the `endpoint` configuration. + ```yaml tab="File (YAML)" tracing: otlp: http: - endpoint: http://localhost:4318/v1/traces + endpoint: https://collector:4318/v1/traces ``` ```toml tab="File (TOML)" [tracing] [tracing.otlp.http] - endpoint = "http://localhost:4318/v1/traces" + endpoint = "https://collector:4318/v1/traces" ``` ```bash tab="CLI" ---tracing.otlp.http.endpoint=http://localhost:4318/v1/traces +--tracing.otlp.http.endpoint=https://collector:4318/v1/traces ``` #### `headers` diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index 73840f89d0..891ff5c0bb 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -92,29 +92,29 @@ tracing: --tracing.sampleRate=0.2 ``` -#### `globalAttributes` +#### `resourceAttributes` _Optional, Default=empty_ -Applies a list of shared key:value attributes on all spans. +Defines additional resource attributes to be sent to the collector. ```yaml tab="File (YAML)" tracing: - globalAttributes: + resourceAttributes: attr1: foo attr2: bar ``` ```toml tab="File (TOML)" [tracing] - [tracing.globalAttributes] + [tracing.resourceAttributes] attr1 = "foo" attr2 = "bar" ``` ```bash tab="CLI" ---tracing.globalAttributes.attr1=foo ---tracing.globalAttributes.attr2=bar +--tracing.resourceAttributes.attr1=foo +--tracing.resourceAttributes.attr2=bar ``` #### `capturedRequestHeaders` diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 6d72066a84..0c65188fe8 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -39,6 +39,60 @@ Keep access logs with status codes in the specified range. `--accesslog.format`: Access log format: json | common (Default: ```common```) +`--accesslog.otlp`: +Settings for OpenTelemetry. (Default: ```false```) + +`--accesslog.otlp.grpc`: +gRPC configuration for the OpenTelemetry collector. (Default: ```false```) + +`--accesslog.otlp.grpc.endpoint`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) + +`--accesslog.otlp.grpc.headers.`: +Headers sent with payload. + +`--accesslog.otlp.grpc.insecure`: +Disables client transport security for the exporter. (Default: ```false```) + +`--accesslog.otlp.grpc.tls.ca`: +TLS CA + +`--accesslog.otlp.grpc.tls.cert`: +TLS cert + +`--accesslog.otlp.grpc.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--accesslog.otlp.grpc.tls.key`: +TLS key + +`--accesslog.otlp.http`: +HTTP configuration for the OpenTelemetry collector. (Default: ```false```) + +`--accesslog.otlp.http.endpoint`: +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`--accesslog.otlp.http.headers.`: +Headers sent with payload. + +`--accesslog.otlp.http.tls.ca`: +TLS CA + +`--accesslog.otlp.http.tls.cert`: +TLS cert + +`--accesslog.otlp.http.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--accesslog.otlp.http.tls.key`: +TLS key + +`--accesslog.otlp.resourceattributes.`: +Defines additional resource attributes (key:value). + +`--accesslog.otlp.servicename`: +Set the name for this service. (Default: ```traefik```) + `--api`: Enable api/dashboard. (Default: ```false```) @@ -333,6 +387,60 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0 `--log.nocolor`: When using the 'common' format, disables the colorized output. (Default: ```false```) +`--log.otlp`: +Settings for OpenTelemetry. (Default: ```false```) + +`--log.otlp.grpc`: +gRPC configuration for the OpenTelemetry collector. (Default: ```false```) + +`--log.otlp.grpc.endpoint`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) + +`--log.otlp.grpc.headers.`: +Headers sent with payload. + +`--log.otlp.grpc.insecure`: +Disables client transport security for the exporter. (Default: ```false```) + +`--log.otlp.grpc.tls.ca`: +TLS CA + +`--log.otlp.grpc.tls.cert`: +TLS cert + +`--log.otlp.grpc.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--log.otlp.grpc.tls.key`: +TLS key + +`--log.otlp.http`: +HTTP configuration for the OpenTelemetry collector. (Default: ```false```) + +`--log.otlp.http.endpoint`: +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`--log.otlp.http.headers.`: +Headers sent with payload. + +`--log.otlp.http.tls.ca`: +TLS CA + +`--log.otlp.http.tls.cert`: +TLS cert + +`--log.otlp.http.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--log.otlp.http.tls.key`: +TLS key + +`--log.otlp.resourceattributes.`: +Defines additional resource attributes (key:value). + +`--log.otlp.servicename`: +Set the name for this service. (Default: ```traefik```) + `--metrics.addinternals`: Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) @@ -1138,7 +1246,7 @@ Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain). Defines the allowed SPIFFE trust domain. `--tracing`: -OpenTracing configuration. (Default: ```false```) +Tracing configuration. (Default: ```false```) `--tracing.addinternals`: Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```) @@ -1150,7 +1258,7 @@ Request headers to add as attributes for server and client spans. Response headers to add as attributes for server and client spans. `--tracing.globalattributes.`: -Defines additional attributes (key:value) on all spans. +(Deprecated) Defines additional resource attributes (key:value). `--tracing.otlp`: Settings for OpenTelemetry. (Default: ```false```) @@ -1200,6 +1308,9 @@ TLS insecure skip verify (Default: ```false```) `--tracing.otlp.http.tls.key`: TLS key +`--tracing.resourceattributes.`: +Defines additional resource attributes (key:value). + `--tracing.safequeryparams`: Query params to not redact. @@ -1207,4 +1318,4 @@ Query params to not redact. Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) `--tracing.servicename`: -Set the name for this service. (Default: ```traefik```) +Sets the name for this service. (Default: ```traefik```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 296ac60e21..056f9b29f7 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -39,6 +39,60 @@ Keep access logs with status codes in the specified range. `TRAEFIK_ACCESSLOG_FORMAT`: Access log format: json | common (Default: ```common```) +`TRAEFIK_ACCESSLOG_OTLP`: +Settings for OpenTelemetry. (Default: ```false```) + +`TRAEFIK_ACCESSLOG_OTLP_GRPC`: +gRPC configuration for the OpenTelemetry collector. (Default: ```false```) + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_ENDPOINT`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_HEADERS_`: +Headers sent with payload. + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_INSECURE`: +Disables client transport security for the exporter. (Default: ```false```) + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_TLS_CA`: +TLS CA + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_TLS_CERT`: +TLS cert + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_ACCESSLOG_OTLP_GRPC_TLS_KEY`: +TLS key + +`TRAEFIK_ACCESSLOG_OTLP_HTTP`: +HTTP configuration for the OpenTelemetry collector. (Default: ```false```) + +`TRAEFIK_ACCESSLOG_OTLP_HTTP_ENDPOINT`: +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`TRAEFIK_ACCESSLOG_OTLP_HTTP_HEADERS_`: +Headers sent with payload. + +`TRAEFIK_ACCESSLOG_OTLP_HTTP_TLS_CA`: +TLS CA + +`TRAEFIK_ACCESSLOG_OTLP_HTTP_TLS_CERT`: +TLS cert + +`TRAEFIK_ACCESSLOG_OTLP_HTTP_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_ACCESSLOG_OTLP_HTTP_TLS_KEY`: +TLS key + +`TRAEFIK_ACCESSLOG_OTLP_RESOURCEATTRIBUTES_`: +Defines additional resource attributes (key:value). + +`TRAEFIK_ACCESSLOG_OTLP_SERVICENAME`: +Set the name for this service. (Default: ```traefik```) + `TRAEFIK_API`: Enable api/dashboard. (Default: ```false```) @@ -333,6 +387,60 @@ Maximum size in megabytes of the log file before it gets rotated. (Default: ```0 `TRAEFIK_LOG_NOCOLOR`: When using the 'common' format, disables the colorized output. (Default: ```false```) +`TRAEFIK_LOG_OTLP`: +Settings for OpenTelemetry. (Default: ```false```) + +`TRAEFIK_LOG_OTLP_GRPC`: +gRPC configuration for the OpenTelemetry collector. (Default: ```false```) + +`TRAEFIK_LOG_OTLP_GRPC_ENDPOINT`: +Sets the gRPC endpoint (host:port) of the collector. (Default: ```localhost:4317```) + +`TRAEFIK_LOG_OTLP_GRPC_HEADERS_`: +Headers sent with payload. + +`TRAEFIK_LOG_OTLP_GRPC_INSECURE`: +Disables client transport security for the exporter. (Default: ```false```) + +`TRAEFIK_LOG_OTLP_GRPC_TLS_CA`: +TLS CA + +`TRAEFIK_LOG_OTLP_GRPC_TLS_CERT`: +TLS cert + +`TRAEFIK_LOG_OTLP_GRPC_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_LOG_OTLP_GRPC_TLS_KEY`: +TLS key + +`TRAEFIK_LOG_OTLP_HTTP`: +HTTP configuration for the OpenTelemetry collector. (Default: ```false```) + +`TRAEFIK_LOG_OTLP_HTTP_ENDPOINT`: +Sets the HTTP endpoint (scheme://host:port/path) of the collector. (Default: ```https://localhost:4318```) + +`TRAEFIK_LOG_OTLP_HTTP_HEADERS_`: +Headers sent with payload. + +`TRAEFIK_LOG_OTLP_HTTP_TLS_CA`: +TLS CA + +`TRAEFIK_LOG_OTLP_HTTP_TLS_CERT`: +TLS cert + +`TRAEFIK_LOG_OTLP_HTTP_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_LOG_OTLP_HTTP_TLS_KEY`: +TLS key + +`TRAEFIK_LOG_OTLP_RESOURCEATTRIBUTES_`: +Defines additional resource attributes (key:value). + +`TRAEFIK_LOG_OTLP_SERVICENAME`: +Set the name for this service. (Default: ```traefik```) + `TRAEFIK_METRICS_ADDINTERNALS`: Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) @@ -1138,7 +1246,7 @@ Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain). Defines the allowed SPIFFE trust domain. `TRAEFIK_TRACING`: -OpenTracing configuration. (Default: ```false```) +Tracing configuration. (Default: ```false```) `TRAEFIK_TRACING_ADDINTERNALS`: Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```) @@ -1150,7 +1258,7 @@ Request headers to add as attributes for server and client spans. Response headers to add as attributes for server and client spans. `TRAEFIK_TRACING_GLOBALATTRIBUTES_`: -Defines additional attributes (key:value) on all spans. +(Deprecated) Defines additional resource attributes (key:value). `TRAEFIK_TRACING_OTLP`: Settings for OpenTelemetry. (Default: ```false```) @@ -1200,6 +1308,9 @@ TLS insecure skip verify (Default: ```false```) `TRAEFIK_TRACING_OTLP_HTTP_TLS_KEY`: TLS key +`TRAEFIK_TRACING_RESOURCEATTRIBUTES_`: +Defines additional resource attributes (key:value). + `TRAEFIK_TRACING_SAFEQUERYPARAMS`: Query params to not redact. @@ -1207,4 +1318,4 @@ Query params to not redact. Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) `TRAEFIK_TRACING_SERVICENAME`: -Set the name for this service. (Default: ```traefik```) +Sets the name for this service. (Default: ```traefik```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 08b030a6c4..1987627ecb 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -381,6 +381,32 @@ maxAge = 42 maxBackups = 42 compress = true + [log.otlp] + serviceName = "foobar" + [log.otlp.resourceAttributes] + name0 = "foobar" + name1 = "foobar" + [log.otlp.grpc] + endpoint = "foobar" + insecure = true + [log.otlp.grpc.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [log.otlp.grpc.headers] + name0 = "foobar" + name1 = "foobar" + [log.otlp.http] + endpoint = "foobar" + [log.otlp.http.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [log.otlp.http.headers] + name0 = "foobar" + name1 = "foobar" [accessLog] filePath = "foobar" @@ -401,6 +427,32 @@ [accessLog.fields.headers.names] name0 = "foobar" name1 = "foobar" + [accessLog.otlp] + serviceName = "foobar" + [accessLog.otlp.resourceAttributes] + name0 = "foobar" + name1 = "foobar" + [accessLog.otlp.grpc] + endpoint = "foobar" + insecure = true + [accessLog.otlp.grpc.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [accessLog.otlp.grpc.headers] + name0 = "foobar" + name1 = "foobar" + [accessLog.otlp.http] + endpoint = "foobar" + [accessLog.otlp.http.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [accessLog.otlp.http.headers] + name0 = "foobar" + name1 = "foobar" [tracing] serviceName = "foobar" @@ -409,7 +461,7 @@ safeQueryParams = ["foobar", "foobar"] sampleRate = 42.0 addInternals = true - [tracing.globalAttributes] + [tracing.resourceAttributes] name0 = "foobar" name1 = "foobar" [tracing.otlp] @@ -434,6 +486,9 @@ [tracing.otlp.http.headers] name0 = "foobar" name1 = "foobar" + [tracing.globalAttributes] + name0 = "foobar" + name1 = "foobar" [hostResolver] cnameFlattening = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index cbc8dc0201..2bebf70177 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -418,6 +418,32 @@ log: maxAge: 42 maxBackups: 42 compress: true + otlp: + serviceName: foobar + resourceAttributes: + name0: foobar + name1: foobar + grpc: + endpoint: foobar + insecure: true + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + headers: + name0: foobar + name1: foobar + http: + endpoint: foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + headers: + name0: foobar + name1: foobar accessLog: filePath: foobar format: foobar @@ -439,9 +465,35 @@ accessLog: name1: foobar bufferingSize: 42 addInternals: true + otlp: + serviceName: foobar + resourceAttributes: + name0: foobar + name1: foobar + grpc: + endpoint: foobar + insecure: true + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + headers: + name0: foobar + name1: foobar + http: + endpoint: foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + headers: + name0: foobar + name1: foobar tracing: serviceName: foobar - globalAttributes: + resourceAttributes: name0: foobar name1: foobar capturedRequestHeaders: @@ -477,6 +529,9 @@ tracing: headers: name0: foobar name1: foobar + globalAttributes: + name0: foobar + name1: foobar hostResolver: cnameFlattening: true resolvConfig: foobar diff --git a/go.mod b/go.mod index 40b544195e..aef9401b13 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.6.1 github.com/quic-go/quic-go v0.47.0 - github.com/rs/zerolog v1.29.0 + github.com/rs/zerolog v1.33.0 github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.1.1 github.com/stealthrocket/wasi-go v0.8.0 @@ -72,23 +72,28 @@ require ( github.com/vulcand/oxy/v2 v2.0.0 github.com/vulcand/predicate v1.2.0 go.opentelemetry.io/collector/pdata v1.10.0 + go.opentelemetry.io/contrib/bridges/otellogrus v0.7.0 go.opentelemetry.io/contrib/propagators/autoprop v0.53.0 - go.opentelemetry.io/otel v1.29.0 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 - go.opentelemetry.io/otel/metric v1.29.0 - go.opentelemetry.io/otel/sdk v1.28.0 + go.opentelemetry.io/otel/log v0.8.0 + go.opentelemetry.io/otel/metric v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/sdk/log v0.8.0 go.opentelemetry.io/otel/sdk/metric v1.28.0 - go.opentelemetry.io/otel/trace v1.29.0 + go.opentelemetry.io/otel/trace v1.32.0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // No tag on the repo. golang.org/x/mod v0.21.0 golang.org/x/net v0.30.0 - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.26.0 - golang.org/x/text v0.19.0 + golang.org/x/sync v0.9.0 + golang.org/x/sys v0.27.0 + golang.org/x/text v0.20.0 golang.org/x/time v0.7.0 golang.org/x/tools v0.25.0 google.golang.org/grpc v1.67.1 @@ -219,7 +224,7 @@ require ( github.com/gophercloud/gophercloud v1.14.1 // indirect github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -364,8 +369,8 @@ require ( golang.org/x/term v0.25.0 // indirect google.golang.org/api v0.204.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/h2non/gock.v1 v1.0.16 // indirect diff --git a/go.sum b/go.sum index 6c8112747c..b9b9878035 100644 --- a/go.sum +++ b/go.sum @@ -262,7 +262,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -576,8 +575,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -818,6 +817,7 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -1046,13 +1046,13 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= -github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c= @@ -1288,6 +1288,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/collector/pdata v1.10.0 h1:oLyPLGvPTQrcRT64ZVruwvmH/u3SHTfNo01pteS4WOE= go.opentelemetry.io/collector/pdata v1.10.0/go.mod h1:IHxHsp+Jq/xfjORQMDJjSH6jvedOSTOyu3nbxqhWSYE= +go.opentelemetry.io/contrib/bridges/otellogrus v0.7.0 h1:vPSzn6dQvdPq9ZiXFs+jUSJnzoKJkADD9yBdx/a1WgI= +go.opentelemetry.io/contrib/bridges/otellogrus v0.7.0/go.mod h1:yZFNJIjn97IBhuMB3tTGPti9xasYLIdh3ChZIzyhz8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/propagators/autoprop v0.53.0 h1:4zaVLcJ5mvYw0vlk63TX62qS4qty/4jAY1BKZ1usu18= @@ -1300,8 +1302,12 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.28.0 h1:xQ3ktSVS128JWIaN1DiPGI go.opentelemetry.io/contrib/propagators/jaeger v1.28.0/go.mod h1:O9HIyI2kVBrFoEwQZ0IN6PHXykGoit4mZV2aEjkTRH4= go.opentelemetry.io/contrib/propagators/ot v1.28.0 h1:rmlG+2pc5k5M7Y7izDrxAHZUIwDERdGMTD9oMV7llMk= go.opentelemetry.io/contrib/propagators/ot v1.28.0/go.mod h1:MNgXIn+UrMbNGpd7xyckyo2LCHIgCdmdjEE7YNZGG+w= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= @@ -1312,14 +1318,18 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6Z go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= @@ -1508,8 +1518,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1597,11 +1607,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1631,8 +1642,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1770,10 +1781,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/pkg/cli/deprecation.go b/pkg/cli/deprecation.go index 7466b2758e..822c2c030e 100644 --- a/pkg/cli/deprecation.go +++ b/pkg/cli/deprecation.go @@ -502,14 +502,17 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { return false } +// + type tracing struct { - SpanNameLimit *int `json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty"` - Jaeger map[string]any `json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" label:"allowEmpty" file:"allowEmpty"` - Zipkin map[string]any `json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" label:"allowEmpty" file:"allowEmpty"` - Datadog map[string]any `json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty"` - Instana map[string]any `json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" label:"allowEmpty" file:"allowEmpty"` - Haystack map[string]any `json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" label:"allowEmpty" file:"allowEmpty"` - Elastic map[string]any `json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" label:"allowEmpty" file:"allowEmpty"` + SpanNameLimit *int `json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty"` + GlobalAttributes map[string]string `json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` + Jaeger map[string]any `json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" label:"allowEmpty" file:"allowEmpty"` + Zipkin map[string]any `json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" label:"allowEmpty" file:"allowEmpty"` + Datadog map[string]any `json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty"` + Instana map[string]any `json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" label:"allowEmpty" file:"allowEmpty"` + Haystack map[string]any `json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" label:"allowEmpty" file:"allowEmpty"` + Elastic map[string]any `json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" label:"allowEmpty" file:"allowEmpty"` } func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { @@ -523,6 +526,14 @@ func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.2/migration/v2-to-v3/#tracing") } + if t.GlobalAttributes != nil { + log.Warn().Msgf("tracing.globalAttributes option is now deprecated, please use tracing.resourceAttributes instead.") + + logger.Error().Msg("`tracing.globalAttributes` option has been deprecated in v3.3, and will be removed in the next major version." + + "Please use the `tracing.resourceAttributes` option instead." + + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.3/migration/v3/#tracing-global-attributes") + } + if t.Jaeger != nil { incompatible = true logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing static configuration for Traefik to start." + diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 979baa3fd9..4f6dad22aa 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -28,7 +28,6 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kv/zk" "github.com/traefik/traefik/v3/pkg/provider/nomad" "github.com/traefik/traefik/v3/pkg/provider/rest" - "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" "github.com/traefik/traefik/v3/pkg/types" ) @@ -69,7 +68,7 @@ type Configuration struct { Log *types.TraefikLog `description:"Traefik log settings." json:"log,omitempty" toml:"log,omitempty" yaml:"log,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` AccessLog *types.AccessLog `description:"Access log settings." json:"accessLog,omitempty" toml:"accessLog,omitempty" yaml:"accessLog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Tracing *Tracing `description:"OpenTracing configuration." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Tracing *Tracing `description:"Tracing configuration." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening." json:"hostResolver,omitempty" toml:"hostResolver,omitempty" yaml:"hostResolver,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -200,15 +199,17 @@ func (a *LifeCycle) SetDefaults() { // Tracing holds the tracing configuration. type Tracing struct { - ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` - GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` - CapturedRequestHeaders []string `description:"Request headers to add as attributes for server and client spans." json:"capturedRequestHeaders,omitempty" toml:"capturedRequestHeaders,omitempty" yaml:"capturedRequestHeaders,omitempty" export:"true"` - CapturedResponseHeaders []string `description:"Response headers to add as attributes for server and client spans." json:"capturedResponseHeaders,omitempty" toml:"capturedResponseHeaders,omitempty" yaml:"capturedResponseHeaders,omitempty" export:"true"` - SafeQueryParams []string `description:"Query params to not redact." json:"safeQueryParams,omitempty" toml:"safeQueryParams,omitempty" yaml:"safeQueryParams,omitempty" export:"true"` - SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` - AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` - - OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ServiceName string `description:"Sets the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + ResourceAttributes map[string]string `description:"Defines additional resource attributes (key:value)." json:"resourceAttributes,omitempty" toml:"resourceAttributes,omitempty" yaml:"resourceAttributes,omitempty" export:"true"` + CapturedRequestHeaders []string `description:"Request headers to add as attributes for server and client spans." json:"capturedRequestHeaders,omitempty" toml:"capturedRequestHeaders,omitempty" yaml:"capturedRequestHeaders,omitempty" export:"true"` + CapturedResponseHeaders []string `description:"Response headers to add as attributes for server and client spans." json:"capturedResponseHeaders,omitempty" toml:"capturedResponseHeaders,omitempty" yaml:"capturedResponseHeaders,omitempty" export:"true"` + SafeQueryParams []string `description:"Query params to not redact." json:"safeQueryParams,omitempty" toml:"safeQueryParams,omitempty" yaml:"safeQueryParams,omitempty" export:"true"` + SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` + AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` + OTLP *types.OTelTracing `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + + // Deprecated: please use ResourceAttributes instead. + GlobalAttributes map[string]string `description:"(Deprecated) Defines additional resource attributes (key:value)." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` } // SetDefaults sets the default values. @@ -216,7 +217,7 @@ func (t *Tracing) SetDefaults() { t.ServiceName = "traefik" t.SampleRate = 1.0 - t.OTLP = &opentelemetry.Config{} + t.OTLP = &types.OTelTracing{} t.OTLP.SetDefaults() } @@ -270,6 +271,10 @@ func (c *Configuration) SetEffectiveConfiguration() { } } + if c.Tracing != nil && c.Tracing.GlobalAttributes != nil && c.Tracing.ResourceAttributes == nil { + c.Tracing.ResourceAttributes = c.Tracing.GlobalAttributes + } + if c.Providers.Docker != nil { if c.Providers.Docker.HTTPClientTimeout < 0 { c.Providers.Docker.HTTPClientTimeout = 0 @@ -381,6 +386,18 @@ func (c *Configuration) ValidateConfiguration() error { } } + if c.AccessLog != nil && c.AccessLog.OTLP != nil { + if c.AccessLog.OTLP.GRPC != nil && c.AccessLog.OTLP.GRPC.TLS != nil && c.AccessLog.OTLP.GRPC.Insecure { + return errors.New("access logs OTLP GRPC: TLS and Insecure options are mutually exclusive") + } + } + + if c.Log != nil && c.Log.OTLP != nil { + if c.Log.OTLP.GRPC != nil && c.Log.OTLP.GRPC.TLS != nil && c.Log.OTLP.GRPC.Insecure { + return errors.New("logs OTLP GRPC: TLS and Insecure options are mutually exclusive") + } + } + if c.Tracing != nil && c.Tracing.OTLP != nil { if c.Tracing.OTLP.GRPC != nil && c.Tracing.OTLP.GRPC.TLS != nil && c.Tracing.OTLP.GRPC.Insecure { return errors.New("tracing OTLP GRPC: TLS and Insecure options are mutually exclusive") diff --git a/pkg/logs/otel.go b/pkg/logs/otel.go new file mode 100644 index 0000000000..bc8f95443b --- /dev/null +++ b/pkg/logs/otel.go @@ -0,0 +1,120 @@ +package logs + +import ( + "encoding/json" + "fmt" + "reflect" + "time" + + "github.com/rs/zerolog" + "github.com/traefik/traefik/v3/pkg/types" + otellog "go.opentelemetry.io/otel/log" +) + +// SetupOTelLogger sets up the OpenTelemetry logger. +func SetupOTelLogger(logger zerolog.Logger, config *types.OTelLog) (zerolog.Logger, error) { + if config == nil { + return logger, nil + } + + provider, err := config.NewLoggerProvider() + if err != nil { + return zerolog.Logger{}, fmt.Errorf("setting up OpenTelemetry logger provider: %w", err) + } + + return logger.Hook(&otelLoggerHook{logger: provider.Logger("traefik")}), nil +} + +// otelLoggerHook is a zerolog hook that forwards logs to OpenTelemetry. +type otelLoggerHook struct { + logger otellog.Logger +} + +// Run forwards the log message to OpenTelemetry. +func (h *otelLoggerHook) Run(e *zerolog.Event, level zerolog.Level, message string) { + if level == zerolog.Disabled { + return + } + + // Discard the event to avoid double logging. + e.Discard() + + var record otellog.Record + record.SetTimestamp(time.Now().UTC()) + record.SetSeverity(otelLogSeverity(level)) + record.SetBody(otellog.StringValue(message)) + + // See https://github.com/rs/zerolog/issues/493. + // This is a workaround to get the log fields from the event. + // At the moment there's no way to get the log fields from the event, so we use reflection to get the buffer and parse it. + logData := make(map[string]any) + eventBuffer := fmt.Sprintf("%s}", reflect.ValueOf(e).Elem().FieldByName("buf")) + if err := json.Unmarshal([]byte(eventBuffer), &logData); err != nil { + record.AddAttributes(otellog.String("parsing_error", fmt.Sprintf("parsing log fields: %s", err))) + h.logger.Emit(e.GetCtx(), record) + return + } + + recordAttributes := make([]otellog.KeyValue, 0, len(logData)) + for k, v := range logData { + if k == "level" { + continue + } + if k == "time" { + eventTimestamp, ok := v.(string) + if !ok { + continue + } + t, err := time.Parse(time.RFC3339, eventTimestamp) + if err == nil { + record.SetTimestamp(t) + continue + } + } + var attributeValue otellog.Value + switch v := v.(type) { + case string: + attributeValue = otellog.StringValue(v) + case int: + attributeValue = otellog.IntValue(v) + case int64: + attributeValue = otellog.Int64Value(v) + case float64: + attributeValue = otellog.Float64Value(v) + case bool: + attributeValue = otellog.BoolValue(v) + case []byte: + attributeValue = otellog.BytesValue(v) + default: + attributeValue = otellog.StringValue(fmt.Sprintf("%v", v)) + } + recordAttributes = append(recordAttributes, otellog.KeyValue{ + Key: k, + Value: attributeValue, + }) + } + record.AddAttributes(recordAttributes...) + + h.logger.Emit(e.GetCtx(), record) +} + +func otelLogSeverity(level zerolog.Level) otellog.Severity { + switch level { + case zerolog.TraceLevel: + return otellog.SeverityTrace + case zerolog.DebugLevel: + return otellog.SeverityDebug + case zerolog.InfoLevel: + return otellog.SeverityInfo + case zerolog.WarnLevel: + return otellog.SeverityWarn + case zerolog.ErrorLevel: + return otellog.SeverityError + case zerolog.FatalLevel: + return otellog.SeverityFatal + case zerolog.PanicLevel: + return otellog.SeverityFatal4 + default: + return otellog.SeverityUndefined + } +} diff --git a/pkg/logs/otel_test.go b/pkg/logs/otel_test.go new file mode 100644 index 0000000000..60c68cc099 --- /dev/null +++ b/pkg/logs/otel_test.go @@ -0,0 +1,197 @@ +package logs + +import ( + "compress/gzip" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/types" + "go.opentelemetry.io/collector/pdata/plog/plogotlp" + "go.opentelemetry.io/otel/trace" +) + +func TestLog(t *testing.T) { + tests := []struct { + desc string + level zerolog.Level + assertFn func(*testing.T, string) + noLog bool + }{ + { + desc: "no level log", + level: zerolog.NoLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityUndefined Severity = 0 // UNDEFINED + assert.NotContains(t, log, `"severityNumber"`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "trace log", + level: zerolog.TraceLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityTrace1 Severity = 1 // TRACE + assert.Contains(t, log, `"severityNumber":1`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "debug log", + level: zerolog.DebugLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityDebug1 Severity = 5 // DEBUG + assert.Contains(t, log, `"severityNumber":5`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "info log", + level: zerolog.InfoLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityInfo1 Severity = 9 // INFO + assert.Contains(t, log, `"severityNumber":9`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "warn log", + level: zerolog.WarnLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityWarn1 Severity = 13 // WARN + assert.Contains(t, log, `"severityNumber":13`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "error log", + level: zerolog.ErrorLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityError1 Severity = 17 // ERROR + assert.Contains(t, log, `"severityNumber":17`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "fatal log", + level: zerolog.FatalLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityFatal Severity = 21 // FATAL + assert.Contains(t, log, `"severityNumber":21`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + { + desc: "panic log", + level: zerolog.PanicLevel, + assertFn: func(t *testing.T, log string) { + t.Helper() + // SeverityFatal4 Severity = 24 // FATAL + assert.Contains(t, log, `"severityNumber":24`) + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `"body":{"stringValue":"test"}`, log) + assert.Regexp(t, `{"key":"foo","value":{"stringValue":"bar"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + }, + }, + } + + logCh := make(chan string) + collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gzr, err := gzip.NewReader(r.Body) + require.NoError(t, err) + + body, err := io.ReadAll(gzr) + require.NoError(t, err) + + req := plogotlp.NewExportRequest() + err = req.UnmarshalProto(body) + require.NoError(t, err) + + marshalledReq, err := json.Marshal(req) + require.NoError(t, err) + + logCh <- string(marshalledReq) + })) + t.Cleanup(collector.Close) + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + config := &types.OTelLog{ + ServiceName: "test", + ResourceAttributes: map[string]string{"resource": "attribute"}, + HTTP: &types.OTelHTTP{ + Endpoint: collector.URL, + }, + } + + out := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}) + logger := zerolog.New(out).With().Caller().Logger() + + logger, err := SetupOTelLogger(logger, config) + require.NoError(t, err) + + ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + })) + logger = logger.With().Ctx(ctx).Logger() + + logger.WithLevel(test.level).Str("foo", "bar").Msg("test") + + select { + case <-time.After(5 * time.Second): + t.Error("Log not exported") + + case log := <-logCh: + if test.assertFn != nil { + test.assertFn(t, log) + } + } + }) + } +} diff --git a/pkg/metrics/opentelemetry.go b/pkg/metrics/otel.go similarity index 99% rename from pkg/metrics/opentelemetry.go rename to pkg/metrics/otel.go index 21f91a89ab..2b1b1b5b74 100644 --- a/pkg/metrics/opentelemetry.go +++ b/pkg/metrics/otel.go @@ -237,7 +237,7 @@ func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OTLP) (*sd return meterProvider, nil } -func newHTTPExporter(ctx context.Context, config *types.OtelHTTP) (sdkmetric.Exporter, error) { +func newHTTPExporter(ctx context.Context, config *types.OTelHTTP) (sdkmetric.Exporter, error) { endpoint, err := url.Parse(config.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", config.Endpoint, err) @@ -269,7 +269,7 @@ func newHTTPExporter(ctx context.Context, config *types.OtelHTTP) (sdkmetric.Exp return otlpmetrichttp.New(ctx, opts...) } -func newGRPCExporter(ctx context.Context, config *types.OtelGRPC) (sdkmetric.Exporter, error) { +func newGRPCExporter(ctx context.Context, config *types.OTelGRPC) (sdkmetric.Exporter, error) { host, port, err := net.SplitHostPort(config.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", config.Endpoint, err) diff --git a/pkg/metrics/opentelemetry_test.go b/pkg/metrics/otel_test.go similarity index 99% rename from pkg/metrics/opentelemetry_test.go rename to pkg/metrics/otel_test.go index 61f1cb9d80..4337fb40f6 100644 --- a/pkg/metrics/opentelemetry_test.go +++ b/pkg/metrics/otel_test.go @@ -327,7 +327,7 @@ func TestOpenTelemetry(t *testing.T) { var cfg types.OTLP (&cfg).SetDefaults() cfg.AddRoutersLabels = true - cfg.HTTP = &types.OtelHTTP{ + cfg.HTTP = &types.OTelHTTP{ Endpoint: ts.URL, } cfg.PushInterval = ptypes.Duration(10 * time.Millisecond) diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index d6055795e0..dec5b29009 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -23,6 +23,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/capture" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" + "go.opentelemetry.io/contrib/bridges/otellogrus" ) type key string @@ -52,6 +53,7 @@ func (n noopCloser) Close() error { } type handlerParams struct { + ctx context.Context logDataTable *LogData } @@ -106,6 +108,16 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { Level: logrus.InfoLevel, } + if config.OTLP != nil { + otelLoggerProvider, err := config.OTLP.NewLoggerProvider() + if err != nil { + return nil, fmt.Errorf("setting up OpenTelemetry logger provider: %w", err) + } + + logger.Hooks.Add(otellogrus.NewHook("traefik", otellogrus.WithLoggerProvider(otelLoggerProvider))) + logger.Out = io.Discard + } + // Transform header names to a canonical form, to be used as is without further transformations, // and transform field names to lower case, to enable case-insensitive lookup. if config.Fields != nil { @@ -150,7 +162,7 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { go func() { defer logHandler.wg.Done() for handlerParams := range logHandler.logHandlerChan { - logHandler.logTheRoundTrip(handlerParams.logDataTable) + logHandler.logTheRoundTrip(handlerParams.ctx, handlerParams.logDataTable) } }() } @@ -256,12 +268,13 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http if h.config.BufferingSize > 0 { h.logHandlerChan <- handlerParams{ + ctx: req.Context(), logDataTable: logDataTable, } return } - h.logTheRoundTrip(logDataTable) + h.logTheRoundTrip(req.Context(), logDataTable) }() next.ServeHTTP(rw, reqWithDataTable) @@ -313,7 +326,7 @@ func usernameIfPresent(theURL *url.URL) string { } // Logging handler to log frontend name, backend name, and elapsed time. -func (h *Handler) logTheRoundTrip(logDataTable *LogData) { +func (h *Handler) logTheRoundTrip(ctx context.Context, logDataTable *LogData) { core := logDataTable.Core retryAttempts, ok := core[RetryAttempts].(int) @@ -359,7 +372,7 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData) { h.mu.Lock() defer h.mu.Unlock() - h.logger.WithFields(fields).Println() + h.logger.WithContext(ctx).WithFields(fields).Println() } } diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 7f18bc188f..0bd2e47375 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -2,6 +2,7 @@ package accesslog import ( "bytes" + "compress/gzip" "context" "crypto/tls" "crypto/x509" @@ -25,6 +26,8 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/middlewares/capture" "github.com/traefik/traefik/v3/pkg/types" + "go.opentelemetry.io/collector/pdata/plog/plogotlp" + "go.opentelemetry.io/otel/trace" ) const delta float64 = 1e-10 @@ -49,6 +52,75 @@ var ( testStart = time.Now() ) +func TestOTelAccessLog(t *testing.T) { + logCh := make(chan string) + collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gzr, err := gzip.NewReader(r.Body) + require.NoError(t, err) + + body, err := io.ReadAll(gzr) + require.NoError(t, err) + + req := plogotlp.NewExportRequest() + err = req.UnmarshalProto(body) + require.NoError(t, err) + + marshalledReq, err := json.Marshal(req) + require.NoError(t, err) + + logCh <- string(marshalledReq) + })) + t.Cleanup(collector.Close) + + config := &types.AccessLog{ + OTLP: &types.OTelLog{ + ServiceName: "test", + ResourceAttributes: map[string]string{"resource": "attribute"}, + HTTP: &types.OTelHTTP{ + Endpoint: collector.URL, + }, + }, + } + logHandler, err := NewHandler(config) + require.NoError(t, err) + t.Cleanup(func() { + err := logHandler.Close() + require.NoError(t, err) + }) + + req := &http.Request{ + Header: map[string][]string{}, + URL: &url.URL{ + Path: testPath, + }, + } + ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + })) + req = req.WithContext(ctx) + + chain := alice.New() + chain = chain.Append(capture.Wrap) + chain = chain.Append(WrapHandler(logHandler)) + handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + require.NoError(t, err) + handler.ServeHTTP(httptest.NewRecorder(), req) + + select { + case <-time.After(5 * time.Second): + t.Error("AccessLog not exported") + + case log := <-logCh: + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `{"key":"DownstreamStatus","value":{"intValue":"200"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + } +} + func TestLogRotation(t *testing.T) { fileName := filepath.Join(t.TempDir(), "traefik.log") rotatedFileName := fileName + ".rotated" diff --git a/pkg/middlewares/observability/entrypoint.go b/pkg/middlewares/observability/entrypoint.go index 4849f25b2d..5d1d1b8776 100644 --- a/pkg/middlewares/observability/entrypoint.go +++ b/pkg/middlewares/observability/entrypoint.go @@ -9,6 +9,7 @@ import ( "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" @@ -64,7 +65,11 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) start := time.Now() tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start)) - req = req.WithContext(tracingCtx) + // Associate the request context with the logger. + logger := log.Ctx(tracingCtx).With().Ctx(tracingCtx).Logger() + loggerCtx := logger.WithContext(tracingCtx) + + req = req.WithContext(loggerCtx) span.SetAttributes(attribute.String("entry_point", e.entryPoint)) diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index b19e6d4a25..f26acd329b 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -30,7 +30,6 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kv/zk" "github.com/traefik/traefik/v3/pkg/provider/rest" traefiktls "github.com/traefik/traefik/v3/pkg/tls" - "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" "github.com/traefik/traefik/v3/pkg/types" ) @@ -831,6 +830,25 @@ func TestDo_staticConfiguration(t *testing.T) { MaxAge: 3, MaxBackups: 4, Compress: true, + OTLP: &types.OTelLog{ + ServiceName: "foobar", + ResourceAttributes: map[string]string{ + "foobar": "foobar", + }, + GRPC: &types.OTelGRPC{ + Endpoint: "foobar", + Insecure: true, + Headers: map[string]string{ + "foobar": "foobar", + }, + }, + HTTP: &types.OTelHTTP{ + Endpoint: "foobar", + Headers: map[string]string{ + "foobar": "foobar", + }, + }, + }, } config.AccessLog = &types.AccessLog{ @@ -854,18 +872,46 @@ func TestDo_staticConfiguration(t *testing.T) { }, }, BufferingSize: 42, + OTLP: &types.OTelLog{ + ServiceName: "foobar", + ResourceAttributes: map[string]string{ + "foobar": "foobar", + }, + GRPC: &types.OTelGRPC{ + Endpoint: "foobar", + Insecure: true, + Headers: map[string]string{ + "foobar": "foobar", + }, + }, + HTTP: &types.OTelHTTP{ + Endpoint: "foobar", + Headers: map[string]string{ + "foobar": "foobar", + }, + }, + }, } config.Tracing = &static.Tracing{ ServiceName: "myServiceName", + ResourceAttributes: map[string]string{ + "foobar": "foobar", + }, GlobalAttributes: map[string]string{ "foobar": "foobar", }, SampleRate: 42, - OTLP: &opentelemetry.Config{ - HTTP: &types.OtelHTTP{ + OTLP: &types.OTelTracing{ + HTTP: &types.OTelHTTP{ + Endpoint: "foobar", + Headers: map[string]string{ + "foobar": "foobar", + }, + }, + GRPC: &types.OTelGRPC{ Endpoint: "foobar", - TLS: nil, + Insecure: true, Headers: map[string]string{ "foobar": "foobar", }, diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json index 1dbd65fd7d..afd1d19b8f 100644 --- a/pkg/redactor/testdata/anonymized-static-config.json +++ b/pkg/redactor/testdata/anonymized-static-config.json @@ -315,7 +315,17 @@ "maxSize": 5, "maxAge": 3, "maxBackups": 4, - "compress": true + "compress": true, + "otlp": { + "serviceName": "foobar", + "grpc": { + "endpoint": "xxxx", + "insecure": true + }, + "http": { + "endpoint": "xxxx" + } + } }, "accessLog": { "filePath": "xxxx", @@ -340,18 +350,35 @@ } } }, - "bufferingSize": 42 + "bufferingSize": 42, + "otlp": { + "serviceName": "foobar", + "grpc": { + "endpoint": "xxxx", + "insecure": true + }, + "http": { + "endpoint": "xxxx" + } + } }, "tracing": { "serviceName": "myServiceName", - "globalAttributes": { + "resourceAttributes": { "foobar": "foobar" }, "sampleRate": 42, "otlp": { + "grpc": { + "endpoint": "xxxx", + "insecure": true + }, "http": { "endpoint": "xxxx" } + }, + "globalAttributes": { + "foobar": "foobar" } }, "hostResolver": { @@ -370,11 +397,11 @@ "certificatesDuration": 42, "dnsChallenge": { "provider": "DNSProvider", - "delayBeforeCheck": "42ns", "resolvers": [ "xxxx", "xxxx" ], + "delayBeforeCheck": "42ns", "disablePropagationCheck": true }, "httpChallenge": { diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go index cf8386999a..902938acb4 100644 --- a/pkg/server/middleware/observability.go +++ b/pkg/server/middleware/observability.go @@ -54,6 +54,12 @@ func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName stri } } + // 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.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName) { chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware)) chain = chain.Append(func(next http.Handler) (http.Handler, error) { @@ -61,10 +67,6 @@ func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName stri }) } - 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.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName) { metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName) diff --git a/pkg/tracing/opentelemetry/opentelemetry_test.go b/pkg/tracing/opentelemetry/opentelemetry_test.go deleted file mode 100644 index 3d6ebfd164..0000000000 --- a/pkg/tracing/opentelemetry/opentelemetry_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package opentelemetry_test - -import ( - "compress/gzip" - "context" - "encoding/json" - "io" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/containous/alice" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "github.com/traefik/traefik/v3/pkg/tracing" - "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" - "github.com/traefik/traefik/v3/pkg/types" - "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" -) - -func TestTracing(t *testing.T) { - tests := []struct { - desc string - propagators string - headers map[string]string - wantServiceHeadersFn func(t *testing.T, headers http.Header) - assertFn func(*testing.T, string) - }{ - { - desc: "service name and version", - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `({"key":"service.name","value":{"stringValue":"traefik"}})`, trace) - assert.Regexp(t, `({"key":"service.version","value":{"stringValue":"dev"}})`, trace) - }, - }, - { - desc: "TraceContext propagation", - propagators: "tracecontext", - headers: map[string]string{ - "traceparent": "00-00000000000000000000000000000001-0000000000000001-01", - "tracestate": "foo=bar", - }, - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(00-00000000000000000000000000000001-\w{16}-01)`, headers["Traceparent"][0]) - assert.Equal(t, []string{"foo=bar"}, headers["Tracestate"]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"0000000000000001")`, trace) - assert.Regexp(t, `("traceState":"foo=bar")`, trace) - }, - }, - { - desc: "root span TraceContext propagation", - propagators: "tracecontext", - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(00-\w{32}-\w{16}-01)`, headers["Traceparent"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - { - desc: "B3 propagation", - propagators: "b3", - headers: map[string]string{ - "b3": "00000000000000000000000000000001-0000000000000002-1-0000000000000001", - }, - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(00000000000000000000000000000001-\w{16}-1)`, headers["B3"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"0000000000000002")`, trace) - }, - }, - { - desc: "root span B3 propagation", - propagators: "b3", - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(\w{32}-\w{16}-1)`, headers["B3"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - { - desc: "B3 propagation Multiple Headers", - propagators: "b3multi", - headers: map[string]string{ - "x-b3-traceid": "00000000000000000000000000000001", - "x-b3-parentspanid": "0000000000000001", - "x-b3-spanid": "0000000000000002", - "x-b3-sampled": "1", - }, - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Equal(t, "00000000000000000000000000000001", headers["X-B3-Traceid"][0]) - assert.Equal(t, "0000000000000001", headers["X-B3-Parentspanid"][0]) - assert.Equal(t, "1", headers["X-B3-Sampled"][0]) - assert.Len(t, headers["X-B3-Spanid"][0], 16) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"0000000000000002")`, trace) - }, - }, - { - desc: "root span B3 propagation Multiple Headers", - propagators: "b3multi", - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(\w{32})`, headers["X-B3-Traceid"][0]) - assert.Equal(t, "1", headers["X-B3-Sampled"][0]) - assert.Regexp(t, `(\w{16})`, headers["X-B3-Spanid"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"")`, trace) - }, - }, - { - desc: "Baggage propagation", - propagators: "baggage", - headers: map[string]string{ - "baggage": "userId=id", - }, - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Equal(t, []string{"userId=id"}, headers["Baggage"]) - }, - }, - { - desc: "Jaeger propagation", - propagators: "jaeger", - headers: map[string]string{ - "uber-trace-id": "00000000000000000000000000000001:0000000000000002:0000000000000001:1", - }, - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(00000000000000000000000000000001:\w{16}:0:1)`, headers["Uber-Trace-Id"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - { - desc: "root span Jaeger propagation", - propagators: "jaeger", - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(\w{32}:\w{16}:0:1)`, headers["Uber-Trace-Id"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - { - desc: "XRay propagation", - propagators: "xray", - headers: map[string]string{ - "X-Amzn-Trace-Id": "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1", - }, - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(Root=1-5759e988-bd862e3fe1be46a994272793;Parent=\w{16};Sampled=1)`, headers["X-Amzn-Trace-Id"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"5759e988bd862e3fe1be46a994272793")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - { - desc: "root span XRay propagation", - propagators: "xray", - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Regexp(t, `(Root=1-\w{8}-\w{24};Parent=\w{16};Sampled=1)`, headers["X-Amzn-Trace-Id"][0]) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - { - desc: "no propagation", - propagators: "none", - wantServiceHeadersFn: func(t *testing.T, headers http.Header) { - t.Helper() - - assert.Empty(t, headers) - }, - assertFn: func(t *testing.T, trace string) { - t.Helper() - - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) - }, - }, - } - - traceCh := make(chan string) - collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gzr, err := gzip.NewReader(r.Body) - require.NoError(t, err) - - body, err := io.ReadAll(gzr) - require.NoError(t, err) - - req := ptraceotlp.NewExportRequest() - err = req.UnmarshalProto(body) - require.NoError(t, err) - - marshalledReq, err := json.Marshal(req) - require.NoError(t, err) - - traceCh <- string(marshalledReq) - })) - t.Cleanup(collector.Close) - - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - t.Setenv("OTEL_PROPAGATORS", test.propagators) - - service := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tracer := tracing.TracerFromContext(r.Context()) - ctx, span := tracer.Start(r.Context(), "service") - defer span.End() - - r = r.WithContext(ctx) - - tracing.InjectContextIntoCarrier(r) - - if test.wantServiceHeadersFn != nil { - test.wantServiceHeadersFn(t, r.Header) - } - }) - - tracingConfig := &static.Tracing{ - ServiceName: "traefik", - SampleRate: 1.0, - OTLP: &opentelemetry.Config{ - HTTP: &types.OtelHTTP{ - Endpoint: collector.URL, - }, - }, - } - - newTracing, closer, err := tracing.NewTracing(tracingConfig) - require.NoError(t, err) - t.Cleanup(func() { - _ = closer.Close() - }) - - chain := alice.New(observability.WrapEntryPointHandler(context.Background(), newTracing, nil, "test")) - epHandler, err := chain.Then(service) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "http://www.test.com", nil) - for k, v := range test.headers { - req.Header.Set(k, v) - } - - rw := httptest.NewRecorder() - - epHandler.ServeHTTP(rw, req) - - select { - case <-time.After(10 * time.Second): - t.Error("Trace not exported") - - case trace := <-traceCh: - assert.Equal(t, http.StatusOK, rw.Code) - if test.assertFn != nil { - test.assertFn(t, trace) - } - } - }) - } -} diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index 1dc2f898d1..884c96b7ca 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -13,7 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/tracing/opentelemetry" + "github.com/traefik/traefik/v3/pkg/types" "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -38,7 +38,7 @@ func NewTracing(conf *static.Tracing) (*Tracer, io.Closer, error) { if backend == nil { log.Debug().Msg("Could not initialize tracing, using OpenTelemetry by default") - defaultBackend := &opentelemetry.Config{} + defaultBackend := &types.OTelTracing{} backend = defaultBackend } diff --git a/pkg/tracing/tracing_test.go b/pkg/tracing/tracing_test.go index c9b4a511c8..f6d69a7165 100644 --- a/pkg/tracing/tracing_test.go +++ b/pkg/tracing/tracing_test.go @@ -1,10 +1,22 @@ package tracing import ( + "compress/gzip" + "encoding/json" + "io" + "net/http" + "net/http/httptest" "net/url" "testing" + "time" + "github.com/containous/alice" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/types" + "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" + "go.opentelemetry.io/otel/trace" ) func Test_safeFullURL(t *testing.T) { @@ -55,3 +67,315 @@ func Test_safeFullURL(t *testing.T) { }) } } + +func TestTracing(t *testing.T) { + tests := []struct { + desc string + propagators string + headers map[string]string + wantServiceHeadersFn func(t *testing.T, headers http.Header) + assertFn func(*testing.T, string) + }{ + { + desc: "service name and version", + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `({"key":"service.name","value":{"stringValue":"traefik"}})`, trace) + assert.Regexp(t, `({"key":"service.version","value":{"stringValue":"dev"}})`, trace) + }, + }, + { + desc: "TraceContext propagation", + propagators: "tracecontext", + headers: map[string]string{ + "traceparent": "00-00000000000000000000000000000001-0000000000000001-01", + "tracestate": "foo=bar", + }, + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(00-00000000000000000000000000000001-\w{16}-01)`, headers["Traceparent"][0]) + assert.Equal(t, []string{"foo=bar"}, headers["Tracestate"]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) + assert.Regexp(t, `("parentSpanId":"0000000000000001")`, trace) + assert.Regexp(t, `("traceState":"foo=bar")`, trace) + }, + }, + { + desc: "root span TraceContext propagation", + propagators: "tracecontext", + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(00-\w{32}-\w{16}-01)`, headers["Traceparent"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"\w{32}")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + { + desc: "B3 propagation", + propagators: "b3", + headers: map[string]string{ + "b3": "00000000000000000000000000000001-0000000000000002-1-0000000000000001", + }, + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(00000000000000000000000000000001-\w{16}-1)`, headers["B3"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) + assert.Regexp(t, `("parentSpanId":"0000000000000002")`, trace) + }, + }, + { + desc: "root span B3 propagation", + propagators: "b3", + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(\w{32}-\w{16}-1)`, headers["B3"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"\w{32}")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + { + desc: "B3 propagation Multiple Headers", + propagators: "b3multi", + headers: map[string]string{ + "x-b3-traceid": "00000000000000000000000000000001", + "x-b3-parentspanid": "0000000000000001", + "x-b3-spanid": "0000000000000002", + "x-b3-sampled": "1", + }, + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Equal(t, "00000000000000000000000000000001", headers["X-B3-Traceid"][0]) + assert.Equal(t, "0000000000000001", headers["X-B3-Parentspanid"][0]) + assert.Equal(t, "1", headers["X-B3-Sampled"][0]) + assert.Len(t, headers["X-B3-Spanid"][0], 16) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) + assert.Regexp(t, `("parentSpanId":"0000000000000002")`, trace) + }, + }, + { + desc: "root span B3 propagation Multiple Headers", + propagators: "b3multi", + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(\w{32})`, headers["X-B3-Traceid"][0]) + assert.Equal(t, "1", headers["X-B3-Sampled"][0]) + assert.Regexp(t, `(\w{16})`, headers["X-B3-Spanid"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"\w{32}")`, trace) + assert.Regexp(t, `("parentSpanId":"")`, trace) + }, + }, + { + desc: "Baggage propagation", + propagators: "baggage", + headers: map[string]string{ + "baggage": "userId=id", + }, + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Equal(t, []string{"userId=id"}, headers["Baggage"]) + }, + }, + { + desc: "Jaeger propagation", + propagators: "jaeger", + headers: map[string]string{ + "uber-trace-id": "00000000000000000000000000000001:0000000000000002:0000000000000001:1", + }, + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(00000000000000000000000000000001:\w{16}:0:1)`, headers["Uber-Trace-Id"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + { + desc: "root span Jaeger propagation", + propagators: "jaeger", + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(\w{32}:\w{16}:0:1)`, headers["Uber-Trace-Id"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"\w{32}")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + { + desc: "XRay propagation", + propagators: "xray", + headers: map[string]string{ + "X-Amzn-Trace-Id": "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1", + }, + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(Root=1-5759e988-bd862e3fe1be46a994272793;Parent=\w{16};Sampled=1)`, headers["X-Amzn-Trace-Id"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"5759e988bd862e3fe1be46a994272793")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + { + desc: "root span XRay propagation", + propagators: "xray", + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Regexp(t, `(Root=1-\w{8}-\w{24};Parent=\w{16};Sampled=1)`, headers["X-Amzn-Trace-Id"][0]) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"\w{32}")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + { + desc: "no propagation", + propagators: "none", + wantServiceHeadersFn: func(t *testing.T, headers http.Header) { + t.Helper() + + assert.Empty(t, headers) + }, + assertFn: func(t *testing.T, trace string) { + t.Helper() + + assert.Regexp(t, `("traceId":"\w{32}")`, trace) + assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + }, + }, + } + + traceCh := make(chan string) + collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gzr, err := gzip.NewReader(r.Body) + require.NoError(t, err) + + body, err := io.ReadAll(gzr) + require.NoError(t, err) + + req := ptraceotlp.NewExportRequest() + err = req.UnmarshalProto(body) + require.NoError(t, err) + + marshalledReq, err := json.Marshal(req) + require.NoError(t, err) + + traceCh <- string(marshalledReq) + })) + t.Cleanup(collector.Close) + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Setenv("OTEL_PROPAGATORS", test.propagators) + + service := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tracer := TracerFromContext(r.Context()) + ctx, span := tracer.Start(r.Context(), "service") + defer span.End() + + r = r.WithContext(ctx) + + InjectContextIntoCarrier(r) + + if test.wantServiceHeadersFn != nil { + test.wantServiceHeadersFn(t, r.Header) + } + }) + + tracingConfig := &static.Tracing{ + ServiceName: "traefik", + SampleRate: 1.0, + OTLP: &types.OTelTracing{ + HTTP: &types.OTelHTTP{ + Endpoint: collector.URL, + }, + }, + } + + tracer, closer, err := NewTracing(tracingConfig) + require.NoError(t, err) + t.Cleanup(func() { + _ = closer.Close() + }) + + chain := alice.New(func(next http.Handler) (http.Handler, error) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tracingCtx := ExtractCarrierIntoContext(r.Context(), r.Header) + start := time.Now() + tracingCtx, span := tracer.Start(tracingCtx, "test", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start)) + end := time.Now() + span.End(trace.WithTimestamp(end)) + next.ServeHTTP(w, r.WithContext(tracingCtx)) + }), nil + }) + + epHandler, err := chain.Then(service) + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodGet, "http://www.test.com", nil) + for k, v := range test.headers { + req.Header.Set(k, v) + } + + rw := httptest.NewRecorder() + + epHandler.ServeHTTP(rw, req) + + select { + case <-time.After(10 * time.Second): + t.Error("Trace not exported") + + case trace := <-traceCh: + assert.Equal(t, http.StatusOK, rw.Code) + if test.assertFn != nil { + test.assertFn(t, trace) + } + } + }) + } +} diff --git a/pkg/types/logs.go b/pkg/types/logs.go index e0a50ec712..87aef4e404 100644 --- a/pkg/types/logs.go +++ b/pkg/types/logs.go @@ -1,6 +1,22 @@ package types -import "github.com/traefik/paerser/types" +import ( + "context" + "fmt" + "net" + "net/url" + + "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v3/pkg/version" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + otelsdk "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding/gzip" +) const ( // AccessLogKeep is the keep string value. @@ -14,11 +30,10 @@ const ( const ( // CommonFormat is the common logging format (CLF). CommonFormat string = "common" - - // JSONFormat is the JSON logging format. - JSONFormat string = "json" ) +const OTelTraefikServiceName = "traefik" + // TraefikLog holds the configuration settings for the traefik logger. type TraefikLog struct { Level string `description:"Log level set to traefik logs." json:"level,omitempty" toml:"level,omitempty" yaml:"level,omitempty" export:"true"` @@ -30,6 +45,8 @@ type TraefikLog struct { MaxAge int `description:"Maximum number of days to retain old log files based on the timestamp encoded in their filename." json:"maxAge,omitempty" toml:"maxAge,omitempty" yaml:"maxAge,omitempty" export:"true"` MaxBackups int `description:"Maximum number of old log files to retain." json:"maxBackups,omitempty" toml:"maxBackups,omitempty" yaml:"maxBackups,omitempty" export:"true"` Compress bool `description:"Determines if the rotated log files should be compressed using gzip." json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" export:"true"` + + OTLP *OTelLog `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // SetDefaults sets the default values. @@ -46,6 +63,8 @@ type AccessLog struct { Fields *AccessLogFields `description:"AccessLogFields." json:"fields,omitempty" toml:"fields,omitempty" yaml:"fields,omitempty" export:"true"` BufferingSize int64 `description:"Number of access log lines to process in a buffered way." json:"bufferingSize,omitempty" toml:"bufferingSize,omitempty" yaml:"bufferingSize,omitempty" export:"true"` AddInternals bool `description:"Enables access log for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` + + OTLP *OTelLog `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // SetDefaults sets the default values. @@ -128,3 +147,123 @@ func checkFieldHeaderValue(value, defaultValue string) string { } return defaultValue } + +// OTelLog provides configuration settings for the open-telemetry logger. +type OTelLog struct { + ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + ResourceAttributes map[string]string `description:"Defines additional resource attributes (key:value)." json:"resourceAttributes,omitempty" toml:"resourceAttributes,omitempty" yaml:"resourceAttributes,omitempty"` + GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + HTTP *OTelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` +} + +// SetDefaults sets the default values. +func (o *OTelLog) SetDefaults() { + o.ServiceName = OTelTraefikServiceName + o.HTTP = &OTelHTTP{} + o.HTTP.SetDefaults() +} + +// NewLoggerProvider creates a new OpenTelemetry logger provider. +func (o *OTelLog) NewLoggerProvider() (*otelsdk.LoggerProvider, error) { + var ( + err error + exporter otelsdk.Exporter + ) + if o.GRPC != nil { + exporter, err = o.buildGRPCExporter() + } else { + exporter, err = o.buildHTTPExporter() + } + if err != nil { + return nil, fmt.Errorf("setting up exporter: %w", err) + } + + attr := []attribute.KeyValue{ + semconv.ServiceNameKey.String(o.ServiceName), + semconv.ServiceVersionKey.String(version.Version), + } + + for k, v := range o.ResourceAttributes { + attr = append(attr, attribute.String(k, v)) + } + + res, err := resource.New(context.Background(), + resource.WithAttributes(attr...), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + resource.WithOSType(), + resource.WithProcessCommandArgs(), + ) + if err != nil { + return nil, fmt.Errorf("building resource: %w", err) + } + + // Register the trace provider to allow the global logger to access it. + bp := otelsdk.NewBatchProcessor(exporter) + loggerProvider := otelsdk.NewLoggerProvider( + otelsdk.WithResource(res), + otelsdk.WithProcessor(bp), + ) + + return loggerProvider, nil +} + +func (o *OTelLog) buildHTTPExporter() (*otlploghttp.Exporter, error) { + endpoint, err := url.Parse(o.HTTP.Endpoint) + if err != nil { + return nil, fmt.Errorf("invalid collector endpoint %q: %w", o.HTTP.Endpoint, err) + } + + opts := []otlploghttp.Option{ + otlploghttp.WithEndpoint(endpoint.Host), + otlploghttp.WithHeaders(o.HTTP.Headers), + otlploghttp.WithCompression(otlploghttp.GzipCompression), + } + + if endpoint.Scheme == "http" { + opts = append(opts, otlploghttp.WithInsecure()) + } + + if endpoint.Path != "" { + opts = append(opts, otlploghttp.WithURLPath(endpoint.Path)) + } + + if o.HTTP.TLS != nil { + tlsConfig, err := o.HTTP.TLS.CreateTLSConfig(context.Background()) + if err != nil { + return nil, fmt.Errorf("creating TLS client config: %w", err) + } + + opts = append(opts, otlploghttp.WithTLSClientConfig(tlsConfig)) + } + + return otlploghttp.New(context.Background(), opts...) +} + +func (o *OTelLog) buildGRPCExporter() (*otlploggrpc.Exporter, error) { + host, port, err := net.SplitHostPort(o.GRPC.Endpoint) + if err != nil { + return nil, fmt.Errorf("invalid collector endpoint %q: %w", o.GRPC.Endpoint, err) + } + + opts := []otlploggrpc.Option{ + otlploggrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), + otlploggrpc.WithHeaders(o.GRPC.Headers), + otlploggrpc.WithCompressor(gzip.Name), + } + + if o.GRPC.Insecure { + opts = append(opts, otlploggrpc.WithInsecure()) + } + + if o.GRPC.TLS != nil { + tlsConfig, err := o.GRPC.TLS.CreateTLSConfig(context.Background()) + if err != nil { + return nil, fmt.Errorf("creating TLS client config: %w", err) + } + + opts = append(opts, otlploggrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) + } + + return otlploggrpc.New(context.Background(), opts...) +} diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index 88b62c0031..8ca8dec68d 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -108,8 +108,8 @@ func (i *InfluxDB2) SetDefaults() { // OTLP contains specific configuration used by the OpenTelemetry Metrics exporter. type OTLP struct { - GRPC *OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - HTTP *OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + HTTP *OTelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"` @@ -121,14 +121,14 @@ type OTLP struct { // SetDefaults sets the default values. func (o *OTLP) SetDefaults() { - o.HTTP = &OtelHTTP{} + o.HTTP = &OTelHTTP{} o.HTTP.SetDefaults() o.AddEntryPointsLabels = true o.AddServicesLabels = true o.ExplicitBoundaries = []float64{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10} o.PushInterval = types.Duration(10 * time.Second) - o.ServiceName = "traefik" + o.ServiceName = OTelTraefikServiceName } // Statistics provides options for monitoring request and response stats. @@ -140,28 +140,3 @@ type Statistics struct { func (s *Statistics) SetDefaults() { s.RecentErrors = 10 } - -// OtelGRPC provides configuration settings for the gRPC open-telemetry. -type OtelGRPC struct { - Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` - TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` - Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` -} - -// SetDefaults sets the default values. -func (c *OtelGRPC) SetDefaults() { - c.Endpoint = "localhost:4317" -} - -// OtelHTTP provides configuration settings for the HTTP open-telemetry. -type OtelHTTP struct { - Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/path) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` - Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` -} - -// SetDefaults sets the default values. -func (c *OtelHTTP) SetDefaults() { - c.Endpoint = "https://localhost:4318" -} diff --git a/pkg/types/otel.go b/pkg/types/otel.go new file mode 100644 index 0000000000..281d83e1d4 --- /dev/null +++ b/pkg/types/otel.go @@ -0,0 +1,26 @@ +package types + +// OTelGRPC provides configuration settings for the gRPC open-telemetry. +type OTelGRPC struct { + Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` +} + +// SetDefaults sets the default values. +func (o *OTelGRPC) SetDefaults() { + o.Endpoint = "localhost:4317" +} + +// OTelHTTP provides configuration settings for the HTTP open-telemetry. +type OTelHTTP struct { + Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/path) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` +} + +// SetDefaults sets the default values. +func (o *OTelHTTP) SetDefaults() { + o.Endpoint = "https://localhost:4318" +} diff --git a/pkg/tracing/opentelemetry/opentelemetry.go b/pkg/types/tracing.go similarity index 80% rename from pkg/tracing/opentelemetry/opentelemetry.go rename to pkg/types/tracing.go index cc15335240..2346e3b69c 100644 --- a/pkg/tracing/opentelemetry/opentelemetry.go +++ b/pkg/types/tracing.go @@ -1,4 +1,4 @@ -package opentelemetry +package types import ( "context" @@ -9,7 +9,6 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -18,26 +17,26 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" ) -// Config provides configuration settings for the open-telemetry tracer. -type Config struct { - GRPC *types.OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - HTTP *types.OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` +// OTelTracing provides configuration settings for the open-telemetry tracer. +type OTelTracing struct { + GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + HTTP *OTelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // SetDefaults sets the default values. -func (c *Config) SetDefaults() { - c.HTTP = &types.OtelHTTP{} +func (c *OTelTracing) SetDefaults() { + c.HTTP = &OTelHTTP{} c.HTTP.SetDefaults() } // Setup sets up the tracer. -func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string) (trace.Tracer, io.Closer, error) { +func (c *OTelTracing) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string) (trace.Tracer, io.Closer, error) { var ( err error exporter *otlptrace.Exporter @@ -87,7 +86,7 @@ func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes return tracerProvider.Tracer("github.com/traefik/traefik"), &tpCloser{provider: tracerProvider}, err } -func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) { +func (c *OTelTracing) setupHTTPExporter() (*otlptrace.Exporter, error) { endpoint, err := url.Parse(c.HTTP.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", c.HTTP.Endpoint, err) @@ -119,7 +118,7 @@ func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) { return otlptrace.New(context.Background(), otlptracehttp.NewClient(opts...)) } -func (c *Config) setupGRPCExporter() (*otlptrace.Exporter, error) { +func (c *OTelTracing) setupGRPCExporter() (*otlptrace.Exporter, error) { host, port, err := net.SplitHostPort(c.GRPC.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", c.GRPC.Endpoint, err)