Skip to content

Commit

Permalink
exporters/autoexport: add support for signal-specific protocols envir…
Browse files Browse the repository at this point in the history
…onment variables (#5816)

This PR adds support for signal-specific environment variables to
configure the OTLP protocol used with the exporter.

The following environment variables have been added to configure
signal-specific protocols:

- `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`
- `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`
- `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL`

The package will first attempt to load the protocol for a signal from
the corresponding environment variable. If it is not set or is empty, it
will try to determine the protocol from `OTEL_EXPORTER_OTLP_PROTOCOL`.
If this is also not defined or is empty, it will fall back to
`http/protobuf`.

**Note for reviewers**: As you'll see in the code, I use my own
[env](https://github.com/thomasgouveia/goutils/tree/main/env) package
(MIT License) to facilitate environment variable management. If needed,
I can backport the used functions into the code here and remove the
dependency. Let me know.

Closes
#5807

---------

Signed-off-by: thomasgouveia <[email protected]>
Co-authored-by: Robert Pająk <[email protected]>
Co-authored-by: Tyler Yahn <[email protected]>
  • Loading branch information
3 people authored Jul 1, 2024
1 parent fbc3302 commit 5b6a960
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 11 additions & 1 deletion exporters/autoexport/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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.
Expand All @@ -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"
}
Expand Down
26 changes: 26 additions & 0 deletions exporters/autoexport/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 11 additions & 1 deletion exporters/autoexport/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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.
Expand Down Expand Up @@ -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"
}
Expand Down
27 changes: 27 additions & 0 deletions exporters/autoexport/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 11 additions & 1 deletion exporters/autoexport/spans.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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.
Expand All @@ -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"
}
Expand Down
27 changes: 27 additions & 0 deletions exporters/autoexport/spans_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 5b6a960

Please sign in to comment.