diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 774caa365a..a2f308aeed 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -78,11 +78,14 @@ type startupInfo struct { PartialFlushMinSpans int `json:"partial_flush_min_spans"` // The min number of spans to trigger a partial flush Orchestrion orchestrionConfig `json:"orchestrion"` // Orchestrion (auto-instrumentation) configuration. FeatureFlags []string `json:"feature_flags"` - PropagationStyleInject string `json:"propagation_style_inject"` // Propagation style for inject - PropagationStyleExtract string `json:"propagation_style_extract"` // Propagation style for extract - TracingAsTransport bool `json:"tracing_as_transport"` // Whether the tracer is disabled and other products are using it as a transport - DogstatsdAddr string `json:"dogstatsd_address"` // Destination of statsd payloads - DataStreamsEnabled bool `json:"data_streams_enabled"` // Whether Data Streams is enabled + PropagationStyleInject string `json:"propagation_style_inject"` // Propagation style for inject + PropagationStyleExtract string `json:"propagation_style_extract"` // Propagation style for extract + TracingAsTransport bool `json:"tracing_as_transport"` // Whether the tracer is disabled and other products are using it as a transport + DogstatsdAddr string `json:"dogstatsd_address"` // Destination of statsd payloads + DataStreamsEnabled bool `json:"data_streams_enabled"` // Whether Data Streams is enabled + OTLPTracesExportEnabled bool `json:"otlp_traces_export_enabled"` // Whether traces are exported over OTLP + OTLPMetricsExportEnabled bool `json:"otlp_metrics_export_enabled"` // Whether metrics are exported over OTLP + OTLPLogsExportEnabled bool `json:"otlp_logs_export_enabled"` // Whether logs are exported over OTLP } // checkEndpoint tries to connect to the URL specified by endpoint. @@ -187,6 +190,14 @@ func logStartup(t *tracer) { TracingAsTransport: t.config.tracingAsTransport, DogstatsdAddr: t.config.internalConfig.DogstatsdAddr(), DataStreamsEnabled: t.config.internalConfig.DataStreamsMonitoringEnabled(), + OTLPTracesExportEnabled: t.otlpExportMode, + // Reports the tracer's resolved decision to start OTel runtime metrics over + // OTLP: the same gate the tracer uses (config enabled and opentelemetry/metric + // imported, so StartHook != nil), not a config-only guess. + OTLPMetricsExportEnabled: t.config.otelRuntimeMetricsShouldBeEnabled, + // The user starts the OTel logs integration (opentelemetry/log.Start) after + // this log is emitted, so no started-state is knowable here; report config intent. + OTLPLogsExportEnabled: t.config.internalConfig.LogsOTelEnabled(), } if limit, ok := t.rulesSampling.TraceRateLimit(); ok { info.SampleRateLimit = fmt.Sprintf("%v", limit) diff --git a/ddtrace/tracer/log_test.go b/ddtrace/tracer/log_test.go index fb3c817124..9c5da1ebd6 100644 --- a/ddtrace/tracer/log_test.go +++ b/ddtrace/tracer/log_test.go @@ -13,6 +13,7 @@ import ( "github.com/DataDog/dd-trace-go/v2/internal/globalconfig" "github.com/DataDog/dd-trace-go/v2/internal/log" + "github.com/DataDog/dd-trace-go/v2/internal/otelmetricsinstall" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,6 +26,15 @@ const logPrefixRegexp = `Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+(-((rc|beta)\.[0- var commonLogIgnore = []string{"appsec: ", "telemetry", "Runtime metrics v2 enabled"} func TestStartupLog(t *testing.T) { + // Make the OTLP export fields deterministic regardless of any OTEL_*/DD_* + // env pollution in the dev/CI shell. Empty values are treated as unset by + // the config provider, so each field falls back to its default of false: + // traces export to the agent, the OTel metrics signal is off, and logs are + // off. This matches the golden output in the sub-tests below. + t.Setenv("OTEL_TRACES_EXPORTER", "") + t.Setenv("DD_METRICS_OTEL_ENABLED", "") + t.Setenv("DD_LOGS_OTEL_ENABLED", "") + t.Run("basic", func(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) @@ -36,7 +46,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore(commonLogIgnore...) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v1.0/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v1.0/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false,"otlp_traces_export_enabled":false,"otlp_metrics_export_enabled":false,"otlp_logs_export_enabled":false}`, tp.Logs()[1]) }) t.Run("configured", func(t *testing.T) { @@ -68,7 +78,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore(commonLogIgnore...) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v1.0/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":(false|true,"metadata":{"version":"v\d+.\d+.\d+(-[^"]+)?"})},"feature_flags":\["discovery"\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v1.0/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":(false|true,"metadata":{"version":"v\d+.\d+.\d+(-[^"]+)?"})},"feature_flags":\["discovery"\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false,"otlp_traces_export_enabled":false,"otlp_metrics_export_enabled":false,"otlp_logs_export_enabled":false}`, tp.Logs()[1]) }) t.Run("limit", func(t *testing.T) { @@ -99,7 +109,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore(commonLogIgnore...) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v1.0/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"1000.001","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v1.0/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"1000.001","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false,"otlp_traces_export_enabled":false,"otlp_metrics_export_enabled":false,"otlp_logs_export_enabled":false}`, tp.Logs()[1]) }) t.Run("errors", func(t *testing.T) { @@ -121,7 +131,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore(commonLogIgnore...) logStartup(tracer) assert.Len(tp.Logs(), 1) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v1.0/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false}`, tp.Logs()[0]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v1.0/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":true,"Stats":true,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext,baggage","propagation_style_extract":"datadog,tracecontext,baggage","tracing_as_transport":false,"dogstatsd_address":"localhost:8125","data_streams_enabled":false,"otlp_traces_export_enabled":false,"otlp_metrics_export_enabled":false,"otlp_logs_export_enabled":false}`, tp.Logs()[0]) }) t.Run("integrations", func(t *testing.T) { @@ -140,6 +150,47 @@ func TestStartupLog(t *testing.T) { assert.Contains(tp.Logs()[1], expect, "expected integration %s", expect) } }) + + t.Run("otlp_metrics_export", func(t *testing.T) { + assert := assert.New(t) + tp := new(log.RecordLogger) + // otlp_metrics_export_enabled tracks otelRuntimeMetricsShouldBeEnabled, whose + // gate includes StartHook != nil (normally set when opentelemetry/metric is + // imported). withTestHooks stands in for that import so the pipeline is active. + withTestHooks(t) + t.Setenv("DD_METRICS_OTEL_ENABLED", "true") + t.Setenv("OTEL_METRICS_EXPORTER", "otlp") + tracer, _, _, stop, err := startTestTracer(t, WithLogger(tp)) + require.NoError(t, err) + defer stop() + require.True(t, tracer.config.otelRuntimeMetricsShouldBeEnabled, + "OTLP metrics pipeline should be active for this assertion to be meaningful") + tp.Reset() + tp.Ignore(commonLogIgnore...) + logStartup(tracer) + require.Len(t, tp.Logs(), 2) + assert.Contains(tp.Logs()[1], `"otlp_metrics_export_enabled":true`) + }) + + t.Run("otlp_metrics_export_config_without_pipeline", func(t *testing.T) { + assert := assert.New(t) + tp := new(log.RecordLogger) + // Config intent is present but opentelemetry/metric is not imported, so + // StartHook is nil and the pipeline never starts: the field must report false + // rather than over-reporting from config alone. + require.Nil(t, otelmetricsinstall.StartHook, "test binary must not have the metric package imported") + t.Setenv("DD_METRICS_OTEL_ENABLED", "true") + t.Setenv("OTEL_METRICS_EXPORTER", "otlp") + tracer, _, _, stop, err := startTestTracer(t, WithLogger(tp)) + require.NoError(t, err) + defer stop() + require.False(t, tracer.config.otelRuntimeMetricsShouldBeEnabled) + tp.Reset() + tp.Ignore(commonLogIgnore...) + logStartup(tracer) + require.Len(t, tp.Logs(), 2) + assert.Contains(tp.Logs()[1], `"otlp_metrics_export_enabled":false`) + }) } func TestLogSamplingRules(t *testing.T) {