mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat: support standard OTEL env vars via autoexport (#11864)
This commit is contained in:
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _ExporterTypeName = "NoneStdOutStdErrGRPCHTTPGooglePrometheus"
|
||||
const _ExporterTypeName = "NoneStdOutStdErrGRPCHTTPGooglePrometheusAuto"
|
||||
|
||||
var _ExporterTypeIndex = [...]uint8{0, 0, 4, 10, 16, 20, 24, 30, 40}
|
||||
var _ExporterTypeIndex = [...]uint8{0, 0, 4, 10, 16, 20, 24, 30, 40, 44}
|
||||
|
||||
const _ExporterTypeLowerName = "nonestdoutstderrgrpchttpgoogleprometheus"
|
||||
const _ExporterTypeLowerName = "nonestdoutstderrgrpchttpgoogleprometheusauto"
|
||||
|
||||
func (i ExporterType) String() string {
|
||||
if i < 0 || i >= ExporterType(len(_ExporterTypeIndex)-1) {
|
||||
@@ -32,9 +32,10 @@ func _ExporterTypeNoOp() {
|
||||
_ = x[ExporterTypeHTTP-(5)]
|
||||
_ = x[ExporterTypeGoogle-(6)]
|
||||
_ = x[ExporterTypePrometheus-(7)]
|
||||
_ = x[ExporterTypeAuto-(8)]
|
||||
}
|
||||
|
||||
var _ExporterTypeValues = []ExporterType{ExporterTypeUnspecified, ExporterTypeNone, ExporterTypeStdOut, ExporterTypeStdErr, ExporterTypeGRPC, ExporterTypeHTTP, ExporterTypeGoogle, ExporterTypePrometheus}
|
||||
var _ExporterTypeValues = []ExporterType{ExporterTypeUnspecified, ExporterTypeNone, ExporterTypeStdOut, ExporterTypeStdErr, ExporterTypeGRPC, ExporterTypeHTTP, ExporterTypeGoogle, ExporterTypePrometheus, ExporterTypeAuto}
|
||||
|
||||
var _ExporterTypeNameToValueMap = map[string]ExporterType{
|
||||
_ExporterTypeName[0:0]: ExporterTypeUnspecified,
|
||||
@@ -53,6 +54,8 @@ var _ExporterTypeNameToValueMap = map[string]ExporterType{
|
||||
_ExporterTypeLowerName[24:30]: ExporterTypeGoogle,
|
||||
_ExporterTypeName[30:40]: ExporterTypePrometheus,
|
||||
_ExporterTypeLowerName[30:40]: ExporterTypePrometheus,
|
||||
_ExporterTypeName[40:44]: ExporterTypeAuto,
|
||||
_ExporterTypeLowerName[40:44]: ExporterTypeAuto,
|
||||
}
|
||||
|
||||
var _ExporterTypeNames = []string{
|
||||
@@ -64,6 +67,7 @@ var _ExporterTypeNames = []string{
|
||||
_ExporterTypeName[20:24],
|
||||
_ExporterTypeName[24:30],
|
||||
_ExporterTypeName[30:40],
|
||||
_ExporterTypeName[40:44],
|
||||
}
|
||||
|
||||
// ExporterTypeString retrieves an enum value from the enum constants string name.
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.opentelemetry.io/contrib/exporters/autoexport"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog"
|
||||
@@ -15,6 +16,30 @@ import (
|
||||
func newLoggerProvider(ctx context.Context, cfg ExporterConfig, resource *resource.Resource) (_ *sdk_log.LoggerProvider, err error) {
|
||||
var exporter sdk_log.Exporter
|
||||
switch cfg.Type {
|
||||
case ExporterTypeAuto:
|
||||
// autoexport delegates exporter selection to the standard OTEL env vars.
|
||||
// We can't just call autoexport.NewLogExporter unconditionally because
|
||||
// autoexport defaults to "otlp" when OTEL_LOGS_EXPORTER is unset, and
|
||||
// the OTLP exporter silently points at localhost:4318 even with no env
|
||||
// vars configured. That would cause every ZITADEL instance to start
|
||||
// attempting OTLP connections after upgrading, spamming logs with
|
||||
// connection errors.
|
||||
//
|
||||
// This guard is a heuristic: we check the env vars that realistically
|
||||
// indicate someone has intentionally configured OTEL export. It does not
|
||||
// cover every possible OTLP env var (e.g. OTEL_EXPORTER_OTLP_HEADERS or
|
||||
// OTEL_EXPORTER_OTLP_CERTIFICATE on their own), but those vars are
|
||||
// meaningless without an endpoint or exporter type being set too.
|
||||
if os.Getenv("OTEL_LOGS_EXPORTER") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL") != "" {
|
||||
exporter, err = autoexport.NewLogExporter(ctx)
|
||||
if err == nil && autoexport.IsNoneLogExporter(exporter) {
|
||||
exporter = nil
|
||||
}
|
||||
}
|
||||
case ExporterTypeUnspecified, ExporterTypeNone:
|
||||
// no exporter
|
||||
case ExporterTypeStdOut, ExporterTypeStdErr:
|
||||
|
||||
@@ -79,3 +79,140 @@ func Test_newLoggerProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newLoggerProvider_autoexport(t *testing.T) {
|
||||
resource, err := resource.New(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg ExporterConfig
|
||||
envVars map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "auto type with OTEL_LOGS_EXPORTER=console creates provider",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_LOGS_EXPORTER": "console",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with no OTEL env vars creates provider (noop fallback)",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with OTEL_LOGS_EXPORTER=none creates provider (autoexport none)",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_LOGS_EXPORTER": "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_ENDPOINT uses OTLP default",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_LOGS_ENDPOINT uses OTLP default",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_PROTOCOL uses OTLP default",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_LOGS_PROTOCOL uses OTLP default",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": "http/protobuf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "none type ignores OTEL env vars (explicit disable)",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_LOGS_EXPORTER": "invalid_exporter",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: ExporterTypeNone ignores global OTEL endpoint
|
||||
{
|
||||
name: "none type ignores OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: ExporterTypeNone ignores per-signal OTEL var
|
||||
{
|
||||
name: "none type ignores OTEL_LOGS_EXPORTER=console",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_LOGS_EXPORTER": "console",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: explicit ZITADEL types take priority over OTEL env vars
|
||||
{
|
||||
name: "stdout type ignores OTEL_LOGS_EXPORTER",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeStdOut,
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_LOGS_EXPORTER": "grpc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "grpc type ignores OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
cfg: ExporterConfig{
|
||||
Type: ExporterTypeGRPC,
|
||||
Endpoint: "localhost:4317",
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://some-other-host:4318",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for k, v := range tt.envVars {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
got, gotErr := newLoggerProvider(t.Context(), tt.cfg, resource)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, gotErr)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, gotErr)
|
||||
assert.NotNil(t, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
google_metric "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/contrib/exporters/autoexport"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||
@@ -160,6 +161,31 @@ func labelsToAttributes(labels map[string]attribute.Value) []attribute.KeyValue
|
||||
func newMeterProvider(ctx context.Context, cfg MetricConfig, resource *resource.Resource) (_ *sdk_metric.MeterProvider, err error) {
|
||||
var readerOption sdk_metric.Option
|
||||
switch cfg.Exporter.Type {
|
||||
case ExporterTypeAuto:
|
||||
// autoexport delegates reader selection to the standard OTEL env vars.
|
||||
// We can't just call autoexport.NewMetricReader unconditionally because
|
||||
// autoexport defaults to "otlp" when OTEL_METRICS_EXPORTER is unset, and
|
||||
// the OTLP exporter silently points at localhost:4318 even with no env
|
||||
// vars configured. That would cause every ZITADEL instance to start
|
||||
// attempting OTLP connections after upgrading, spamming logs with
|
||||
// connection errors.
|
||||
//
|
||||
// This guard is a heuristic: we check the env vars that realistically
|
||||
// indicate someone has intentionally configured OTEL export. It does not
|
||||
// cover every possible OTLP env var (e.g. OTEL_EXPORTER_OTLP_HEADERS or
|
||||
// OTEL_EXPORTER_OTLP_CERTIFICATE on their own), but those vars are
|
||||
// meaningless without an endpoint or exporter type being set too.
|
||||
if os.Getenv("OTEL_METRICS_EXPORTER") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL") != "" {
|
||||
var reader sdk_metric.Reader
|
||||
reader, err = autoexport.NewMetricReader(ctx)
|
||||
if err == nil && !autoexport.IsNoneMetricReader(reader) {
|
||||
readerOption = sdk_metric.WithReader(reader)
|
||||
}
|
||||
}
|
||||
case ExporterTypeUnspecified, ExporterTypeNone:
|
||||
// no reader option
|
||||
case ExporterTypeStdOut, ExporterTypeStdErr:
|
||||
|
||||
@@ -77,6 +77,22 @@ func TestMetricConfig_SetLegacyConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type is not overridden by legacy config",
|
||||
target: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
lc: &LegacyMetricConfig{
|
||||
Type: "otel",
|
||||
},
|
||||
want: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -184,3 +200,164 @@ func Test_newMeterProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newMeterProvider_autoexport(t *testing.T) {
|
||||
resource, err := resource.New(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg MetricConfig
|
||||
envVars map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "auto type with OTEL_METRICS_EXPORTER=console creates provider",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_METRICS_EXPORTER": "console",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with no OTEL env vars creates provider (noop fallback)",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with OTEL_METRICS_EXPORTER=none creates provider (autoexport none)",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_METRICS_EXPORTER": "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_ENDPOINT uses OTLP default",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_METRICS_ENDPOINT uses OTLP default",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_PROTOCOL uses OTLP default",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_METRICS_PROTOCOL uses OTLP default",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": "http/protobuf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "none type ignores OTEL env vars (explicit disable)",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_METRICS_EXPORTER": "invalid_exporter",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: ExporterTypeNone ignores global OTEL endpoint
|
||||
{
|
||||
name: "none type ignores OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: ExporterTypeNone ignores per-signal OTEL var
|
||||
{
|
||||
name: "none type ignores OTEL_METRICS_EXPORTER=console",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_METRICS_EXPORTER": "console",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: explicit ZITADEL types take priority over OTEL env vars
|
||||
{
|
||||
name: "stdout type ignores OTEL_METRICS_EXPORTER",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeStdOut,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_METRICS_EXPORTER": "grpc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "grpc type ignores OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
cfg: MetricConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeGRPC,
|
||||
Endpoint: "localhost:4317",
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://some-other-host:4318",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for k, v := range tt.envVars {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
got, gotErr := newMeterProvider(t.Context(), tt.cfg, resource)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, gotErr)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, gotErr)
|
||||
assert.NotNil(t, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
ExporterTypeHTTP
|
||||
ExporterTypeGoogle
|
||||
ExporterTypePrometheus
|
||||
ExporterTypeAuto
|
||||
)
|
||||
|
||||
// isNone returns true if the ExporterType is either Unspecified or None.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"slices"
|
||||
|
||||
google_trace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
|
||||
"go.opentelemetry.io/contrib/exporters/autoexport"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
@@ -136,6 +137,30 @@ func (s *Span) SetStatusByError(err error) {
|
||||
func newTracerProvider(ctx context.Context, cfg TraceConfig, resource *resource.Resource) (_ *sdk_trace.TracerProvider, err error) {
|
||||
var exporter sdk_trace.SpanExporter
|
||||
switch cfg.Exporter.Type {
|
||||
case ExporterTypeAuto:
|
||||
// autoexport delegates exporter selection to the standard OTEL env vars.
|
||||
// We can't just call autoexport.NewSpanExporter unconditionally because
|
||||
// autoexport defaults to "otlp" when OTEL_TRACES_EXPORTER is unset, and
|
||||
// the OTLP exporter silently points at localhost:4318 even with no env
|
||||
// vars configured. That would cause every ZITADEL instance to start
|
||||
// attempting OTLP connections after upgrading, spamming logs with
|
||||
// connection errors.
|
||||
//
|
||||
// This guard is a heuristic: we check the env vars that realistically
|
||||
// indicate someone has intentionally configured OTEL export. It does not
|
||||
// cover every possible OTLP env var (e.g. OTEL_EXPORTER_OTLP_HEADERS or
|
||||
// OTEL_EXPORTER_OTLP_CERTIFICATE on their own), but those vars are
|
||||
// meaningless without an endpoint or exporter type being set too.
|
||||
if os.Getenv("OTEL_TRACES_EXPORTER") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL") != "" ||
|
||||
os.Getenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL") != "" {
|
||||
exporter, err = autoexport.NewSpanExporter(ctx)
|
||||
if err == nil && autoexport.IsNoneSpanExporter(exporter) {
|
||||
exporter = nil
|
||||
}
|
||||
}
|
||||
case ExporterTypeUnspecified, ExporterTypeNone:
|
||||
// no exporter
|
||||
case ExporterTypeStdOut, ExporterTypeStdErr:
|
||||
@@ -147,7 +172,7 @@ func newTracerProvider(ctx context.Context, cfg TraceConfig, resource *resource.
|
||||
case ExporterTypeGoogle:
|
||||
exporter, err = traceGoogleExporter(ctx, cfg.Exporter)
|
||||
case ExporterTypePrometheus:
|
||||
fallthrough // prometheus is not supported for logs
|
||||
fallthrough // prometheus is not supported for traces
|
||||
default:
|
||||
err = errExporterType(cfg.Exporter.Type, "tracer")
|
||||
}
|
||||
@@ -160,7 +185,7 @@ func newTracerProvider(ctx context.Context, cfg TraceConfig, resource *resource.
|
||||
sdk_trace.WithSampler(sampler),
|
||||
}
|
||||
if exporter != nil {
|
||||
opts = append(opts, sdk_trace.WithBatcher(exporter))
|
||||
opts = append(opts, sdk_trace.WithBatcher(exporter, sdk_trace.WithBatchTimeout(cfg.Exporter.BatchDuration)))
|
||||
}
|
||||
tracerProvider := sdk_trace.NewTracerProvider(opts...)
|
||||
return tracerProvider, nil
|
||||
|
||||
@@ -87,6 +87,24 @@ func TestTraceConfig_SetLegacyConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type is not overridden by legacy config",
|
||||
target: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
lc: &LegacyTraceConfig{
|
||||
Type: "google",
|
||||
Fraction: 0.5,
|
||||
Endpoint: "endpoint",
|
||||
},
|
||||
want: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -189,3 +207,164 @@ func Test_newTracerProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newTracerProvider_autoexport(t *testing.T) {
|
||||
resource, err := resource.New(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg TraceConfig
|
||||
envVars map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "auto type with OTEL_TRACES_EXPORTER=console creates provider",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_TRACES_EXPORTER": "console",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with no OTEL env vars creates provider (noop fallback)",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with OTEL_TRACES_EXPORTER=none creates provider (autoexport none)",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_TRACES_EXPORTER": "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_ENDPOINT uses OTLP default",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_TRACES_ENDPOINT uses OTLP default",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_PROTOCOL uses OTLP default",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "auto type with only OTEL_EXPORTER_OTLP_TRACES_PROTOCOL uses OTLP default",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeAuto,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": "http/protobuf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "none type ignores OTEL env vars (explicit disable)",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_TRACES_EXPORTER": "invalid_exporter",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: ExporterTypeNone ignores global OTEL endpoint
|
||||
{
|
||||
name: "none type ignores OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: ExporterTypeNone ignores per-signal OTEL var
|
||||
{
|
||||
name: "none type ignores OTEL_TRACES_EXPORTER=console",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeNone,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_TRACES_EXPORTER": "console",
|
||||
},
|
||||
},
|
||||
// Backward compatibility: explicit ZITADEL types take priority over OTEL env vars
|
||||
{
|
||||
name: "stdout type ignores OTEL_TRACES_EXPORTER",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeStdOut,
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_TRACES_EXPORTER": "grpc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "grpc type ignores OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
cfg: TraceConfig{
|
||||
Exporter: ExporterConfig{
|
||||
Type: ExporterTypeGRPC,
|
||||
Endpoint: "localhost:4317",
|
||||
},
|
||||
},
|
||||
envVars: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://some-other-host:4318",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for k, v := range tt.envVars {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
got, gotErr := newTracerProvider(t.Context(), tt.cfg, resource)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, gotErr)
|
||||
assert.Nil(t, got)
|
||||
} else {
|
||||
assert.NoError(t, gotErr)
|
||||
assert.NotNil(t, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -8,6 +8,7 @@ Instrumentation:
|
||||
Exporter:
|
||||
# The following exporter types are supported:
|
||||
# "none": Disables trace exporting
|
||||
# "auto": Delegates to standard OTEL env vars (OTEL_TRACES_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT)
|
||||
# "stdOut": Exports traces to standard output
|
||||
# "stdErr": Exports traces to standard error
|
||||
# "grpc": Exports traces using the OTEL gRPC exporter (recommended)
|
||||
@@ -26,6 +27,7 @@ Instrumentation:
|
||||
Exporter:
|
||||
# The following exporter types are supported:
|
||||
# "none": Disables metric exporting
|
||||
# "auto": Delegates to standard OTEL env vars (OTEL_METRICS_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT)
|
||||
# "stdOut": Exports metrics to standard output
|
||||
# "stdErr": Exports metrics to standard error
|
||||
# "grpc": Exports metrics using the OTEL gRPC exporter (recommended)
|
||||
@@ -91,6 +93,7 @@ Instrumentation:
|
||||
Exporter:
|
||||
# The following exporter types are supported:
|
||||
# - "none": Disables OTEL log exporter
|
||||
# - "auto": Delegates to standard OTEL env vars (OTEL_LOGS_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT)
|
||||
# - "stdOut": Exports OTEL logs to standard output
|
||||
# - "stdErr": Exports OTEL logs to standard error
|
||||
# - "grpc": Exports OTEL logs using the OTEL gRPC exporter (recommended)
|
||||
@@ -100,7 +103,7 @@ Instrumentation:
|
||||
Endpoint: "" # ZITADEL_INSTRUMENTATION_LOG_EXPORTER_ENDPOINT
|
||||
# Disable TLS for grpc and http exporters
|
||||
Insecure: false # ZITADEL_INSTRUMENTATION_LOG_EXPORTER_INSECURE
|
||||
# Interval at which metrics are exported
|
||||
# Interval at which logs are batched for export
|
||||
BatchDuration: 1s # ZITADEL_INSTRUMENTATION_LOG_EXPORTER_BATCHDURATION
|
||||
Profile:
|
||||
Exporter:
|
||||
|
||||
@@ -106,6 +106,7 @@ require (
|
||||
github.com/zitadel/schema v1.3.2
|
||||
github.com/zitadel/sloggcp v0.6.0
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.17.0
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
|
||||
go.opentelemetry.io/otel v1.42.0
|
||||
@@ -257,6 +258,7 @@ require (
|
||||
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
|
||||
@@ -856,8 +856,12 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.17.0 h1:NFIS6x7wyObQ7cR84x7bt1sr8nYBx89s3x3GwRjw40k=
|
||||
go.opentelemetry.io/contrib/bridges/otelslog v0.17.0/go.mod h1:39SaByOyDMRMe872AE7uelMuQZidIw7LLFAnQi0FWTE=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
|
||||
Reference in New Issue
Block a user