From 47c5b23f8c6ffdb1c6aecc05d8e53eb58d681e8a Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Tue, 21 Jan 2025 20:02:34 +0000 Subject: [PATCH 1/7] config: support HeadersList --- CHANGELOG.md | 1 + config/v0.3.0/config.go | 32 +++++++++---- config/v0.3.0/config_test.go | 93 ++++++++++++++++++++++++++++++++++++ config/v0.3.0/log.go | 16 +++++-- config/v0.3.0/metric.go | 16 +++++-- config/v0.3.0/trace.go | 16 +++++-- 6 files changed, 154 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78089b67694..0152cfb21c0 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 - Add support for configuring `ClientCertificate` and `ClientKey` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6378) +- Add support for configuring `HeadersList` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (TODO) ### Fixed diff --git a/config/v0.3.0/config.go b/config/v0.3.0/config.go index b4097ac2552..14d01448c49 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,14 +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 { - 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{} @@ -186,3 +179,26 @@ func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile * } return tlsConfig, nil } + +// createHeadersConfig combines the two header config fields and returns a map[string]string. +func createHeadersConfig(headers []NameStringValuePair, headersList *string) (map[string]string, error) { + result := make(map[string]string) + if headersList != nil { + 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 868d928e3df..b89a4819c59 100644 --- a/config/v0.3.0/config_test.go +++ b/config/v0.3.0/config_test.go @@ -568,6 +568,99 @@ func TestCreateTLSConfig(t *testing.T) { } } +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 supercedes 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 { return &v } diff --git a/config/v0.3.0/log.go b/config/v0.3.0/log.go index bca5d235594..d4bdc2df1d9 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/metric.go b/config/v0.3.0/metric.go index b7d68106b43..d8af381845d 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/trace.go b/config/v0.3.0/trace.go index 498686e15f5..03ed3ed223d 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) From 11a5854ba618441d6cff9a852ae1124e150afc54 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Tue, 21 Jan 2025 20:21:28 +0000 Subject: [PATCH 2/7] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0152cfb21c0..1d7c207d3bf 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 - Add support for configuring `ClientCertificate` and `ClientKey` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6378) -- Add support for configuring `HeadersList` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (TODO) +- Add support for configuring `HeadersList` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6657) ### Fixed From 42133ddec21c8d1159d96dfd15153aed800534e3 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Tue, 21 Jan 2025 20:31:16 +0000 Subject: [PATCH 3/7] fix spelling of supersedes --- config/v0.3.0/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/v0.3.0/config_test.go b/config/v0.3.0/config_test.go index b89a4819c59..5444cac45bc 100644 --- a/config/v0.3.0/config_test.go +++ b/config/v0.3.0/config_test.go @@ -624,7 +624,7 @@ func TestCreateHeadersConfig(t *testing.T) { }, }, { - name: "headers supercedes headerslist", + name: "headers supersedes headerslist", headers: []NameStringValuePair{ { Name: "a", From c561db22ec73145f7bce4125ab2f8eaeb67c71e6 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Tue, 21 Jan 2025 21:52:34 +0000 Subject: [PATCH 4/7] add tests --- config/v0.3.0/log_test.go | 79 +++++++++++++++++++++++++---------- config/v0.3.0/metric_test.go | 76 ++++++++++++++++++++++++--------- config/v0.3.0/trace_test.go | 81 ++++++++++++++++++++++++++---------- 3 files changed, 174 insertions(+), 62 deletions(-) diff --git a/config/v0.3.0/log_test.go b/config/v0.3.0/log_test.go index 2c2e2d2212d..11641e99984 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("grpc"), + 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_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_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 { From 8adaf3efa244d5bdf89690ac31529a17168fb5be Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Tue, 21 Jan 2025 22:11:20 +0000 Subject: [PATCH 5/7] fix test --- config/v0.3.0/log_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/v0.3.0/log_test.go b/config/v0.3.0/log_test.go index 11641e99984..4f73c289eb7 100644 --- a/config/v0.3.0/log_test.go +++ b/config/v0.3.0/log_test.go @@ -463,7 +463,7 @@ func TestLogProcessor(t *testing.T) { Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ - Protocol: ptr("grpc"), + Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), From 81c7111827da82ce5979caa936278d1f26c5ada1 Mon Sep 17 00:00:00 2001 From: Matthew Sainsbury Date: Mon, 17 Feb 2025 06:56:00 -0800 Subject: [PATCH 6/7] Update config/v0.3.0/config.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert PajÄ…k --- config/v0.3.0/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/v0.3.0/config.go b/config/v0.3.0/config.go index 14d01448c49..91c1cbf27f2 100644 --- a/config/v0.3.0/config.go +++ b/config/v0.3.0/config.go @@ -180,7 +180,7 @@ func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile * return tlsConfig, nil } -// createHeadersConfig combines the two header config fields and returns a map[string]string. +// 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 { From c5dafcb97c359f28b7596fb5afc9323e9d8bd732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Tue, 18 Feb 2025 22:21:18 +0100 Subject: [PATCH 7/7] Update config/v0.3.0/config.go --- config/v0.3.0/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/config/v0.3.0/config.go b/config/v0.3.0/config.go index 91c1cbf27f2..6a3b2d3d04e 100644 --- a/config/v0.3.0/config.go +++ b/config/v0.3.0/config.go @@ -184,6 +184,7 @@ func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile * 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)