Skip to content

Commit

Permalink
feat(exporters/autoexport): add support of OTEL_EXPORTER_OTLP_{TRACES…
Browse files Browse the repository at this point in the history
…,METRICS,LOGS}_PROTOCOL env var (open-telemetry#5807)

This commit adds support for signal-specific environment variables to configure the OTLP protocol used with the exporter.
As stated in the [specification](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.34.0/specification/protocol/exporter.md#configuration-options), you
can now use the following environment variables 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`.

Signed-off-by: thomasgouveia <gouveia.thomas@outlook.fr>
  • Loading branch information
thomasgouveia committed Jun 27, 2024
1 parent b2a21f6 commit b17b828
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 14 deletions.
1 change: 1 addition & 0 deletions exporters/autoexport/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.22.4
require (
github.com/prometheus/client_golang v1.19.1
github.com/stretchr/testify v1.9.0
github.com/thomasgouveia/goutils/env v1.0.1
go.opentelemetry.io/contrib/bridges/prometheus v0.52.0
go.opentelemetry.io/otel v1.27.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0
Expand Down
2 changes: 2 additions & 0 deletions exporters/autoexport/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/thomasgouveia/goutils/env v1.0.1 h1:cn1RGQ+uuOJ8xY2ZKiVVA/WzEhziXy2meCevzRKKw+E=
github.com/thomasgouveia/goutils/env v1.0.1/go.mod h1:GJrLBNv7WGXGZhVvr6w6BQTiT3UJk4iYludCCwxNdOY=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 h1:ccBrA8nCY5mM0y5uO7FT0ze4S0TuFcWdDB2FxGMTjkI=
Expand Down
16 changes: 11 additions & 5 deletions exporters/autoexport/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport"

import (
"context"
"os"

"github.com/thomasgouveia/goutils/env"

"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog"
"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 +33,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,10 +57,10 @@ 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)
if proto == "" {
proto = "http/protobuf"
}
proto := env.WithDefaultString(
otelExporterOTLPLogsProtoEnvKey,
env.WithDefaultString(otelExporterOTLPProtoEnvKey, "http/protobuf"),
)

switch proto {
// grpc is not supported yet, should comment out when it is supported
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
15 changes: 11 additions & 4 deletions exporters/autoexport/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"strings"
"time"

"github.com/thomasgouveia/goutils/env"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"

Expand All @@ -25,6 +27,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 +54,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,10 +113,10 @@ func init() {
readerOpts = append(readerOpts, metric.WithProducer(producer))
}

proto := os.Getenv(otelExporterOTLPProtoEnvKey)
if proto == "" {
proto = "http/protobuf"
}
proto := env.WithDefaultString(
otelExporterOTLPMetricsProtoEnvKey,
env.WithDefaultString(otelExporterOTLPProtoEnvKey, "http/protobuf"),
)

switch proto {
case "grpc":
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
16 changes: 11 additions & 5 deletions exporters/autoexport/spans.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport"

import (
"context"
"os"

"github.com/thomasgouveia/goutils/env"

"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"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 +45,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,10 +71,10 @@ var tracesSignal = newSignal[trace.SpanExporter]("OTEL_TRACES_EXPORTER")

func init() {
RegisterSpanExporter("otlp", func(ctx context.Context) (trace.SpanExporter, error) {
proto := os.Getenv(otelExporterOTLPProtoEnvKey)
if proto == "" {
proto = "http/protobuf"
}
proto := env.WithDefaultString(
otelExporterOTLPTracesProtoEnvKey,
env.WithDefaultString(otelExporterOTLPProtoEnvKey, "http/protobuf"),
)

switch proto {
case "grpc":
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 b17b828

Please sign in to comment.