From 23b70dd0b234011d3ab9ef4e07445e4409192b5a Mon Sep 17 00:00:00 2001 From: Bora Tanrikulu Date: Sun, 25 Jan 2026 17:46:08 +0300 Subject: [PATCH] [SC-23705] fix(sematext): deduplicate corrupted measurement name prefixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Apache metrics corruption where field names contained 64+ repeated "apache." prefixes (e.g., "apache.apache.apache...bytespersec"). The root cause: when originalName arrives corrupted, the lookup key becomes "apache.apache.apache.BusyWorkers" which doesn't match fieldReplaces["apache.BusyWorkers"], falling back to ChangeNames() which preserves the corruption. The fix adds deduplicatePrefix() to normalize measurement names before building lookup keys (e.g., "apache.apache.apache" → "apache"). --- plugins/outputs/sematext/processors/rename.go | 28 ++++++++++++ .../sematext/processors/rename_test.go | 43 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/plugins/outputs/sematext/processors/rename.go b/plugins/outputs/sematext/processors/rename.go index aa7dbbdcbd272..9334a756499ec 100644 --- a/plugins/outputs/sematext/processors/rename.go +++ b/plugins/outputs/sematext/processors/rename.go @@ -210,6 +210,10 @@ func NewRename() BatchProcessor { return &Rename{} } func (r *Rename) Process(points []telegraf.Metric) []telegraf.Metric { for _, point := range points { originalName := point.Name() + + // Fix corrupted measurement names (e.g., "apache.apache.apache" → "apache") + originalName = deduplicatePrefix(originalName) + replace, ok := measurementReplaces[originalName] if !ok { replace = ChangeNames(originalName) @@ -275,3 +279,27 @@ func ChangeNames(name string) string { } func (Rename) Close() {} + +// deduplicatePrefix fixes recursive prefix corruption. +// Example: "apache.apache.apache" → "apache" +func deduplicatePrefix(name string) string { + parts := strings.Split(name, ".") + if len(parts) < 2 { + return name + } + + prefix := parts[0] + count := 0 + for count < len(parts) && parts[count] == prefix { + count++ + } + + if count > 1 { + remaining := parts[count:] + if len(remaining) > 0 { + return prefix + "." + strings.Join(remaining, ".") + } + return prefix + } + return name +} diff --git a/plugins/outputs/sematext/processors/rename_test.go b/plugins/outputs/sematext/processors/rename_test.go index 8f8e335f5ca10..bd044f59cb68a 100644 --- a/plugins/outputs/sematext/processors/rename_test.go +++ b/plugins/outputs/sematext/processors/rename_test.go @@ -34,6 +34,49 @@ func TestRename(t *testing.T) { assert.Equal(t, "mongodb_shard_stats.in_use", results[8].FieldList()[0].Key) } +func TestDeduplicatePrefix(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"apache", "apache"}, + {"apache.apache", "apache"}, + {"apache.apache.apache", "apache"}, + {"apache.apache.apache.apache", "apache"}, + {"nginx", "nginx"}, + {"nginx.nginx", "nginx"}, + {"nginx.nginx.nginx", "nginx"}, + {"mongodb", "mongodb"}, + {"mongodb.mongodb.mongodb", "mongodb"}, + // Edge cases + {"", ""}, + {"a", "a"}, + {"a.b", "a.b"}, + {"a.a.b", "a.b"}, + {"foo.foo.bar.baz", "foo.bar.baz"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := deduplicatePrefix(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestRenameWithCorruptedMeasurementName(t *testing.T) { + r := Rename{} + + // Simulate corrupted measurement name with 64+ repeated prefixes + corruptedName := "apache.apache.apache.apache.apache" + m := newMetric(corruptedName, nil, map[string]interface{}{"BusyWorkers": 10}) + + results := r.Process([]telegraf.Metric{m}) + + assert.Equal(t, "apache", results[0].Name()) + assert.Equal(t, "workers.busy", results[0].FieldList()[0].Key) +} + func newMetric(name string, tags map[string]string, fields map[string]interface{}) telegraf.Metric { if tags == nil { tags = map[string]string{}