From 0d7ab2c2ae8c180ddad1b44160a69883dd29c7b0 Mon Sep 17 00:00:00 2001 From: rogerogers Date: Thu, 6 Oct 2022 14:55:45 +0800 Subject: [PATCH 1/5] Add User-Agent header to OTLP exporter requests Signed-off-by: rogerogers --- CHANGELOG.md | 1 + exporters/otlp/internal/header.go | 24 +++++++++++++++++ exporters/otlp/internal/header_test.go | 26 +++++++++++++++++++ .../otlp/otlpmetric/otlpmetricgrpc/client.go | 3 +++ .../otlpmetric/otlpmetricgrpc/client_test.go | 1 + .../otlp/otlpmetric/otlpmetrichttp/client.go | 3 +++ .../otlpmetric/otlpmetrichttp/client_test.go | 1 + .../otlp/otlptrace/otlptracegrpc/client.go | 2 ++ .../otlptrace/otlptracegrpc/client_test.go | 1 + .../otlp/otlptrace/otlptracehttp/client.go | 2 ++ 10 files changed, 64 insertions(+) create mode 100644 exporters/otlp/internal/header.go create mode 100644 exporters/otlp/internal/header_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index cb99127ad4f..54b496b51da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Added an example of using metric views to customize instruments. (#3177) +- Add User-Agent header to OTLP exporter requests (#3261) ### Changed diff --git a/exporters/otlp/internal/header.go b/exporters/otlp/internal/header.go new file mode 100644 index 00000000000..9aa62ed9e8e --- /dev/null +++ b/exporters/otlp/internal/header.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package internal contains common functionality for all OTLP exporters. +package internal // import "go.opentelemetry.io/otel/exporters/otlp/internal" + +import "go.opentelemetry.io/otel" + +// GetUserAgentHeader return an OTLP header value form "OTel OTLP Exporter Go/{{ .Version }}" +// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#user-agent +func GetUserAgentHeader() string { + return "OTel OTLP Exporter Go/" + otel.Version() +} diff --git a/exporters/otlp/internal/header_test.go b/exporters/otlp/internal/header_test.go new file mode 100644 index 00000000000..ecca1a9490e --- /dev/null +++ b/exporters/otlp/internal/header_test.go @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package internal contains common functionality for all OTLP exporters. +package internal // import "go.opentelemetry.io/otel/exporters/otlp/internal" + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetUserAgentHeader(t *testing.T) { + require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", GetUserAgentHeader()) +} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go index 4c5beb8f384..d2f4be38bce 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go @@ -24,6 +24,7 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "go.opentelemetry.io/otel/exporters/otlp/internal" "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" @@ -77,6 +78,8 @@ func newClient(ctx context.Context, options ...Option) (otlpmetric.Client, error } if c.conn == nil { + // Add a User-Agent header when no ClientConn was provided + cfg.DialOptions = append(cfg.DialOptions, grpc.WithUserAgent(internal.GetUserAgentHeader())) // If the caller did not provide a ClientConn when the client was // created, create one using the configuration they did provide. conn, err := grpc.DialContext(ctx, cfg.Metrics.Endpoint, cfg.DialOptions...) diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go index 1cb8cd815e3..7d949a16c4e 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go @@ -169,6 +169,7 @@ func TestConfig(t *testing.T) { require.NoError(t, exp.Shutdown(ctx)) got := coll.Headers() + require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", got) require.Contains(t, got, key) assert.Equal(t, got[key], []string{headers[key]}) }) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 7a8d7e14707..0840a2c8fa6 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -29,6 +29,7 @@ import ( "google.golang.org/protobuf/proto" + "go.opentelemetry.io/otel/exporters/otlp/internal" "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" @@ -101,6 +102,8 @@ func newClient(opts ...Option) (otlpmetric.Client, error) { return nil, err } + req.Header.Set("User-Agent", internal.GetUserAgentHeader()) + if n := len(cfg.Metrics.Headers); n > 0 { for k, v := range cfg.Metrics.Headers { req.Header.Set(k, v) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index 22740252b02..bc78656bbda 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -75,6 +75,7 @@ func TestConfig(t *testing.T) { require.NoError(t, exp.Shutdown(ctx)) got := coll.Headers() + require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", got) require.Contains(t, got, key) assert.Equal(t, got[key], []string{headers[key]}) }) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/client.go b/exporters/otlp/otlptrace/otlptracegrpc/client.go index 9d6e1898b14..0eb3cd29c66 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/client.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/client.go @@ -91,6 +91,8 @@ func newClient(opts ...Option) *client { // Start establishes a gRPC connection to the collector. func (c *client) Start(ctx context.Context) error { if c.conn == nil { + // Add a User-Agent header when no ClientConn was provided + c.dialOpts = append(c.dialOpts, grpc.WithUserAgent(internal.GetUserAgentHeader())) // If the caller did not provide a ClientConn when the client was // created, create one using the configuration they did provide. conn, err := grpc.DialContext(ctx, c.endpoint, c.dialOpts...) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/client_test.go b/exporters/otlp/otlptrace/otlptracegrpc/client_test.go index d11111ed126..063c4a987b5 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/client_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/client_test.go @@ -212,6 +212,7 @@ func TestNewWithHeaders(t *testing.T) { require.NoError(t, exp.ExportSpans(ctx, roSpans)) headers := mc.getHeaders() + require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", headers.Get("user-agent")) require.Len(t, headers.Get("header1"), 1) assert.Equal(t, "value1", headers.Get("header1")[0]) } diff --git a/exporters/otlp/otlptrace/otlptracehttp/client.go b/exporters/otlp/otlptrace/otlptracehttp/client.go index 745b6541d42..8f742dfc1bd 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/client.go +++ b/exporters/otlp/otlptrace/otlptracehttp/client.go @@ -208,6 +208,8 @@ func (d *client) newRequest(body []byte) (request, error) { return request{Request: r}, err } + r.Header.Set("User-Agent", internal.GetUserAgentHeader()) + for k, v := range d.cfg.Headers { r.Header.Set(k, v) } From 5a2ae33eaec2416e793c6ca26476811a489f04e9 Mon Sep 17 00:00:00 2001 From: rogerogers Date: Thu, 6 Oct 2022 21:07:01 +0800 Subject: [PATCH 2/5] allow override grpc user-agent Signed-off-by: rogerogers --- exporters/otlp/otlpmetric/internal/oconf/options.go | 1 + exporters/otlp/otlpmetric/otlpmetricgrpc/client.go | 3 --- exporters/otlp/otlptrace/internal/otlpconfig/options.go | 1 + exporters/otlp/otlptrace/otlptracegrpc/client.go | 2 -- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/exporters/otlp/otlpmetric/internal/oconf/options.go b/exporters/otlp/otlpmetric/internal/oconf/options.go index f5a82d6db17..f7d44071442 100644 --- a/exporters/otlp/otlpmetric/internal/oconf/options.go +++ b/exporters/otlp/otlpmetric/internal/oconf/options.go @@ -104,6 +104,7 @@ func NewGRPCConfig(opts ...GRPCOption) Config { Timeout: DefaultTimeout, }, RetryConfig: retry.DefaultConfig, + DialOptions: []grpc.DialOption{grpc.WithUserAgent(internal.GetUserAgentHeader())}, } cfg = ApplyGRPCEnvConfigs(cfg) for _, opt := range opts { diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go index d2f4be38bce..4c5beb8f384 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client.go @@ -24,7 +24,6 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "go.opentelemetry.io/otel/exporters/otlp/internal" "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" @@ -78,8 +77,6 @@ func newClient(ctx context.Context, options ...Option) (otlpmetric.Client, error } if c.conn == nil { - // Add a User-Agent header when no ClientConn was provided - cfg.DialOptions = append(cfg.DialOptions, grpc.WithUserAgent(internal.GetUserAgentHeader())) // If the caller did not provide a ClientConn when the client was // created, create one using the configuration they did provide. conn, err := grpc.DialContext(ctx, cfg.Metrics.Endpoint, cfg.DialOptions...) diff --git a/exporters/otlp/otlptrace/internal/otlpconfig/options.go b/exporters/otlp/otlptrace/internal/otlpconfig/options.go index 56e83b85334..c48ffd53081 100644 --- a/exporters/otlp/otlptrace/internal/otlpconfig/options.go +++ b/exporters/otlp/otlptrace/internal/otlpconfig/options.go @@ -97,6 +97,7 @@ func NewGRPCConfig(opts ...GRPCOption) Config { Timeout: DefaultTimeout, }, RetryConfig: retry.DefaultConfig, + DialOptions: []grpc.DialOption{grpc.WithUserAgent(internal.GetUserAgentHeader())}, } cfg = ApplyGRPCEnvConfigs(cfg) for _, opt := range opts { diff --git a/exporters/otlp/otlptrace/otlptracegrpc/client.go b/exporters/otlp/otlptrace/otlptracegrpc/client.go index 0eb3cd29c66..9d6e1898b14 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/client.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/client.go @@ -91,8 +91,6 @@ func newClient(opts ...Option) *client { // Start establishes a gRPC connection to the collector. func (c *client) Start(ctx context.Context) error { if c.conn == nil { - // Add a User-Agent header when no ClientConn was provided - c.dialOpts = append(c.dialOpts, grpc.WithUserAgent(internal.GetUserAgentHeader())) // If the caller did not provide a ClientConn when the client was // created, create one using the configuration they did provide. conn, err := grpc.DialContext(ctx, c.endpoint, c.dialOpts...) From 02a5cba4915cda7d85ae7d1315cda52c9804b36f Mon Sep 17 00:00:00 2001 From: rogerogers Date: Fri, 7 Oct 2022 16:34:09 +0800 Subject: [PATCH 3/5] Fixed changelog description Signed-off-by: rogerogers --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b496b51da..6860a271c29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Added an example of using metric views to customize instruments. (#3177) -- Add User-Agent header to OTLP exporter requests (#3261) +- Add default User-Agent header to OTLP exporter requests (`go.opentelemetry.io/otel/exporters/otlpmetric/otlpmetricgrpc`, `go.opentelemetry.io/otel/exporters/otlpmetric/otlpmetrichttp`, `go.opentelemetry.io/otel/exporters/otlptrace/otlptracegrpc` and `go.opentelemetry.io/otel/exporters/otlptrace/otlptracehttp`). (#3261) ### Changed From 7b49815fe4f749d60181be964c6718d499272284 Mon Sep 17 00:00:00 2001 From: rogerogers Date: Fri, 7 Oct 2022 16:44:26 +0800 Subject: [PATCH 4/5] Fix lint Signed-off-by: rogerogers --- exporters/otlp/otlpmetric/otlpmetrichttp/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index ada9ef43f21..f1f2f64706c 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/stretchr/testify v1.7.1 + go.opentelemetry.io/otel v1.10.0 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.32.1 go.opentelemetry.io/otel/metric v0.32.1 @@ -21,7 +22,6 @@ require ( github.com/google/go-cmp v0.5.8 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.opentelemetry.io/otel v1.10.0 // indirect go.opentelemetry.io/otel/sdk v1.10.0 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect From 7a437d89ec26a714d7b653a06e8cc53a7b68c24e Mon Sep 17 00:00:00 2001 From: rogerogers Date: Fri, 7 Oct 2022 17:41:15 +0800 Subject: [PATCH 5/5] add test for with custom user agent of exporter/otlp Signed-off-by: rogerogers --- .../otlp/otlpmetric/otlpmetricgrpc/client_test.go | 15 +++++++++++++++ .../otlp/otlpmetric/otlpmetrichttp/client_test.go | 15 +++++++++++++++ .../otlp/otlptrace/otlptracegrpc/client_test.go | 15 +++++++++++++++ .../otlp/otlptrace/otlptracehttp/client_test.go | 13 +++++++++++++ 4 files changed, 58 insertions(+) diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go index 7d949a16c4e..64d3b216a2d 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/durationpb" @@ -189,4 +190,18 @@ func TestConfig(t *testing.T) { err := exp.Export(ctx, metricdata.ResourceMetrics{}) assert.ErrorContains(t, err, context.DeadlineExceeded.Error()) }) + + t.Run("WithCustomUserAgent", func(t *testing.T) { + key := "user-agent" + customerUserAgent := "custom-user-agent" + exp, coll := factoryFunc(nil, WithDialOption(grpc.WithUserAgent(customerUserAgent))) + t.Cleanup(coll.Shutdown) + ctx := context.Background() + require.NoError(t, exp.Export(ctx, metricdata.ResourceMetrics{})) + // Ensure everything is flushed. + require.NoError(t, exp.Shutdown(ctx)) + + got := coll.Headers() + assert.Contains(t, got[key][0], customerUserAgent) + }) } diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index bc78656bbda..09a6c15d82c 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -162,4 +162,19 @@ func TestConfig(t *testing.T) { assert.NoError(t, exp.Export(ctx, metricdata.ResourceMetrics{})) assert.Len(t, coll.Collect().Dump(), 1) }) + + t.Run("WithCustomUserAgent", func(t *testing.T) { + key := http.CanonicalHeaderKey("user-agent") + headers := map[string]string{key: "custom-user-agent"} + exp, coll := factoryFunc("", nil, WithHeaders(headers)) + ctx := context.Background() + t.Cleanup(func() { require.NoError(t, coll.Shutdown(ctx)) }) + require.NoError(t, exp.Export(ctx, metricdata.ResourceMetrics{})) + // Ensure everything is flushed. + require.NoError(t, exp.Shutdown(ctx)) + + got := coll.Headers() + require.Contains(t, got, key) + assert.Equal(t, got[key], []string{headers[key]}) + }) } diff --git a/exporters/otlp/otlptrace/otlptracegrpc/client_test.go b/exporters/otlp/otlptrace/otlptracegrpc/client_test.go index 063c4a987b5..395ccc28bf4 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/client_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/client_test.go @@ -412,3 +412,18 @@ func TestPartialSuccess(t *testing.T) { require.Contains(t, errors[0].Error(), "partially successful") require.Contains(t, errors[0].Error(), "2 spans rejected") } + +func TestCustomUserAgent(t *testing.T) { + customUserAgent := "custom-user-agent" + mc := runMockCollector(t) + t.Cleanup(func() { require.NoError(t, mc.stop()) }) + + ctx := context.Background() + exp := newGRPCExporter(t, ctx, mc.endpoint, + otlptracegrpc.WithDialOption(grpc.WithUserAgent(customUserAgent))) + t.Cleanup(func() { require.NoError(t, exp.Shutdown(ctx)) }) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) + + headers := mc.getHeaders() + require.Contains(t, headers.Get("user-agent")[0], customUserAgent) +} diff --git a/exporters/otlp/otlptrace/otlptracehttp/client_test.go b/exporters/otlp/otlptrace/otlptracehttp/client_test.go index bf497bad4be..135b6517622 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/client_test.go +++ b/exporters/otlp/otlptrace/otlptracehttp/client_test.go @@ -42,6 +42,10 @@ var ( "Otel-Go-Key-1": "somevalue", "Otel-Go-Key-2": "someothervalue", } + + customUserAgentHeader = map[string]string{ + "user-agent": "custome-user-agent", + } ) func TestEndToEnd(t *testing.T) { @@ -142,6 +146,15 @@ func TestEndToEnd(t *testing.T) { ExpectedHeaders: testHeaders, }, }, + { + name: "with custom user agent", + opts: []otlptracehttp.Option{ + otlptracehttp.WithHeaders(customUserAgentHeader), + }, + mcCfg: mockCollectorConfig{ + ExpectedHeaders: customUserAgentHeader, + }, + }, } for _, tc := range tests {