Skip to content

Commit

Permalink
Add User-Agent header to OTLP exporter requests (#3261)
Browse files Browse the repository at this point in the history
* Add User-Agent header to OTLP exporter requests
* allow override grpc user-agent

Signed-off-by: rogerogers <rogers@rogerogers.com>
  • Loading branch information
rogerogers committed Oct 11, 2022
1 parent c5ebbc4 commit 4ec2ae6
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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 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

Expand Down
24 changes: 24 additions & 0 deletions 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()
}
26 changes: 26 additions & 0 deletions 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())
}
1 change: 1 addition & 0 deletions exporters/otlp/otlpmetric/internal/oconf/options.go
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions exporters/otlp/otlpmetric/otlpmetricgrpc/client_test.go
Expand Up @@ -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"
Expand Down Expand Up @@ -169,6 +170,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]})
})
Expand All @@ -188,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)
})
}
3 changes: 3 additions & 0 deletions exporters/otlp/otlpmetric/otlpmetrichttp/client.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go
Expand Up @@ -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]})
})
Expand Down Expand Up @@ -161,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]})
})
}
2 changes: 1 addition & 1 deletion exporters/otlp/otlpmetric/otlpmetrichttp/go.mod
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions exporters/otlp/otlptrace/internal/otlpconfig/options.go
Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions exporters/otlp/otlptrace/otlptracegrpc/client_test.go
Expand Up @@ -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])
}
Expand Down Expand Up @@ -411,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)
}
2 changes: 2 additions & 0 deletions exporters/otlp/otlptrace/otlptracehttp/client.go
Expand Up @@ -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)
}
Expand Down
13 changes: 13 additions & 0 deletions exporters/otlp/otlptrace/otlptracehttp/client_test.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 4ec2ae6

Please sign in to comment.