diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index b88d477c97..f64d2dadee 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -43,6 +43,7 @@ type startupInfo struct { ServiceMappings map[string]string `json:"service_mappings"` // Service Mappings Tags map[string]string `json:"tags"` // Global tags RuntimeMetricsEnabled bool `json:"runtime_metrics_enabled"` // Whether runtime metrics are enabled + RuntimeMetricsV2Enabled bool `json:"runtime_metrics_v2_enabled"` // Whether runtime metrics v2 are enabled HealthMetricsEnabled bool `json:"health_metrics_enabled"` // Whether health metrics are enabled ProfilerCodeHotspotsEnabled bool `json:"profiler_code_hotspots_enabled"` // Whether profiler code hotspots are enabled ProfilerEndpointsEnabled bool `json:"profiler_endpoints_enabled"` // Whether profiler endpoints are enabled @@ -124,6 +125,7 @@ func logStartup(t *tracer) { ServiceMappings: t.config.serviceMappings, Tags: tags, RuntimeMetricsEnabled: t.config.runtimeMetrics, + RuntimeMetricsV2Enabled: t.config.runtimeMetricsV2, HealthMetricsEnabled: t.config.runtimeMetrics, ApplicationVersion: t.config.version, ProfilerCodeHotspotsEnabled: t.config.profilerHotspots, diff --git a/ddtrace/tracer/log_test.go b/ddtrace/tracer/log_test.go index 64e6aa5826..c743a3fe00 100644 --- a/ddtrace/tracer/log_test.go +++ b/ddtrace/tracer/log_test.go @@ -32,7 +32,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) 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/v0.4/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,"health_metrics_enabled":false,"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)|(false)),"Stats":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, 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/v0.4/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":false,"health_metrics_enabled":false,"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)|(false)),"Stats":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("configured", func(t *testing.T) { @@ -64,7 +64,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) 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/v0.4/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,"health_metrics_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":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":true,"metadata":{"version":"v1"}},"feature_flags":\["discovery"\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, 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/v0.4/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":false,"health_metrics_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":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":true,"metadata":{"version":"v1"}},"feature_flags":\["discovery"\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("limit", func(t *testing.T) { @@ -94,7 +94,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) 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/v0.4/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,"health_metrics_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":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, 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/v0.4/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":false,"health_metrics_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":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("errors", func(t *testing.T) { @@ -108,7 +108,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) 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/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"some\.service","sample_rate":0\.234}\],"span_sampling_rules":null,"sampling_rules_error":"\\n\\tat index 1: ignoring rule {Service:other.service Rate:2}: rate is out of \[0\.0, 1\.0] range","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"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)|(false)),"Stats":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, 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/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"some\.service","sample_rate":0\.234}\],"span_sampling_rules":null,"sampling_rules_error":"\\n\\tat index 1: ignoring rule {Service:other.service Rate:2}: rate is out of \[0\.0, 1\.0] range","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":false,"health_metrics_enabled":false,"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)|(false)),"Stats":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("lambda", func(t *testing.T) { @@ -121,7 +121,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) 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/v0.4/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,"health_metrics_enabled":false,"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":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, 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/v0.4/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":false,"health_metrics_enabled":false,"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":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[0]) }) t.Run("integrations", func(t *testing.T) { @@ -180,8 +180,9 @@ func TestLogAgentReachable(t *testing.T) { func TestLogFormat(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) - tracer := newTracer(WithLogger(tp), WithRuntimeMetrics(), WithDebugMode(true)) - defer tracer.Stop() + + tracer, _, _, stop := startTestTracer(t, WithLogger(tp), WithRuntimeMetrics(), WithDebugMode(true)) + defer stop() tp.Reset() tp.Ignore("appsec: ", telemetry.LogPrefix) tracer.StartSpan("test", ServiceName("test-service"), ResourceName("/"), WithSpanID(12345)) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index aa1b0459ea..2e9b08c7cf 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -190,6 +190,9 @@ type config struct { // runtimeMetrics specifies whether collection of runtime metrics is enabled. runtimeMetrics bool + // runtimeMetricsV2 specifies whether collection of runtime metrics v2 is enabled. + runtimeMetricsV2 bool + // dogstatsdAddr specifies the address to connect for sending metrics to the // Datadog Agent. If not set, it defaults to "localhost:8125" or to the // combination of the environment variables DD_AGENT_HOST and DD_DOGSTATSD_PORT. @@ -385,6 +388,7 @@ func newConfig(opts ...StartOption) *config { } c.logStartup = internal.BoolEnv("DD_TRACE_STARTUP_LOGS", true) c.runtimeMetrics = internal.BoolVal(getDDorOtelConfig("metrics"), false) + c.runtimeMetricsV2 = internal.BoolEnv("DD_RUNTIME_METRICS_V2_ENABLED", false) c.debug = internal.BoolVal(getDDorOtelConfig("debugMode"), false) c.logDirectory = os.Getenv("DD_TRACE_LOG_DIRECTORY") c.enabled = newDynamicConfig("tracing_enabled", internal.BoolVal(getDDorOtelConfig("enabled"), true), func(b bool) bool { return true }, equal[bool]) diff --git a/ddtrace/tracer/slog.go b/ddtrace/tracer/slog.go new file mode 100644 index 0000000000..06a477d02d --- /dev/null +++ b/ddtrace/tracer/slog.go @@ -0,0 +1,69 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "context" + "log/slog" + "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +// slogHandler implements the slog.Handler interface to dispatch messages to our +// internal logger. +type slogHandler struct { + attrs []string + groups []string +} + +func (h slogHandler) Enabled(ctx context.Context, lvl slog.Level) bool { + if lvl <= slog.LevelDebug { + return log.DebugEnabled() + } + // TODO(fg): Implement generic log level checking in the internal logger. + // But we're we're not concerned with slog perf, so this is okay for now. + return true +} + +func (h slogHandler) Handle(ctx context.Context, r slog.Record) error { + parts := make([]string, 0, 1+len(h.attrs)+r.NumAttrs()) + parts = append(parts, r.Message) + parts = append(parts, h.attrs...) + r.Attrs(func(a slog.Attr) bool { + parts = append(parts, formatAttr(a, h.groups)) + return true + }) + + msg := strings.Join(parts, " ") + switch r.Level { + case slog.LevelDebug: + log.Debug(msg) + case slog.LevelInfo: + log.Info(msg) + case slog.LevelWarn: + log.Warn(msg) + case slog.LevelError: + log.Error(msg) + } + return nil +} + +func (h slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + for _, a := range attrs { + h.attrs = append(h.attrs, formatAttr(a, h.groups)) + } + return h +} + +func (h slogHandler) WithGroup(name string) slog.Handler { + h.groups = append(h.groups, name) + return h +} + +func formatAttr(a slog.Attr, groups []string) string { + return strings.Join(append(groups, a.String()), ".") +} diff --git a/ddtrace/tracer/slog_test.go b/ddtrace/tracer/slog_test.go new file mode 100644 index 0000000000..352cac0183 --- /dev/null +++ b/ddtrace/tracer/slog_test.go @@ -0,0 +1,45 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "log/slog" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +func Test_slogHandler(t *testing.T) { + // Create a record logger to capture the logs and restore the original + // logger at the end. + rl := &log.RecordLogger{} + defer log.UseLogger(rl)() + + // Ensure the logger is set to the default level. This may not be the case + // when previous tests pollute the global state. We leave the logger in the + // state we found it to not contribute to this pollution ourselves. + oldLevel := log.GetLevel() + log.SetLevel(log.LevelWarn) + defer log.SetLevel(oldLevel) + + // Log a few messages at different levels. The debug message gets discarded + // because the internal logger does not have debug enabled by default. + l := slog.New(slogHandler{}) + l = l.With("foo", "bar") + l = l.WithGroup("a").WithGroup("b") + l.Debug("debug test", "n", 0) + l.Info("info test", "n", 1) + l.Warn("warn test", "n", 2) + l.Error("error test", "n", 3) + log.Flush() // needed to get the error log flushed + + // Check that the logs were written correctly. + require.Len(t, rl.Logs(), 3) + require.Contains(t, rl.Logs()[0], "info test foo=bar a.b.n=1") + require.Contains(t, rl.Logs()[1], "warn test foo=bar a.b.n=2") + require.Contains(t, rl.Logs()[2], "error test foo=bar a.b.n=3") +} diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index fe4615916c..df0d598aee 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -54,6 +54,7 @@ func startTelemetry(c *config) { {Name: "trace_agent_url", Value: c.agentURL.String()}, {Name: "agent_hostname", Value: c.hostname}, {Name: "runtime_metrics_enabled", Value: c.runtimeMetrics}, + {Name: "runtime_metrics_v2_enabled", Value: c.runtimeMetricsV2}, {Name: "dogstatsd_addr", Value: c.dogstatsdAddr}, {Name: "debug_stack_enabled", Value: !c.noDebugStack}, {Name: "profiling_hotspots_enabled", Value: c.profilerHotspots}, diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 378c0ff418..89277045de 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -8,6 +8,7 @@ package tracer import ( gocontext "context" "encoding/binary" + "log/slog" "math" "os" "runtime/pprof" @@ -32,6 +33,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" "github.com/DataDog/datadog-agent/pkg/obfuscate" + "github.com/DataDog/go-runtime-metrics-internal/pkg/runtimemetrics" ) var _ ddtrace.Tracer = (*tracer)(nil) @@ -332,6 +334,14 @@ func newTracer(opts ...StartOption) *tracer { t.reportRuntimeMetrics(defaultMetricsReportInterval) }() } + if c.runtimeMetricsV2 { + l := slog.New(slogHandler{}) + if err := runtimemetrics.Start(t.statsd, l); err == nil { + l.Debug("Runtime metrics v2 enabled.") + } else { + l.Error("Failed to enable runtime metrics v2", "err", err.Error()) + } + } if c.debugAbandonedSpans { log.Info("Abandoned spans logs enabled.") t.abandonedSpansDebugger = newAbandonedSpansDebugger() diff --git a/go.mod b/go.mod index 41a876a7b7..b7d344bbb9 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/DataDog/datadog-agent/pkg/trace v0.58.0 github.com/DataDog/datadog-go/v5 v5.5.0 github.com/DataDog/go-libddwaf/v3 v3.5.1 + github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 github.com/DataDog/gostackparse v0.7.0 github.com/DataDog/sketches-go v1.4.5 github.com/IBM/sarama v1.40.0 diff --git a/go.sum b/go.sum index 19cabce2db..5545ae9297 100644 --- a/go.sum +++ b/go.sum @@ -649,6 +649,8 @@ github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= github.com/DataDog/go-libddwaf/v3 v3.5.1 h1:GWA4ln4DlLxiXm+X7HA/oj0ZLcdCwOS81KQitegRTyY= github.com/DataDog/go-libddwaf/v3 v3.5.1/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= +github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 h1:s4hgS6gqbXIakEMMujYiHCVVsB3R3oZtqEzPBMnFU2w= +github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59/go.mod h1:quaQJ+wPN41xEC458FCpTwyROZm3MzmTZ8q8XOXQiPs= github.com/DataDog/go-sqllexer v0.0.14 h1:xUQh2tLr/95LGxDzLmttLgTo/1gzFeOyuwrQa/Iig4Q= github.com/DataDog/go-sqllexer v0.0.14/go.mod h1:KwkYhpFEVIq+BfobkTC1vfqm4gTi65skV/DpDBXtexc= github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= diff --git a/internal/apps/apps.go b/internal/apps/apps.go index a0eb8157ff..580719ea0a 100644 --- a/internal/apps/apps.go +++ b/internal/apps/apps.go @@ -42,6 +42,11 @@ func (c *Config) RunHTTP(handler func() http.Handler) { os.Setenv("DD_PROFILING_EXECUTION_TRACE_PERIOD", "1s") } + // Enabled runtime metrics v2 by default + if v := os.Getenv("DD_RUNTIME_METRICS_V2_ENABLED"); v == "" { + os.Setenv("DD_RUNTIME_METRICS_V2_ENABLED", "true") + } + // Setup context that gets canceled on receiving SIGINT ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() diff --git a/internal/apps/go.mod b/internal/apps/go.mod index 8b2398cb8b..7be8bd55a2 100644 --- a/internal/apps/go.mod +++ b/internal/apps/go.mod @@ -14,6 +14,7 @@ require ( github.com/DataDog/datadog-agent/pkg/util/log v0.58.0 // indirect github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 // indirect github.com/DataDog/go-libddwaf/v3 v3.5.1 // indirect + github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 // indirect github.com/DataDog/go-sqllexer v0.0.14 // indirect github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect diff --git a/internal/apps/go.sum b/internal/apps/go.sum index 133c81441d..41a28f03b3 100644 --- a/internal/apps/go.sum +++ b/internal/apps/go.sum @@ -16,6 +16,8 @@ github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= github.com/DataDog/go-libddwaf/v3 v3.5.1 h1:GWA4ln4DlLxiXm+X7HA/oj0ZLcdCwOS81KQitegRTyY= github.com/DataDog/go-libddwaf/v3 v3.5.1/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= +github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 h1:s4hgS6gqbXIakEMMujYiHCVVsB3R3oZtqEzPBMnFU2w= +github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59/go.mod h1:quaQJ+wPN41xEC458FCpTwyROZm3MzmTZ8q8XOXQiPs= github.com/DataDog/go-sqllexer v0.0.14 h1:xUQh2tLr/95LGxDzLmttLgTo/1gzFeOyuwrQa/Iig4Q= github.com/DataDog/go-sqllexer v0.0.14/go.mod h1:KwkYhpFEVIq+BfobkTC1vfqm4gTi65skV/DpDBXtexc= github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= diff --git a/internal/exectracetest/go.mod b/internal/exectracetest/go.mod index 18bc552f29..09917ff4f6 100644 --- a/internal/exectracetest/go.mod +++ b/internal/exectracetest/go.mod @@ -19,6 +19,7 @@ require ( github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 // indirect github.com/DataDog/datadog-go/v5 v5.5.0 // indirect github.com/DataDog/go-libddwaf/v3 v3.5.1 // indirect + github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 // indirect github.com/DataDog/go-sqllexer v0.0.14 // indirect github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect diff --git a/internal/exectracetest/go.sum b/internal/exectracetest/go.sum index 5e9a403a39..2edfe16aa1 100644 --- a/internal/exectracetest/go.sum +++ b/internal/exectracetest/go.sum @@ -16,6 +16,8 @@ github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= github.com/DataDog/go-libddwaf/v3 v3.5.1 h1:GWA4ln4DlLxiXm+X7HA/oj0ZLcdCwOS81KQitegRTyY= github.com/DataDog/go-libddwaf/v3 v3.5.1/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4= +github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59 h1:s4hgS6gqbXIakEMMujYiHCVVsB3R3oZtqEzPBMnFU2w= +github.com/DataDog/go-runtime-metrics-internal v0.0.0-20241106155157-194426bbbd59/go.mod h1:quaQJ+wPN41xEC458FCpTwyROZm3MzmTZ8q8XOXQiPs= github.com/DataDog/go-sqllexer v0.0.14 h1:xUQh2tLr/95LGxDzLmttLgTo/1gzFeOyuwrQa/Iig4Q= github.com/DataDog/go-sqllexer v0.0.14/go.mod h1:KwkYhpFEVIq+BfobkTC1vfqm4gTi65skV/DpDBXtexc= github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4= diff --git a/internal/log/log.go b/internal/log/log.go index e26da38e8f..dc4549323c 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -118,6 +118,13 @@ func SetLevel(lvl Level) { level = lvl } +// GetLevel returns the currrent log level. +func GetLevel() Level { + mu.Lock() + defer mu.Unlock() + return level +} + // DebugEnabled returns true if debug log messages are enabled. This can be used in extremely // hot code paths to avoid allocating the ...interface{} argument. func DebugEnabled() bool { diff --git a/internal/statsd.go b/internal/statsd.go index a901759f27..df1d18a307 100644 --- a/internal/statsd.go +++ b/internal/statsd.go @@ -16,7 +16,9 @@ const DefaultDogstatsdAddr = "localhost:8125" type StatsdClient interface { Incr(name string, tags []string, rate float64) error Count(name string, value int64, tags []string, rate float64) error + CountWithTimestamp(name string, value int64, tags []string, rate float64, timestamp time.Time) error Gauge(name string, value float64, tags []string, rate float64) error + GaugeWithTimestamp(name string, value float64, tags []string, rate float64, timestamp time.Time) error Timing(name string, value time.Duration, tags []string, rate float64) error Flush() error Close() error diff --git a/internal/statsdtest/statsdtest.go b/internal/statsdtest/statsdtest.go index b48e3b753a..8845e6465c 100644 --- a/internal/statsdtest/statsdtest.go +++ b/internal/statsdtest/statsdtest.go @@ -17,8 +17,10 @@ type callType int64 const ( callTypeGauge callType = iota + callTypeGaugeWithTimestamp callTypeIncr callTypeCount + callTypeCountWithTimestamp callTypeTiming ) @@ -62,6 +64,16 @@ func (tg *TestStatsdClient) Gauge(name string, value float64, tags []string, rat }) } +func (tg *TestStatsdClient) GaugeWithTimestamp(name string, value float64, tags []string, rate float64, _ time.Time) error { + // TODO: handle timestamp argument + return tg.addMetric(callTypeGaugeWithTimestamp, tags, TestStatsdCall{ + name: name, + floatVal: value, + tags: make([]string, len(tags)), + rate: rate, + }) +} + func (tg *TestStatsdClient) Incr(name string, tags []string, rate float64) error { tg.addCount(name, 1) return tg.addMetric(callTypeIncr, tags, TestStatsdCall{ @@ -81,6 +93,17 @@ func (tg *TestStatsdClient) Count(name string, value int64, tags []string, rate }) } +func (tg *TestStatsdClient) CountWithTimestamp(name string, value int64, tags []string, rate float64, _ time.Time) error { + // TODO: handle timestamp argument + tg.addCount(name, value) + return tg.addMetric(callTypeCountWithTimestamp, tags, TestStatsdCall{ + name: name, + intVal: value, + tags: make([]string, len(tags)), + rate: rate, + }) +} + func (tg *TestStatsdClient) Timing(name string, value time.Duration, tags []string, rate float64) error { return tg.addMetric(callTypeTiming, tags, TestStatsdCall{ name: name,