Repository: fabiolb/fabio (go)

Issue:
Fabio is using Datadog reserved tag keys 
Some metrics are sent with tags `service`, `host`, `path`, `target`:

https://github.com/fabiolb/fabio/blob/642e425cf664e23d257ca357b7b1bfd1e432683d/route/route.go#L64-L66

But `service` and `host` are [reserved tag keys in Datadog](https://docs.datadoghq.com/getting_started/tagging/#overview)

According to [Host tag](https://docs.datadoghq.com/metrics/custom_metrics/dogstatsd_metrics_submission/#host-tag)  
> The host tag is assigned automatically by the Datadog Agent aggregating the metrics. Metrics submitted with a host tag not matching the Agent hostname lose reference to the original host. The submitted host tag overrides any hostname collected by or configured in the Agent.

and https://github.com/DataDog/datadog-agent/issues/6245, these metric loose any configuration in the datadog-agent. In our case it is the `env` tag that is being lost. 

It can be tested with:
```
echo -n "custom.metric.nameA:1|c|#host:foo" |nc -vu -w1 localhost 8125
```
![image](https://user-images.githubusercontent.com/8249379/217234290-b532c511-ae65-46ea-ab30-900c429c7cb5.png)

```
echo -n "custom.metric.nameB:1|c|#host1:foo" |nc -vu -w1 localhost 8125
```
![Screenshot 2023-02-07 at 11-35-13 Metrics Summary Datadog2](https://user-images.githubusercontent.com/8249379/217234591-8f901560-6b48-49fa-953f-4395faa0fab6.png)



Pull request description:
Adds handling of Datadog reserved tag keys
Prepends "fabio-" to reserved tag keys

https://docs.datadoghq.com/getting_started/tagging/#overview

fixes #923

Patch:
diff --git a/.github/workflows/go-releaser.yml b/.github/workflows/go-releaser.yml
new file mode 100644
index 00000000..3ebd4026
--- /dev/null
+++ b/.github/workflows/go-releaser.yml
@@ -0,0 +1,44 @@
+name: goreleaser
+on:
+  push:
+    tags:
+      - '*'
+permissions:
+  contents: write
+jobs:
+  goreleaser:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v3
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version-file: 'go.mod'
+          check-latest: true
+          cache: true
+      - name: Docker Login
+        uses: docker/login-action@v3
+        with:
+          registry: https://index.docker.io/v1/
+          username: ${{ secrets.DOCKER_HUB_USER }}
+          password: ${{ secrets.DOCKER_HUB_TOKEN }}
+      - name: Import GPG key
+        id: import_gpg
+        uses: crazy-max/ghaction-import-gpg@v6
+        with:
+          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+          passphrase: ${{ secrets.GPG_PASSPHRASE }}
+      - name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v6
+        with:
+          distribution: goreleaser
+          version: '~> v2'
+          args: release --clean
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 714d1684..c5ba0fb3 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,6 +1,6 @@
+version: 2
 builds:
-  -
-    binary: fabio
+  - binary: fabio
     env:
       - CGO_ENABLED=0
     goos:
@@ -17,25 +17,26 @@ builds:
       - arm64
     goarm:
       - 7
-
 archives:
   - id: bin
     name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}'
     format: binary
-
 source:
   enabled: true
   name_template: '{{ .ProjectName }}-{{.Version }}.src'
   prefix_template: '{{ .ProjectName }}-{{.Version }}/'
-
-
 checksum:
   name_template: '{{.ProjectName}}-{{.Version}}.sha256'
-
 signs:
-  -
-    artifacts: checksum
-
+  - artifacts: checksum
+    args:
+      - "--batch"
+      - "--local-user"
+      - "{{ .Env.GPG_FINGERPRINT }}"
+      - "--output"
+      - "${signature}"
+      - "--detach-sign"
+      - "${artifact}"
 dockers:
   - dockerfile: Dockerfile-goreleaser
     use: buildx
@@ -52,7 +53,6 @@ dockers:
       - "--label=org.opencontainers.image.version={{.Version}}"
     extra_files:
       - fabio.properties
-      
   - dockerfile: Dockerfile-goreleaser
     use: buildx
     goos: linux
@@ -68,7 +68,6 @@ dockers:
       - "--label=org.opencontainers.image.version={{.Version}}"
     extra_files:
       - fabio.properties
-
 docker_manifests:
   - name_template: 'fabiolb/fabio:latest'
     image_templates:
diff --git a/main.go b/main.go
index 4ef96521..bab8bf28 100644
--- a/main.go
+++ b/main.go
@@ -53,7 +53,7 @@ import (
 // It is also set by the linker when fabio
 // is built via the Makefile or the build/docker.sh
 // script to ensure the correct version number
-var version = "1.6.3"
+var version = "1.6.4"
 
 var shuttingDown int32
 
diff --git a/metrics/provider_dogstatsd.go b/metrics/provider_dogstatsd.go
index 22d562e2..c9a24c7c 100644
--- a/metrics/provider_dogstatsd.go
+++ b/metrics/provider_dogstatsd.go
@@ -16,11 +16,11 @@ type DogstatsdProvider struct {
 }
 
 func (dp *DogstatsdProvider) NewCounter(name string, labels ...string) gkm.Counter {
-	return dp.D.NewCounter(name, 1)
+	return &dogstatsdCounter{dp.D.NewCounter(name, 1)}
 }
 
 func (dp *DogstatsdProvider) NewGauge(name string, labels ...string) gkm.Gauge {
-	return dp.D.NewGauge(name)
+	return &dogstatsdGauge{dp.D.NewGauge(name)}
 }
 
 func (dp *DogstatsdProvider) NewHistogram(name string, labels ...string) gkm.Histogram {
@@ -42,6 +42,12 @@ func NewDogstatsdProvider(prefix, addr string, interval time.Duration) (*Dogstat
 	return d, nil
 }
 
+type dogstatsdCounter struct {
+	gkm.Counter
+}
+type dogstatsdGauge struct {
+	gkm.Gauge
+}
 type dogstatsdHistogram struct {
 	gkm.Histogram
 }
@@ -49,3 +55,46 @@ type dogstatsdHistogram struct {
 func (dh *dogstatsdHistogram) Observe(value float64) {
 	dh.Histogram.Observe(value * 1000.0)
 }
+
+func (dh *dogstatsdCounter) With(labelValues ...string) gkm.Counter {
+	return dh.Counter.With(correctReservedTagKeys(labelValues)...)
+}
+
+func (dh *dogstatsdGauge) With(labelValues ...string) gkm.Gauge {
+	return dh.Gauge.With(correctReservedTagKeys(labelValues)...)
+}
+
+func (dh *dogstatsdHistogram) With(labelValues ...string) gkm.Histogram {
+	return dh.Histogram.With(correctReservedTagKeys(labelValues)...)
+}
+
+func correctReservedTagKeys(labelValues []string) []string {
+	var rval []string
+	for i, v := range labelValues {
+		if i%2 == 0 {
+			rval = append(rval, correctReservedTagKey(v))
+		} else {
+			rval = append(rval, v)
+		}
+	}
+	return rval
+}
+
+func correctReservedTagKey(label string) string {
+	switch label {
+	case "host":
+		return "fabio-host"
+	case "device":
+		return "fabio-device"
+	case "source":
+		return "fabio-source"
+	case "service":
+		return "fabio-service"
+	case "env":
+		return "fabio-env"
+	case "version":
+		return "fabio-version"
+	default:
+		return label
+	}
+}


Test patch:
diff --git a/metrics/provider_dogstatsd_test.go b/metrics/provider_dogstatsd_test.go
index f673e87e..dd169937 100644
--- a/metrics/provider_dogstatsd_test.go
+++ b/metrics/provider_dogstatsd_test.go
@@ -14,22 +14,24 @@ func TestDogstatsdProvider(t *testing.T) {
 	d := dogstatsd.New(prefix, log.NewNopLogger())
 	provider := &DogstatsdProvider{D: d}
 	for _, tst := range []struct {
-		name     string
-		labels   []string
-		values   []string
-		prefix   string
-		countval float64
-		gaugeval float64
-		histoval float64
+		name            string
+		labels          []string
+		values          []string
+		expected_values []string
+		prefix          string
+		countval        float64
+		gaugeval        float64
+		histoval        float64
 	}{
 		{
-			name:     "simpleTest",
-			labels:   []string{"service", "host", "path", "target", "other"},
-			values:   []string{"service", "foo", "host", "bar", "path", "/asdf", "target", "http://jkl.org:1234", "other", "trailer"},
-			prefix:   "tst",
-			countval: 20,
-			gaugeval: 30,
-			histoval: (time.Microsecond * 50).Seconds(),
+			name:            "simpleTest",
+			labels:          []string{"service", "host", "path", "target", "other"},
+			values:          []string{"service", "foo", "host", "bar", "path", "/asdf", "target", "http://jkl.org:1234", "other", "trailer"},
+			expected_values: []string{"fabio-service", "foo", "fabio-host", "bar", "path", "/asdf", "target", "http://jkl.org:1234", "other", "trailer"},
+			prefix:          "tst",
+			countval:        20,
+			gaugeval:        30,
+			histoval:        (time.Microsecond * 50).Seconds(),
 		},
 	} {
 		t.Run(tst.name, func(t *testing.T) {
@@ -59,7 +61,7 @@ func TestDogstatsdProvider(t *testing.T) {
 					if se.value != v.v {
 						t.Errorf("%s failed: expected: %.02f, got %02f", v.n, v.v, se.value)
 					}
-					if len(tst.values) > 0 && !reflect.DeepEqual(se.tags, tst.values) {
+					if len(tst.expected_values) > 0 && !reflect.DeepEqual(se.tags, tst.expected_values) {
 						t.Errorf("tags did not survive round trip parsing")
 					}
 				} else {