1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-01-12 02:28:07 +02:00

Add User-Agent header to OTLP exporter requests (#3261)

* Add User-Agent header to OTLP exporter requests
* allow override grpc user-agent

Signed-off-by: rogerogers <rogers@rogerogers.com>
This commit is contained in:
copy rogers 2022-10-11 22:55:50 +08:00 committed by GitHub
parent c5ebbc4d4b
commit 4ec2ae60f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 120 additions and 1 deletions

View File

@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added ### Added
- Added an example of using metric views to customize instruments. (#3177) - 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 ### Changed

View File

@ -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()
}

View File

@ -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())
}

View File

@ -104,6 +104,7 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
Timeout: DefaultTimeout, Timeout: DefaultTimeout,
}, },
RetryConfig: retry.DefaultConfig, RetryConfig: retry.DefaultConfig,
DialOptions: []grpc.DialOption{grpc.WithUserAgent(internal.GetUserAgentHeader())},
} }
cfg = ApplyGRPCEnvConfigs(cfg) cfg = ApplyGRPCEnvConfigs(cfg)
for _, opt := range opts { for _, opt := range opts {

View File

@ -22,6 +22,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/durationpb"
@ -169,6 +170,7 @@ func TestConfig(t *testing.T) {
require.NoError(t, exp.Shutdown(ctx)) require.NoError(t, exp.Shutdown(ctx))
got := coll.Headers() got := coll.Headers()
require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", got)
require.Contains(t, got, key) require.Contains(t, got, key)
assert.Equal(t, got[key], []string{headers[key]}) assert.Equal(t, got[key], []string{headers[key]})
}) })
@ -188,4 +190,18 @@ func TestConfig(t *testing.T) {
err := exp.Export(ctx, metricdata.ResourceMetrics{}) err := exp.Export(ctx, metricdata.ResourceMetrics{})
assert.ErrorContains(t, err, context.DeadlineExceeded.Error()) 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)
})
} }

View File

@ -29,6 +29,7 @@ import (
"google.golang.org/protobuf/proto" "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/internal/retry"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf"
@ -101,6 +102,8 @@ func newClient(opts ...Option) (otlpmetric.Client, error) {
return nil, err return nil, err
} }
req.Header.Set("User-Agent", internal.GetUserAgentHeader())
if n := len(cfg.Metrics.Headers); n > 0 { if n := len(cfg.Metrics.Headers); n > 0 {
for k, v := range cfg.Metrics.Headers { for k, v := range cfg.Metrics.Headers {
req.Header.Set(k, v) req.Header.Set(k, v)

View File

@ -75,6 +75,7 @@ func TestConfig(t *testing.T) {
require.NoError(t, exp.Shutdown(ctx)) require.NoError(t, exp.Shutdown(ctx))
got := coll.Headers() got := coll.Headers()
require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", got)
require.Contains(t, got, key) require.Contains(t, got, key)
assert.Equal(t, got[key], []string{headers[key]}) assert.Equal(t, got[key], []string{headers[key]})
}) })
@ -161,4 +162,19 @@ func TestConfig(t *testing.T) {
assert.NoError(t, exp.Export(ctx, metricdata.ResourceMetrics{})) assert.NoError(t, exp.Export(ctx, metricdata.ResourceMetrics{}))
assert.Len(t, coll.Collect().Dump(), 1) 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]})
})
} }

View File

@ -4,6 +4,7 @@ go 1.18
require ( require (
github.com/stretchr/testify v1.7.1 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/internal/retry v1.10.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.32.1 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.32.1
go.opentelemetry.io/otel/metric 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/google/go-cmp v0.5.8 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/pmezard/go-difflib v1.0.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/sdk v1.10.0 // indirect
go.opentelemetry.io/otel/trace v1.10.0 // indirect go.opentelemetry.io/otel/trace v1.10.0 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect

View File

@ -97,6 +97,7 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
Timeout: DefaultTimeout, Timeout: DefaultTimeout,
}, },
RetryConfig: retry.DefaultConfig, RetryConfig: retry.DefaultConfig,
DialOptions: []grpc.DialOption{grpc.WithUserAgent(internal.GetUserAgentHeader())},
} }
cfg = ApplyGRPCEnvConfigs(cfg) cfg = ApplyGRPCEnvConfigs(cfg)
for _, opt := range opts { for _, opt := range opts {

View File

@ -212,6 +212,7 @@ func TestNewWithHeaders(t *testing.T) {
require.NoError(t, exp.ExportSpans(ctx, roSpans)) require.NoError(t, exp.ExportSpans(ctx, roSpans))
headers := mc.getHeaders() headers := mc.getHeaders()
require.Regexp(t, "OTel OTLP Exporter Go/1\\..*", headers.Get("user-agent"))
require.Len(t, headers.Get("header1"), 1) require.Len(t, headers.Get("header1"), 1)
assert.Equal(t, "value1", headers.Get("header1")[0]) assert.Equal(t, "value1", headers.Get("header1")[0])
} }
@ -411,3 +412,18 @@ func TestPartialSuccess(t *testing.T) {
require.Contains(t, errors[0].Error(), "partially successful") require.Contains(t, errors[0].Error(), "partially successful")
require.Contains(t, errors[0].Error(), "2 spans rejected") 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)
}

View File

@ -208,6 +208,8 @@ func (d *client) newRequest(body []byte) (request, error) {
return request{Request: r}, err return request{Request: r}, err
} }
r.Header.Set("User-Agent", internal.GetUserAgentHeader())
for k, v := range d.cfg.Headers { for k, v := range d.cfg.Headers {
r.Header.Set(k, v) r.Header.Set(k, v)
} }

View File

@ -42,6 +42,10 @@ var (
"Otel-Go-Key-1": "somevalue", "Otel-Go-Key-1": "somevalue",
"Otel-Go-Key-2": "someothervalue", "Otel-Go-Key-2": "someothervalue",
} }
customUserAgentHeader = map[string]string{
"user-agent": "custome-user-agent",
}
) )
func TestEndToEnd(t *testing.T) { func TestEndToEnd(t *testing.T) {
@ -142,6 +146,15 @@ func TestEndToEnd(t *testing.T) {
ExpectedHeaders: testHeaders, ExpectedHeaders: testHeaders,
}, },
}, },
{
name: "with custom user agent",
opts: []otlptracehttp.Option{
otlptracehttp.WithHeaders(customUserAgentHeader),
},
mcCfg: mockCollectorConfig{
ExpectedHeaders: customUserAgentHeader,
},
},
} }
for _, tc := range tests { for _, tc := range tests {