Repository: traefik/traefik (language: go)

Issue:
Filtering out ping access logs
<!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! -->

### Do you want to request a *feature* or report a *bug*?

<!--
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.

The issue tracker is for reporting bugs and feature requests only.
For end-user related support questions, please refer to one of the following:

- the Traefik community forum: https://community.containo.us/

-->

Feature

### What did you expect to see?
I would like for the option of filtering out calls to /ping in the access log.
My readiness checks in Kubernetes are called very frequently, so having all these calls to /ping in the access log clutters the view.
A possible solution is adding additional filtering of access logs, for example based on RequestPath (e.g.: /ping), RouterName (e.g.: ping@internal), or RequestPort (e.g.: 8080).

I cannot find any issue mentioning this, and only a dead community post: https://community.containo.us/t/is-there-a-way-to-filter-out-ping-requests-in-access-logs-output/4356
<!--

HOW TO WRITE A GOOD ISSUE?

- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions which led you to report this issue: the context.
- The context should lead to something, an idea or a problem that you’re facing.
- Remain clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown

-->



Discussion / hints:
Hi Mikael, thank you for the feature request.

One possible solution to this would be to add a regex filter to the `accesslog` configuration provider.

(inspired by: https://docs.nginx.com/nginx/admin-guide/monitoring/logging/#enabling-conditional-logging)
> Hi Mikael, thank you for the feature request.
> 
> One possible solution to this would be to add a regex filter to the `accesslog` configuration provider.
> 
> (inspired by: https://docs.nginx.com/nginx/admin-guide/monitoring/logging/#enabling-conditional-logging)

That would do the trick. :)
I have the same problem but with prometheus /metrics endpoint, will be nice to filter it out. 
the same request
This would be a very valuable feature. On top of hiding useful information in a flood of irrelevant logs it also dramatically increases the *cost* of monitoring `traefik`. On my AKS cluster, monitoring the `traefik` logs with Azure Monitor is by far my biggest monitoring cost to the point where I will probably have to turn off access logs entirely.

![image](https://user-images.githubusercontent.com/881019/107311678-7e575700-6ada-11eb-9747-5fbb1035ffa7.png)

***xref:*** #6059
I use Traefik behind an AWS ALB. The ALB is configured to hit Traefik's /ping endpoint every 15 seconds. Since I always have at least 3 different ip's, this can fill my logs quickly. Would love to filter this somehow. I've managed to use `--accesslog.filters.minduration` as these requests take usually less than 1 ms but this also means that all internally produced codes (without any route request) will also not show, like 429 http codes on requests triggered by rate limiting. Ideally, a filter on /ping path or even the router name ping@internal would be great. Thanks.
Hi, I have the same problem and same request.

I would like this option, could be configurable per service with annotation ?

Thanks for your support
We're evaluating Traefik and the lack of ability to filter these "administrative" endpoints from access logs is very surprising.

In our case, we have Traefik behind NLB, deployed via Nomad & registered to Consul and monitored by Prometheus.

So the amount of access log generated by simply letting it run without any actual traffic hitting it is quite large:

- Target Group health checks
- Consul health checks
- Prometheus polling

I think the "cleanest" solution would be to add route filter that allows to filter out any access logs per HTTP route. For example, filter out `ping@internal`, `prometheus@internal`, etc.
Hey, I recently encountered the following issue.

It seems websockets can only use GET requests, meaning every time the bitwarden extension in my browser calls the websocket URL that's proxied by traefik, it also sends an access token in the GET request, which effectively saves the token in traefik's log files. I thought it would be a great idea to use some sort of log filtering mechanism by traefik, to ignore specific routers, or to ignore logs based on some other type of filtering.

[Issue for reference](https://github.com/bitwarden/clients/issues/4290)
Hello,

We want to address this (as well as #9170, including also the metrics) for the next major version (v3).
So far we are planning to remove the access logging for internal services completely.

Fix patch:
diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go
index 1274e5104f..0ddcceb003 100644
--- a/cmd/traefik/traefik.go
+++ b/cmd/traefik/traefik.go
@@ -193,10 +193,13 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
 
 	tsProviders := initTailscaleProviders(staticConfiguration, &providerAggregator)
 
-	// Metrics
+	// Observability
 
 	metricRegistries := registerMetricClients(staticConfiguration.Metrics)
 	metricsRegistry := metrics.NewMultiRegistry(metricRegistries)
+	accessLog := setupAccessLog(staticConfiguration.AccessLog)
+	tracer, tracerCloser := setupTracing(staticConfiguration.Tracing)
+	observabilityMgr := middleware.NewObservabilityMgr(*staticConfiguration, metricsRegistry, accessLog, tracer, tracerCloser)
 
 	// Entrypoints
 
@@ -263,14 +266,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
 	roundTripperManager := service.NewRoundTripperManager(spiffeX509Source)
 	dialerManager := tcp.NewDialerManager(spiffeX509Source)
 	acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider)
-	managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler)
+	managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, observabilityMgr, roundTripperManager, acmeHTTPHandler)
 
 	// Router factory
-	accessLog := setupAccessLog(staticConfiguration.AccessLog)
 
-	tracer, tracerCloser := setupTracing(staticConfiguration.Tracing)
-	chainBuilder := middleware.NewChainBuilder(metricsRegistry, accessLog, tracer)
-	routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry, dialerManager)
+	routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager)
 
 	// Watcher
 
@@ -351,7 +351,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
 		}
 	})
 
-	return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog, tracerCloser), nil
+	return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, observabilityMgr), nil
 }
 
 func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler {
diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md
index b9f7c5102b..08a45d7417 100644
--- a/docs/content/migration/v2-to-v3.md
+++ b/docs/content/migration/v2-to-v3.md
@@ -693,3 +693,13 @@ Here are two possible transition strategies:
     This allows continued compatibility with the existing infrastructure.
 
 Please check the [OpenTelemetry Tracing provider documention](../observability/tracing/opentelemetry.md) for more information.
+
+#### Internal Resources Observability (AccessLogs, Metrics and Tracing)
+
+In v3, observability for internal routers or services (e.g.: `ping@internal`) is disabled by default.
+To enable it one should use the new `addInternals` option for AccessLogs, Metrics or Tracing.
+Please take a look at the observability documentation for more information:
+
+- [AccessLogs](../observability/access-logs.md#addinternals)
+- [Metrics](../observability/metrics/overview.md#addinternals)
+- [AccessLogs](../observability/tracing/overview.md#addinternals)
diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md
index dac71805f7..beb114db10 100644
--- a/docs/content/observability/access-logs.md
+++ b/docs/content/observability/access-logs.md
@@ -26,6 +26,26 @@ accessLog: {}
 --accesslog=true
 ```
 
+### `addInternals`
+
+_Optional, Default="false"_
+
+Enables accessLogs for internal resources.
+
+```yaml tab="File (YAML)"
+accesslog:
+  addInternals: true
+```
+
+```toml tab="File (TOML)"
+[accesslog]
+  addInternals = true
+```
+
+```bash tab="CLI"
+--accesslog.addinternals
+```
+
 ### `filePath`
 
 By default access logs are written to the standard output.
diff --git a/docs/content/observability/metrics/overview.md b/docs/content/observability/metrics/overview.md
index 1d6f04b157..f968e77e6a 100644
--- a/docs/content/observability/metrics/overview.md
+++ b/docs/content/observability/metrics/overview.md
@@ -14,6 +14,28 @@ Traefik supports these metrics backends:
 
 Traefik Proxy hosts an official Grafana dashboard for both [on-premises](https://grafana.com/grafana/dashboards/17346) and [Kubernetes](https://grafana.com/grafana/dashboards/17347) deployments.
 
+## Common Options
+
+### `addInternals`
+
+_Optional, Default="false"_
+
+Enables metrics for internal resources.
+
+```yaml tab="File (YAML)"
+metrics:
+  addInternals: true
+```
+
+```toml tab="File (TOML)"
+[metrics]
+  addInternals = true
+```
+
+```bash tab="CLI"
+--metrics.addinternals
+```
+
 ## Global Metrics
 
 | Metric                     | Type  | [Labels](#labels)        | Description                                                        |
diff --git a/docs/content/observability/overview.md b/docs/content/observability/overview.md
new file mode 100644
index 0000000000..2de429b4ef
--- /dev/null
+++ b/docs/content/observability/overview.md
@@ -0,0 +1,42 @@
+---
+title: "Traefik Observability Overview"
+description: "Traefik provides Logs, Access Logs, Metrics and Tracing. Read the full documentation to get started."
+---
+
+# Overview
+
+Traefik's Observability system
+{: .subtitle }
+
+## Logs
+
+Traefik logs informs about everything that happens within Traefik (startup, configuration, events, shutdown, and so on).
+
+Read the [Logs documentation](./logs.md) to learn how to configure it.
+
+## Access Logs
+
+Access logs are a key part of observability in Traefik.
+
+They are providing valuable insights about incoming traffic, and allow to monitor it.
+The access logs record detailed information about each request received by Traefik,
+including the source IP address, requested URL, response status code, and more.
+
+Read the [Access Logs documentation](./access-logs.md) to learn how to configure it.
+
+## Metrics
+
+Traefik offers a metrics feature that provides valuable insights about the performance and usage.
+These metrics include the number of requests received, the requests duration, and more.
+
+Traefik supports these metrics systems: Prometheus, Datadog, InfluxDB 2.X, and StatsD.
+
+Read the [Metrics documentation](./metrics/overview.md) to learn how to configure it.
+
+## Tracing
+
+The Traefik tracing system allows developers to gain deep visibility into the flow of requests through their infrastructure.
+
+Traefik supports these tracing with OpenTelemetry.
+
+Read the [Tracing documentation](./tracing/overview.md) to learn how to configure it.
diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md
index 18b8c4f3e4..52cbb483a8 100644
--- a/docs/content/observability/tracing/overview.md
+++ b/docs/content/observability/tracing/overview.md
@@ -14,10 +14,8 @@ Traefik uses [OpenTelemetry](https://opentelemetry.io/ "Link to website of OTel"
 
 Please check our dedicated [OTel docs](./opentelemetry.md) to learn more.
 
-
 ## Configuration
 
-
 To enable the tracing:
 
 ```yaml tab="File (YAML)"
@@ -34,6 +32,26 @@ tracing: {}
 
 ### Common Options
 
+#### `addInternals`
+
+_Optional, Default="false"_
+
+Enables tracing for internal resources.
+
+```yaml tab="File (YAML)"
+tracing:
+  addInternals: true
+```
+
+```toml tab="File (TOML)"
+[tracing]
+  addInternals = true
+```
+
+```bash tab="CLI"
+--tracing.addinternals
+```
+
 #### `serviceName`
 
 _Required, Default="traefik"_
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index d2a5db058c..a3bd3d3912 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
 `--accesslog`:  
 Access log settings. (Default: ```false```)
 
+`--accesslog.addinternals`:  
+Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```)
+
 `--accesslog.bufferingsize`:  
 Number of access log lines to process in a buffered way. (Default: ```0```)
 
@@ -267,6 +270,9 @@ 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```)
 
+`--metrics.addinternals`:  
+Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```)
+
 `--metrics.datadog`:  
 Datadog metrics exporter type. (Default: ```false```)
 
@@ -993,6 +999,9 @@ Defines the allowed SPIFFE trust domain.
 `--tracing`:  
 OpenTracing configuration. (Default: ```false```)
 
+`--tracing.addinternals`:  
+Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```)
+
 `--tracing.globalattributes.<name>`:  
 Defines additional attributes (key:value) on all spans.
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index cc813b154a..79cc1797d7 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -6,6 +6,9 @@ THIS FILE MUST NOT BE EDITED BY HAND
 `TRAEFIK_ACCESSLOG`:  
 Access log settings. (Default: ```false```)
 
+`TRAEFIK_ACCESSLOG_ADDINTERNALS`:  
+Enables access log for internal services (ping, dashboard, etc...). (Default: ```false```)
+
 `TRAEFIK_ACCESSLOG_BUFFERINGSIZE`:  
 Number of access log lines to process in a buffered way. (Default: ```0```)
 
@@ -267,6 +270,9 @@ 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_METRICS_ADDINTERNALS`:  
+Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```)
+
 `TRAEFIK_METRICS_DATADOG`:  
 Datadog metrics exporter type. (Default: ```false```)
 
@@ -993,6 +999,9 @@ Defines the allowed SPIFFE trust domain.
 `TRAEFIK_TRACING`:  
 OpenTracing configuration. (Default: ```false```)
 
+`TRAEFIK_TRACING_ADDINTERNALS`:  
+Enables tracing for internal services (ping, dashboard, etc...). (Default: ```false```)
+
 `TRAEFIK_TRACING_GLOBALATTRIBUTES_<NAME>`:  
 Defines additional attributes (key:value) on all spans.
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index 79226cb705..e2a459ae6e 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -277,6 +277,7 @@
   disableDashboardAd = true
 
 [metrics]
+  addInternals = true
   [metrics.prometheus]
     buckets = [42.0, 42.0]
     addEntryPointsLabels = true
@@ -351,6 +352,7 @@
   filePath = "foobar"
   format = "foobar"
   bufferingSize = 42
+  addInternals = true
   [accessLog.filters]
     statusCodes = ["foobar", "foobar"]
     retryAttempts = true
@@ -369,6 +371,7 @@
 [tracing]
   serviceName = "foobar"
   sampleRate = 42.0
+  addInternals = true
   [tracing.headers]
     name0 = "foobar"
     name1 = "foobar"
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index 45092911fe..2779e09979 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -308,6 +308,7 @@ api:
   debug: true
   disableDashboardAd: true
 metrics:
+  addInternals: true
   prometheus:
     buckets:
       - 42
@@ -399,6 +400,7 @@ accessLog:
         name0: foobar
         name1: foobar
   bufferingSize: 42
+  addInternals: true
 tracing:
   serviceName: foobar
   headers:
@@ -408,6 +410,7 @@ tracing:
     name0: foobar
     name1: foobar
   sampleRate: 42
+  addInternals: true
   otlp:
     grpc:
       endpoint: foobar
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 0b7ffaa2c6..bbb3751aea 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -149,6 +149,7 @@ nav:
       - 'API': 'operations/api.md'
       - 'Ping': 'operations/ping.md'
   - 'Observability':
+      - 'Overview': 'observability/overview.md'
       - 'Logs': 'observability/logs.md'
       - 'Access Logs': 'observability/access-logs.md'
       - 'Metrics':
diff --git a/integration/fixtures/access_log_config.toml b/integration/fixtures/access_log/access_log_base.toml
similarity index 100%
rename from integration/fixtures/access_log_config.toml
rename to integration/fixtures/access_log/access_log_base.toml
diff --git a/integration/fixtures/access_log/access_log_ping.toml b/integration/fixtures/access_log/access_log_ping.toml
new file mode 100644
index 0000000000..4e85b93bca
--- /dev/null
+++ b/integration/fixtures/access_log/access_log_ping.toml
@@ -0,0 +1,30 @@
+[global]
+  checkNewVersion = false
+  sendAnonymousUsage = false
+
+[log]
+  level = "ERROR"
+  filePath = "traefik.log"
+
+[accessLog]
+  filePath = "access.log"
+
+[entryPoints]
+  [entryPoints.web]
+    address = ":8000"
+
+[api]
+  insecure = true
+
+[ping]
+
+[providers]
+  [providers.file]
+    filename = "{{ .SelfFilename }}"
+
+## dynamic configuration ##
+[http.routers]
+  [http.routers.customPing]
+    entryPoints = ["web"]
+    rule = "PathPrefix(`/ping`)"
+    service = "ping@internal"
diff --git a/integration/fixtures/throttling/simple.toml b/integration/fixtures/throttling/simple.toml
index 668d4988ba..cd98c44f1d 100644
--- a/integration/fixtures/throttling/simple.toml
+++ b/integration/fixtures/throttling/simple.toml
@@ -19,5 +19,6 @@
     insecure = true
 
 [metrics]
+  addInternals = true
   [metrics.prometheus]
     buckets = [0.1,0.3,1.2,5.0]
diff --git a/integration/fixtures/tracing/simple-opentelemetry.toml b/integration/fixtures/tracing/simple-opentelemetry.toml
index 77d779800a..0599bb18da 100644
--- a/integration/fixtures/tracing/simple-opentelemetry.toml
+++ b/integration/fixtures/tracing/simple-opentelemetry.toml
@@ -9,6 +9,8 @@
 [api]
   insecure = true
 
+[ping]
+
 [entryPoints]
   [entryPoints.web]
     address = ":8000"
@@ -47,6 +49,10 @@
     Service = "service3"
     Middlewares = ["retry", "basic-auth"]
     Rule = "Path(`/auth`)"
+  [http.routers.customPing]
+    entryPoints = ["web"]
+    rule = "PathPrefix(`/ping`)"
+    service = "ping@internal"
 
 [http.middlewares]
   [http.middlewares.retry.retry]
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index bb5778671b..19d6ea24cb 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -197,6 +197,7 @@ type Tracing struct {
 	Headers          map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,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"`
 	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"`
 }
@@ -218,7 +219,7 @@ type Providers struct {
 	KubernetesIngress *ingress.Provider              `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	KubernetesCRD     *crd.Provider                  `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	KubernetesGateway *gateway.Provider              `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
-	Rest              *rest.Provider                 ` description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+	Rest              *rest.Provider                 `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	ConsulCatalog     *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	Nomad             *nomad.ProviderBuilder         `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	Ecs               *ecs.Provider                  `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go
index 33e7006971..1d84a1433a 100644
--- a/pkg/muxer/http/mux.go
+++ b/pkg/muxer/http/mux.go
@@ -12,9 +12,10 @@ import (
 
 // Muxer handles routing with rules.
 type Muxer struct {
-	routes   routes
-	parser   predicate.Parser
-	parserV2 predicate.Parser
+	routes         routes
+	parser         predicate.Parser
+	parserV2       predicate.Parser
+	defaultHandler http.Handler
 }
 
 // NewMuxer returns a new muxer instance.
@@ -40,8 +41,9 @@ func NewMuxer() (*Muxer, error) {
 	}
 
 	return &Muxer{
-		parser:   parser,
-		parserV2: parserV2,
+		parser:         parser,
+		parserV2:       parserV2,
+		defaultHandler: http.NotFoundHandler(),
 	}, nil
 }
 
@@ -55,7 +57,12 @@ func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 		}
 	}
 
-	http.NotFoundHandler().ServeHTTP(rw, req)
+	m.defaultHandler.ServeHTTP(rw, req)
+}
+
+// SetDefaultHandler sets the muxer default handler.
+func (m *Muxer) SetDefaultHandler(handler http.Handler) {
+	m.defaultHandler = handler
 }
 
 // GetRulePriority computes the priority for a given rule.
diff --git a/pkg/server/middleware/chainbuilder.go b/pkg/server/middleware/chainbuilder.go
deleted file mode 100644
index 980d7a2b51..0000000000
--- a/pkg/server/middleware/chainbuilder.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package middleware
-
-import (
-	"context"
-
-	"github.com/containous/alice"
-	"github.com/rs/zerolog/log"
-	"github.com/traefik/traefik/v3/pkg/metrics"
-	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
-	"github.com/traefik/traefik/v3/pkg/middlewares/capture"
-	metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
-	tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
-	"go.opentelemetry.io/otel/trace"
-)
-
-// ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others.
-type ChainBuilder struct {
-	metricsRegistry        metrics.Registry
-	accessLoggerMiddleware *accesslog.Handler
-	tracer                 trace.Tracer
-}
-
-// NewChainBuilder Creates a new ChainBuilder.
-func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer) *ChainBuilder {
-	return &ChainBuilder{
-		metricsRegistry:        metricsRegistry,
-		accessLoggerMiddleware: accessLoggerMiddleware,
-		tracer:                 tracer,
-	}
-}
-
-// Build a middleware chain by entry point.
-func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.Chain {
-	chain := alice.New()
-
-	if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) {
-		chain = chain.Append(capture.Wrap)
-	}
-
-	if c.accessLoggerMiddleware != nil {
-		chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware))
-	}
-
-	if c.tracer != nil {
-		chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
-	}
-
-	if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() {
-		metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)
-		chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler))
-	}
-
-	return chain
-}
-
-// Close accessLogger and tracer.
-func (c *ChainBuilder) Close() {
-	if c.accessLoggerMiddleware != nil {
-		if err := c.accessLoggerMiddleware.Close(); err != nil {
-			log.Error().Err(err).Msg("Could not close the access log file")
-		}
-	}
-}
diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go
index 655c1a0473..3065b16ae3 100644
--- a/pkg/server/middleware/middlewares.go
+++ b/pkg/server/middleware/middlewares.go
@@ -387,6 +387,9 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
 		return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
 	}
 
+	// The tracing middleware is a NOOP if tracing is not setup on the middleware chain.
+	// Hence, regarding internal resources' observability deactivation,
+	// this would not enable tracing.
 	return tracing.WrapMiddleware(ctx, middleware), nil
 }
 
diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go
new file mode 100644
index 0000000000..af65cf480a
--- /dev/null
+++ b/pkg/server/middleware/observability.go
@@ -0,0 +1,140 @@
+package middleware
+
+import (
+	"context"
+	"io"
+	"net/http"
+	"strings"
+
+	"github.com/containous/alice"
+	"github.com/rs/zerolog/log"
+	"github.com/traefik/traefik/v3/pkg/config/static"
+	"github.com/traefik/traefik/v3/pkg/logs"
+	"github.com/traefik/traefik/v3/pkg/metrics"
+	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
+	"github.com/traefik/traefik/v3/pkg/middlewares/capture"
+	metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
+	tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
+	"go.opentelemetry.io/otel/trace"
+)
+
+// ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement.
+type ObservabilityMgr struct {
+	config                 static.Configuration
+	accessLoggerMiddleware *accesslog.Handler
+	metricsRegistry        metrics.Registry
+	tracer                 trace.Tracer
+	tracerCloser           io.Closer
+}
+
+// NewObservabilityMgr creates a new ObservabilityMgr.
+func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer, tracerCloser io.Closer) *ObservabilityMgr {
+	return &ObservabilityMgr{
+		config:                 config,
+		metricsRegistry:        metricsRegistry,
+		accessLoggerMiddleware: accessLoggerMiddleware,
+		tracer:                 tracer,
+		tracerCloser:           tracerCloser,
+	}
+}
+
+// BuildEPChain an observability middleware chain by entry point.
+func (c *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string) alice.Chain {
+	chain := alice.New()
+
+	if c == nil {
+		return chain
+	}
+
+	if c.accessLoggerMiddleware != nil || c.metricsRegistry != nil && (c.metricsRegistry.IsEpEnabled() || c.metricsRegistry.IsRouterEnabled() || c.metricsRegistry.IsSvcEnabled()) {
+		if c.ShouldAddAccessLogs(resourceName) || c.ShouldAddMetrics(resourceName) {
+			chain = chain.Append(capture.Wrap)
+		}
+	}
+
+	if c.accessLoggerMiddleware != nil && c.ShouldAddAccessLogs(resourceName) {
+		chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware))
+		chain = chain.Append(func(next http.Handler) (http.Handler, error) {
+			return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
+		})
+	}
+
+	if c.tracer != nil && c.ShouldAddTracing(resourceName) {
+		chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
+	}
+
+	if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() && c.ShouldAddMetrics(resourceName) {
+		metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)
+
+		if c.tracer != nil && c.ShouldAddTracing(resourceName) {
+			chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler))
+		} else {
+			chain = chain.Append(metricsHandler)
+		}
+	}
+
+	return chain
+}
+
+// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource.
+func (c *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool {
+	if c == nil {
+		return false
+	}
+
+	return c.config.AccessLog != nil && (c.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
+}
+
+// ShouldAddMetrics returns whether the metrics should be enabled for the given resource.
+func (c *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool {
+	if c == nil {
+		return false
+	}
+
+	return c.config.Metrics != nil && (c.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
+}
+
+// ShouldAddTracing returns whether the tracing should be enabled for the given resource.
+func (c *ObservabilityMgr) ShouldAddTracing(resourceName string) bool {
+	if c == nil {
+		return false
+	}
+
+	return c.config.Tracing != nil && (c.config.Tracing.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
+}
+
+// MetricsRegistry is an accessor to the metrics registry.
+func (c *ObservabilityMgr) MetricsRegistry() metrics.Registry {
+	if c == nil {
+		return nil
+	}
+
+	return c.metricsRegistry
+}
+
+// Close closes the accessLogger and tracer.
+func (c *ObservabilityMgr) Close() {
+	if c == nil {
+		return
+	}
+
+	if c.accessLoggerMiddleware != nil {
+		if err := c.accessLoggerMiddleware.Close(); err != nil {
+			log.Error().Err(err).Msg("Could not close the access log file")
+		}
+	}
+
+	if c.tracerCloser != nil {
+		if err := c.tracerCloser.Close(); err != nil {
+			log.Error().Err(err).Msg("Could not close the tracer")
+		}
+	}
+}
+
+func (c *ObservabilityMgr) RotateAccessLogs() error {
+	if c.accessLoggerMiddleware == nil {
+		return nil
+	}
+
+	return c.accessLoggerMiddleware.Rotate()
+}
diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go
index bfa99cfb07..eae6453836 100644
--- a/pkg/server/router/router.go
+++ b/pkg/server/router/router.go
@@ -10,7 +10,6 @@ import (
 	"github.com/rs/zerolog/log"
 	"github.com/traefik/traefik/v3/pkg/config/runtime"
 	"github.com/traefik/traefik/v3/pkg/logs"
-	"github.com/traefik/traefik/v3/pkg/metrics"
 	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
 	"github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion"
 	metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
@@ -35,21 +34,19 @@ type serviceManager interface {
 type Manager struct {
 	routerHandlers     map[string]http.Handler
 	serviceManager     serviceManager
-	metricsRegistry    metrics.Registry
+	observabilityMgr   *middleware.ObservabilityMgr
 	middlewaresBuilder middlewareBuilder
-	chainBuilder       *middleware.ChainBuilder
 	conf               *runtime.Configuration
 	tlsManager         *tls.Manager
 }
 
 // NewManager creates a new Manager.
-func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
+func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, observabilityMgr *middleware.ObservabilityMgr, tlsManager *tls.Manager) *Manager {
 	return &Manager{
 		routerHandlers:     make(map[string]http.Handler),
 		serviceManager:     serviceManager,
-		metricsRegistry:    metricsRegistry,
+		observabilityMgr:   observabilityMgr,
 		middlewaresBuilder: middlewaresBuilder,
-		chainBuilder:       chainBuilder,
 		conf:               conf,
 		tlsManager:         tlsManager,
 	}
@@ -73,49 +70,49 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
 		logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
 		ctx := logger.WithContext(rootCtx)
 
-		handler, err := m.buildEntryPointHandler(ctx, routers)
+		handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers)
 		if err != nil {
 			logger.Error().Err(err).Send()
 			continue
 		}
 
-		handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
-			return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
-		}).Then(handler)
-		if err != nil {
-			logger.Error().Err(err).Send()
-			entryPointHandlers[entryPointName] = handler
-		} else {
-			entryPointHandlers[entryPointName] = handlerWithAccessLog
-		}
+		entryPointHandlers[entryPointName] = handler
 	}
 
+	// Create default handlers.
 	for _, entryPointName := range entryPoints {
 		logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger()
 		ctx := logger.WithContext(rootCtx)
 
 		handler, ok := entryPointHandlers[entryPointName]
-		if !ok || handler == nil {
-			handler = BuildDefaultHTTPRouter()
+		if ok || handler != nil {
+			continue
 		}
 
-		handlerWithMiddlewares, err := m.chainBuilder.Build(ctx, entryPointName).Then(handler)
+		handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter())
 		if err != nil {
 			logger.Error().Err(err).Send()
 			continue
 		}
-		entryPointHandlers[entryPointName] = handlerWithMiddlewares
+		entryPointHandlers[entryPointName] = handler
 	}
 
 	return entryPointHandlers
 }
 
-func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
+func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
 	muxer, err := httpmuxer.NewMuxer()
 	if err != nil {
 		return nil, err
 	}
 
+	defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "defaultHandler").Then(http.NotFoundHandler())
+	if err != nil {
+		return nil, err
+	}
+
+	muxer.SetDefaultHandler(defaultHandler)
+
 	for routerName, routerConfig := range configs {
 		logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
 		ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
@@ -131,6 +128,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
 			continue
 		}
 
+		observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service)
+		handler, err = observabilityChain.Then(handler)
+		if err != nil {
+			routerConfig.AddError(err, true)
+			logger.Error().Err(err).Send()
+			continue
+		}
+
 		if err = muxer.AddRoute(routerConfig.Rule, routerConfig.RuleSyntax, routerConfig.Priority, handler); err != nil {
 			routerConfig.AddError(err, true)
 			logger.Error().Err(err).Send()
@@ -167,6 +172,12 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
 		return nil, err
 	}
 
+	// Prevents from enabling observability for internal resources.
+	if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service)) {
+		m.routerHandlers[routerName] = handler
+		return m.routerHandlers[routerName], nil
+	}
+
 	handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
 		return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil
 	}).Then(handler)
@@ -200,10 +211,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
 
 	chain := alice.New()
 
+	if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
+		m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service)) {
+		chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
+	}
+
+	// Prevents from enabling tracing for internal resources.
+	if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service)) {
+		return chain.Extend(*mHandler).Then(sHandler)
+	}
+
 	chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
 
-	if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() {
-		metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service))
+	if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() {
+		metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))
 		chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler))
 	}
 
diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go
index d7a1bf68ce..1ece7096a0 100644
--- a/pkg/server/routerfactory.go
+++ b/pkg/server/routerfactory.go
@@ -6,7 +6,6 @@ import (
 	"github.com/rs/zerolog/log"
 	"github.com/traefik/traefik/v3/pkg/config/runtime"
 	"github.com/traefik/traefik/v3/pkg/config/static"
-	"github.com/traefik/traefik/v3/pkg/metrics"
 	"github.com/traefik/traefik/v3/pkg/server/middleware"
 	tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp"
 	"github.com/traefik/traefik/v3/pkg/server/router"
@@ -25,13 +24,12 @@ type RouterFactory struct {
 	entryPointsTCP []string
 	entryPointsUDP []string
 
-	managerFactory  *service.ManagerFactory
-	metricsRegistry metrics.Registry
+	managerFactory *service.ManagerFactory
 
 	pluginBuilder middleware.PluginsBuilder
 
-	chainBuilder *middleware.ChainBuilder
-	tlsManager   *tls.Manager
+	observabilityMgr *middleware.ObservabilityMgr
+	tlsManager       *tls.Manager
 
 	dialerManager *tcp.DialerManager
 
@@ -40,7 +38,7 @@ type RouterFactory struct {
 
 // NewRouterFactory creates a new RouterFactory.
 func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager,
-	chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder, metricsRegistry metrics.Registry, dialerManager *tcp.DialerManager,
+	observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager,
 ) *RouterFactory {
 	var entryPointsTCP, entryPointsUDP []string
 	for name, cfg := range staticConfiguration.EntryPoints {
@@ -58,14 +56,13 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
 	}
 
 	return &RouterFactory{
-		entryPointsTCP:  entryPointsTCP,
-		entryPointsUDP:  entryPointsUDP,
-		managerFactory:  managerFactory,
-		metricsRegistry: metricsRegistry,
-		tlsManager:      tlsManager,
-		chainBuilder:    chainBuilder,
-		pluginBuilder:   pluginBuilder,
-		dialerManager:   dialerManager,
+		entryPointsTCP:   entryPointsTCP,
+		entryPointsUDP:   entryPointsUDP,
+		managerFactory:   managerFactory,
+		observabilityMgr: observabilityMgr,
+		tlsManager:       tlsManager,
+		pluginBuilder:    pluginBuilder,
+		dialerManager:    dialerManager,
 	}
 }
 
@@ -83,7 +80,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
 
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
 
-	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
+	routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager)
 
 	handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
 	handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 0f4379f049..9c96bdcc64 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -3,47 +3,39 @@ package server
 import (
 	"context"
 	"errors"
-	"io"
 	"os"
 	"os/signal"
 	"time"
 
 	"github.com/rs/zerolog/log"
 	"github.com/traefik/traefik/v3/pkg/metrics"
-	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
 	"github.com/traefik/traefik/v3/pkg/safe"
 	"github.com/traefik/traefik/v3/pkg/server/middleware"
 )
 
 // Server is the reverse-proxy/load-balancer engine.
 type Server struct {
-	watcher        *ConfigurationWatcher
-	tcpEntryPoints TCPEntryPoints
-	udpEntryPoints UDPEntryPoints
-	chainBuilder   *middleware.ChainBuilder
-
-	accessLoggerMiddleware *accesslog.Handler
+	watcher          *ConfigurationWatcher
+	tcpEntryPoints   TCPEntryPoints
+	udpEntryPoints   UDPEntryPoints
+	observabilityMgr *middleware.ObservabilityMgr
 
 	signals  chan os.Signal
 	stopChan chan bool
 
 	routinesPool *safe.Pool
-
-	tracerCloser io.Closer
 }
 
 // NewServer returns an initialized Server.
-func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, tracerCloser io.Closer) *Server {
+func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, observabilityMgr *middleware.ObservabilityMgr) *Server {
 	srv := &Server{
-		watcher:                watcher,
-		tcpEntryPoints:         entryPoints,
-		chainBuilder:           chainBuilder,
-		accessLoggerMiddleware: accessLoggerMiddleware,
-		signals:                make(chan os.Signal, 1),
-		stopChan:               make(chan bool, 1),
-		routinesPool:           routinesPool,
-		udpEntryPoints:         entryPointsUDP,
-		tracerCloser:           tracerCloser,
+		watcher:          watcher,
+		tcpEntryPoints:   entryPoints,
+		observabilityMgr: observabilityMgr,
+		signals:          make(chan os.Signal, 1),
+		stopChan:         make(chan bool, 1),
+		routinesPool:     routinesPool,
+		udpEntryPoints:   entryPointsUDP,
 	}
 
 	srv.configureSignals()
@@ -105,13 +97,7 @@ func (s *Server) Close() {
 
 	close(s.stopChan)
 
-	s.chainBuilder.Close()
-
-	if s.tracerCloser != nil {
-		if err := s.tracerCloser.Close(); err != nil {
-			log.Error().Err(err).Msg("Could not close the tracer")
-		}
-	}
+	s.observabilityMgr.Close()
 
 	cancel()
 }
diff --git a/pkg/server/server_signals.go b/pkg/server/server_signals.go
index 0987abb301..c582fce5c4 100644
--- a/pkg/server/server_signals.go
+++ b/pkg/server/server_signals.go
@@ -24,10 +24,8 @@ func (s *Server) listenSignals(ctx context.Context) {
 			if sig == syscall.SIGUSR1 {
 				log.Info().Msgf("Closing and re-opening log files for rotation: %+v", sig)
 
-				if s.accessLoggerMiddleware != nil {
-					if err := s.accessLoggerMiddleware.Rotate(); err != nil {
-						log.Error().Err(err).Msg("Error rotating access log")
-					}
+				if err := s.observabilityMgr.RotateAccessLogs(); err != nil {
+					log.Error().Err(err).Msg("Error rotating access log")
 				}
 			}
 		}
diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go
index 524ae01d9e..abff2d5a67 100644
--- a/pkg/server/service/managerfactory.go
+++ b/pkg/server/service/managerfactory.go
@@ -10,11 +10,12 @@ import (
 	"github.com/traefik/traefik/v3/pkg/config/static"
 	"github.com/traefik/traefik/v3/pkg/metrics"
 	"github.com/traefik/traefik/v3/pkg/safe"
+	"github.com/traefik/traefik/v3/pkg/server/middleware"
 )
 
 // ManagerFactory a factory of service manager.
 type ManagerFactory struct {
-	metricsRegistry metrics.Registry
+	observabilityMgr *middleware.ObservabilityMgr
 
 	roundTripperManager *RoundTripperManager
 
@@ -29,9 +30,9 @@ type ManagerFactory struct {
 }
 
 // NewManagerFactory creates a new ManagerFactory.
-func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory {
+func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, observabilityMgr *middleware.ObservabilityMgr, roundTripperManager *RoundTripperManager, acmeHTTPHandler http.Handler) *ManagerFactory {
 	factory := &ManagerFactory{
-		metricsRegistry:     metricsRegistry,
+		observabilityMgr:    observabilityMgr,
 		routinesPool:        routinesPool,
 		roundTripperManager: roundTripperManager,
 		acmeHTTPHandler:     acmeHTTPHandler,
@@ -72,7 +73,7 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
 
 // Build creates a service manager.
 func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
-	svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager)
+	svcManager := NewManager(configuration.Services, f.observabilityMgr, f.routinesPool, f.roundTripperManager)
 
 	var apiHandler http.Handler
 	if f.api != nil {
diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go
index 2d71791a83..b01d2327c1 100644
--- a/pkg/server/service/service.go
+++ b/pkg/server/service/service.go
@@ -20,12 +20,12 @@ import (
 	"github.com/traefik/traefik/v3/pkg/config/runtime"
 	"github.com/traefik/traefik/v3/pkg/healthcheck"
 	"github.com/traefik/traefik/v3/pkg/logs"
-	"github.com/traefik/traefik/v3/pkg/metrics"
 	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
 	metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
 	tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
 	"github.com/traefik/traefik/v3/pkg/safe"
 	"github.com/traefik/traefik/v3/pkg/server/cookie"
+	"github.com/traefik/traefik/v3/pkg/server/middleware"
 	"github.com/traefik/traefik/v3/pkg/server/provider"
 	"github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/failover"
 	"github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/mirror"
@@ -42,7 +42,7 @@ type RoundTripperGetter interface {
 // Manager The service manager.
 type Manager struct {
 	routinePool         *safe.Pool
-	metricsRegistry     metrics.Registry
+	observabilityMgr    *middleware.ObservabilityMgr
 	bufferPool          httputil.BufferPool
 	roundTripperManager RoundTripperGetter
 
@@ -53,10 +53,10 @@ type Manager struct {
 }
 
 // NewManager creates a new Manager.
-func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager {
+func NewManager(configs map[string]*runtime.ServiceInfo, observabilityMgr *middleware.ObservabilityMgr, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager {
 	return &Manager{
 		routinePool:         routinePool,
-		metricsRegistry:     metricsRegistry,
+		observabilityMgr:    observabilityMgr,
 		bufferPool:          newBufferPool(),
 		roundTripperManager: roundTripperManager,
 		services:            make(map[string]http.Handler),
@@ -302,12 +302,17 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
 
 		proxy := buildSingleHostProxy(target, passHostHeader, time.Duration(flushInterval), roundTripper, m.bufferPool)
 
-		proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
-		proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
-		proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
+		// Prevents from enabling observability for internal resources.
 
-		if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
-			metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName)
+		if m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, serviceName)) {
+			proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
+			proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
+			proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
+		}
+
+		if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() &&
+			m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, serviceName)) {
+			metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
 
 			proxy, err = alice.New().
 				Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)).
@@ -317,7 +322,9 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
 			}
 		}
 
-		proxy = tracingMiddle.NewService(ctx, serviceName, proxy)
+		if m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, serviceName)) {
+			proxy = tracingMiddle.NewService(ctx, serviceName, proxy)
+		}
 
 		lb.Add(proxyName, proxy, server.Weight)
 
@@ -330,7 +337,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
 	if service.HealthCheck != nil {
 		m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker(
 			ctx,
-			m.metricsRegistry,
+			m.observabilityMgr.MetricsRegistry(),
 			service.HealthCheck,
 			lb,
 			info,
diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go
index 6bb3829202..79c6cf5d36 100644
--- a/pkg/tracing/tracing.go
+++ b/pkg/tracing/tracing.go
@@ -42,6 +42,11 @@ func NewTracing(conf *static.Tracing) (trace.Tracer, io.Closer, error) {
 
 // TracerFromContext extracts the trace.Tracer from the given context.
 func TracerFromContext(ctx context.Context) trace.Tracer {
+	// Prevent picking trace.noopSpan tracer.
+	if !trace.SpanContextFromContext(ctx).IsValid() {
+		return nil
+	}
+
 	span := trace.SpanFromContext(ctx)
 	if span != nil && span.TracerProvider() != nil {
 		return span.TracerProvider().Tracer("github.com/traefik/traefik")
diff --git a/pkg/types/logs.go b/pkg/types/logs.go
index 657e56102d..e0a50ec712 100644
--- a/pkg/types/logs.go
+++ b/pkg/types/logs.go
@@ -45,6 +45,7 @@ type AccessLog struct {
 	Filters       *AccessLogFilters `description:"Access log filters, used to keep only specific access logs." json:"filters,omitempty" toml:"filters,omitempty" yaml:"filters,omitempty" export:"true"`
 	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"`
 }
 
 // SetDefaults sets the default values.
diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go
index 665333630c..571666e234 100644
--- a/pkg/types/metrics.go
+++ b/pkg/types/metrics.go
@@ -10,6 +10,8 @@ import (
 
 // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems.
 type Metrics struct {
+	AddInternals bool `description:"Enables metrics for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"`
+
 	Prometheus    *Prometheus    `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	Datadog       *Datadog       `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 	StatsD        *Statsd        `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`


Test patch:
diff --git a/integration/access_log_test.go b/integration/access_log_test.go
index 8e2c9581f1..b5e4ec1ef9 100644
--- a/integration/access_log_test.go
+++ b/integration/access_log_test.go
@@ -61,7 +61,7 @@ func (s *AccessLogSuite) TestAccessLog() {
 	ensureWorkingDirectoryIsClean()
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	defer func() {
 		traefikLog, err := os.ReadFile(traefikTestLogFile)
@@ -130,7 +130,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -194,7 +194,7 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthMiddleware() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -304,7 +304,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -410,7 +410,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -454,7 +454,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.waitForTraefik("server1")
 
@@ -494,7 +494,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendAllowlist() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -534,7 +534,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontendSuccess() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -575,7 +575,7 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
 	}
 
 	// Start Traefik
-	s.traefikCmd(withConfigFile("fixtures/access_log_config.toml"))
+	s.traefikCmd(withConfigFile("fixtures/access_log/access_log_base.toml"))
 
 	s.checkStatsForLogFile()
 
@@ -603,6 +603,56 @@ func (s *AccessLogSuite) TestAccessLogPreflightHeadersMiddleware() {
 	s.checkNoOtherTraefikProblems()
 }
 
+func (s *AccessLogSuite) TestAccessLogDisabledForInternals() {
+	ensureWorkingDirectoryIsClean()
+
+	file := s.adaptFile("fixtures/access_log/access_log_ping.toml", struct{}{})
+
+	// Start Traefik.
+	s.traefikCmd(withConfigFile(file))
+
+	defer func() {
+		traefikLog, err := os.ReadFile(traefikTestLogFile)
+		require.NoError(s.T(), err)
+		log.Info().Msg(string(traefikLog))
+	}()
+
+	// waitForTraefik makes at least one call to the rawdata api endpoint,
+	// but the logs for this endpoint are ignored in checkAccessLogOutput.
+	s.waitForTraefik("customPing")
+
+	s.checkStatsForLogFile()
+
+	// Verify Traefik started OK.
+	s.checkTraefikStarted()
+
+	// Make some requests on the internal ping router.
+	req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/ping", nil)
+	require.NoError(s.T(), err)
+
+	err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
+	require.NoError(s.T(), err)
+	err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
+	require.NoError(s.T(), err)
+
+	// Make some requests on the custom ping router.
+	req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/ping", nil)
+	require.NoError(s.T(), err)
+
+	err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
+	require.NoError(s.T(), err)
+	err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.HasBody())
+	require.NoError(s.T(), err)
+
+	// Verify access.log output as expected.
+	count := s.checkAccessLogOutput()
+
+	require.Equal(s.T(), 0, count)
+
+	// Verify no other Traefik problems.
+	s.checkNoOtherTraefikProblems()
+}
+
 func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
 	traefikLog, err := os.ReadFile(traefikTestLogFile)
 	require.NoError(s.T(), err)
@@ -612,6 +662,8 @@ func (s *AccessLogSuite) checkNoOtherTraefikProblems() {
 }
 
 func (s *AccessLogSuite) checkAccessLogOutput() int {
+	s.T().Helper()
+
 	lines := s.extractLines()
 	count := 0
 	for i, line := range lines {
@@ -624,6 +676,8 @@ func (s *AccessLogSuite) checkAccessLogOutput() int {
 }
 
 func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue) int {
+	s.T().Helper()
+
 	lines := s.extractLines()
 	count := 0
 	for i, line := range lines {
@@ -641,6 +695,8 @@ func (s *AccessLogSuite) checkAccessLogExactValuesOutput(values []accessLogValue
 }
 
 func (s *AccessLogSuite) extractLines() []string {
+	s.T().Helper()
+
 	accessLog, err := os.ReadFile(traefikTestAccessLogFile)
 	require.NoError(s.T(), err)
 
@@ -656,6 +712,8 @@ func (s *AccessLogSuite) extractLines() []string {
 }
 
 func (s *AccessLogSuite) checkStatsForLogFile() {
+	s.T().Helper()
+
 	err := try.Do(1*time.Second, func() error {
 		if _, errStat := os.Stat(traefikTestLogFile); errStat != nil {
 			return fmt.Errorf("could not get stats for log file: %w", errStat)
@@ -671,6 +729,8 @@ func ensureWorkingDirectoryIsClean() {
 }
 
 func (s *AccessLogSuite) checkTraefikStarted() []byte {
+	s.T().Helper()
+
 	traefikLog, err := os.ReadFile(traefikTestLogFile)
 	require.NoError(s.T(), err)
 	if len(traefikLog) > 0 {
@@ -680,6 +740,8 @@ func (s *AccessLogSuite) checkTraefikStarted() []byte {
 }
 
 func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
+	s.T().Helper()
+
 	results, err := accesslog.ParseAccessLog(line)
 	require.NoError(s.T(), err)
 	assert.Len(s.T(), results, 14)
@@ -692,6 +754,8 @@ func (s *BaseSuite) CheckAccessLogFormat(line string, i int) {
 }
 
 func (s *AccessLogSuite) checkAccessLogExactValues(line string, i int, v accessLogValue) {
+	s.T().Helper()
+
 	results, err := accesslog.ParseAccessLog(line)
 	require.NoError(s.T(), err)
 	assert.Len(s.T(), results, 14)
diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go
index 28d72f8cd8..269d17d694 100644
--- a/integration/log_rotation_test.go
+++ b/integration/log_rotation_test.go
@@ -57,7 +57,7 @@ func (s *LogRotationSuite) TearDownSuite() {
 
 func (s *LogRotationSuite) TestAccessLogRotation() {
 	// Start Traefik
-	cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log_config.toml"))
+	cmd, _ := s.cmdTraefik(withConfigFile("fixtures/access_log/access_log_base.toml"))
 	defer s.displayTraefikLogFile(traefikTestLogFile)
 
 	// Verify Traefik started ok
diff --git a/integration/simple_test.go b/integration/simple_test.go
index d2c3be0772..8e2f14ec14 100644
--- a/integration/simple_test.go
+++ b/integration/simple_test.go
@@ -287,6 +287,10 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntryPoint() {
 
 	err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyContains("_service_"))
 	require.NoError(s.T(), err)
+
+	// No metrics for internals.
+	err = try.GetRequest("http://127.0.0.1:8080/metrics", 1*time.Second, try.BodyNotContains("router=\"api@internal\"", "service=\"api@internal\""))
+	require.NoError(s.T(), err)
 }
 
 func (s *SimpleSuite) TestMetricsPrometheusTwoRoutersOneService() {
diff --git a/integration/tracing_test.go b/integration/tracing_test.go
index c94f3c58bb..ef92015bde 100644
--- a/integration/tracing_test.go
+++ b/integration/tracing_test.go
@@ -414,6 +414,67 @@ func (s *TracingSuite) TestOpentelemetryAuth() {
 	s.checkTraceContent(contains)
 }
 
+func (s *TracingSuite) TestNoInternals() {
+	file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
+		WhoamiIP:   s.whoamiIP,
+		WhoamiPort: s.whoamiPort,
+		IP:         s.otelCollectorIP,
+		IsHTTP:     true,
+	})
+
+	s.traefikCmd(withConfigFile(file))
+
+	// wait for traefik
+	err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
+	require.NoError(s.T(), err)
+
+	err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
+	require.NoError(s.T(), err)
+
+	err = try.GetRequest("http://127.0.0.1:8000/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
+	require.NoError(s.T(), err)
+	err = try.GetRequest("http://127.0.0.1:8080/ping", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
+	require.NoError(s.T(), err)
+
+	baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/search")
+	require.NoError(s.T(), err)
+
+	req := &http.Request{
+		Method: http.MethodGet,
+		URL:    baseURL,
+	}
+	// Wait for traces to be available.
+	time.Sleep(10 * time.Second)
+	resp, err := try.Response(req, 5*time.Second)
+	require.NoError(s.T(), err)
+
+	out := &TraceResponse{}
+	content, err := io.ReadAll(resp.Body)
+	require.NoError(s.T(), err)
+	err = json.Unmarshal(content, &out)
+	require.NoError(s.T(), err)
+
+	s.NotEmptyf(len(out.Traces), "expected at least one trace")
+
+	for _, t := range out.Traces {
+		baseURL, err := url.Parse("http://" + s.tempoIP + ":3200/api/traces/" + t.TraceID)
+		require.NoError(s.T(), err)
+
+		req := &http.Request{
+			Method: http.MethodGet,
+			URL:    baseURL,
+		}
+
+		resp, err := try.Response(req, 5*time.Second)
+		require.NoError(s.T(), err)
+
+		content, err := io.ReadAll(resp.Body)
+		require.NoError(s.T(), err)
+
+		require.NotContains(s.T(), content, "@internal")
+	}
+}
+
 func (s *TracingSuite) checkTraceContent(expectedJSON []map[string]string) {
 	s.T().Helper()
 
diff --git a/pkg/redactor/testdata/anonymized-static-config.json b/pkg/redactor/testdata/anonymized-static-config.json
index 97e678c85b..d756ba779f 100644
--- a/pkg/redactor/testdata/anonymized-static-config.json
+++ b/pkg/redactor/testdata/anonymized-static-config.json
@@ -407,4 +407,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go
index 21e0de141e..eb83ed25de 100644
--- a/pkg/server/router/router_test.go
+++ b/pkg/server/router/router_test.go
@@ -9,21 +9,15 @@ import (
 	"testing"
 	"time"
 
-	"github.com/containous/alice"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 	ptypes "github.com/traefik/paerser/types"
 	"github.com/traefik/traefik/v3/pkg/config/dynamic"
 	"github.com/traefik/traefik/v3/pkg/config/runtime"
-	"github.com/traefik/traefik/v3/pkg/metrics"
-	"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
-	"github.com/traefik/traefik/v3/pkg/middlewares/capture"
 	"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
 	"github.com/traefik/traefik/v3/pkg/server/middleware"
 	"github.com/traefik/traefik/v3/pkg/server/service"
 	"github.com/traefik/traefik/v3/pkg/testhelpers"
 	"github.com/traefik/traefik/v3/pkg/tls"
-	"github.com/traefik/traefik/v3/pkg/types"
 )
 
 func TestRouterManager_Get(t *testing.T) {
@@ -319,10 +313,9 @@ func TestRouterManager_Get(t *testing.T) {
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
-			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
 			tlsManager := tls.NewManager()
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
 
 			handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
 
@@ -341,126 +334,6 @@ func TestRouterManager_Get(t *testing.T) {
 	}
 }
 
-func TestAccessLog(t *testing.T) {
-	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
-
-	t.Cleanup(func() { server.Close() })
-
-	testCases := []struct {
-		desc              string
-		routersConfig     map[string]*dynamic.Router
-		serviceConfig     map[string]*dynamic.Service
-		middlewaresConfig map[string]*dynamic.Middleware
-		entryPoints       []string
-		expected          string
-	}{
-		{
-			desc: "apply routerName in accesslog (first match)",
-			routersConfig: map[string]*dynamic.Router{
-				"foo": {
-					EntryPoints: []string{"web"},
-					Service:     "foo-service",
-					Rule:        "Host(`foo.bar`)",
-				},
-				"bar": {
-					EntryPoints: []string{"web"},
-					Service:     "foo-service",
-					Rule:        "Host(`bar.foo`)",
-				},
-			},
-			serviceConfig: map[string]*dynamic.Service{
-				"foo-service": {
-					LoadBalancer: &dynamic.ServersLoadBalancer{
-						Servers: []dynamic.Server{
-							{
-								URL: server.URL,
-							},
-						},
-					},
-				},
-			},
-			entryPoints: []string{"web"},
-			expected:    "foo",
-		},
-		{
-			desc: "apply routerName in accesslog (second match)",
-			routersConfig: map[string]*dynamic.Router{
-				"foo": {
-					EntryPoints: []string{"web"},
-					Service:     "foo-service",
-					Rule:        "Host(`bar.foo`)",
-				},
-				"bar": {
-					EntryPoints: []string{"web"},
-					Service:     "foo-service",
-					Rule:        "Host(`foo.bar`)",
-				},
-			},
-			serviceConfig: map[string]*dynamic.Service{
-				"foo-service": {
-					LoadBalancer: &dynamic.ServersLoadBalancer{
-						Servers: []dynamic.Server{
-							{
-								URL: server.URL,
-							},
-						},
-					},
-				},
-			},
-			entryPoints: []string{"web"},
-			expected:    "bar",
-		},
-	}
-
-	for _, test := range testCases {
-		t.Run(test.desc, func(t *testing.T) {
-			rtConf := runtime.NewConfig(dynamic.Configuration{
-				HTTP: &dynamic.HTTPConfiguration{
-					Services:    test.serviceConfig,
-					Routers:     test.routersConfig,
-					Middlewares: test.middlewaresConfig,
-				},
-			})
-
-			roundTripperManager := service.NewRoundTripperManager(nil)
-			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
-			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
-			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
-			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
-			tlsManager := tls.NewManager()
-
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
-
-			handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
-
-			w := httptest.NewRecorder()
-			req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
-
-			accesslogger, err := accesslog.NewHandler(&types.AccessLog{
-				Format: "json",
-			})
-			require.NoError(t, err)
-
-			reqHost := requestdecorator.New(nil)
-
-			chain := alice.New()
-			chain = chain.Append(capture.Wrap)
-			chain = chain.Append(accesslog.WrapHandler(accesslogger))
-			handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
-				reqHost.ServeHTTP(w, req, handlers["web"].ServeHTTP)
-
-				data := accesslog.GetLogData(req)
-				require.NotNil(t, data)
-
-				assert.Equal(t, test.expected, data.Core[accesslog.RouterName])
-			}))
-			require.NoError(t, err)
-
-			handler.ServeHTTP(w, req)
-		})
-	}
-}
-
 func TestRuntimeConfiguration(t *testing.T) {
 	testCases := []struct {
 		desc             string
@@ -788,11 +661,10 @@ func TestRuntimeConfiguration(t *testing.T) {
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 			serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 			middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
-			chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
 			tlsManager := tls.NewManager()
 			tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
 
-			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
+			routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
 
 			_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
 			_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
@@ -866,10 +738,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
 	roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
-	chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
 	tlsManager := tls.NewManager()
 
-	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
+	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
 
 	_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
 
@@ -935,10 +806,9 @@ func BenchmarkRouterServe(b *testing.B) {
 
 	serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
 	middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
-	chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
 	tlsManager := tls.NewManager()
 
-	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
+	routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager)
 
 	handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
 
diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go
index b935df89ef..ebd923f62a 100644
--- a/pkg/server/routerfactory_test.go
+++ b/pkg/server/routerfactory_test.go
@@ -9,7 +9,6 @@ import (
 	"github.com/traefik/traefik/v3/pkg/config/dynamic"
 	"github.com/traefik/traefik/v3/pkg/config/runtime"
 	"github.com/traefik/traefik/v3/pkg/config/static"
-	"github.com/traefik/traefik/v3/pkg/metrics"
 	"github.com/traefik/traefik/v3/pkg/server/middleware"
 	"github.com/traefik/traefik/v3/pkg/server/service"
 	"github.com/traefik/traefik/v3/pkg/tcp"
@@ -51,12 +50,12 @@ func TestReuseService(t *testing.T) {
 
 	roundTripperManager := service.NewRoundTripperManager(nil)
 	roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
-	managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
+	managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil)
 	tlsManager := tls.NewManager()
 
 	dialerManager := tcp.NewDialerManager(nil)
 	dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
-	factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager)
+	factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager)
 
 	entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))
 
@@ -189,12 +188,13 @@ func TestServerResponseEmptyBackend(t *testing.T) {
 
 			roundTripperManager := service.NewRoundTripperManager(nil)
 			roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
-			managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
+			managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil)
 			tlsManager := tls.NewManager()
 
 			dialerManager := tcp.NewDialerManager(nil)
 			dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
-			factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(nil, nil, nil), nil, metrics.NewVoidRegistry(), dialerManager)
+			observabiltyMgr := middleware.NewObservabilityMgr(staticConfig, nil, nil, nil, nil)
+			factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, observabiltyMgr, nil, dialerManager)
 
 			entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)}))
 
@@ -232,14 +232,12 @@ func TestInternalServices(t *testing.T) {
 
 	roundTripperManager := service.NewRoundTripperManager(nil)
 	roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
-	managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
+	managerFactory := service.NewManagerFactory(staticConfig, nil, nil, roundTripperManager, nil)
 	tlsManager := tls.NewManager()
 
-	voidRegistry := metrics.NewVoidRegistry()
-
 	dialerManager := tcp.NewDialerManager(nil)
 	dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
-	factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(voidRegistry, nil, nil), nil, voidRegistry, dialerManager)
+	factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager)
 
 	entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs}))