Repository: traefik/traefik (language: go)

Issue:
Support logging via OpenTelemetry
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you expect to see?

I was very happy when I recently discovered that Traefik Proxy v3 brings support for exporting traces and metrics to the OpenTelemetry collector. Given that one of the other big ticket items for v3 is improved logging, why not also support exporting logs via OTLP to the collector?

In our environments we route all telemetry via collectors where possible. This strategy allows the collector to act as an abstraction over whatever services we then forward the logs, metrics and traces out to. It also gives us one place to manage adding/removing labels, and filtering out noise. Overall, supporting applications and the platform is made simpler.

An example of a Traefik problem I would like to solve using logging to OTLP is how the Traefik and access logs are handled today. I'm in a kubernetes environment, and can only scrape logs output from Traefik currently. Traefik doesn't apply any metadata to the logs it outputs so it isn't easy to separate the two log types during collection or querying. If logs were instead pushed via OTLP with attributes marking the log type, this would be solved.



Discussion / hints:
why not also support exporting logs via OTLP to the collector?

opentelemetry-go [does not support logging yet.](https://github.com/open-telemetry/opentelemetry-go/tree/main#project-status)
In this time, opentelemetry-go logs promoted to beta.
Hey @arukiidou.

Thank you for the update.

We are interested in this issue, but we’re focusing on other topics.
For such a reason, if you or another community member would like to build it, let us know, and we will work with you to make sure you have all the information needed.

Let us know here before you start. We prefer to work with our community members at the beginning of the design process to ensure that we are aligned and can move quickly with the review and merge process.


Fix patch:
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="`<scheme>://<host>:<port><path>`"_
+
+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="`<host>:<port>`"_
+
+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="`<scheme>://<host>:<port><path>`"_
+
+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="`<host>:<port>`"_
+
+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="`<scheme>://<host>:<port><path>`"_
+_Optional, Default="https://localhost:4318/v1/metrics", Format="`<scheme>://<host>:<port><path>`"_
 
 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="`<scheme>://<host>:<port><path>`"_
+_Optional, Default="https://localhost:4318/v1/traces", Format="`<scheme>://<host>:<port><path>`"_
 
 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.<name>`:  
+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.<name>`:  
+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.<name>`:  
+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.<name>`:  
+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.<name>`:  
+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.<name>`:  
+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.<name>`:  
-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.<name>`:  
+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_<NAME>`:  
+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_<NAME>`:  
+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_<NAME>`:  
+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_<NAME>`:  
+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_<NAME>`:  
+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_<NAME>`:  
+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_<NAME>`:  
-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_<NAME>`:  
+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/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/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/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/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/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/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)


Test patch:
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_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_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/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/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_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)
+				}
+			}
+		})
+	}
+}