Repository: traefik/traefik (language: go)

Issue:
Add support to send DataDog metrics via Unix Socket
### 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?

Hey Treafik Team,
thanks for building such an awesome reverse proxy. The label based autodiscovery really lifts the burden for setting up networking and the extensibility is great. An equally helpful tool is DataDog. The DataDog Agent now supports Unix Sockets.  Instead of setting up docker networks to route to the docker Agent StatsD endpoint, we can use volumes and unix sockets. 
Please add support to send DataDog metrics via Unix Socket.
```
metrics:
   datadog:
      localAgentSocket: /var/run/datadog/dsd.socket
```

Please refer to https://github.com/traefik/traefik/pull/9714 to understand how this was already implemented in Traefik for DataDog Tracing (different from DogStatsD). 
Thank you.
Liam


Discussion / hints:
I now have a workaround, but still think this should be added. Assumptions:
- Both your DataDog Agent and Traefik run in Containers on a host.
- You cannot setup docker networking between them in an easy to automate setup (i. e. use Docker Compose, ...).

You setup your DataDog Agent with Unix Sockets as described [in the documentation](https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=docker). Let's assume you stick with Volume `/var/run/datadog:/var/run/datadog` and have 
```
DD_TRACE_AGENT_URL=unix:///var/run/datadog/apm.socket
DD_DOGSTATSD_URL=unix:///var/run/datadog/dsd.socket
```
With the normal Traefik Docker Image you can already do 
```
--tracing.datadog.localAgentSocket=/var/run/datadog/apm.socket
--metrics.datadog.address=127.0.0.1:8125
```
but everything going to `127.0.0.1:8125` would be lost. We'll now use the official workaround proposed by DataDog, where all traffic send to :8125 will be forwarded to the unix socket. The most lightweight tool for that is [socat proxy](https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=docker#socat-proxy) with
`socat -s -u UDP-RECV:8125 UNIX-SENDTO:/var/run/datadog/dsd.socket`. 

Instead of the official traefik:v2.10 image use the public docker image `botbrains/traefiksocat:v2.10`, it is a 4 line wrapper image that just installs socat and changes the Docker entrypoint to start socat in the background. 

This should alleviate any immediate pain.

You can also view the Dockerfile, Entrypoint and GHA Workflow here: 
- https://github.com/liamvdv/traefiksocat

You can easily adopt this, just add your own Dockerhub credentials via `gh secret set DOCKERHUB_USERNAME` and `gh secret set DOCKERHUB_PASSWORD`. 
Best,
Liam
Hello @liamvdv,

Thanks for the suggestion.

We would love community support to address it.  

If you or another community member would like to build it, let us know and we will work with you to ensure you have all the information needed to merge.  

Don’t forget to check out the [contributor docs](https://doc.traefik.io/traefik/contributing/submitting-pull-requests) and link the PR to this issue.
If not, we will leave the issue open to any interested community member. 

Fix patch:
diff --git a/docs/content/observability/metrics/datadog.md b/docs/content/observability/metrics/datadog.md
index 00a5d6c9f1..134b726df3 100644
--- a/docs/content/observability/metrics/datadog.md
+++ b/docs/content/observability/metrics/datadog.md
@@ -27,6 +27,8 @@ _Required, Default="127.0.0.1:8125"_
 
 Address instructs exporter to send metrics to datadog-agent at this address.
 
+This address can be a Unix Domain Socket (UDS) address with the following form: `unix:///path/to/datadog.socket`.  
+
 ```yaml tab="File (YAML)"
 metrics:
   datadog:
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 6ba875a3fd..0b7ffaa2c6 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -153,7 +153,11 @@ nav:
       - 'Access Logs': 'observability/access-logs.md'
       - 'Metrics':
           - 'Overview': 'observability/metrics/overview.md'
+          - 'Datadog': 'observability/metrics/datadog.md'
+          - 'InfluxDB2': 'observability/metrics/influxdb2.md'
           - 'OpenTelemetry': 'observability/metrics/opentelemetry.md'
+          - 'Prometheus': 'observability/metrics/prometheus.md'
+          - 'StatsD': 'observability/metrics/statsd.md'
       - 'Tracing':
           - 'Overview': 'observability/tracing/overview.md'
           - 'OpenTelemetry': 'observability/tracing/opentelemetry.md'
diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go
index b09e3407aa..fcb8be6d22 100644
--- a/pkg/metrics/datadog.go
+++ b/pkg/metrics/datadog.go
@@ -2,6 +2,7 @@ package metrics
 
 import (
 	"context"
+	"strings"
 	"time"
 
 	"github.com/go-kit/kit/metrics/dogstatsd"
@@ -16,6 +17,8 @@ var (
 	datadogLoopCancelFunc context.CancelFunc
 )
 
+const unixAddressPrefix = "unix://"
+
 // Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64
 const (
 	ddConfigReloadsName           = "config.reload.total"
@@ -99,10 +102,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry {
 }
 
 func initDatadogClient(ctx context.Context, config *types.Datadog) {
-	address := config.Address
-	if len(address) == 0 {
-		address = "localhost:8125"
-	}
+	network, address := parseDatadogAddress(config.Address)
 
 	ctx, datadogLoopCancelFunc = context.WithCancel(ctx)
 
@@ -110,10 +110,27 @@ func initDatadogClient(ctx context.Context, config *types.Datadog) {
 		ticker := time.NewTicker(time.Duration(config.PushInterval))
 		defer ticker.Stop()
 
-		datadogClient.SendLoop(ctx, ticker.C, "udp", address)
+		datadogClient.SendLoop(ctx, ticker.C, network, address)
 	})
 }
 
+func parseDatadogAddress(address string) (string, string) {
+	network := "udp"
+
+	var addr string
+	switch {
+	case strings.HasPrefix(address, unixAddressPrefix):
+		network = "unix"
+		addr = address[len(unixAddressPrefix):]
+	case address != "":
+		addr = address
+	default:
+		addr = "localhost:8125"
+	}
+
+	return network, addr
+}
+
 // StopDatadog stops the Datadog metrics pusher.
 func StopDatadog() {
 	if datadogLoopCancelFunc != nil {


Test patch:
diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go
index 6a53bf0650..cd4b994332 100644
--- a/pkg/metrics/datadog_test.go
+++ b/pkg/metrics/datadog_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stvp/go-udp-testing"
 	ptypes "github.com/traefik/paerser/types"
 	"github.com/traefik/traefik/v3/pkg/types"
@@ -39,6 +40,44 @@ func TestDatadogWithPrefix(t *testing.T) {
 	testDatadogRegistry(t, "testPrefix", datadogRegistry)
 }
 
+func TestDatadog_parseDatadogAddress(t *testing.T) {
+	tests := []struct {
+		desc       string
+		address    string
+		expNetwork string
+		expAddress string
+	}{
+		{
+			desc:       "empty address",
+			expNetwork: "udp",
+			expAddress: "localhost:8125",
+		},
+		{
+			desc:       "udp address",
+			address:    "127.0.0.1:8080",
+			expNetwork: "udp",
+			expAddress: "127.0.0.1:8080",
+		},
+		{
+			desc:       "unix address",
+			address:    "unix:///path/to/datadog.socket",
+			expNetwork: "unix",
+			expAddress: "/path/to/datadog.socket",
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			gotNetwork, gotAddress := parseDatadogAddress(test.address)
+			assert.Equal(t, test.expNetwork, gotNetwork)
+			assert.Equal(t, test.expAddress, gotAddress)
+		})
+	}
+}
+
 func testDatadogRegistry(t *testing.T, metricsPrefix string, datadogRegistry Registry) {
 	t.Helper()