From d5aff96db94f3a381563c6b124c10ba778773fc5 Mon Sep 17 00:00:00 2001 From: Artyom Antonov Date: Wed, 24 Jun 2026 16:43:06 +0500 Subject: [PATCH 1/6] feat(outputs.opentelemetry): Add a config option for specifying name/field separator --- go.mod | 2 + go.sum | 4 +- .../outputs/opentelemetry/opentelemetry.go | 29 +- .../opentelemetry/opentelemetry_test.go | 255 ++++++++++++++++++ 4 files changed, 275 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 0776bc758d20b..5ff4e43f4b222 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.26.0 godebug x509negativeserial=1 +replace github.com/influxdata/influxdb-observability/influx2otel => github.com/mchrome/influxdb-observability/influx2otel v0.0.0-20260623155732-db200540d054 + require ( cloud.google.com/go/auth v0.20.0 cloud.google.com/go/bigquery v1.77.0 diff --git a/go.sum b/go.sum index 78e478c30f1a5..790d0f270d2f0 100644 --- a/go.sum +++ b/go.sum @@ -1661,8 +1661,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb-observability/common v0.5.12 h1:4YwZ+vsodz6VfoiX+ZqVotmnyCa9vCCPksSBK/WLjBs= github.com/influxdata/influxdb-observability/common v0.5.12/go.mod h1:u+CABnGO/F1IK51pDlZQroh4+igJNo695XrbLGDBhVc= -github.com/influxdata/influxdb-observability/influx2otel v0.5.12 h1:u0lNE3+63rILk4mtmCYsNyczH/1wEXnM+1aBzBe5akk= -github.com/influxdata/influxdb-observability/influx2otel v0.5.12/go.mod h1:bM407XIJYnrJYJ9Q3q2ytDSOyFhiYmGm0Sz1Qf48RPk= github.com/influxdata/influxdb-observability/otel2influx v0.5.12 h1:t9gmVOOHbZyEAvIYSoO97Tde1KArVtiYdM0/0Dhmuio= github.com/influxdata/influxdb-observability/otel2influx v0.5.12/go.mod h1:YGsb8xYfjHvcr2y0+Nj7kOHMTw7fWDbAA4g/qJKkvaU= github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98= @@ -1866,6 +1864,8 @@ github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEj github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mchrome/influxdb-observability/influx2otel v0.0.0-20260623155732-db200540d054 h1:WXWL5QDbST9SMghx58SgxKadDTS1YqFyngMS+7BHnPk= +github.com/mchrome/influxdb-observability/influx2otel v0.0.0-20260623155732-db200540d054/go.mod h1:YgNF+ESSKXizCNRvkIAFMS8rWaP8t/86GLuycJ7vOTw= github.com/mdlayher/apcupsd v0.0.0-20220319200143-473c7b5f3c6a h1:JOlLsLUQnokTyWWwEvOVoKH3XUl6oDMP8jisO54l6J8= github.com/mdlayher/apcupsd v0.0.0-20220319200143-473c7b5f3c6a/go.mod h1:960H6oqSawdujauTeLX9BOx+ZdYX0TdG9xE9br5bino= github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= diff --git a/plugins/outputs/opentelemetry/opentelemetry.go b/plugins/outputs/opentelemetry/opentelemetry.go index ccaed80960a99..848bf3a88fc44 100644 --- a/plugins/outputs/opentelemetry/opentelemetry.go +++ b/plugins/outputs/opentelemetry/opentelemetry.go @@ -34,12 +34,13 @@ type OpenTelemetry struct { EncodingType string `toml:"encoding_type"` tls.ClientConfig - Timeout config.Duration `toml:"timeout"` - Compression string `toml:"compression"` - Token config.Secret `toml:"token"` - Headers map[string]string `toml:"headers"` - Attributes map[string]string `toml:"attributes"` - Coralogix *CoralogixConfig `toml:"coralogix"` + Timeout config.Duration `toml:"timeout"` + Compression string `toml:"compression"` + OtelNameSeparator string `toml:"otel_name_separator"` + Token config.Secret `toml:"token"` + Headers map[string]string `toml:"headers"` + Attributes map[string]string `toml:"attributes"` + Coralogix *CoralogixConfig `toml:"coralogix"` proxy.HTTPProxy proxy.TCPProxy @@ -105,7 +106,7 @@ func (o *OpenTelemetry) Connect() error { o.Headers["Authorization"] = "Bearer " + o.Coralogix.PrivateKey } - metricsConverter, err := influx2otel.NewLineProtocolToOtelMetrics(logger) + metricsConverter, err := influx2otel.NewLineProtocolToOtelMetricsWithSeparator(logger, o.OtelNameSeparator) if err != nil { return err } @@ -232,17 +233,19 @@ func (o *OpenTelemetry) sendBatch(metrics []telegraf.Metric) error { } const ( - defaultServiceAddress = "localhost:4317" - defaultTimeout = config.Duration(5 * time.Second) - defaultCompression = "gzip" + defaultServiceAddress = "localhost:4317" + defaultTimeout = config.Duration(5 * time.Second) + defaultCompression = "gzip" + defaultOtelNameSeparator = "_" ) func init() { outputs.Add("opentelemetry", func() telegraf.Output { return &OpenTelemetry{ - ServiceAddress: defaultServiceAddress, - Timeout: defaultTimeout, - Compression: defaultCompression, + ServiceAddress: defaultServiceAddress, + Timeout: defaultTimeout, + Compression: defaultCompression, + OtelNameSeparator: defaultOtelNameSeparator, } }) } diff --git a/plugins/outputs/opentelemetry/opentelemetry_test.go b/plugins/outputs/opentelemetry/opentelemetry_test.go index b74ba43a7ed46..4a8bb05e6c2a6 100644 --- a/plugins/outputs/opentelemetry/opentelemetry_test.go +++ b/plugins/outputs/opentelemetry/opentelemetry_test.go @@ -284,6 +284,261 @@ func TestOpenTelemetryHTTPJSON(t *testing.T) { require.JSONEq(t, string(expectJSON), string(gotJSON)) } +func TestOpenTelemetrySeparator(t *testing.T) { + expect := pmetric.NewMetrics() + { + rm := expect.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("host.name", "potato") + rm.Resource().Attributes().PutStr("attr-key", "attr-val") + ilm := rm.ScopeMetrics().AppendEmpty() + ilm.Scope().SetName("My Library Name") + m := ilm.Metrics().AppendEmpty() + m.SetName("mem.used_percent") + m.SetEmptyGauge() + dp := m.Gauge().DataPoints().AppendEmpty() + dp.Attributes().PutStr("foo", "bar") + dp.SetTimestamp(pcommon.Timestamp(1622848686000000000)) + dp.SetDoubleValue(87.332) + } + m := newMockOtelService(t) + t.Cleanup(m.Cleanup) + + metricsConverter, err := influx2otel.NewLineProtocolToOtelMetricsWithSeparator(common.NoopLogger{}, ".") + require.NoError(t, err) + plugin := &OpenTelemetry{ + ServiceAddress: m.Address(), + Timeout: config.Duration(time.Second), + Headers: map[string]string{"test": "header1"}, + Attributes: map[string]string{"attr-key": "attr-val"}, + metricsConverter: metricsConverter, + otlpMetricClient: &gRPCClient{ + grpcClientConn: m.GrpcClient(), + metricsServiceClient: pmetricotlp.NewGRPCClient(m.GrpcClient()), + }, + Log: testutil.Logger{}, + } + + input := metric.New( + "mem", + map[string]string{ + "foo": "bar", + "otel.library.name": "My Library Name", + "host.name": "potato", + }, + map[string]interface{}{ + "used_percent": 87.332, + }, + time.Unix(0, 1622848686000000000), + telegraf.Gauge, + ) + + require.NoError(t, plugin.Write([]telegraf.Metric{input})) + + got := m.GotMetrics() + + marshaller := pmetric.JSONMarshaler{} + expectJSON, err := marshaller.MarshalMetrics(expect) + require.NoError(t, err) + + gotJSON, err := marshaller.MarshalMetrics(got) + require.NoError(t, err) + + require.JSONEq(t, string(expectJSON), string(gotJSON)) +} + +func TestOpenTelemetrySeparatorHTTPProtobuf(t *testing.T) { + expect := pmetric.NewMetrics() + { + rm := expect.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("host.name", "potato") + rm.Resource().Attributes().PutStr("attr-key", "attr-val") + ilm := rm.ScopeMetrics().AppendEmpty() + ilm.Scope().SetName("My Library Name") + m := ilm.Metrics().AppendEmpty() + m.SetName("mem.used_percent") + m.SetEmptyGauge() + dp := m.Gauge().DataPoints().AppendEmpty() + dp.Attributes().PutStr("foo", "bar") + dp.SetTimestamp(pcommon.Timestamp(1622848686000000000)) + dp.SetDoubleValue(87.332) + } + + var receivedMetrics pmetric.Metrics + var receivedContentType string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedContentType = r.Header.Get("Content-Type") + body, err := io.ReadAll(r.Body) + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + req := pmetricotlp.NewExportRequest() + err = req.UnmarshalProto(body) + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + receivedMetrics = pmetric.NewMetrics() + req.Metrics().CopyTo(receivedMetrics) + + resp := pmetricotlp.NewExportResponse() + respBytes, err := resp.MarshalProto() + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/x-protobuf") + w.WriteHeader(http.StatusOK) + _, err = w.Write(respBytes) + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + })) + defer server.Close() + + plugin := &OpenTelemetry{ + ServiceAddress: server.URL, + EncodingType: "protobuf", + Timeout: config.Duration(time.Second), + Attributes: map[string]string{"attr-key": "attr-val"}, + Compression: "none", + OtelNameSeparator: ".", + Log: testutil.Logger{}, + } + require.NoError(t, plugin.Connect()) + + input := metric.New( + "mem", + map[string]string{ + "foo": "bar", + "otel.library.name": "My Library Name", + "host.name": "potato", + }, + map[string]interface{}{ + "used_percent": 87.332, + }, + time.Unix(0, 1622848686000000000), + telegraf.Gauge, + ) + + require.NoError(t, plugin.Write([]telegraf.Metric{input})) + + require.Equal(t, "application/x-protobuf", receivedContentType) + + marshaller := pmetric.JSONMarshaler{} + expectJSON, err := marshaller.MarshalMetrics(expect) + require.NoError(t, err) + + gotJSON, err := marshaller.MarshalMetrics(receivedMetrics) + require.NoError(t, err) + + require.JSONEq(t, string(expectJSON), string(gotJSON)) +} + +func TestOpenTelemetrySeparatorHTTPJSON(t *testing.T) { + expect := pmetric.NewMetrics() + { + rm := expect.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("host.name", "potato") + rm.Resource().Attributes().PutStr("attr-key", "attr-val") + ilm := rm.ScopeMetrics().AppendEmpty() + ilm.Scope().SetName("My Library Name") + m := ilm.Metrics().AppendEmpty() + m.SetName("mem.used_percent") + m.SetEmptyGauge() + dp := m.Gauge().DataPoints().AppendEmpty() + dp.Attributes().PutStr("foo", "bar") + dp.SetTimestamp(pcommon.Timestamp(1622848686000000000)) + dp.SetDoubleValue(87.332) + } + + var receivedMetrics pmetric.Metrics + var receivedContentType string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedContentType = r.Header.Get("Content-Type") + body, err := io.ReadAll(r.Body) + if err != nil { + t.Error(err) + return + } + + req := pmetricotlp.NewExportRequest() + err = req.UnmarshalJSON(body) + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + receivedMetrics = pmetric.NewMetrics() + req.Metrics().CopyTo(receivedMetrics) + + resp := pmetricotlp.NewExportResponse() + respBytes, err := resp.MarshalJSON() + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err = w.Write(respBytes) + if err != nil { + t.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + })) + defer server.Close() + + plugin := &OpenTelemetry{ + ServiceAddress: server.URL, + EncodingType: "json", + Timeout: config.Duration(time.Second), + Attributes: map[string]string{"attr-key": "attr-val"}, + Compression: "none", + OtelNameSeparator: ".", + Log: testutil.Logger{}, + } + require.NoError(t, plugin.Connect()) + + input := metric.New( + "mem", + map[string]string{ + "foo": "bar", + "otel.library.name": "My Library Name", + "host.name": "potato", + }, + map[string]interface{}{ + "used_percent": 87.332, + }, + time.Unix(0, 1622848686000000000), + telegraf.Gauge, + ) + + require.NoError(t, plugin.Write([]telegraf.Metric{input})) + + require.Equal(t, "application/json", receivedContentType) + + marshaller := pmetric.JSONMarshaler{} + expectJSON, err := marshaller.MarshalMetrics(expect) + require.NoError(t, err) + + gotJSON, err := marshaller.MarshalMetrics(receivedMetrics) + require.NoError(t, err) + + require.JSONEq(t, string(expectJSON), string(gotJSON)) +} + var _ pmetricotlp.GRPCServer = (*mockOtelService)(nil) type mockOtelService struct { From af4cbc5941c098cd2f365efe4bec2b99878668a9 Mon Sep 17 00:00:00 2001 From: Artyom Antonov Date: Fri, 3 Jul 2026 15:26:15 +0500 Subject: [PATCH 2/6] Add description of the new config field to sample.conf & readme --- plugins/outputs/opentelemetry/README.md | 3 +++ plugins/outputs/opentelemetry/sample.conf | 3 +++ 2 files changed, 6 insertions(+) diff --git a/plugins/outputs/opentelemetry/README.md b/plugins/outputs/opentelemetry/README.md index e3322cc10103d..b3e6901e791d7 100644 --- a/plugins/outputs/opentelemetry/README.md +++ b/plugins/outputs/opentelemetry/README.md @@ -50,6 +50,9 @@ plugin ordering. See [CONFIGURATION.md][CONFIGURATION.md] for more details. ## Supports: "gzip", "none" # compression = "gzip" + ## Characters for separating LineProtocol's metric name and field when converting to OpenTelemetry's metric name + # otel_name_separator = "_" + ## Optional proxy settings for HTTP endpoints (service_address starts with http:// or https://) # use_system_proxy = false # http_proxy_url = "" diff --git a/plugins/outputs/opentelemetry/sample.conf b/plugins/outputs/opentelemetry/sample.conf index ed462e9009601..7aa27bb49c610 100644 --- a/plugins/outputs/opentelemetry/sample.conf +++ b/plugins/outputs/opentelemetry/sample.conf @@ -28,6 +28,9 @@ ## Supports: "gzip", "none" # compression = "gzip" + ## Characters for separating LineProtocol's metric name and field when converting to OpenTelemetry's metric name + # otel_name_separator = "_" + ## Optional proxy settings for HTTP endpoints (service_address starts with http:// or https://) # use_system_proxy = false # http_proxy_url = "" From dfcab4900912e5e0f7c3989a37e6dcc297e7990f Mon Sep 17 00:00:00 2001 From: Artyom Antonov Date: Fri, 3 Jul 2026 15:32:40 +0500 Subject: [PATCH 3/6] Rename otel_name_separator to name_separator --- plugins/outputs/opentelemetry/README.md | 2 +- .../outputs/opentelemetry/opentelemetry.go | 32 +++++++++---------- .../opentelemetry/opentelemetry_test.go | 28 ++++++++-------- plugins/outputs/opentelemetry/sample.conf | 2 +- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/plugins/outputs/opentelemetry/README.md b/plugins/outputs/opentelemetry/README.md index b3e6901e791d7..adceb2e4ee321 100644 --- a/plugins/outputs/opentelemetry/README.md +++ b/plugins/outputs/opentelemetry/README.md @@ -51,7 +51,7 @@ plugin ordering. See [CONFIGURATION.md][CONFIGURATION.md] for more details. # compression = "gzip" ## Characters for separating LineProtocol's metric name and field when converting to OpenTelemetry's metric name - # otel_name_separator = "_" + # name_separator = "_" ## Optional proxy settings for HTTP endpoints (service_address starts with http:// or https://) # use_system_proxy = false diff --git a/plugins/outputs/opentelemetry/opentelemetry.go b/plugins/outputs/opentelemetry/opentelemetry.go index 848bf3a88fc44..11b4017e2180c 100644 --- a/plugins/outputs/opentelemetry/opentelemetry.go +++ b/plugins/outputs/opentelemetry/opentelemetry.go @@ -34,13 +34,13 @@ type OpenTelemetry struct { EncodingType string `toml:"encoding_type"` tls.ClientConfig - Timeout config.Duration `toml:"timeout"` - Compression string `toml:"compression"` - OtelNameSeparator string `toml:"otel_name_separator"` - Token config.Secret `toml:"token"` - Headers map[string]string `toml:"headers"` - Attributes map[string]string `toml:"attributes"` - Coralogix *CoralogixConfig `toml:"coralogix"` + Timeout config.Duration `toml:"timeout"` + Compression string `toml:"compression"` + NameSeparator string `toml:"name_separator"` + Token config.Secret `toml:"token"` + Headers map[string]string `toml:"headers"` + Attributes map[string]string `toml:"attributes"` + Coralogix *CoralogixConfig `toml:"coralogix"` proxy.HTTPProxy proxy.TCPProxy @@ -106,7 +106,7 @@ func (o *OpenTelemetry) Connect() error { o.Headers["Authorization"] = "Bearer " + o.Coralogix.PrivateKey } - metricsConverter, err := influx2otel.NewLineProtocolToOtelMetricsWithSeparator(logger, o.OtelNameSeparator) + metricsConverter, err := influx2otel.NewLineProtocolToOtelMetricsWithSeparator(logger, o.NameSeparator) if err != nil { return err } @@ -233,19 +233,19 @@ func (o *OpenTelemetry) sendBatch(metrics []telegraf.Metric) error { } const ( - defaultServiceAddress = "localhost:4317" - defaultTimeout = config.Duration(5 * time.Second) - defaultCompression = "gzip" - defaultOtelNameSeparator = "_" + defaultServiceAddress = "localhost:4317" + defaultTimeout = config.Duration(5 * time.Second) + defaultCompression = "gzip" + defaultNameSeparator = "_" ) func init() { outputs.Add("opentelemetry", func() telegraf.Output { return &OpenTelemetry{ - ServiceAddress: defaultServiceAddress, - Timeout: defaultTimeout, - Compression: defaultCompression, - OtelNameSeparator: defaultOtelNameSeparator, + ServiceAddress: defaultServiceAddress, + Timeout: defaultTimeout, + Compression: defaultCompression, + NameSeparator: defaultNameSeparator, } }) } diff --git a/plugins/outputs/opentelemetry/opentelemetry_test.go b/plugins/outputs/opentelemetry/opentelemetry_test.go index 4a8bb05e6c2a6..9e0fac7320ace 100644 --- a/plugins/outputs/opentelemetry/opentelemetry_test.go +++ b/plugins/outputs/opentelemetry/opentelemetry_test.go @@ -405,13 +405,13 @@ func TestOpenTelemetrySeparatorHTTPProtobuf(t *testing.T) { defer server.Close() plugin := &OpenTelemetry{ - ServiceAddress: server.URL, - EncodingType: "protobuf", - Timeout: config.Duration(time.Second), - Attributes: map[string]string{"attr-key": "attr-val"}, - Compression: "none", - OtelNameSeparator: ".", - Log: testutil.Logger{}, + ServiceAddress: server.URL, + EncodingType: "protobuf", + Timeout: config.Duration(time.Second), + Attributes: map[string]string{"attr-key": "attr-val"}, + Compression: "none", + NameSeparator: ".", + Log: testutil.Logger{}, } require.NoError(t, plugin.Connect()) @@ -501,13 +501,13 @@ func TestOpenTelemetrySeparatorHTTPJSON(t *testing.T) { defer server.Close() plugin := &OpenTelemetry{ - ServiceAddress: server.URL, - EncodingType: "json", - Timeout: config.Duration(time.Second), - Attributes: map[string]string{"attr-key": "attr-val"}, - Compression: "none", - OtelNameSeparator: ".", - Log: testutil.Logger{}, + ServiceAddress: server.URL, + EncodingType: "json", + Timeout: config.Duration(time.Second), + Attributes: map[string]string{"attr-key": "attr-val"}, + Compression: "none", + NameSeparator: ".", + Log: testutil.Logger{}, } require.NoError(t, plugin.Connect()) diff --git a/plugins/outputs/opentelemetry/sample.conf b/plugins/outputs/opentelemetry/sample.conf index 7aa27bb49c610..ae8973eef6095 100644 --- a/plugins/outputs/opentelemetry/sample.conf +++ b/plugins/outputs/opentelemetry/sample.conf @@ -29,7 +29,7 @@ # compression = "gzip" ## Characters for separating LineProtocol's metric name and field when converting to OpenTelemetry's metric name - # otel_name_separator = "_" + # name_separator = "_" ## Optional proxy settings for HTTP endpoints (service_address starts with http:// or https://) # use_system_proxy = false From 894adfaac2c5cb1af9c90275fb879beb0b219601 Mon Sep 17 00:00:00 2001 From: Artyom Antonov Date: Mon, 6 Jul 2026 12:50:49 +0500 Subject: [PATCH 4/6] Use Connect() in all test functions instead of injecting converters directly --- .../opentelemetry/opentelemetry_test.go | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/plugins/outputs/opentelemetry/opentelemetry_test.go b/plugins/outputs/opentelemetry/opentelemetry_test.go index 9e0fac7320ace..e964ace35c5b8 100644 --- a/plugins/outputs/opentelemetry/opentelemetry_test.go +++ b/plugins/outputs/opentelemetry/opentelemetry_test.go @@ -9,8 +9,6 @@ import ( "testing" "time" - "github.com/influxdata/influxdb-observability/common" - "github.com/influxdata/influxdb-observability/influx2otel" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" @@ -45,20 +43,18 @@ func TestOpenTelemetry(t *testing.T) { m := newMockOtelService(t) t.Cleanup(m.Cleanup) - metricsConverter, err := influx2otel.NewLineProtocolToOtelMetrics(common.NoopLogger{}) - require.NoError(t, err) plugin := &OpenTelemetry{ - ServiceAddress: m.Address(), - Timeout: config.Duration(time.Second), - Headers: map[string]string{"test": "header1"}, - Attributes: map[string]string{"attr-key": "attr-val"}, - metricsConverter: metricsConverter, + ServiceAddress: m.Address(), + Timeout: config.Duration(time.Second), + Headers: map[string]string{"test": "header1"}, + Attributes: map[string]string{"attr-key": "attr-val"}, otlpMetricClient: &gRPCClient{ grpcClientConn: m.GrpcClient(), metricsServiceClient: pmetricotlp.NewGRPCClient(m.GrpcClient()), }, Log: testutil.Logger{}, } + require.NoError(t, plugin.Connect()) input := metric.New( "cpu_temp", @@ -145,17 +141,13 @@ func TestOpenTelemetryHTTPProtobuf(t *testing.T) { })) defer server.Close() - metricsConverter, err := influx2otel.NewLineProtocolToOtelMetrics(common.NoopLogger{}) - require.NoError(t, err) - plugin := &OpenTelemetry{ - ServiceAddress: server.URL, - EncodingType: "protobuf", - Timeout: config.Duration(time.Second), - Attributes: map[string]string{"attr-key": "attr-val"}, - Compression: "none", - metricsConverter: metricsConverter, - Log: testutil.Logger{}, + ServiceAddress: server.URL, + EncodingType: "protobuf", + Timeout: config.Duration(time.Second), + Attributes: map[string]string{"attr-key": "attr-val"}, + Compression: "none", + Log: testutil.Logger{}, } require.NoError(t, plugin.Connect()) @@ -243,17 +235,13 @@ func TestOpenTelemetryHTTPJSON(t *testing.T) { })) defer server.Close() - metricsConverter, err := influx2otel.NewLineProtocolToOtelMetrics(common.NoopLogger{}) - require.NoError(t, err) - plugin := &OpenTelemetry{ - ServiceAddress: server.URL, - EncodingType: "json", - Timeout: config.Duration(time.Second), - Attributes: map[string]string{"attr-key": "attr-val"}, - Compression: "none", - metricsConverter: metricsConverter, - Log: testutil.Logger{}, + ServiceAddress: server.URL, + EncodingType: "json", + Timeout: config.Duration(time.Second), + Attributes: map[string]string{"attr-key": "attr-val"}, + Compression: "none", + Log: testutil.Logger{}, } require.NoError(t, plugin.Connect()) @@ -303,20 +291,19 @@ func TestOpenTelemetrySeparator(t *testing.T) { m := newMockOtelService(t) t.Cleanup(m.Cleanup) - metricsConverter, err := influx2otel.NewLineProtocolToOtelMetricsWithSeparator(common.NoopLogger{}, ".") - require.NoError(t, err) plugin := &OpenTelemetry{ - ServiceAddress: m.Address(), - Timeout: config.Duration(time.Second), - Headers: map[string]string{"test": "header1"}, - Attributes: map[string]string{"attr-key": "attr-val"}, - metricsConverter: metricsConverter, + ServiceAddress: m.Address(), + Timeout: config.Duration(time.Second), + Headers: map[string]string{"test": "header1"}, + Attributes: map[string]string{"attr-key": "attr-val"}, + NameSeparator: ".", otlpMetricClient: &gRPCClient{ grpcClientConn: m.GrpcClient(), metricsServiceClient: pmetricotlp.NewGRPCClient(m.GrpcClient()), }, Log: testutil.Logger{}, } + require.NoError(t, plugin.Connect()) input := metric.New( "mem", From 9fd0978d49a3ef795dd38e870bdd5ca24b242141 Mon Sep 17 00:00:00 2001 From: Artyom Antonov Date: Mon, 6 Jul 2026 12:57:26 +0500 Subject: [PATCH 5/6] Add status 500 header to response when failing to parse request body in HTTPJson tests --- plugins/outputs/opentelemetry/opentelemetry_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/outputs/opentelemetry/opentelemetry_test.go b/plugins/outputs/opentelemetry/opentelemetry_test.go index e964ace35c5b8..1aa4477d6a204 100644 --- a/plugins/outputs/opentelemetry/opentelemetry_test.go +++ b/plugins/outputs/opentelemetry/opentelemetry_test.go @@ -202,6 +202,7 @@ func TestOpenTelemetryHTTPJSON(t *testing.T) { body, err := io.ReadAll(r.Body) if err != nil { t.Error(err) + w.WriteHeader(http.StatusInternalServerError) return } @@ -454,6 +455,7 @@ func TestOpenTelemetrySeparatorHTTPJSON(t *testing.T) { body, err := io.ReadAll(r.Body) if err != nil { t.Error(err) + w.WriteHeader(http.StatusInternalServerError) return } From 2eb45e5a0375b88a36786761fa5abc589c519bba Mon Sep 17 00:00:00 2001 From: Artyom Antonov Date: Mon, 6 Jul 2026 13:11:08 +0500 Subject: [PATCH 6/6] Add a new test that uses the default name separator --- .../opentelemetry/opentelemetry_test.go | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/plugins/outputs/opentelemetry/opentelemetry_test.go b/plugins/outputs/opentelemetry/opentelemetry_test.go index 1aa4477d6a204..81f531563b668 100644 --- a/plugins/outputs/opentelemetry/opentelemetry_test.go +++ b/plugins/outputs/opentelemetry/opentelemetry_test.go @@ -334,6 +334,67 @@ func TestOpenTelemetrySeparator(t *testing.T) { require.JSONEq(t, string(expectJSON), string(gotJSON)) } +func TestOpenTelemetryDefaultSeparator(t *testing.T) { + expect := pmetric.NewMetrics() + { + rm := expect.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("host.name", "potato") + rm.Resource().Attributes().PutStr("attr-key", "attr-val") + ilm := rm.ScopeMetrics().AppendEmpty() + ilm.Scope().SetName("My Library Name") + m := ilm.Metrics().AppendEmpty() + m.SetName("mem_used_percent") + m.SetEmptyGauge() + dp := m.Gauge().DataPoints().AppendEmpty() + dp.Attributes().PutStr("foo", "bar") + dp.SetTimestamp(pcommon.Timestamp(1622848686000000000)) + dp.SetDoubleValue(87.332) + } + m := newMockOtelService(t) + t.Cleanup(m.Cleanup) + + plugin := &OpenTelemetry{ + ServiceAddress: m.Address(), + Timeout: config.Duration(time.Second), + Headers: map[string]string{"test": "header1"}, + Attributes: map[string]string{"attr-key": "attr-val"}, + NameSeparator: defaultNameSeparator, + otlpMetricClient: &gRPCClient{ + grpcClientConn: m.GrpcClient(), + metricsServiceClient: pmetricotlp.NewGRPCClient(m.GrpcClient()), + }, + Log: testutil.Logger{}, + } + require.NoError(t, plugin.Connect()) + + input := metric.New( + "mem", + map[string]string{ + "foo": "bar", + "otel.library.name": "My Library Name", + "host.name": "potato", + }, + map[string]interface{}{ + "used_percent": 87.332, + }, + time.Unix(0, 1622848686000000000), + telegraf.Gauge, + ) + + require.NoError(t, plugin.Write([]telegraf.Metric{input})) + + got := m.GotMetrics() + + marshaller := pmetric.JSONMarshaler{} + expectJSON, err := marshaller.MarshalMetrics(expect) + require.NoError(t, err) + + gotJSON, err := marshaller.MarshalMetrics(got) + require.NoError(t, err) + + require.JSONEq(t, string(expectJSON), string(gotJSON)) +} + func TestOpenTelemetrySeparatorHTTPProtobuf(t *testing.T) { expect := pmetric.NewMetrics() {