1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00
Files
opentelemetry-go/exporters/otlp/otlplog/otlploghttp/config_test.go
T

645 lines
20 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otlploghttp
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry"
)
const (
weakCertificate = `
-----BEGIN CERTIFICATE-----
MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa
MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9
nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z
sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA
AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/
1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH
Lhnm4N/QDk5rek0=
-----END CERTIFICATE-----
`
weakPrivateKey = `
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN8HEXiXhvByrJ1zK
SFT6Y2l2KqDWwWzKf+t4CyWrNKehRANCAAS9nWSkmPCxShxnp43F+PrOtbGV7sNf
kbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0ZsJCLHGogQsYnWJBXUZOV
-----END PRIVATE KEY-----
`
)
func newTLSConf(cert, key []byte) (*tls.Config, error) {
cp := x509.NewCertPool()
if ok := cp.AppendCertsFromPEM(cert); !ok {
return nil, errors.New("failed to append certificate to the cert pool")
}
crt, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, err
}
crts := []tls.Certificate{crt}
return &tls.Config{RootCAs: cp, Certificates: crts}, nil
}
func TestNewConfig(t *testing.T) {
orig := readFile
readFile = func() func(name string) ([]byte, error) {
index := map[string][]byte{
"cert_path": []byte(weakCertificate),
"key_path": []byte(weakPrivateKey),
"invalid_cert": []byte("invalid certificate file."),
"invalid_key": []byte("invalid key file."),
}
return func(name string) ([]byte, error) {
b, ok := index[name]
if !ok {
err := fmt.Errorf("file does not exist: %s", name)
return nil, err
}
return b, nil
}
}()
t.Cleanup(func() { readFile = orig })
tlsCfg, err := newTLSConf([]byte(weakCertificate), []byte(weakPrivateKey))
require.NoError(t, err, "testing TLS config")
headers := map[string]string{"a": "A"}
rc := retry.Config{}
testcases := []struct {
name string
options []Option
envars map[string]string
want config
errs []string
}{
{
name: "Defaults",
want: config{
endpoint: newSetting(defaultEndpoint),
path: newSetting(defaultPath),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "Options",
options: []Option{
WithEndpoint("test"),
WithURLPath("/path"),
WithInsecure(),
WithTLSClientConfig(tlsCfg),
WithCompression(GzipCompression),
WithHeaders(headers),
WithTimeout(time.Second),
WithRetry(RetryConfig(rc)),
// Do not test WithProxy. Requires func comparison.
},
want: config{
endpoint: newSetting("test"),
path: newSetting("/path"),
insecure: newSetting(true),
tlsCfg: newSetting(tlsCfg),
headers: newSetting(headers),
compression: newSetting(GzipCompression),
timeout: newSetting(time.Second),
retryCfg: newSetting(rc),
},
},
{
name: "WithEndpointURL",
options: []Option{
WithEndpointURL("http://test:8080/path"),
},
want: config{
endpoint: newSetting("test:8080"),
path: newSetting("/path"),
insecure: newSetting(true),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "EndpointPrecedence",
options: []Option{
WithEndpointURL("https://test:8080/path"),
WithEndpoint("not-test:9090"),
WithURLPath("/alt"),
WithInsecure(),
},
want: config{
endpoint: newSetting("not-test:9090"),
path: newSetting("/alt"),
insecure: newSetting(true),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "EndpointURLPrecedence",
options: []Option{
WithEndpoint("not-test:9090"),
WithURLPath("/alt"),
WithInsecure(),
WithEndpointURL("https://test:8080/path"),
},
want: config{
endpoint: newSetting("test:8080"),
path: newSetting("/path"),
insecure: newSetting(false),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "WithEndpointURL secure when Environment Endpoint is set insecure",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "http://env.endpoint:8080/prefix",
},
options: []Option{
WithEndpointURL("https://test:8080/path"),
},
want: config{
endpoint: newSetting("test:8080"),
path: newSetting("/path"),
insecure: newSetting(false),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "WithEndpointURL secure when Environment insecure is set false",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
},
options: []Option{
WithEndpointURL("https://test:8080/path"),
},
want: config{
endpoint: newSetting("test:8080"),
path: newSetting("/path"),
insecure: newSetting(false),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "LogEnvironmentVariables",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/prefix",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a=A",
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
insecure: newSetting(false),
tlsCfg: newSetting(tlsCfg),
headers: newSetting(headers),
compression: newSetting(GzipCompression),
timeout: newSetting(15 * time.Second),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "LogEndpointEnvironmentVariablesDefaultPath",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "http://env.endpoint",
},
want: config{
endpoint: newSetting("env.endpoint"),
path: newSetting("/"),
insecure: newSetting(true),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "OTLPEnvironmentVariables",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env.endpoint:8080/prefix",
"OTEL_EXPORTER_OTLP_HEADERS": "a=A",
"OTEL_EXPORTER_OTLP_COMPRESSION": "none",
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_CLIENT_KEY": "key_path",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix/v1/logs"),
insecure: newSetting(true),
tlsCfg: newSetting(tlsCfg),
headers: newSetting(headers),
compression: newSetting(NoCompression),
timeout: newSetting(15 * time.Second),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "OTLPEndpointEnvironmentVariablesDefaultPath",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env.endpoint",
},
want: config{
endpoint: newSetting("env.endpoint"),
path: newSetting(defaultPath),
insecure: newSetting(true),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "EnvironmentVariablesPrecedence",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://ignored:9090/alt",
"OTEL_EXPORTER_OTLP_HEADERS": "b=B",
"OTEL_EXPORTER_OTLP_COMPRESSION": "none",
"OTEL_EXPORTER_OTLP_TIMEOUT": "30000",
"OTEL_EXPORTER_OTLP_CERTIFICATE": "invalid_cert",
"OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE": "invalid_cert",
"OTEL_EXPORTER_OTLP_CLIENT_KEY": "invalid_key",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/path",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a=A",
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/path"),
insecure: newSetting(false),
tlsCfg: newSetting(tlsCfg),
headers: newSetting(headers),
compression: newSetting(GzipCompression),
timeout: newSetting(15 * time.Second),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "OptionsPrecedence",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://ignored:9090/alt",
"OTEL_EXPORTER_OTLP_HEADERS": "b=B",
"OTEL_EXPORTER_OTLP_COMPRESSION": "none",
"OTEL_EXPORTER_OTLP_TIMEOUT": "30000",
"OTEL_EXPORTER_OTLP_CERTIFICATE": "invalid_cert",
"OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE": "invalid_cert",
"OTEL_EXPORTER_OTLP_CLIENT_KEY": "invalid_key",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/prefix",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a=A",
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
},
options: []Option{
WithEndpoint("test"),
WithEndpointURL("https://test2/path2"),
WithURLPath("/path"),
WithInsecure(),
WithTLSClientConfig(tlsCfg),
WithCompression(GzipCompression),
WithHeaders(headers),
WithTimeout(time.Second),
WithRetry(RetryConfig(rc)),
},
want: config{
endpoint: newSetting("test2"),
path: newSetting("/path"),
insecure: newSetting(true),
tlsCfg: newSetting(tlsCfg),
headers: newSetting(headers),
compression: newSetting(GzipCompression),
timeout: newSetting(time.Second),
retryCfg: newSetting(rc),
},
},
{
name: "InvalidEnvironmentVariables",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "%invalid",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "invalid key=value",
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "xz",
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "100 seconds",
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "invalid_cert",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "invalid_cert",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "invalid_key",
},
want: config{
endpoint: newSetting(defaultEndpoint),
path: newSetting(defaultPath),
timeout: newSetting(defaultTimeout),
retryCfg: newSetting(defaultRetryCfg),
},
errs: []string{
`invalid OTEL_EXPORTER_OTLP_LOGS_ENDPOINT value %invalid: parse "%invalid": invalid URL escape "%in"`,
`failed to load TLS:`,
`certificate not added`,
`tls: failed to find any PEM data in certificate input`,
`invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value invalid key=value: invalid header key: invalid key`,
`invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz`,
`invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax`,
},
},
{
name: "with percent-encoded headers",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/prefix",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "user%2Did=42,user%20name=alice%20smith",
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
insecure: newSetting(false),
tlsCfg: newSetting(tlsCfg),
headers: newSetting(map[string]string{"user%2Did": "42", "user%20name": "alice smith"}),
compression: newSetting(GzipCompression),
timeout: newSetting(15 * time.Second),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "with invalid header key",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/prefix",
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "valid-key=value,invalid key=value",
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
insecure: newSetting(false),
tlsCfg: newSetting(tlsCfg),
compression: newSetting(GzipCompression),
timeout: newSetting(15 * time.Second),
retryCfg: newSetting(defaultRetryCfg),
},
},
{
name: "OptionEndpointURLWithoutScheme",
options: []Option{
WithEndpointURL("//env.endpoint:8080/prefix"),
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
retryCfg: newSetting(defaultRetryCfg),
timeout: newSetting(defaultTimeout),
},
},
{
name: "EnvEndpointWithoutScheme",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "//env.endpoint:8080/prefix",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
retryCfg: newSetting(defaultRetryCfg),
timeout: newSetting(defaultTimeout),
},
},
{
name: "DefaultEndpointWithEnvInsecure",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
},
want: config{
endpoint: newSetting(defaultEndpoint),
path: newSetting(defaultPath),
insecure: newSetting(true),
retryCfg: newSetting(defaultRetryCfg),
timeout: newSetting(defaultTimeout),
},
},
{
name: "EnvEndpointWithoutSchemeWithEnvInsecure",
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "//env.endpoint:8080/prefix",
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
insecure: newSetting(true),
retryCfg: newSetting(defaultRetryCfg),
timeout: newSetting(defaultTimeout),
},
},
{
name: "OptionEndpointURLWithoutSchemeWithEnvInsecure",
options: []Option{
WithEndpointURL("//env.endpoint:8080/prefix"),
},
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting("/prefix"),
insecure: newSetting(true),
retryCfg: newSetting(defaultRetryCfg),
timeout: newSetting(defaultTimeout),
},
},
{
name: "OptionEndpointWithEnvInsecure",
options: []Option{
WithEndpoint("env.endpoint:8080"),
},
envars: map[string]string{
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
},
want: config{
endpoint: newSetting("env.endpoint:8080"),
path: newSetting(defaultPath),
insecure: newSetting(true),
retryCfg: newSetting(defaultRetryCfg),
timeout: newSetting(defaultTimeout),
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
for key, value := range tc.envars {
t.Setenv(key, value)
}
var err error
t.Cleanup(func(orig otel.ErrorHandler) func() {
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(e error) {
err = errors.Join(err, e)
}))
return func() { otel.SetErrorHandler(orig) }
}(otel.GetErrorHandler()))
c := newConfig(tc.options)
// Do not compare pointer values.
assertTLSConfig(t, tc.want.tlsCfg, c.tlsCfg)
var emptyTLS setting[*tls.Config]
c.tlsCfg, tc.want.tlsCfg = emptyTLS, emptyTLS
// Cannot compare funcs, see TestWithProxy.
c.proxy = setting[HTTPTransportProxyFunc]{}
assert.Equal(t, tc.want, c)
for _, errMsg := range tc.errs {
assert.ErrorContains(t, err, errMsg)
}
})
}
}
func assertTLSConfig(t *testing.T, want, got setting[*tls.Config]) {
t.Helper()
assert.Equal(t, want.Set, got.Set, "setting Set")
if !want.Set {
return
}
if want.Value == nil {
assert.Nil(t, got.Value, "*tls.Config")
return
}
require.NotNil(t, got.Value, "*tls.Config")
if want.Value.RootCAs == nil {
assert.Nil(t, got.Value.RootCAs, "*tls.Config.RootCAs")
} else if assert.NotNil(t, got.Value.RootCAs, "RootCAs") {
assert.True(t, want.Value.RootCAs.Equal(got.Value.RootCAs), "RootCAs equal")
}
assert.Equal(t, want.Value.Certificates, got.Value.Certificates, "Certificates")
}
func TestWithProxy(t *testing.T) {
proxy := func(*http.Request) (*url.URL, error) { return nil, nil }
opts := []Option{WithProxy(HTTPTransportProxyFunc(proxy))}
c := newConfig(opts)
assert.True(t, c.proxy.Set)
assert.NotNil(t, c.proxy.Value)
}
func TestConvHeaders(t *testing.T) {
tests := []struct {
name string
value string
want map[string]string
wantErr bool
}{
{
name: "simple test",
value: "userId=alice",
want: map[string]string{"userId": "alice"},
wantErr: false,
},
{
name: "simple test with spaces",
value: " userId = alice ",
want: map[string]string{"userId": "alice"},
wantErr: false,
},
{
name: "simple header conforms to RFC 3986 spec",
value: " userId = alice+test ",
want: map[string]string{"userId": "alice+test"},
wantErr: false,
},
{
name: "multiple headers encoded",
value: "userId=alice,serverNode=DF%3A28,isProduction=false",
want: map[string]string{
"userId": "alice",
"serverNode": "DF:28",
"isProduction": "false",
},
wantErr: false,
},
{
name: "multiple headers encoded per RFC 3986 spec",
value: "userId=alice+test,serverNode=DF%3A28,isProduction=false,namespace=localhost/test",
want: map[string]string{
"userId": "alice+test",
"serverNode": "DF:28",
"isProduction": "false",
"namespace": "localhost/test",
},
wantErr: false,
},
{
name: "invalid headers format",
value: "userId:alice",
want: map[string]string{},
wantErr: true,
},
{
name: "invalid key",
value: "%XX=missing,userId=alice",
want: map[string]string{
"%XX": "missing",
"userId": "alice",
},
wantErr: false,
},
{
name: "invalid value",
value: "missing=%XX,userId=alice",
want: map[string]string{
"userId": "alice",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
keyValues, err := convHeaders(tt.value)
assert.Equal(t, tt.want, keyValues)
if tt.wantErr {
assert.Error(t, err, "expected an error but got nil")
} else {
assert.NoError(t, err, "expected no error but got one")
}
})
}
}