Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions commons/opentelemetry/otel.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"maps"
"net/http"
"os"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -99,6 +100,7 @@ func NewTelemetry(cfg TelemetryConfig) (*Telemetry, error) {
}

normalizeEndpoint(&cfg)
normalizeEndpointEnvVars()

if cfg.EnableTelemetry && strings.TrimSpace(cfg.CollectorExporterEndpoint) == "" {
return handleEmptyEndpoint(cfg)
Expand Down Expand Up @@ -143,6 +145,25 @@ func normalizeEndpoint(cfg *TelemetryConfig) {
}
}

// normalizeEndpointEnvVars ensures OTEL exporter endpoint environment variables
// contain a URL scheme. The OTEL SDK's envconfig reads these via url.Parse(),
// which fails on bare "host:port" values. Adding "http://" prevents noisy
// "parse url" errors from the SDK's internal logger.
func normalizeEndpointEnvVars() {
for _, key := range []string{
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
} {
v := strings.TrimSpace(os.Getenv(key))
if v == "" || strings.HasPrefix(v, "http://") || strings.HasPrefix(v, "https://") {
continue
}

_ = os.Setenv(key, "http://"+v)
}
}

// handleEmptyEndpoint handles the case where telemetry is enabled but the collector
// endpoint is empty, returning noop providers installed as globals.
func handleEmptyEndpoint(cfg TelemetryConfig) (*Telemetry, error) {
Expand Down
84 changes: 84 additions & 0 deletions commons/opentelemetry/otel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package opentelemetry

import (
"context"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -196,6 +197,89 @@ func TestNewTelemetry_EndpointNormalization(t *testing.T) {
}
}

// ===========================================================================
// 1c. Endpoint environment variable normalization
// ===========================================================================

func TestNormalizeEndpointEnvVars(t *testing.T) {
envKeys := []string{
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
}

tests := []struct {
name string
value string
set bool
expected string
}{
{
name: "bare host:port gets http scheme",
value: "10.10.0.202:4317",
set: true,
expected: "http://10.10.0.202:4317",
},
{
name: "hostname:port gets http scheme",
value: "otel-collector:4317",
set: true,
expected: "http://otel-collector:4317",
},
{
name: "http scheme preserved",
value: "http://otel-collector:4317",
set: true,
expected: "http://otel-collector:4317",
},
{
name: "https scheme preserved",
value: "https://otel-collector:4317",
set: true,
expected: "https://otel-collector:4317",
},
{
name: "whitespace trimmed before adding scheme",
value: " 10.10.0.202:4317 ",
set: true,
expected: "http://10.10.0.202:4317",
},
{
name: "empty value skipped",
value: "",
set: true,
expected: "",
},
{
name: "whitespace-only value skipped",
value: " ",
set: true,
expected: " ",
},
{
name: "unset env var skipped",
value: "",
set: false,
expected: "",
},
}

for _, tt := range tests {
for _, key := range envKeys {
t.Run(tt.name+"/"+key, func(t *testing.T) {
if tt.set {
t.Setenv(key, tt.value)
}

normalizeEndpointEnvVars()

got := os.Getenv(key)
assert.Equal(t, tt.expected, got)
})
}
}
}

// ===========================================================================
// 2. Telemetry methods on nil receiver
// ===========================================================================
Expand Down
Loading