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:
parent
c5ebbc4d4b
commit
4ec2ae60f2
@ -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
|
||||||
|
|
||||||
|
24
exporters/otlp/internal/header.go
Normal file
24
exporters/otlp/internal/header.go
Normal 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()
|
||||||
|
}
|
26
exporters/otlp/internal/header_test.go
Normal file
26
exporters/otlp/internal/header_test.go
Normal 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())
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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]})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user