Skip to content

otelconf: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT overrides file-based config endpoint's scheme #8294

@axw

Description

@axw

Component

Config

Describe the issue you're facing

See original bug report for opentelemetry-collector: open-telemetry/opentelemetry-collector#14286

If you configure an OTLP/HTTP trace exporter with an https:// endpoint URL via a file (which uses otelconf), and also set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT but to a different endpoint URL with an http:// scheme, the two configs seem to be mixed together, and the scheme of the env var overrides the scheme of the file-based config and nothing else.

Expected behavior

Either the endpoint in the config file should be used, or the one in the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (I don't know if there's a defined precedence, but I would typically expect an env var to override a config file.)

Steps to Reproduce

See open-telemetry/opentelemetry-collector#14286

Also, this test (added to otelconf) reproduces the issue:

func TestOTLPExporterTracesEndpointEnvOverride(t *testing.T) {
        // Pass an HTTPS server URL in via OpenTelemetryConfiguration ...
        var httpsCalled atomic.Bool
        httpsServer := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
                httpsCalled.Store(true)
        }))
        defer httpsServer.Close()

        // ... and an HTTP server URL in via the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env var
        var httpCalled atomic.Bool
        httpServer := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
                httpCalled.Store(true)
        }))
        defer httpServer.Close()
        t.Setenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", httpServer.URL)

        tempdir := t.TempDir()
        caFile := filepath.Join(tempdir, "ca.crt")
        err := os.WriteFile(
                caFile,
                pem.EncodeToMemory(&pem.Block{
                        Type:  "CERTIFICATE",
                        Bytes: httpsServer.Certificate().Raw,
                }),
                0644,
        )
        require.NoError(t, err)

        cfg := OpenTelemetryConfiguration{
                TracerProvider: &TracerProviderJson{
                        Processors: []SpanProcessor{{
                                Batch: &BatchSpanProcessor{
                                        ScheduleDelay: ptr(100),
                                        Exporter: SpanExporter{
                                                OTLPHttp: &OTLPHttpExporter{
                                                        Endpoint:        ptr(httpsServer.URL + "/foo"),
                                                        CertificateFile: ptr(caFile),
                                                },
                                        },
                                },
                        }},
                },
        }

        sdk, err := NewSDK(
                WithOpenTelemetryConfiguration(cfg),
        )
        require.NoError(t, err)
        defer func() {
                assert.NoError(t, sdk.Shutdown(t.Context()))
        }()

        tracer := sdk.TracerProvider().Tracer("test")
        _, span := tracer.Start(t.Context(), "span")
        span.End()

        assert.EventuallyWithT(t, func(t *assert.CollectT) {
                // NOTE: unclear whether it should be HTTP or HTTPS, i.e. what's the precedence?
                assert.True(t, httpsCalled.Load())
                assert.False(t, httpCalled.Load())
        }, 10*time.Second, time.Millisecond*100)
}

We should see the SDK send to either the HTTP or HTTPS server, but instead we get this:

$ go test -v -run Env
=== RUN   TestNewSDKWithEnvVar
--- PASS: TestNewSDKWithEnvVar (0.01s)
=== RUN   TestParseYAMLWithEnvironmentVariables
=== RUN   TestParseYAMLWithEnvironmentVariables/valid_v1.0.0_config_with_env_vars
--- PASS: TestParseYAMLWithEnvironmentVariables (0.00s)
    --- PASS: TestParseYAMLWithEnvironmentVariables/valid_v1.0.0_config_with_env_vars (0.00s)
=== RUN   TestOTLPExporterTracesEndpointEnvOverride
2025/12/15 14:47:59 http: TLS handshake error from 127.0.0.1:36266: client sent an HTTP request to an HTTPS server
2025/12/15 14:47:59 traces export: failed to send to http://127.0.0.1:45307/foo: 400 Bad Request (body: Client sent an HTTP request to an HTTPS server.)
    trace_test.go:1093: 
                Error Trace:    /home/andrew/projects/opentelemetry-go-contrib/otelconf/trace_test.go:1095
                                                        /home/andrew/go/toolchain/1.25.3/src/runtime/asm_amd64.s:1693
                Error:          Should be true
    trace_test.go:1093: 
                Error Trace:    /home/andrew/projects/opentelemetry-go-contrib/otelconf/trace_test.go:1093
                Error:          Condition never satisfied
                Test:           TestOTLPExporterTracesEndpointEnvOverride
--- FAIL: TestOTLPExporterTracesEndpointEnvOverride (10.00s)
FAIL
exit status 1
FAIL    go.opentelemetry.io/contrib/otelconf    10.065s

Operating System

Ubuntu 24.04

Device Architecture

x86_64

Go Version

1.25

Component Version

otelconf 2da6197

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: file-configRelated to file-based configurationbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions