diff --git a/CHANGELOG.md b/CHANGELOG.md index 438d96917e8..e5ef17eb946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add support to configure views when creating MeterProvider using the config package. (#5654) - Add log support for the autoexport package. (#5733) - Add support for disabling the old runtime metrics using the `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false` environment variable. (#5747) +- Add support for signal-specific protocols environment variables (`OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`, `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL`) in `go.opentelemetry.io/contrib/exporters/autoexport`. (#5816) ### Fixed diff --git a/exporters/autoexport/logs.go b/exporters/autoexport/logs.go index e066a74649e..9e926ce32e1 100644 --- a/exporters/autoexport/logs.go +++ b/exporters/autoexport/logs.go @@ -12,6 +12,8 @@ import ( "go.opentelemetry.io/otel/sdk/log" ) +const otelExporterOTLPLogsProtoEnvKey = "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL" + // LogOption applies an autoexport configuration option. type LogOption = option[log.Exporter] @@ -30,6 +32,9 @@ var logsSignal = newSignal[log.Exporter]("OTEL_LOGS_EXPORTER") // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp] // +// OTEL_EXPORTER_OTLP_LOGS_PROTOCOL defines OTLP exporter's transport protocol for the logs signal; +// supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. +// // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterLogExporter] to handle more values of OTEL_LOGS_EXPORTER. @@ -51,7 +56,12 @@ func RegisterLogExporter(name string, factory func(context.Context) (log.Exporte func init() { RegisterLogExporter("otlp", func(ctx context.Context) (log.Exporter, error) { - proto := os.Getenv(otelExporterOTLPProtoEnvKey) + proto := os.Getenv(otelExporterOTLPLogsProtoEnvKey) + if proto == "" { + proto = os.Getenv(otelExporterOTLPProtoEnvKey) + } + + // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } diff --git a/exporters/autoexport/logs_test.go b/exporters/autoexport/logs_test.go index 889a9ea3a82..8b6c2a5a686 100644 --- a/exporters/autoexport/logs_test.go +++ b/exporters/autoexport/logs_test.go @@ -60,6 +60,32 @@ func TestLogExporterOTLP(t *testing.T) { } } +func TestLogExporterOTLPWithDedicatedProtocol(t *testing.T) { + t.Setenv("OTEL_LOGS_EXPORTER", "otlp") + + for _, tc := range []struct { + protocol, clientType string + }{ + {"http/protobuf", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, + {"", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, + } { + t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { + t.Setenv("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", tc.protocol) + + got, err := NewLogExporter(context.Background()) + assert.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, got.Shutdown(context.Background())) + }) + assert.Implements(t, new(log.Exporter), got) + + // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. + clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Type() + assert.Equal(t, tc.clientType, clientType.String()) + }) + } +} + func TestLogExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") diff --git a/exporters/autoexport/metrics.go b/exporters/autoexport/metrics.go index 5e16b170b4e..f7543eeed52 100644 --- a/exporters/autoexport/metrics.go +++ b/exporters/autoexport/metrics.go @@ -25,6 +25,8 @@ import ( "go.opentelemetry.io/otel/sdk/metric" ) +const otelExporterOTLPMetricsProtoEnvKey = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" + // MetricOption applies an autoexport configuration option. type MetricOption = option[metric.Reader] @@ -50,6 +52,9 @@ func WithFallbackMetricReader(metricReaderFactory func(ctx context.Context) (met // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp] // +// OTEL_EXPORTER_OTLP_METRICS_PROTOCOL defines OTLP exporter's transport protocol for the metrics signal; +// supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. +// // OTEL_EXPORTER_PROMETHEUS_HOST (defaulting to "localhost") and // OTEL_EXPORTER_PROMETHEUS_PORT (defaulting to 9464) define the host and port for the // Prometheus exporter's HTTP server. @@ -106,7 +111,12 @@ func init() { readerOpts = append(readerOpts, metric.WithProducer(producer)) } - proto := os.Getenv(otelExporterOTLPProtoEnvKey) + proto := os.Getenv(otelExporterOTLPMetricsProtoEnvKey) + if proto == "" { + proto = os.Getenv(otelExporterOTLPProtoEnvKey) + } + + // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } diff --git a/exporters/autoexport/metrics_test.go b/exporters/autoexport/metrics_test.go index da35b4aa570..c95a077d176 100644 --- a/exporters/autoexport/metrics_test.go +++ b/exporters/autoexport/metrics_test.go @@ -74,6 +74,33 @@ func TestMetricExporterOTLP(t *testing.T) { } } +func TestMetricExporterOTLPWithDedicatedProtocol(t *testing.T) { + t.Setenv("OTEL_METRICS_EXPORTER", "otlp") + + for _, tc := range []struct { + protocol, exporterType string + }{ + {"http/protobuf", "*otlpmetrichttp.Exporter"}, + {"", "*otlpmetrichttp.Exporter"}, + {"grpc", "*otlpmetricgrpc.Exporter"}, + } { + t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { + t.Setenv("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", tc.protocol) + + got, err := NewMetricReader(context.Background()) + assert.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, got.Shutdown(context.Background())) + }) + assert.IsType(t, &metric.PeriodicReader{}, got) + + // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. + exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() + assert.Equal(t, tc.exporterType, exporterType.String()) + }) + } +} + func TestMetricExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") diff --git a/exporters/autoexport/spans.go b/exporters/autoexport/spans.go index 0a627ae8be5..8970d18f725 100644 --- a/exporters/autoexport/spans.go +++ b/exporters/autoexport/spans.go @@ -13,6 +13,8 @@ import ( "go.opentelemetry.io/otel/sdk/trace" ) +const otelExporterOTLPTracesProtoEnvKey = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" + // SpanOption applies an autoexport configuration option. type SpanOption = option[trace.SpanExporter] @@ -42,6 +44,9 @@ func WithFallbackSpanExporter(spanExporterFactory func(ctx context.Context) (tra // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp] // +// OTEL_EXPORTER_OTLP_TRACES_PROTOCOL defines OTLP exporter's transport protocol for the traces signal; +// supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. +// // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterSpanExporter] to handle more values of OTEL_TRACES_EXPORTER. @@ -65,7 +70,12 @@ var tracesSignal = newSignal[trace.SpanExporter]("OTEL_TRACES_EXPORTER") func init() { RegisterSpanExporter("otlp", func(ctx context.Context) (trace.SpanExporter, error) { - proto := os.Getenv(otelExporterOTLPProtoEnvKey) + proto := os.Getenv(otelExporterOTLPTracesProtoEnvKey) + if proto == "" { + proto = os.Getenv(otelExporterOTLPProtoEnvKey) + } + + // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } diff --git a/exporters/autoexport/spans_test.go b/exporters/autoexport/spans_test.go index e411379e0b2..191f955d325 100644 --- a/exporters/autoexport/spans_test.go +++ b/exporters/autoexport/spans_test.go @@ -59,6 +59,33 @@ func TestSpanExporterOTLP(t *testing.T) { } } +func TestSpanExporterOTLPWithDedicatedProtocol(t *testing.T) { + t.Setenv("OTEL_TRACES_EXPORTER", "otlp") + + for _, tc := range []struct { + protocol, clientType string + }{ + {"http/protobuf", "*otlptracehttp.client"}, + {"", "*otlptracehttp.client"}, + {"grpc", "*otlptracegrpc.client"}, + } { + t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { + t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", tc.protocol) + + got, err := NewSpanExporter(context.Background()) + assert.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, got.Shutdown(context.Background())) + }) + assert.IsType(t, &otlptrace.Exporter{}, got) + + // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. + clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Elem().Type() + assert.Equal(t, tc.clientType, clientType.String()) + }) + } +} + func TestSpanExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol")