diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3a2c28ed2..844c4e1dbe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The next release will require at least [Go 1.23]. - Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `instrumentation/github.com/emicklei/go-restful/otelrestful` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6710) - Added metrics support, and emit all stable metrics from the [Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md) in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6747) - Support [Go 1.24]. (#6765) +- Add support for configuring `HeadersList` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6657) ### Changed diff --git a/config/v0.3.0/config.go b/config/v0.3.0/config.go index 1dd458d5a4a..6a3b2d3d04e 100644 --- a/config/v0.3.0/config.go +++ b/config/v0.3.0/config.go @@ -13,6 +13,7 @@ import ( "gopkg.in/yaml.v3" + "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/log" nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" @@ -152,16 +153,6 @@ func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { return &cfg, nil } -func toStringMap(pairs []NameStringValuePair) map[string]string { - output := make(map[string]string) - for _, v := range pairs { - if v.Value != nil && len(v.Name) > 0 { - output[v.Name] = *v.Value - } - } - return output -} - // createTLSConfig creates a tls.Config from certificate files. func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile *string) (*tls.Config, error) { tlsConfig := &tls.Config{} @@ -188,3 +179,27 @@ func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile * } return tlsConfig, nil } + +// createHeadersConfig combines the two header config fields. Headers take precedence over headersList. +func createHeadersConfig(headers []NameStringValuePair, headersList *string) (map[string]string, error) { + result := make(map[string]string) + if headersList != nil { + // Parsing follows https://github.com/open-telemetry/opentelemetry-configuration/blob/568e5080816d40d75792eb754fc96bde09654159/schema/type_descriptions.yaml#L584. + headerslist, err := baggage.Parse(*headersList) + if err != nil { + return nil, fmt.Errorf("invalid headers list: %w", err) + } + for _, kv := range headerslist.Members() { + result[kv.Key()] = kv.Value() + } + } + // Headers take precedence over HeadersList, so this has to be after HeadersList is processed + if len(headers) > 0 { + for _, kv := range headers { + if kv.Value != nil { + result[kv.Name] = *kv.Value + } + } + } + return result, nil +} diff --git a/config/v0.3.0/config_test.go b/config/v0.3.0/config_test.go index a25cfc9ff88..4ab519c84bb 100644 --- a/config/v0.3.0/config_test.go +++ b/config/v0.3.0/config_test.go @@ -568,10 +568,97 @@ func TestCreateTLSConfig(t *testing.T) { } } -func TestToStringMap(t *testing.T) { - require.Equal(t, map[string]string{}, toStringMap([]NameStringValuePair{})) - require.Equal(t, map[string]string{}, toStringMap([]NameStringValuePair{{Name: "test"}})) - require.Equal(t, map[string]string{}, toStringMap([]NameStringValuePair{{Value: ptr("test")}})) +func TestCreateHeadersConfig(t *testing.T) { + tests := []struct { + name string + headers []NameStringValuePair + headersList *string + wantHeaders map[string]string + wantErr string + }{ + { + name: "no headers", + headers: []NameStringValuePair{}, + headersList: nil, + wantHeaders: map[string]string{}, + }, + { + name: "headerslist only", + headers: []NameStringValuePair{}, + headersList: ptr("a=b,c=d"), + wantHeaders: map[string]string{ + "a": "b", + "c": "d", + }, + }, + { + name: "headers only", + headers: []NameStringValuePair{ + { + Name: "a", + Value: ptr("b"), + }, + { + Name: "c", + Value: ptr("d"), + }, + }, + headersList: nil, + wantHeaders: map[string]string{ + "a": "b", + "c": "d", + }, + }, + { + name: "both headers and headerslist", + headers: []NameStringValuePair{ + { + Name: "a", + Value: ptr("b"), + }, + }, + headersList: ptr("c=d"), + wantHeaders: map[string]string{ + "a": "b", + "c": "d", + }, + }, + { + name: "headers supersedes headerslist", + headers: []NameStringValuePair{ + { + Name: "a", + Value: ptr("b"), + }, + { + Name: "c", + Value: ptr("override"), + }, + }, + headersList: ptr("c=d"), + wantHeaders: map[string]string{ + "a": "b", + "c": "override", + }, + }, + { + name: "invalid headerslist", + headersList: ptr("==="), + wantErr: "invalid headers list: invalid key: \"\"", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + headersMap, err := createHeadersConfig(tt.headers, tt.headersList) + if tt.wantErr != "" { + require.Error(t, err) + require.Equal(t, tt.wantErr, err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantHeaders, headersMap) + }) + } } func ptr[T any](v T) *T { diff --git a/config/v0.3.0/log.go b/config/v0.3.0/log.go index 7bb527cc08b..714ed4faebd 100644 --- a/config/v0.3.0/log.go +++ b/config/v0.3.0/log.go @@ -152,8 +152,12 @@ func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlploghttp.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) @@ -200,8 +204,12 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploggrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlploggrpc.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlploggrpc.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) diff --git a/config/v0.3.0/log_test.go b/config/v0.3.0/log_test.go index 2c2e2d2212d..4f73c289eb7 100644 --- a/config/v0.3.0/log_test.go +++ b/config/v0.3.0/log_test.go @@ -6,8 +6,6 @@ package config import ( "context" "errors" - "fmt" - "net/url" "path/filepath" "reflect" "testing" @@ -79,12 +77,12 @@ func TestLogProcessor(t *testing.T) { name string processor LogRecordProcessor args any - wantErr error + wantErr string wantProcessor sdklog.Processor }{ { name: "no processor", - wantErr: errors.New("unsupported log processor type, must be one of simple or batch"), + wantErr: "unsupported log processor type, must be one of simple or batch", }, { name: "multiple processor types", @@ -94,7 +92,7 @@ func TestLogProcessor(t *testing.T) { }, Simple: &SimpleLogRecordProcessor{}, }, - wantErr: errors.New("must not specify multiple log processor type"), + wantErr: "must not specify multiple log processor type", }, { name: "batch processor invalid batch size otlphttp exporter", @@ -109,7 +107,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid batch size -1"), + wantErr: "invalid batch size -1", }, { name: "batch processor invalid export timeout otlphttp exporter", @@ -123,7 +121,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid export timeout -2"), + wantErr: "invalid export timeout -2", }, { name: "batch processor invalid queue size otlphttp exporter", @@ -138,7 +136,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid queue size -3"), + wantErr: "invalid queue size -3", }, { name: "batch processor invalid schedule delay console exporter", @@ -152,7 +150,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid schedule delay -4"), + wantErr: "invalid schedule delay -4", }, { name: "batch processor invalid exporter", @@ -161,7 +159,7 @@ func TestLogProcessor(t *testing.T) { Exporter: LogRecordExporter{}, }, }, - wantErr: errors.New("no valid log exporter"), + wantErr: "no valid log exporter", }, { name: "batch/console", @@ -278,7 +276,24 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not create certificate authority chain from certificate"), + wantErr: "could not create certificate authority chain from certificate", + }, + { + name: "batch/otlp-grpc-bad-headerslist", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + HeadersList: ptr("==="), + }, + }, + }, + }, + wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-grpc-bad-client-certificate", @@ -296,7 +311,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")), + wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", }, { name: "batch/otlp-grpc-exporter-no-scheme", @@ -342,7 +357,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-grpc-invalid-compression", @@ -365,7 +380,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported compression \"invalid\""), + wantErr: "unsupported compression \"invalid\"", }, { name: "batch/otlp-http-exporter", @@ -422,7 +437,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not create certificate authority chain from certificate"), + wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-http-bad-client-certificate", @@ -440,7 +455,24 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")), + wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", + }, + { + name: "batch/otlp-http-bad-headerslist", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + HeadersList: ptr("==="), + }, + }, + }, + }, + wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-http-exporter-with-path", @@ -531,7 +563,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported protocol \"invalid\""), + wantErr: "unsupported protocol \"invalid\"", }, { name: "batch/otlp-http-invalid-endpoint", @@ -554,7 +586,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-http-none-compression", @@ -600,7 +632,7 @@ func TestLogProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported compression \"invalid\""), + wantErr: "unsupported compression \"invalid\"", }, { name: "simple/no-exporter", @@ -609,7 +641,7 @@ func TestLogProcessor(t *testing.T) { Exporter: LogRecordExporter{}, }, }, - wantErr: errors.New("no valid log exporter"), + wantErr: "no valid log exporter", }, { name: "simple/console", @@ -645,7 +677,12 @@ func TestLogProcessor(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := logProcessor(context.Background(), tt.processor) - require.Equal(t, tt.wantErr, err) + if tt.wantErr != "" { + require.Error(t, err) + require.Equal(t, tt.wantErr, err.Error()) + } else { + require.NoError(t, err) + } if tt.wantProcessor == nil { require.Nil(t, got) } else { diff --git a/config/v0.3.0/metric.go b/config/v0.3.0/metric.go index 039eff232c3..eb43a531136 100644 --- a/config/v0.3.0/metric.go +++ b/config/v0.3.0/metric.go @@ -166,8 +166,12 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet if otlpConfig.Timeout != nil { opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlpmetrichttp.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlpmetrichttp.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { @@ -227,8 +231,12 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlpmetricgrpc.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlpmetricgrpc.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { diff --git a/config/v0.3.0/metric_test.go b/config/v0.3.0/metric_test.go index 0b2c17a2a5d..4f3c0597639 100644 --- a/config/v0.3.0/metric_test.go +++ b/config/v0.3.0/metric_test.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "net/url" "path/filepath" "reflect" "testing" @@ -106,19 +105,19 @@ func TestReader(t *testing.T) { name string reader MetricReader args any - wantErr error + wantErr string wantReader sdkmetric.Reader }{ { name: "no reader", - wantErr: errors.New("no valid metric reader"), + wantErr: "no valid metric reader", }, { name: "pull/no-exporter", reader: MetricReader{ Pull: &PullMetricReader{}, }, - wantErr: errors.New("no valid metric exporter"), + wantErr: "no valid metric exporter", }, { name: "pull/prometheus-no-host", @@ -129,7 +128,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("host must be specified"), + wantErr: "host must be specified", }, { name: "pull/prometheus-no-port", @@ -142,7 +141,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("port must be specified"), + wantErr: "port must be specified", }, { name: "pull/prometheus", @@ -176,7 +175,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported protocol \"http/invalid\""), + wantErr: "unsupported protocol \"http/invalid\"", }, { name: "periodic/otlp-grpc-exporter", @@ -248,7 +247,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("could not create certificate authority chain from certificate"), + wantErr: "could not create certificate authority chain from certificate", }, { name: "periodic/otlp-grpc-bad-client-certificate", @@ -266,7 +265,24 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")), + wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", + }, + { + name: "periodic/otlp-grpc-bad-headerslist", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + HeadersList: ptr("==="), + }, + }, + }, + }, + wantErr: "invalid headers list: invalid key: \"\"", }, { name: "periodic/otlp-grpc-exporter-no-endpoint", @@ -341,7 +357,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + wantErr: "parse \" \": invalid URI for request", }, { name: "periodic/otlp-grpc-none-compression", @@ -440,7 +456,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported temporality preference \"invalid\""), + wantErr: "unsupported temporality preference \"invalid\"", }, { name: "periodic/otlp-grpc-invalid-compression", @@ -459,7 +475,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported compression \"invalid\""), + wantErr: "unsupported compression \"invalid\"", }, { name: "periodic/otlp-http-exporter", @@ -512,7 +528,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("could not create certificate authority chain from certificate"), + wantErr: "could not create certificate authority chain from certificate", }, { name: "periodic/otlp-http-bad-client-certificate", @@ -530,7 +546,24 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")), + wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", + }, + { + name: "periodic/otlp-http-bad-headerslist", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + HeadersList: ptr("==="), + }, + }, + }, + }, + wantErr: "invalid headers list: invalid key: \"\"", }, { name: "periodic/otlp-http-exporter-with-path", @@ -605,7 +638,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + wantErr: "parse \" \": invalid URI for request", }, { name: "periodic/otlp-http-none-compression", @@ -704,7 +737,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported temporality preference \"invalid\""), + wantErr: "unsupported temporality preference \"invalid\"", }, { name: "periodic/otlp-http-invalid-compression", @@ -723,7 +756,7 @@ func TestReader(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported compression \"invalid\""), + wantErr: "unsupported compression \"invalid\"", }, { name: "periodic/no-exporter", @@ -732,7 +765,7 @@ func TestReader(t *testing.T) { Exporter: PushMetricExporter{}, }, }, - wantErr: errors.New("no valid metric exporter"), + wantErr: "no valid metric exporter", }, { name: "periodic/console-exporter", @@ -766,7 +799,12 @@ func TestReader(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := metricReader(context.Background(), tt.reader) - require.Equal(t, tt.wantErr, err) + if tt.wantErr != "" { + require.Error(t, err) + require.Equal(t, tt.wantErr, err.Error()) + } else { + require.NoError(t, err) + } if tt.wantReader == nil { require.Nil(t, got) } else { diff --git a/config/v0.3.0/trace.go b/config/v0.3.0/trace.go index 5c5bc0123bc..b977348a6ee 100644 --- a/config/v0.3.0/trace.go +++ b/config/v0.3.0/trace.go @@ -125,8 +125,12 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlptracegrpc.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlptracegrpc.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) @@ -168,8 +172,12 @@ func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlptracehttp.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlptracehttp.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) diff --git a/config/v0.3.0/trace_test.go b/config/v0.3.0/trace_test.go index 2fe078cf92e..a9e3b270913 100644 --- a/config/v0.3.0/trace_test.go +++ b/config/v0.3.0/trace_test.go @@ -6,8 +6,6 @@ package config import ( "context" "errors" - "fmt" - "net/url" "path/filepath" "reflect" "testing" @@ -100,12 +98,12 @@ func TestSpanProcessor(t *testing.T) { name string processor SpanProcessor args any - wantErr error + wantErr string wantProcessor sdktrace.SpanProcessor }{ { name: "no processor", - wantErr: errors.New("unsupported span processor type, must be one of simple or batch"), + wantErr: "unsupported span processor type, must be one of simple or batch", }, { name: "multiple processor types", @@ -115,7 +113,7 @@ func TestSpanProcessor(t *testing.T) { }, Simple: &SimpleSpanProcessor{}, }, - wantErr: errors.New("must not specify multiple span processor type"), + wantErr: "must not specify multiple span processor type", }, { name: "batch processor invalid exporter", @@ -124,7 +122,7 @@ func TestSpanProcessor(t *testing.T) { Exporter: SpanExporter{}, }, }, - wantErr: errors.New("no valid span exporter"), + wantErr: "no valid span exporter", }, { name: "batch processor invalid batch size console exporter", @@ -136,7 +134,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid batch size -1"), + wantErr: "invalid batch size -1", }, { name: "batch processor invalid export timeout console exporter", @@ -148,7 +146,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid export timeout -2"), + wantErr: "invalid export timeout -2", }, { name: "batch processor invalid queue size console exporter", @@ -160,7 +158,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid queue size -3"), + wantErr: "invalid queue size -3", }, { name: "batch processor invalid schedule delay console exporter", @@ -172,7 +170,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("invalid schedule delay -4"), + wantErr: "invalid schedule delay -4", }, { name: "batch processor with multiple exporters", @@ -184,7 +182,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("must not specify multiple exporters"), + wantErr: "must not specify multiple exporters", }, { name: "batch processor console exporter", @@ -216,7 +214,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported protocol \"http/invalid\""), + wantErr: "unsupported protocol \"http/invalid\"", }, { name: "batch/otlp-grpc-exporter-no-endpoint", @@ -318,7 +316,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("could not create certificate authority chain from certificate"), + wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-grpc-bad-client-certificate", @@ -336,7 +334,24 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")), + wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", + }, + { + name: "batch/otlp-grpc-bad-headerslist", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("grpc"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + HeadersList: ptr("==="), + }, + }, + }, + }, + wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-grpc-exporter-no-scheme", @@ -382,7 +397,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-grpc-invalid-compression", @@ -405,7 +420,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported compression \"invalid\""), + wantErr: "unsupported compression \"invalid\"", }, { name: "batch/otlp-http-exporter", @@ -462,7 +477,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("could not create certificate authority chain from certificate"), + wantErr: "could not create certificate authority chain from certificate", }, { name: "batch/otlp-http-bad-client-certificate", @@ -480,7 +495,24 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: fmt.Errorf("could not use client certificate: %w", errors.New("tls: failed to find any PEM data in certificate input")), + wantErr: "could not use client certificate: tls: failed to find any PEM data in certificate input", + }, + { + name: "batch/otlp-http-bad-headerslist", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr("localhost:4317"), + Compression: ptr("gzip"), + Timeout: ptr(1000), + HeadersList: ptr("==="), + }, + }, + }, + }, + wantErr: "invalid headers list: invalid key: \"\"", }, { name: "batch/otlp-http-exporter-with-path", @@ -571,7 +603,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + wantErr: "parse \" \": invalid URI for request", }, { name: "batch/otlp-http-none-compression", @@ -617,7 +649,7 @@ func TestSpanProcessor(t *testing.T) { }, }, }, - wantErr: errors.New("unsupported compression \"invalid\""), + wantErr: "unsupported compression \"invalid\"", }, { name: "simple/no-exporter", @@ -626,7 +658,7 @@ func TestSpanProcessor(t *testing.T) { Exporter: SpanExporter{}, }, }, - wantErr: errors.New("no valid span exporter"), + wantErr: "no valid span exporter", }, { name: "simple/console-exporter", @@ -643,7 +675,12 @@ func TestSpanProcessor(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := spanProcessor(context.Background(), tt.processor) - require.Equal(t, tt.wantErr, err) + if tt.wantErr != "" { + require.Error(t, err) + require.Equal(t, tt.wantErr, err.Error()) + } else { + require.NoError(t, err) + } if tt.wantProcessor == nil { require.Nil(t, got) } else {