From b05560cf66b67183780315df87209be27142ae0b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 22 Oct 2025 16:47:55 -0400 Subject: [PATCH 01/26] Introduce internal config package: Config object, ConfigProvider object, ConfigSource object, and tests --- ddtrace/tracer/option.go | 2 +- internal/config/config.go | 116 +++++ internal/config/config_test.go | 109 +++++ internal/config/configprovider.go | 90 ++++ internal/config/configprovider_test.go | 61 +++ internal/config/declarativeconfig.go | 35 ++ internal/config/declarativeconfigsource.go | 99 ++++ .../knownmetrics/known_metric.golang.go | 6 +- .../knownmetrics/known_metrics.common.go | 432 +++++++++--------- 9 files changed, 730 insertions(+), 220 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go create mode 100644 internal/config/configprovider.go create mode 100644 internal/config/configprovider_test.go create mode 100644 internal/config/declarativeconfig.go create mode 100644 internal/config/declarativeconfigsource.go diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 2413a6a624..449f28d475 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -138,7 +138,7 @@ const ( // config holds the tracer configuration. type config struct { - // debug, when true, writes details to logs. + // debug, when true,instrumentationSource writes details to logs. debug bool // appsecStartOptions controls the options used when starting appsec features. diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000000..8e5d289be2 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,116 @@ +// 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 2025 Datadog, Inc. + +package config + +import ( + "net/http" + "net/url" + "time" +) + +var globalConfig *Config + +// Config represents global configuration properties. +type Config struct { + // AgentURL is the URL of the Datadog agent. + AgentURL *url.URL `json:"DD_AGENT_URL"` + + // Debug enables debug logging. + Debug bool `json:"DD_TRACE_DEBUG"` // has trace in the name, but impacts all products? + + // HTTPClient is the HTTP client to use for sending requests to the Datadog agent. + HTTPClient *http.Client + + FeatureFlags map[string]struct{} `json:"DD_TRACE_FEATURE_FLAGS"` + + LogToStdout bool `json:"DD_TRACE_LOG_TO_STDOUT"` + + SendRetries int `json:"DD_TRACE_SEND_RETRIES"` + + RetryInterval int64 `json:"DD_TRACE_RETRY_INTERVAL"` + + LogStartup bool `json:"DD_TRACE_LOG_STARTUP"` + + ServiceName string `json:"DD_TRACE_SERVICE_NAME"` + + UniversalVersion bool `json:"DD_TRACE_UNIVERSAL_VERSION"` + + Version string `json:"DD_TRACE_VERSION"` + + Env string `json:"DD_TRACE_ENV"` + + ServiceMappings map[string]string `json:"DD_TRACE_SERVICE_MAPPING"` + + HTTPClientTimeout int64 `json:"DD_TRACE_HTTP_CLIENT_TIMEOUT"` + + Hostname string `json:"DD_TRACE_HOSTNAME"` + + RuntimeMetrics bool `json:"DD_TRACE_RUNTIME_METRICS"` + + RuntimeMetricsV2 bool `json:"DD_TRACE_RUNTIME_METRICS_V2"` + + DogstatsdAddr string `json:"DD_TRACE_DOGSTATSD_ADDR"` + + TickChan <-chan time.Time `json:"DD_TRACE_TICK_CHAN"` + + NoDebugStack bool `json:"DD_TRACE_NO_DEBUG_STACK"` + + ProfilerHotspots bool `json:"DD_TRACE_PROFILER_HOTSPOTS"` + + ProfilerEndpoints bool `json:"DD_TRACE_PROFILER_ENDPOINTS"` + + EnableHostnameDetection bool `json:"DD_TRACE_ENABLE_HOSTNAME_DETECTION"` + + SpanAttributeSchemaVersion int `json:"DD_TRACE_SPAN_ATTRIBUTE_SCHEMA_VERSION"` + + PeerServiceDefaultsEnabled bool `json:"DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED"` + + PeerServiceMappings map[string]string `json:"DD_TRACE_PEER_SERVICE_MAPPING"` + + DebugAbandonedSpans bool `json:"DD_TRACE_DEBUG_ABANDONED_SPANS"` + + SpanTimeout time.Duration `json:"DD_TRACE_SPAN_TIMEOUT"` + + PartialFlushMinSpans int `json:"DD_TRACE_PARTIAL_FLUSH_MIN_SPANS"` + + PartialFlushEnabled bool `json:"DD_TRACE_PARTIAL_FLUSH_ENABLED"` + + StatsComputationEnabled bool `json:"DD_TRACE_STATS_COMPUTATION_ENABLED"` + + DataStreamsMonitoringEnabled bool `json:"DD_TRACE_DATA_STREAMS_MONITORING_ENABLED"` + + DynamicInstrumentationEnabled bool `json:"DD_TRACE_DYNAMIC_INSTRUMENTATION_ENABLED"` + + GlobalSampleRate float64 `json:"DD_TRACE_GLOBAL_SAMPLE_RATE"` + + CIVisibilityEnabled bool `json:"DD_TRACE_CI_VISIBILITY_ENABLED"` + + CIVisibilityAgentless bool `json:"DD_TRACE_CI_VISIBILITY_AGENTLESS"` + + LogDirectory string `json:"DD_TRACE_LOG_DIRECTORY"` + + TracingAsTransport bool `json:"DD_TRACE_TRACING_AS_TRANSPORT"` + + TraceRateLimitPerSecond float64 `json:"DD_TRACE_TRACE_RATE_LIMIT_PER_SECOND"` + + TraceProtocol float64 `json:"DD_TRACE_TRACE_PROTOCOL"` +} + +func loadConfig() *Config { + cfg := new(Config) + + cfg.AgentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) + cfg.Debug = provider.getBool("DD_TRACE_DEBUG", false) + + return cfg +} + +func Get() *Config { + if globalConfig == nil { + globalConfig = loadConfig() + } + return globalConfig +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000000..e436dcabaf --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,109 @@ +// 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 2025 Datadog, Inc. + +package config + +import ( + "net/http" + "net/url" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestConfigHasFields(t *testing.T) { + // Define expected fields with their types + expectedFields := map[string]reflect.Type{ + "AgentURL": reflect.TypeOf((*url.URL)(nil)), + "Debug": reflect.TypeOf(false), + "HTTPClient": reflect.TypeOf((*http.Client)(nil)), + //appsecStartOptions? + // "Agent": reflect.TypeOf((*agentFeatures)(nil)), + // "Integrations": reflect.TypeOf((*map[string]integrationConfig)(nil)), + "FeatureFlags": reflect.TypeOf((map[string]struct{})(nil)), + "LogToStdout": reflect.TypeOf(false), + "SendRetries": reflect.TypeOf(0), + "RetryInterval": reflect.TypeOf(int64(0)), + "LogStartup": reflect.TypeOf(false), + "ServiceName": reflect.TypeOf(""), + "UniversalVersion": reflect.TypeOf(false), + "Version": reflect.TypeOf(""), + "Env": reflect.TypeOf(""), + //"Sampler": reflect.TypeOf((*RateSampler)(nil)), + // "OriginalAgentURL": reflect.TypeOf((*url.URL)(nil)), // We probably don't need this anymore + "ServiceMappings": reflect.TypeOf((map[string]string)(nil)), + // "GlobalTags": reflect.TypeOf((*dynamicConfig[map[string]interface{}])(nil)), + // "Transport": reflect.TypeOf((*transport)(nil)), + "HTTPClientTimeout": reflect.TypeOf(int64(0)), + // "Propagator": reflect.TypeOf((*Propagator)(nil)), + "Hostname": reflect.TypeOf(""), + // "Logger": reflect.TypeOf((*Logger)(nil)), + "RuntimeMetrics": reflect.TypeOf(false), + "RuntimeMetricsV2": reflect.TypeOf(false), + "DogstatsdAddr": reflect.TypeOf(""), + // "StatsdClient": reflect.TypeOf((*internal.StatsdClient)(nil)), + // "SpanRules": reflect.TypeOf((*[]SamplingRule)(nil)), + // "TraceRules": reflect.TypeOf((*[]SamplingRule)(nil)), + "TickChan": reflect.TypeOf((<-chan time.Time)(nil)), + "NoDebugStack": reflect.TypeOf(false), + "ProfilerHotspots": reflect.TypeOf(false), + "ProfilerEndpoints": reflect.TypeOf(false), + // "TracingEnabled": reflect.TypeOf((*dynamicConfig[bool])(nil)), + "EnableHostnameDetection": reflect.TypeOf(false), + "SpanAttributeSchemaVersion": reflect.TypeOf(0), + "PeerServiceDefaultsEnabled": reflect.TypeOf(false), + "PeerServiceMappings": reflect.TypeOf((map[string]string)(nil)), + "DebugAbandonedSpans": reflect.TypeOf(false), + "SpanTimeout": reflect.TypeOf(time.Duration(int64(0))), + "PartialFlushMinSpans": reflect.TypeOf(0), + "PartialFlushEnabled": reflect.TypeOf(false), + "StatsComputationEnabled": reflect.TypeOf(false), + "DataStreamsMonitoringEnabled": reflect.TypeOf(false), + // "OrchestrionCfg": reflect.TypeOf((*orchestrionConfig)(nil)), + // "TraceSampleRate": reflect.TypeOf((*dynamicConfig[float64])(nil)), + // "TraceSampleRules": reflect.TypeOf((*dynamicConfig[[]SamplingRule])(nil)), + // "HeaderAsTags": reflect.TypeOf((*dynamicConfig[[]string])(nil)), + "DynamicInstrumentationEnabled": reflect.TypeOf(false), + "GlobalSampleRate": reflect.TypeOf(float64(0)), + "CIVisibilityEnabled": reflect.TypeOf(false), + "CIVisibilityAgentless": reflect.TypeOf(false), + "LogDirectory": reflect.TypeOf(""), + "TracingAsTransport": reflect.TypeOf(false), + "TraceRateLimitPerSecond": reflect.TypeOf(float64(0)), + "TraceProtocol": reflect.TypeOf(float64(0)), + // "LLMObsEnabled": reflect.TypeOf(false), + // "LLMObsMLApp": reflect.TypeOf(""), + // "LLMObsAgentlessEnabled": reflect.TypeOf(false), + // "LLMObsProjectName": reflect.TypeOf(""), + } + + // Get the Config struct type + configType := reflect.TypeOf(Config{}) + + // Verify the number of expected fields matches the actual number of fields + actualFieldCount := configType.NumField() + expectedFieldCount := len(expectedFields) + assert.Equal(t, expectedFieldCount, actualFieldCount, + "Expected %d fields in Config struct, but found %d. Update the test when adding/removing fields.", + expectedFieldCount, actualFieldCount) + + // Verify each expected field exists with the correct type + for fieldName, expectedType := range expectedFields { + field, found := configType.FieldByName(fieldName) + assert.True(t, found, "Field %s should exist on Config struct", fieldName) + + if found { + assert.Equal(t, expectedType, field.Type, + "Field %s should have type %s, but has type %s", + fieldName, expectedType, field.Type) + } + } + + // Verify we can instantiate the config + cfg := new(Config) + assert.NotNil(t, cfg, "Should be able to create new Config instance") +} diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go new file mode 100644 index 0000000000..212fda2a9b --- /dev/null +++ b/internal/config/configprovider.go @@ -0,0 +1,90 @@ +// 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 2025 Datadog, Inc. + +package config + +import ( + "net/url" + "strconv" +) + +var provider = DefaultConfigProvider() + +type ConfigProvider struct { + sources []ConfigSource // In order of priority +} + +func (p *ConfigProvider) getString(key string, def string) string { + // TODO: Eventually, iterate over all sources and report telemetry + for _, source := range p.sources { + if v := source.Get(key); v != "" { + return v + } + } + return def +} + +func (p *ConfigProvider) getBool(key string, def bool) bool { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + if v == "true" { + return true + } else if v == "false" { + return false + } + } + } + return def +} + +func (p *ConfigProvider) getInt(key string, def int) int { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + v, err := strconv.Atoi(v) + if err == nil { + return v + } + } + } + return def +} + +func (p *ConfigProvider) getFloat(key string, def float64) float64 { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + v, err := strconv.ParseFloat(v, 64) + if err == nil { + return v + } + } + } + return def +} + +func (p *ConfigProvider) getURL(key string, def *url.URL) *url.URL { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + u, err := url.Parse(v) + if err == nil { + return u + } + } + } + return def +} + +type ConfigSource interface { + Get(key string) string +} + +func DefaultConfigProvider() *ConfigProvider { + return &ConfigProvider{ + sources: []ConfigSource{ + LocalDeclarativeConfig, + // EnvSource, + ManagedDeclarativeConfig, + }, + } +} diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go new file mode 100644 index 0000000000..538d665d04 --- /dev/null +++ b/internal/config/configprovider_test.go @@ -0,0 +1,61 @@ +// 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 2025 Datadog, Inc. + +package config + +import ( + "testing" + + "net/url" + + "github.com/stretchr/testify/assert" +) + +func newTestConfigProvider(sources ...ConfigSource) *ConfigProvider { + return &ConfigProvider{ + sources: sources, + } +} + +type testConfigSource struct { + entries map[string]string +} + +func newTestConfigSource() *testConfigSource { + t := &testConfigSource{ + entries: make(map[string]string), + } + + t.entries["STRING_KEY"] = "string" + t.entries["BOOL_KEY"] = "true" + t.entries["INT_KEY"] = "1" + t.entries["FLOAT_KEY"] = "1.0" + t.entries["URL_KEY"] = "https://localhost:8126" + + return t +} + +func (s *testConfigSource) Get(key string) string { + return s.entries[key] +} + +func TestGetMethods(t *testing.T) { + t.Run("defaults", func(t *testing.T) { + provider := newTestConfigProvider(newTestConfigSource()) + assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) + assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) + assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) + assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "otherhost:1234"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "otherhost:1234"})) + }) + t.Run("non-defaults", func(t *testing.T) { + provider := newTestConfigProvider(newTestConfigSource()) + assert.Equal(t, "string", provider.getString("STRING_KEY", "value")) + assert.Equal(t, true, provider.getBool("BOOL_KEY", false)) + assert.Equal(t, 1, provider.getInt("INT_KEY", 0)) + assert.Equal(t, 1.0, provider.getFloat("FLOAT_KEY", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("URL_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + }) +} diff --git a/internal/config/declarativeconfig.go b/internal/config/declarativeconfig.go new file mode 100644 index 0000000000..67efd4e9b2 --- /dev/null +++ b/internal/config/declarativeconfig.go @@ -0,0 +1,35 @@ +// 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 2025 Datadog, Inc. + +package config + +import "github.com/DataDog/dd-trace-go/v2/internal/telemetry" + +// declarativeConfig represents a configuration loaded from a YAML source file. +type declarativeConfig struct { + Config map[string]string `yaml:"apm_configuration_default,omitempty"` // Configuration key-value pairs. + ID string `yaml:"config_id,omitempty"` // Identifier for the config set. +} + +func (d *declarativeConfig) get(key string) string { + return d.Config[key] +} + +func (d *declarativeConfig) getID() string { + return d.ID +} + +// To be used by tests +// func (d *declarativeConfig) isEmpty() bool { +// return d.ID == telemetry.EmptyID && len(d.Config) == 0 +// } + +// emptyDeclarativeConfig creates and returns a new, empty declarativeConfig instance. +func emptyDeclarativeConfig() *declarativeConfig { + return &declarativeConfig{ + Config: make(map[string]string), + ID: telemetry.EmptyID, + } +} diff --git a/internal/config/declarativeconfigsource.go b/internal/config/declarativeconfigsource.go new file mode 100644 index 0000000000..f6fce97c33 --- /dev/null +++ b/internal/config/declarativeconfigsource.go @@ -0,0 +1,99 @@ +// 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 2025 Datadog, Inc. + +package config + +import ( + "os" + + "go.yaml.in/yaml/v3" + + "github.com/DataDog/dd-trace-go/v2/internal/log" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" +) + +const ( + // File paths are supported on linux only + localFilePath = "/etc/datadog-agent/application_monitoring.yaml" + managedFilePath = "/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml" + + // maxFileSize defines the maximum size in bytes for declarative config files (4KB). This limit ensures predictable memory use and guards against malformed large files. + maxFileSize = 4 * 1024 +) + +// LocalDeclarativeConfig holds the configuration loaded from the user-managed file. +var LocalDeclarativeConfig = newDeclarativeConfigSource(localFilePath, telemetry.OriginLocalStableConfig) + +// ManagedDeclarativeConfig holds the configuration loaded from the fleet-managed file. +var ManagedDeclarativeConfig = newDeclarativeConfigSource(managedFilePath, telemetry.OriginManagedStableConfig) + +// declarativeConfigSource represents a source of declarative configuration loaded from a file. +type declarativeConfigSource struct { + filePath string // Path to the configuration file. + origin telemetry.Origin // Origin identifier for telemetry. + config *declarativeConfig // Parsed declarative configuration. +} + +func (d *declarativeConfigSource) Get(key string) string { + return d.config.get(key) +} + +func (d *declarativeConfigSource) GetID() string { + return d.config.getID() +} + +// newDeclarativeConfigSource initializes a new declarativeConfigSource from the given file. +func newDeclarativeConfigSource(filePath string, origin telemetry.Origin) *declarativeConfigSource { + return &declarativeConfigSource{ + filePath: filePath, + origin: origin, + config: parseFile(filePath), + } +} + +// ParseFile reads and parses the config file at the given path. +// Returns an empty config if the file doesn't exist or is invalid. +func parseFile(filePath string) *declarativeConfig { + info, err := os.Stat(filePath) + if err != nil { + // It's expected that the declarative config file may not exist; its absence is not an error. + if !os.IsNotExist(err) { + log.Warn("Failed to stat declarative config file %q, dropping: %v", filePath, err.Error()) + } + return emptyDeclarativeConfig() + } + + if info.Size() > maxFileSize { + log.Warn("Declarative config file %s exceeds size limit (%d bytes > %d bytes), dropping", + filePath, info.Size(), maxFileSize) + return emptyDeclarativeConfig() + } + + data, err := os.ReadFile(filePath) + if err != nil { + // It's expected that the declarative config file may not exist; its absence is not an error. + if !os.IsNotExist(err) { + log.Warn("Failed to read declarative config file %q, dropping: %v", filePath, err.Error()) + } + return emptyDeclarativeConfig() + } + + return fileContentsToConfig(data, filePath) +} + +// fileContentsToConfig parses YAML data into a declarativeConfig struct. +// Returns an empty config if parsing fails or the data is malformed. +func fileContentsToConfig(data []byte, fileName string) *declarativeConfig { + dc := &declarativeConfig{} + err := yaml.Unmarshal(data, dc) + if err != nil { + log.Warn("Parsing declarative config file %s failed due to error, dropping: %v", fileName, err.Error()) + return emptyDeclarativeConfig() + } + if dc.Config == nil { + dc.Config = make(map[string]string) + } + return dc +} diff --git a/internal/telemetry/internal/knownmetrics/known_metric.golang.go b/internal/telemetry/internal/knownmetrics/known_metric.golang.go index da51ebcf19..c11b11ea89 100644 --- a/internal/telemetry/internal/knownmetrics/known_metric.golang.go +++ b/internal/telemetry/internal/knownmetrics/known_metric.golang.go @@ -8,7 +8,7 @@ package knownmetrics var golangMetrics = []Declaration{ - { Type: "count", Name: "errorstack.source" }, - { Type: "distribution", Name: "errorstack.duration" }, - { Type: "gauge", Name: "orchestrion.enabled" }, + {Type: "count", Name: "errorstack.source"}, + {Type: "distribution", Name: "errorstack.duration"}, + {Type: "gauge", Name: "orchestrion.enabled"}, } diff --git a/internal/telemetry/internal/knownmetrics/known_metrics.common.go b/internal/telemetry/internal/knownmetrics/known_metrics.common.go index 1c2925f127..097779b069 100644 --- a/internal/telemetry/internal/knownmetrics/known_metrics.common.go +++ b/internal/telemetry/internal/knownmetrics/known_metrics.common.go @@ -8,220 +8,220 @@ package knownmetrics var commonMetrics = []Declaration{ - { Namespace: "appsec", Type: "count", Name: "api_security.missing_route" }, - { Namespace: "appsec", Type: "count", Name: "api_security.request.no_schema" }, - { Namespace: "appsec", Type: "count", Name: "api_security.request.schema" }, - { Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_id" }, - { Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_login" }, - { Namespace: "appsec", Type: "count", Name: "rasp.error" }, - { Namespace: "appsec", Type: "count", Name: "rasp.rule.eval" }, - { Namespace: "appsec", Type: "count", Name: "rasp.rule.match" }, - { Namespace: "appsec", Type: "count", Name: "rasp.rule.skipped" }, - { Namespace: "appsec", Type: "count", Name: "rasp.timeout" }, - { Namespace: "appsec", Type: "count", Name: "sdk.event" }, - { Namespace: "appsec", Type: "count", Name: "waf.config_errors" }, - { Namespace: "appsec", Type: "count", Name: "waf.error" }, - { Namespace: "appsec", Type: "count", Name: "waf.init" }, - { Namespace: "appsec", Type: "count", Name: "waf.input_truncated" }, - { Namespace: "appsec", Type: "count", Name: "waf.requests" }, - { Namespace: "appsec", Type: "count", Name: "waf.updates" }, - { Namespace: "appsec", Type: "distribution", Name: "rasp.duration" }, - { Namespace: "appsec", Type: "distribution", Name: "rasp.duration_ext" }, - { Namespace: "appsec", Type: "distribution", Name: "waf.duration" }, - { Namespace: "appsec", Type: "distribution", Name: "waf.duration_ext" }, - { Namespace: "appsec", Type: "distribution", Name: "waf.truncated_value_size" }, - { Namespace: "appsec", Type: "gauge", Name: "enabled" }, - { Namespace: "civisibility", Type: "count", Name: "code_coverage.errors" }, - { Namespace: "civisibility", Type: "count", Name: "code_coverage.is_empty" }, - { Namespace: "civisibility", Type: "count", Name: "code_coverage_finished" }, - { Namespace: "civisibility", Type: "count", Name: "code_coverage_started" }, - { Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request" }, - { Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request_errors" }, - { Namespace: "civisibility", Type: "count", Name: "endpoint_payload.dropped" }, - { Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests" }, - { Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests_errors" }, - { Namespace: "civisibility", Type: "count", Name: "event_created" }, - { Namespace: "civisibility", Type: "count", Name: "event_finished" }, - { Namespace: "civisibility", Type: "count", Name: "events_enqueued_for_serialization" }, - { Namespace: "civisibility", Type: "count", Name: "flaky_tests.request" }, - { Namespace: "civisibility", Type: "count", Name: "flaky_tests.request_errors" }, - { Namespace: "civisibility", Type: "count", Name: "git.command" }, - { Namespace: "civisibility", Type: "count", Name: "git.command_errors" }, - { Namespace: "civisibility", Type: "count", Name: "git.commit_sha_discrepancy" }, - { Namespace: "civisibility", Type: "count", Name: "git.commit_sha_match" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack_errors" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits_errors" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.settings" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.settings_errors" }, - { Namespace: "civisibility", Type: "count", Name: "git_requests.settings_response" }, - { Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request" }, - { Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request_errors" }, - { Namespace: "civisibility", Type: "count", Name: "itr_forced_run" }, - { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request" }, - { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request_errors" }, - { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_suites" }, - { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_tests" }, - { Namespace: "civisibility", Type: "count", Name: "itr_skipped" }, - { Namespace: "civisibility", Type: "count", Name: "itr_unskippable" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.dropped" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests_errors" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.logs.dropped" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.logs.submitted" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.traces.dropped" }, - { Namespace: "civisibility", Type: "count", Name: "jenkins.traces.submitted" }, - { Namespace: "civisibility", Type: "count", Name: "known_tests.request" }, - { Namespace: "civisibility", Type: "count", Name: "known_tests.request_errors" }, - { Namespace: "civisibility", Type: "count", Name: "manual_api_events" }, - { Namespace: "civisibility", Type: "count", Name: "test_management_tests.request" }, - { Namespace: "civisibility", Type: "count", Name: "test_management_tests.request_errors" }, - { Namespace: "civisibility", Type: "count", Name: "test_session" }, - { Namespace: "civisibility", Type: "distribution", Name: "code_coverage.files" }, - { Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.request_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_tests" }, - { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_count" }, - { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_serialization_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.requests_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.request_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_tests" }, - { Namespace: "civisibility", Type: "distribution", Name: "git.command_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_files" }, - { Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "git_requests.search_commits_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "git_requests.settings_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.request_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_files" }, - { Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.request_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.response_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.events_count" }, - { Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.requests_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "known_tests.request_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_tests" }, - { Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.request_ms" }, - { Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_bytes" }, - { Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_tests" }, - { Namespace: "general", Type: "count", Name: "logs_created" }, - { Namespace: "general", Type: "distribution", Name: "init_time" }, - { Namespace: "general", Type: "distribution", Name: "tracer_init_time" }, - { Namespace: "iast", Type: "count", Name: "executed.propagation" }, - { Namespace: "iast", Type: "count", Name: "executed.sink" }, - { Namespace: "iast", Type: "count", Name: "executed.source" }, - { Namespace: "iast", Type: "count", Name: "executed.tainted" }, - { Namespace: "iast", Type: "count", Name: "instrumented.propagation" }, - { Namespace: "iast", Type: "count", Name: "instrumented.sink" }, - { Namespace: "iast", Type: "count", Name: "instrumented.source" }, - { Namespace: "iast", Type: "count", Name: "json.tag.size.exceeded" }, - { Namespace: "iast", Type: "count", Name: "request.tainted" }, - { Namespace: "iast", Type: "count", Name: "suppressed.vulnerabilities" }, - { Namespace: "mlobs", Type: "count", Name: "activate_distributed_headers" }, - { Namespace: "mlobs", Type: "count", Name: "annotations" }, - { Namespace: "mlobs", Type: "count", Name: "dropped_eval_events" }, - { Namespace: "mlobs", Type: "count", Name: "dropped_span_events" }, - { Namespace: "mlobs", Type: "count", Name: "evals_submitted" }, - { Namespace: "mlobs", Type: "count", Name: "evaluators.error" }, - { Namespace: "mlobs", Type: "count", Name: "evaluators.init" }, - { Namespace: "mlobs", Type: "count", Name: "evaluators.run" }, - { Namespace: "mlobs", Type: "count", Name: "inject_distributed_headers" }, - { Namespace: "mlobs", Type: "count", Name: "product_enabled" }, - { Namespace: "mlobs", Type: "count", Name: "span.finished" }, - { Namespace: "mlobs", Type: "count", Name: "span.start" }, - { Namespace: "mlobs", Type: "count", Name: "spans_exported" }, - { Namespace: "mlobs", Type: "count", Name: "user_flush" }, - { Namespace: "mlobs", Type: "count", Name: "user_processor_called" }, - { Namespace: "mlobs", Type: "distribution", Name: "evaluators.rule_sample_rate" }, - { Namespace: "mlobs", Type: "distribution", Name: "init_time" }, - { Namespace: "mlobs", Type: "distribution", Name: "span.raw_size" }, - { Namespace: "mlobs", Type: "distribution", Name: "span.size" }, - { Namespace: "profilers", Type: "count", Name: "profile_api.errors" }, - { Namespace: "profilers", Type: "count", Name: "profile_api.requests" }, - { Namespace: "profilers", Type: "count", Name: "profile_api.responses" }, - { Namespace: "profilers", Type: "distribution", Name: "profile_api.bytes" }, - { Namespace: "profilers", Type: "distribution", Name: "profile_api.ms" }, - { Namespace: "rum", Type: "count", Name: "injection.content_security_policy" }, - { Namespace: "rum", Type: "count", Name: "injection.failed" }, - { Namespace: "rum", Type: "count", Name: "injection.initialization.failed" }, - { Namespace: "rum", Type: "count", Name: "injection.initialization.succeed" }, - { Namespace: "rum", Type: "count", Name: "injection.installation" }, - { Namespace: "rum", Type: "count", Name: "injection.skipped" }, - { Namespace: "rum", Type: "count", Name: "injection.succeed" }, - { Namespace: "rum", Type: "distribution", Name: "injection.installation.duration" }, - { Namespace: "rum", Type: "distribution", Name: "injection.ms" }, - { Namespace: "rum", Type: "distribution", Name: "injection.response.bytes" }, - { Namespace: "sidecar", Type: "count", Name: "server.submitted_payloads" }, - { Namespace: "sidecar", Type: "distribution", Name: "server.memory_usage" }, - { Namespace: "sidecar", Type: "gauge", Name: "server.active_sessions" }, - { Namespace: "telemetry", Type: "count", Name: "telemetry_api.errors" }, - { Namespace: "telemetry", Type: "count", Name: "telemetry_api.requests" }, - { Namespace: "telemetry", Type: "count", Name: "telemetry_api.responses" }, - { Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.bytes" }, - { Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.ms" }, - { Namespace: "tracers", Type: "count", Name: "context_header.truncated" }, - { Namespace: "tracers", Type: "count", Name: "context_header_style.extracted" }, - { Namespace: "tracers", Type: "count", Name: "context_header_style.injected" }, - { Namespace: "tracers", Type: "count", Name: "docker_lib_injection.failure" }, - { Namespace: "tracers", Type: "count", Name: "docker_lib_injection.success" }, - { Namespace: "tracers", Type: "count", Name: "exporter_fallback" }, - { Namespace: "tracers", Type: "count", Name: "host_lib_injection.failure" }, - { Namespace: "tracers", Type: "count", Name: "host_lib_injection.success" }, - { Namespace: "tracers", Type: "count", Name: "inject.error" }, - { Namespace: "tracers", Type: "count", Name: "inject.language_detection" }, - { Namespace: "tracers", Type: "count", Name: "inject.skip" }, - { Namespace: "tracers", Type: "count", Name: "inject.success" }, - { Namespace: "tracers", Type: "count", Name: "integration_errors" }, - { Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.failure" }, - { Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.success" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.integration" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.runtime" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.complete" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.error" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.injector.error" }, - { Namespace: "tracers", Type: "count", Name: "library_entrypoint.start" }, - { Namespace: "tracers", Type: "count", Name: "otel.env.hiding" }, - { Namespace: "tracers", Type: "count", Name: "otel.env.invalid" }, - { Namespace: "tracers", Type: "count", Name: "otel.env.unsupported" }, - { Namespace: "tracers", Type: "count", Name: "public_api.called" }, - { Namespace: "tracers", Type: "count", Name: "span_created" }, - { Namespace: "tracers", Type: "count", Name: "span_finished" }, - { Namespace: "tracers", Type: "count", Name: "span_pointer_calculation" }, - { Namespace: "tracers", Type: "count", Name: "span_pointer_calculation.issue" }, - { Namespace: "tracers", Type: "count", Name: "spans_created" }, - { Namespace: "tracers", Type: "count", Name: "spans_dropped" }, - { Namespace: "tracers", Type: "count", Name: "spans_enqueued_for_serialization" }, - { Namespace: "tracers", Type: "count", Name: "spans_finished" }, - { Namespace: "tracers", Type: "count", Name: "stats_api.errors" }, - { Namespace: "tracers", Type: "count", Name: "stats_api.requests" }, - { Namespace: "tracers", Type: "count", Name: "stats_api.responses" }, - { Namespace: "tracers", Type: "count", Name: "trace_api.errors" }, - { Namespace: "tracers", Type: "count", Name: "trace_api.requests" }, - { Namespace: "tracers", Type: "count", Name: "trace_api.responses" }, - { Namespace: "tracers", Type: "count", Name: "trace_chunks_dropped" }, - { Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued" }, - { Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued_for_serialization" }, - { Namespace: "tracers", Type: "count", Name: "trace_chunks_sent" }, - { Namespace: "tracers", Type: "count", Name: "trace_partial_flush.count" }, - { Namespace: "tracers", Type: "count", Name: "trace_segments_closed" }, - { Namespace: "tracers", Type: "count", Name: "trace_segments_created" }, - { Namespace: "tracers", Type: "distribution", Name: "inject.latency.baseline" }, - { Namespace: "tracers", Type: "distribution", Name: "inject.latency.end_to_end" }, - { Namespace: "tracers", Type: "distribution", Name: "inject.latency.init_container" }, - { Namespace: "tracers", Type: "distribution", Name: "stats_api.bytes" }, - { Namespace: "tracers", Type: "distribution", Name: "stats_api.ms" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_api.bytes" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_api.ms" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.bytes" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.ms" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_chunk_size" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_closed" }, - { Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_remaining" }, - { Namespace: "tracers", Type: "gauge", Name: "stats_buckets" }, + {Namespace: "appsec", Type: "count", Name: "api_security.missing_route"}, + {Namespace: "appsec", Type: "count", Name: "api_security.request.no_schema"}, + {Namespace: "appsec", Type: "count", Name: "api_security.request.schema"}, + {Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_id"}, + {Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_login"}, + {Namespace: "appsec", Type: "count", Name: "rasp.error"}, + {Namespace: "appsec", Type: "count", Name: "rasp.rule.eval"}, + {Namespace: "appsec", Type: "count", Name: "rasp.rule.match"}, + {Namespace: "appsec", Type: "count", Name: "rasp.rule.skipped"}, + {Namespace: "appsec", Type: "count", Name: "rasp.timeout"}, + {Namespace: "appsec", Type: "count", Name: "sdk.event"}, + {Namespace: "appsec", Type: "count", Name: "waf.config_errors"}, + {Namespace: "appsec", Type: "count", Name: "waf.error"}, + {Namespace: "appsec", Type: "count", Name: "waf.init"}, + {Namespace: "appsec", Type: "count", Name: "waf.input_truncated"}, + {Namespace: "appsec", Type: "count", Name: "waf.requests"}, + {Namespace: "appsec", Type: "count", Name: "waf.updates"}, + {Namespace: "appsec", Type: "distribution", Name: "rasp.duration"}, + {Namespace: "appsec", Type: "distribution", Name: "rasp.duration_ext"}, + {Namespace: "appsec", Type: "distribution", Name: "waf.duration"}, + {Namespace: "appsec", Type: "distribution", Name: "waf.duration_ext"}, + {Namespace: "appsec", Type: "distribution", Name: "waf.truncated_value_size"}, + {Namespace: "appsec", Type: "gauge", Name: "enabled"}, + {Namespace: "civisibility", Type: "count", Name: "code_coverage.errors"}, + {Namespace: "civisibility", Type: "count", Name: "code_coverage.is_empty"}, + {Namespace: "civisibility", Type: "count", Name: "code_coverage_finished"}, + {Namespace: "civisibility", Type: "count", Name: "code_coverage_started"}, + {Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request"}, + {Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request_errors"}, + {Namespace: "civisibility", Type: "count", Name: "endpoint_payload.dropped"}, + {Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests"}, + {Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests_errors"}, + {Namespace: "civisibility", Type: "count", Name: "event_created"}, + {Namespace: "civisibility", Type: "count", Name: "event_finished"}, + {Namespace: "civisibility", Type: "count", Name: "events_enqueued_for_serialization"}, + {Namespace: "civisibility", Type: "count", Name: "flaky_tests.request"}, + {Namespace: "civisibility", Type: "count", Name: "flaky_tests.request_errors"}, + {Namespace: "civisibility", Type: "count", Name: "git.command"}, + {Namespace: "civisibility", Type: "count", Name: "git.command_errors"}, + {Namespace: "civisibility", Type: "count", Name: "git.commit_sha_discrepancy"}, + {Namespace: "civisibility", Type: "count", Name: "git.commit_sha_match"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack_errors"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits_errors"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.settings"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.settings_errors"}, + {Namespace: "civisibility", Type: "count", Name: "git_requests.settings_response"}, + {Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request"}, + {Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request_errors"}, + {Namespace: "civisibility", Type: "count", Name: "itr_forced_run"}, + {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request"}, + {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request_errors"}, + {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_suites"}, + {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_tests"}, + {Namespace: "civisibility", Type: "count", Name: "itr_skipped"}, + {Namespace: "civisibility", Type: "count", Name: "itr_unskippable"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.dropped"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests_errors"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.logs.dropped"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.logs.submitted"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.traces.dropped"}, + {Namespace: "civisibility", Type: "count", Name: "jenkins.traces.submitted"}, + {Namespace: "civisibility", Type: "count", Name: "known_tests.request"}, + {Namespace: "civisibility", Type: "count", Name: "known_tests.request_errors"}, + {Namespace: "civisibility", Type: "count", Name: "manual_api_events"}, + {Namespace: "civisibility", Type: "count", Name: "test_management_tests.request"}, + {Namespace: "civisibility", Type: "count", Name: "test_management_tests.request_errors"}, + {Namespace: "civisibility", Type: "count", Name: "test_session"}, + {Namespace: "civisibility", Type: "distribution", Name: "code_coverage.files"}, + {Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.request_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_tests"}, + {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_count"}, + {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_serialization_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.requests_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.request_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_tests"}, + {Namespace: "civisibility", Type: "distribution", Name: "git.command_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_files"}, + {Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "git_requests.search_commits_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "git_requests.settings_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.request_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_files"}, + {Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.request_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.response_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.events_count"}, + {Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.requests_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "known_tests.request_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_tests"}, + {Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.request_ms"}, + {Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_bytes"}, + {Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_tests"}, + {Namespace: "general", Type: "count", Name: "logs_created"}, + {Namespace: "general", Type: "distribution", Name: "init_time"}, + {Namespace: "general", Type: "distribution", Name: "tracer_init_time"}, + {Namespace: "iast", Type: "count", Name: "executed.propagation"}, + {Namespace: "iast", Type: "count", Name: "executed.sink"}, + {Namespace: "iast", Type: "count", Name: "executed.source"}, + {Namespace: "iast", Type: "count", Name: "executed.tainted"}, + {Namespace: "iast", Type: "count", Name: "instrumented.propagation"}, + {Namespace: "iast", Type: "count", Name: "instrumented.sink"}, + {Namespace: "iast", Type: "count", Name: "instrumented.source"}, + {Namespace: "iast", Type: "count", Name: "json.tag.size.exceeded"}, + {Namespace: "iast", Type: "count", Name: "request.tainted"}, + {Namespace: "iast", Type: "count", Name: "suppressed.vulnerabilities"}, + {Namespace: "mlobs", Type: "count", Name: "activate_distributed_headers"}, + {Namespace: "mlobs", Type: "count", Name: "annotations"}, + {Namespace: "mlobs", Type: "count", Name: "dropped_eval_events"}, + {Namespace: "mlobs", Type: "count", Name: "dropped_span_events"}, + {Namespace: "mlobs", Type: "count", Name: "evals_submitted"}, + {Namespace: "mlobs", Type: "count", Name: "evaluators.error"}, + {Namespace: "mlobs", Type: "count", Name: "evaluators.init"}, + {Namespace: "mlobs", Type: "count", Name: "evaluators.run"}, + {Namespace: "mlobs", Type: "count", Name: "inject_distributed_headers"}, + {Namespace: "mlobs", Type: "count", Name: "product_enabled"}, + {Namespace: "mlobs", Type: "count", Name: "span.finished"}, + {Namespace: "mlobs", Type: "count", Name: "span.start"}, + {Namespace: "mlobs", Type: "count", Name: "spans_exported"}, + {Namespace: "mlobs", Type: "count", Name: "user_flush"}, + {Namespace: "mlobs", Type: "count", Name: "user_processor_called"}, + {Namespace: "mlobs", Type: "distribution", Name: "evaluators.rule_sample_rate"}, + {Namespace: "mlobs", Type: "distribution", Name: "init_time"}, + {Namespace: "mlobs", Type: "distribution", Name: "span.raw_size"}, + {Namespace: "mlobs", Type: "distribution", Name: "span.size"}, + {Namespace: "profilers", Type: "count", Name: "profile_api.errors"}, + {Namespace: "profilers", Type: "count", Name: "profile_api.requests"}, + {Namespace: "profilers", Type: "count", Name: "profile_api.responses"}, + {Namespace: "profilers", Type: "distribution", Name: "profile_api.bytes"}, + {Namespace: "profilers", Type: "distribution", Name: "profile_api.ms"}, + {Namespace: "rum", Type: "count", Name: "injection.content_security_policy"}, + {Namespace: "rum", Type: "count", Name: "injection.failed"}, + {Namespace: "rum", Type: "count", Name: "injection.initialization.failed"}, + {Namespace: "rum", Type: "count", Name: "injection.initialization.succeed"}, + {Namespace: "rum", Type: "count", Name: "injection.installation"}, + {Namespace: "rum", Type: "count", Name: "injection.skipped"}, + {Namespace: "rum", Type: "count", Name: "injection.succeed"}, + {Namespace: "rum", Type: "distribution", Name: "injection.installation.duration"}, + {Namespace: "rum", Type: "distribution", Name: "injection.ms"}, + {Namespace: "rum", Type: "distribution", Name: "injection.response.bytes"}, + {Namespace: "sidecar", Type: "count", Name: "server.submitted_payloads"}, + {Namespace: "sidecar", Type: "distribution", Name: "server.memory_usage"}, + {Namespace: "sidecar", Type: "gauge", Name: "server.active_sessions"}, + {Namespace: "telemetry", Type: "count", Name: "telemetry_api.errors"}, + {Namespace: "telemetry", Type: "count", Name: "telemetry_api.requests"}, + {Namespace: "telemetry", Type: "count", Name: "telemetry_api.responses"}, + {Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.bytes"}, + {Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.ms"}, + {Namespace: "tracers", Type: "count", Name: "context_header.truncated"}, + {Namespace: "tracers", Type: "count", Name: "context_header_style.extracted"}, + {Namespace: "tracers", Type: "count", Name: "context_header_style.injected"}, + {Namespace: "tracers", Type: "count", Name: "docker_lib_injection.failure"}, + {Namespace: "tracers", Type: "count", Name: "docker_lib_injection.success"}, + {Namespace: "tracers", Type: "count", Name: "exporter_fallback"}, + {Namespace: "tracers", Type: "count", Name: "host_lib_injection.failure"}, + {Namespace: "tracers", Type: "count", Name: "host_lib_injection.success"}, + {Namespace: "tracers", Type: "count", Name: "inject.error"}, + {Namespace: "tracers", Type: "count", Name: "inject.language_detection"}, + {Namespace: "tracers", Type: "count", Name: "inject.skip"}, + {Namespace: "tracers", Type: "count", Name: "inject.success"}, + {Namespace: "tracers", Type: "count", Name: "integration_errors"}, + {Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.failure"}, + {Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.success"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.integration"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.runtime"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.complete"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.error"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.injector.error"}, + {Namespace: "tracers", Type: "count", Name: "library_entrypoint.start"}, + {Namespace: "tracers", Type: "count", Name: "otel.env.hiding"}, + {Namespace: "tracers", Type: "count", Name: "otel.env.invalid"}, + {Namespace: "tracers", Type: "count", Name: "otel.env.unsupported"}, + {Namespace: "tracers", Type: "count", Name: "public_api.called"}, + {Namespace: "tracers", Type: "count", Name: "span_created"}, + {Namespace: "tracers", Type: "count", Name: "span_finished"}, + {Namespace: "tracers", Type: "count", Name: "span_pointer_calculation"}, + {Namespace: "tracers", Type: "count", Name: "span_pointer_calculation.issue"}, + {Namespace: "tracers", Type: "count", Name: "spans_created"}, + {Namespace: "tracers", Type: "count", Name: "spans_dropped"}, + {Namespace: "tracers", Type: "count", Name: "spans_enqueued_for_serialization"}, + {Namespace: "tracers", Type: "count", Name: "spans_finished"}, + {Namespace: "tracers", Type: "count", Name: "stats_api.errors"}, + {Namespace: "tracers", Type: "count", Name: "stats_api.requests"}, + {Namespace: "tracers", Type: "count", Name: "stats_api.responses"}, + {Namespace: "tracers", Type: "count", Name: "trace_api.errors"}, + {Namespace: "tracers", Type: "count", Name: "trace_api.requests"}, + {Namespace: "tracers", Type: "count", Name: "trace_api.responses"}, + {Namespace: "tracers", Type: "count", Name: "trace_chunks_dropped"}, + {Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued"}, + {Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued_for_serialization"}, + {Namespace: "tracers", Type: "count", Name: "trace_chunks_sent"}, + {Namespace: "tracers", Type: "count", Name: "trace_partial_flush.count"}, + {Namespace: "tracers", Type: "count", Name: "trace_segments_closed"}, + {Namespace: "tracers", Type: "count", Name: "trace_segments_created"}, + {Namespace: "tracers", Type: "distribution", Name: "inject.latency.baseline"}, + {Namespace: "tracers", Type: "distribution", Name: "inject.latency.end_to_end"}, + {Namespace: "tracers", Type: "distribution", Name: "inject.latency.init_container"}, + {Namespace: "tracers", Type: "distribution", Name: "stats_api.bytes"}, + {Namespace: "tracers", Type: "distribution", Name: "stats_api.ms"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_api.bytes"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_api.ms"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.bytes"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.ms"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_chunk_size"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_closed"}, + {Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_remaining"}, + {Namespace: "tracers", Type: "gauge", Name: "stats_buckets"}, } From 3393771f2c136add20f691152eec0727954303fa Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Oct 2025 13:58:16 -0400 Subject: [PATCH 02/26] Introduce envconfigsource, and add configprovider tests for all sources --- ddtrace/tracer/option.go | 2 +- internal/config/configprovider.go | 22 ++- internal/config/configprovider_test.go | 152 +++++++++++++++++++ internal/config/declarativeconfigsource.go | 2 +- internal/config/envconfigsource.go | 14 ++ internal/env/supported_configurations.gen.go | 1 + internal/env/supported_configurations.json | 36 +++++ 7 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 internal/config/envconfigsource.go diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 449f28d475..2413a6a624 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -138,7 +138,7 @@ const ( // config holds the tracer configuration. type config struct { - // debug, when true,instrumentationSource writes details to logs. + // debug, when true, writes details to logs. debug bool // appsecStartOptions controls the options used when starting appsec features. diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index 212fda2a9b..e88013677d 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -8,6 +8,7 @@ package config import ( "net/url" "strconv" + "strings" ) var provider = DefaultConfigProvider() @@ -75,16 +76,25 @@ func (p *ConfigProvider) getURL(key string, def *url.URL) *url.URL { return def } -type ConfigSource interface { - Get(key string) string -} - func DefaultConfigProvider() *ConfigProvider { return &ConfigProvider{ sources: []ConfigSource{ - LocalDeclarativeConfig, - // EnvSource, ManagedDeclarativeConfig, + new(envConfigSource), + LocalDeclarativeConfig, }, } } + +type ConfigSource interface { + Get(key string) string +} + +// normalizeKey is a helper function for ConfigSource implementations to normalize the key to a valid environment variable name. +func normalizeKey(key string) string { + // Try to convert key to a valid environment variable name + if strings.HasPrefix(key, "DD_") || strings.HasPrefix(key, "OTEL_") { + return key + } + return "DD_" + strings.ToUpper(key) +} diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index 538d665d04..e70aa17441 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -6,10 +6,12 @@ package config import ( + "os" "testing" "net/url" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" "github.com/stretchr/testify/assert" ) @@ -59,3 +61,153 @@ func TestGetMethods(t *testing.T) { assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("URL_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) }) } + +func TestDefaultConfigProvider(t *testing.T) { + t.Run("Settings only exist in EnvConfigSource", func(t *testing.T) { + // Setup: environment variables of each type + t.Setenv("DD_SERVICE", "string") + t.Setenv("DD_TRACE_DEBUG", "true") + t.Setenv("DD_TRACE_SEND_RETRIES", "1") + t.Setenv("DD_TRACE_SAMPLE_RATE", "1.0") + t.Setenv("DD_TRACE_AGENT_URL", "https://localhost:8126") + // TODO: Add more types as we go along + + provider := DefaultConfigProvider() + + // Configured values are returned correctly + assert.Equal(t, "string", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) + + // Defaults are returned for settings that are not configured + assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) + assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) + assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) + assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + }) + t.Run("Settings only exist in LocalDeclarativeConfigSource", func(t *testing.T) { + const localYaml = ` +apm_configuration_default: + DD_SERVICE: local + DD_TRACE_DEBUG: true + DD_TRACE_SEND_RETRIES: "1" + DD_TRACE_SAMPLE_RATE: 1.0 + DD_TRACE_AGENT_URL: https://localhost:8126 +` + + tempLocalPath := "local.yml" + err := os.WriteFile(tempLocalPath, []byte(localYaml), 0644) + assert.NoError(t, err) + defer os.Remove(tempLocalPath) + + LocalDeclarativeConfig = newDeclarativeConfigSource(tempLocalPath, telemetry.OriginLocalStableConfig) + defer func() { + LocalDeclarativeConfig = newDeclarativeConfigSource(localFilePath, telemetry.OriginLocalStableConfig) + }() + + provider := DefaultConfigProvider() + + assert.Equal(t, "local", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) + + // Defaults are returned for settings that are not configured + assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) + assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) + assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) + assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + }) + + t.Run("Settings only exist in ManagedDeclarativeConfigSource", func(t *testing.T) { + const managedYaml = ` +apm_configuration_default: + DD_SERVICE: managed + DD_TRACE_DEBUG: true + DD_TRACE_SEND_RETRIES: "1" + DD_TRACE_SAMPLE_RATE: 1.0 + DD_TRACE_AGENT_URL: https://localhost:8126` + + tempManagedPath := "managed.yml" + err := os.WriteFile(tempManagedPath, []byte(managedYaml), 0644) + assert.NoError(t, err) + defer os.Remove(tempManagedPath) + + ManagedDeclarativeConfig = newDeclarativeConfigSource(tempManagedPath, telemetry.OriginManagedStableConfig) + defer func() { + ManagedDeclarativeConfig = newDeclarativeConfigSource(managedFilePath, telemetry.OriginManagedStableConfig) + }() + + provider := DefaultConfigProvider() + + assert.Equal(t, "managed", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) + + // Defaults are returned for settings that are not configured + assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) + assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) + assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) + assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + }) + t.Run("Settings exist in all ConfigSources", func(t *testing.T) { + localYaml := ` +apm_configuration_default: + DD_SERVICE: local + DD_TRACE_DEBUG: false + DD_TRACE_HOSTNAME: otherhost + DD_TRACE_SEND_RETRIES: "1"` + + managedYaml := ` +apm_configuration_default: + DD_SERVICE: managed + DD_TRACE_DEBUG: true + DD_TRACE_LOG_TO_STDOUT: true + DD_VERSION: 1.0.0` + + t.Setenv("DD_SERVICE", "env") + t.Setenv("DD_TRACE_LOG_TO_STDOUT", "false") + t.Setenv("DD_ENV", "dev") + t.Setenv("DD_TRACE_HOSTNAME", "otherhost") + + tempLocalPath := "local.yml" + err := os.WriteFile(tempLocalPath, []byte(localYaml), 0644) + assert.NoError(t, err) + defer os.Remove(tempLocalPath) + + LocalDeclarativeConfig = newDeclarativeConfigSource(tempLocalPath, telemetry.OriginLocalStableConfig) + defer func() { + LocalDeclarativeConfig = newDeclarativeConfigSource(localFilePath, telemetry.OriginLocalStableConfig) + }() + + tempManagedPath := "managed.yml" + err = os.WriteFile(tempManagedPath, []byte(managedYaml), 0644) + assert.NoError(t, err) + defer os.Remove(tempManagedPath) + + ManagedDeclarativeConfig = newDeclarativeConfigSource(tempManagedPath, telemetry.OriginManagedStableConfig) + defer func() { + ManagedDeclarativeConfig = newDeclarativeConfigSource(managedFilePath, telemetry.OriginManagedStableConfig) + }() + + provider := DefaultConfigProvider() + assert.Equal(t, "managed", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) + assert.Equal(t, "otherhost", provider.getString("DD_TRACE_HOSTNAME", "value")) + assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, "dev", provider.getString("DD_ENV", "value")) + assert.Equal(t, "1.0.0", provider.getString("DD_VERSION", "0")) + assert.Equal(t, true, provider.getBool("DD_TRACE_LOG_TO_STDOUT", false)) + + // Defaults are returned for settings that are not configured + assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) + }) +} diff --git a/internal/config/declarativeconfigsource.go b/internal/config/declarativeconfigsource.go index f6fce97c33..42623a33da 100644 --- a/internal/config/declarativeconfigsource.go +++ b/internal/config/declarativeconfigsource.go @@ -37,7 +37,7 @@ type declarativeConfigSource struct { } func (d *declarativeConfigSource) Get(key string) string { - return d.config.get(key) + return d.config.get(normalizeKey(key)) } func (d *declarativeConfigSource) GetID() string { diff --git a/internal/config/envconfigsource.go b/internal/config/envconfigsource.go new file mode 100644 index 0000000000..874e72e66f --- /dev/null +++ b/internal/config/envconfigsource.go @@ -0,0 +1,14 @@ +// 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 2025 Datadog, Inc. + +package config + +import "github.com/DataDog/dd-trace-go/v2/internal/env" + +type envConfigSource struct{} + +func (e *envConfigSource) Get(key string) string { + return env.Get(normalizeKey(key)) +} diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index 2ae4274dd2..75129d5926 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -200,6 +200,7 @@ var SupportedConfigurations = map[string]struct{}{ "DD_TRACE_SAMPLING_RULES": {}, "DD_TRACE_SAMPLING_RULES_FILE": {}, "DD_TRACE_SARAMA_ANALYTICS_ENABLED": {}, + "DD_TRACE_SEND_RETRIES": {}, "DD_TRACE_SOURCE_HOSTNAME": {}, "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA": {}, "DD_TRACE_SQL_ANALYTICS_ENABLED": {}, diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index e8a2e86411..8b07aaa77e 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -6,6 +6,9 @@ "DD_AGENT_HOST": [ "A" ], + "DD_AGENT_URL": [ + "A" + ], "DD_API_KEY": [ "A" ], @@ -72,6 +75,9 @@ "DD_APP_KEY": [ "A" ], + "DD_BOOL_KEY": [ + "A" + ], "DD_CIVISIBILITY_AGENTLESS_ENABLED": [ "A" ], @@ -129,6 +135,9 @@ "DD_EXTERNAL_ENV": [ "A" ], + "DD_FLOAT_KEY": [ + "A" + ], "DD_GIT_BRANCH": [ "A" ], @@ -183,6 +192,9 @@ "DD_INSTRUMENTATION_TELEMETRY_ENABLED": [ "A" ], + "DD_INT_KEY": [ + "A" + ], "DD_KEY": [ "A" ], @@ -201,6 +213,9 @@ "DD_LOGGING_RATE": [ "A" ], + "DD_NONEXISTENT_KEY": [ + "A" + ], "DD_PIPELINE_EXECUTION_ID": [ "A" ], @@ -309,6 +324,9 @@ "DD_SPAN_SAMPLING_RULES_FILE": [ "A" ], + "DD_STRING_KEY": [ + "A" + ], "DD_TAGS": [ "A" ], @@ -426,6 +444,9 @@ "DD_TRACE_GIT_METADATA_ENABLED": [ "A" ], + "DD_TRACE_GLOBAL_SAMPLE_RATE": [ + "A" + ], "DD_TRACE_GOCQL_ANALYTICS_ENABLED": [ "A" ], @@ -456,6 +477,9 @@ "DD_TRACE_HEADER_TAGS": [ "A" ], + "DD_TRACE_HOSTNAME": [ + "A" + ], "DD_TRACE_HTTPROUTER_ANALYTICS_ENABLED": [ "A" ], @@ -498,6 +522,9 @@ "DD_TRACE_LOG_DIRECTORY": [ "A" ], + "DD_TRACE_LOG_TO_STDOUT": [ + "A" + ], "DD_TRACE_MEMCACHE_ANALYTICS_ENABLED": [ "A" ], @@ -573,6 +600,12 @@ "DD_TRACE_SARAMA_ANALYTICS_ENABLED": [ "A" ], + "DD_TRACE_SEND_RETRIES": [ + "A" + ], + "DD_TRACE_SERVICE_NAME": [ + "A" + ], "DD_TRACE_SOURCE_HOSTNAME": [ "A" ], @@ -609,6 +642,9 @@ "DD_TRACE__ANALYTICS_ENABLED": [ "A" ], + "DD_URL_KEY": [ + "A" + ], "DD_VERSION": [ "A" ], From 0bb66a1dbccf0f00b5e1281466397e8fa563d9db Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Oct 2025 14:10:14 -0400 Subject: [PATCH 03/26] Test migrating tracer.config.debug over to internal/config.IsDebugEnabled --- ddtrace/tracer/option.go | 4 ++-- internal/config/config.go | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 2413a6a624..5d41d8943e 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -33,6 +33,7 @@ import ( "github.com/DataDog/dd-trace-go/v2/internal" appsecconfig "github.com/DataDog/dd-trace-go/v2/internal/appsec/config" "github.com/DataDog/dd-trace-go/v2/internal/civisibility/constants" + internalconfig "github.com/DataDog/dd-trace-go/v2/internal/config" "github.com/DataDog/dd-trace-go/v2/internal/env" "github.com/DataDog/dd-trace-go/v2/internal/globalconfig" llmobsconfig "github.com/DataDog/dd-trace-go/v2/internal/llmobs/config" @@ -606,10 +607,9 @@ func newConfig(opts ...StartOption) (*config, error) { if c.logger != nil { log.UseLogger(c.logger) } - if c.debug { + if internalconfig.GlobalConfig().IsDebugEnabled() { log.SetLevel(log.LevelDebug) } - // Check if CI Visibility mode is enabled if internal.BoolEnv(constants.CIVisibilityEnabledEnvironmentVariable, false) { c.ciVisibilityEnabled = true // Enable CI Visibility mode diff --git a/internal/config/config.go b/internal/config/config.go index 8e5d289be2..5cfd4e11e5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -108,9 +108,13 @@ func loadConfig() *Config { return cfg } -func Get() *Config { +func GlobalConfig() *Config { if globalConfig == nil { globalConfig = loadConfig() } return globalConfig } + +func (c *Config) IsDebugEnabled() bool { + return c.Debug +} From e34de5dcc3e9e8e969548b030841d35ce1ea71d4 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Oct 2025 14:11:15 -0400 Subject: [PATCH 04/26] nits --- internal/config/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 5cfd4e11e5..3fd4db93d1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -102,8 +102,10 @@ type Config struct { func loadConfig() *Config { cfg := new(Config) + // TODO: Use defaults from config json instead of hardcoding them here cfg.AgentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) cfg.Debug = provider.getBool("DD_TRACE_DEBUG", false) + // TODO: Initialize all fields return cfg } From 8bdab95ef5e9d65784dda6b19352e8ebc3cae1ba Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 27 Oct 2025 11:42:03 -0400 Subject: [PATCH 05/26] fix test env vars; only use real settings --- internal/config/configprovider_test.go | 72 ++++++++++------------ internal/env/supported_configurations.json | 42 ++----------- 2 files changed, 38 insertions(+), 76 deletions(-) diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index e70aa17441..778dfebfbb 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -25,18 +25,13 @@ type testConfigSource struct { entries map[string]string } -func newTestConfigSource() *testConfigSource { - t := &testConfigSource{ - entries: make(map[string]string), +func newTestConfigSource(entries map[string]string) *testConfigSource { + if entries == nil { + entries = make(map[string]string) + } + return &testConfigSource{ + entries: entries, } - - t.entries["STRING_KEY"] = "string" - t.entries["BOOL_KEY"] = "true" - t.entries["INT_KEY"] = "1" - t.entries["FLOAT_KEY"] = "1.0" - t.entries["URL_KEY"] = "https://localhost:8126" - - return t } func (s *testConfigSource) Get(key string) string { @@ -45,20 +40,29 @@ func (s *testConfigSource) Get(key string) string { func TestGetMethods(t *testing.T) { t.Run("defaults", func(t *testing.T) { - provider := newTestConfigProvider(newTestConfigSource()) - assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) - assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) - assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) - assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) - assert.Equal(t, &url.URL{Scheme: "https", Host: "otherhost:1234"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "otherhost:1234"})) + // Test that defaults are used when the queried key does not exist + provider := newTestConfigProvider(newTestConfigSource(nil)) + assert.Equal(t, "value", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", true)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 1)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 1.0)) + assert.Equal(t, &url.URL{Scheme: "http", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"})) }) t.Run("non-defaults", func(t *testing.T) { - provider := newTestConfigProvider(newTestConfigSource()) - assert.Equal(t, "string", provider.getString("STRING_KEY", "value")) - assert.Equal(t, true, provider.getBool("BOOL_KEY", false)) - assert.Equal(t, 1, provider.getInt("INT_KEY", 0)) - assert.Equal(t, 1.0, provider.getFloat("FLOAT_KEY", 0.0)) - assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("URL_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + // Test that non-defaults are used when the queried key exists + entries := map[string]string{ + "DD_SERVICE": "string", + "DD_TRACE_DEBUG": "true", + "DD_TRACE_SEND_RETRIES": "1", + "DD_TRACE_SAMPLE_RATE": "1.0", + "DD_TRACE_AGENT_URL": "https://localhost:8126", + } + provider := newTestConfigProvider(newTestConfigSource(entries)) + assert.Equal(t, "string", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) }) } @@ -79,14 +83,10 @@ func TestDefaultConfigProvider(t *testing.T) { assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) - assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) // Defaults are returned for settings that are not configured - assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) - assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) - assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) - assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) - assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + assert.Equal(t, "value", provider.getString("DD_ENV", "value")) }) t.Run("Settings only exist in LocalDeclarativeConfigSource", func(t *testing.T) { const localYaml = ` @@ -117,11 +117,7 @@ apm_configuration_default: assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) // Defaults are returned for settings that are not configured - assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) - assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) - assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) - assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) - assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + assert.Equal(t, "value", provider.getString("DD_ENV", "value")) }) t.Run("Settings only exist in ManagedDeclarativeConfigSource", func(t *testing.T) { @@ -152,11 +148,7 @@ apm_configuration_default: assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) // Defaults are returned for settings that are not configured - assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) - assert.Equal(t, false, provider.getBool("NONEXISTENT_KEY", false)) - assert.Equal(t, 0, provider.getInt("NONEXISTENT_KEY", 0)) - assert.Equal(t, 0.0, provider.getFloat("NONEXISTENT_KEY", 0.0)) - assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("NONEXISTENT_KEY", &url.URL{Scheme: "https", Host: "localhost:8126"})) + assert.Equal(t, "value", provider.getString("DD_ENV", "value")) }) t.Run("Settings exist in all ConfigSources", func(t *testing.T) { localYaml := ` @@ -208,6 +200,6 @@ apm_configuration_default: assert.Equal(t, true, provider.getBool("DD_TRACE_LOG_TO_STDOUT", false)) // Defaults are returned for settings that are not configured - assert.Equal(t, "value", provider.getString("NONEXISTENT_KEY", "value")) + assert.Equal(t, false, provider.getBool("DD_TRACE_STARTUP_LOGS", false)) }) } diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index 8b07aaa77e..1659ba5e5f 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -6,9 +6,6 @@ "DD_AGENT_HOST": [ "A" ], - "DD_AGENT_URL": [ - "A" - ], "DD_API_KEY": [ "A" ], @@ -75,9 +72,6 @@ "DD_APP_KEY": [ "A" ], - "DD_BOOL_KEY": [ - "A" - ], "DD_CIVISIBILITY_AGENTLESS_ENABLED": [ "A" ], @@ -135,9 +129,6 @@ "DD_EXTERNAL_ENV": [ "A" ], - "DD_FLOAT_KEY": [ - "A" - ], "DD_GIT_BRANCH": [ "A" ], @@ -192,9 +183,6 @@ "DD_INSTRUMENTATION_TELEMETRY_ENABLED": [ "A" ], - "DD_INT_KEY": [ - "A" - ], "DD_KEY": [ "A" ], @@ -213,9 +201,6 @@ "DD_LOGGING_RATE": [ "A" ], - "DD_NONEXISTENT_KEY": [ - "A" - ], "DD_PIPELINE_EXECUTION_ID": [ "A" ], @@ -324,9 +309,6 @@ "DD_SPAN_SAMPLING_RULES_FILE": [ "A" ], - "DD_STRING_KEY": [ - "A" - ], "DD_TAGS": [ "A" ], @@ -444,9 +426,6 @@ "DD_TRACE_GIT_METADATA_ENABLED": [ "A" ], - "DD_TRACE_GLOBAL_SAMPLE_RATE": [ - "A" - ], "DD_TRACE_GOCQL_ANALYTICS_ENABLED": [ "A" ], @@ -477,9 +456,6 @@ "DD_TRACE_HEADER_TAGS": [ "A" ], - "DD_TRACE_HOSTNAME": [ - "A" - ], "DD_TRACE_HTTPROUTER_ANALYTICS_ENABLED": [ "A" ], @@ -522,9 +498,6 @@ "DD_TRACE_LOG_DIRECTORY": [ "A" ], - "DD_TRACE_LOG_TO_STDOUT": [ - "A" - ], "DD_TRACE_MEMCACHE_ANALYTICS_ENABLED": [ "A" ], @@ -585,6 +558,12 @@ "DD_TRACE_REPORT_HOSTNAME": [ "A" ], + "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT": [ + "A" + ], + "DD_TRACE_RESOURCE_RENAMING_ENABLED": [ + "A" + ], "DD_TRACE_RESTFUL_ANALYTICS_ENABLED": [ "A" ], @@ -600,12 +579,6 @@ "DD_TRACE_SARAMA_ANALYTICS_ENABLED": [ "A" ], - "DD_TRACE_SEND_RETRIES": [ - "A" - ], - "DD_TRACE_SERVICE_NAME": [ - "A" - ], "DD_TRACE_SOURCE_HOSTNAME": [ "A" ], @@ -642,9 +615,6 @@ "DD_TRACE__ANALYTICS_ENABLED": [ "A" ], - "DD_URL_KEY": [ - "A" - ], "DD_VERSION": [ "A" ], From 0513a968272ad35fddd201de3718118129c257e4 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 27 Oct 2025 11:44:16 -0400 Subject: [PATCH 06/26] restore unrelated files to their status on main --- internal/env/supported_configurations.gen.go | 3 +- .../knownmetrics/known_metric.golang.go | 6 +- .../knownmetrics/known_metrics.common.go | 432 +++++++++--------- 3 files changed, 221 insertions(+), 220 deletions(-) diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index 75129d5926..9ea9b93f49 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -195,12 +195,13 @@ var SupportedConfigurations = map[string]struct{}{ "DD_TRACE_REDIS_RAW_COMMAND": {}, "DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED": {}, "DD_TRACE_REPORT_HOSTNAME": {}, + "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT": {}, + "DD_TRACE_RESOURCE_RENAMING_ENABLED": {}, "DD_TRACE_RESTFUL_ANALYTICS_ENABLED": {}, "DD_TRACE_SAMPLE_RATE": {}, "DD_TRACE_SAMPLING_RULES": {}, "DD_TRACE_SAMPLING_RULES_FILE": {}, "DD_TRACE_SARAMA_ANALYTICS_ENABLED": {}, - "DD_TRACE_SEND_RETRIES": {}, "DD_TRACE_SOURCE_HOSTNAME": {}, "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA": {}, "DD_TRACE_SQL_ANALYTICS_ENABLED": {}, diff --git a/internal/telemetry/internal/knownmetrics/known_metric.golang.go b/internal/telemetry/internal/knownmetrics/known_metric.golang.go index c11b11ea89..da51ebcf19 100644 --- a/internal/telemetry/internal/knownmetrics/known_metric.golang.go +++ b/internal/telemetry/internal/knownmetrics/known_metric.golang.go @@ -8,7 +8,7 @@ package knownmetrics var golangMetrics = []Declaration{ - {Type: "count", Name: "errorstack.source"}, - {Type: "distribution", Name: "errorstack.duration"}, - {Type: "gauge", Name: "orchestrion.enabled"}, + { Type: "count", Name: "errorstack.source" }, + { Type: "distribution", Name: "errorstack.duration" }, + { Type: "gauge", Name: "orchestrion.enabled" }, } diff --git a/internal/telemetry/internal/knownmetrics/known_metrics.common.go b/internal/telemetry/internal/knownmetrics/known_metrics.common.go index 097779b069..1c2925f127 100644 --- a/internal/telemetry/internal/knownmetrics/known_metrics.common.go +++ b/internal/telemetry/internal/knownmetrics/known_metrics.common.go @@ -8,220 +8,220 @@ package knownmetrics var commonMetrics = []Declaration{ - {Namespace: "appsec", Type: "count", Name: "api_security.missing_route"}, - {Namespace: "appsec", Type: "count", Name: "api_security.request.no_schema"}, - {Namespace: "appsec", Type: "count", Name: "api_security.request.schema"}, - {Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_id"}, - {Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_login"}, - {Namespace: "appsec", Type: "count", Name: "rasp.error"}, - {Namespace: "appsec", Type: "count", Name: "rasp.rule.eval"}, - {Namespace: "appsec", Type: "count", Name: "rasp.rule.match"}, - {Namespace: "appsec", Type: "count", Name: "rasp.rule.skipped"}, - {Namespace: "appsec", Type: "count", Name: "rasp.timeout"}, - {Namespace: "appsec", Type: "count", Name: "sdk.event"}, - {Namespace: "appsec", Type: "count", Name: "waf.config_errors"}, - {Namespace: "appsec", Type: "count", Name: "waf.error"}, - {Namespace: "appsec", Type: "count", Name: "waf.init"}, - {Namespace: "appsec", Type: "count", Name: "waf.input_truncated"}, - {Namespace: "appsec", Type: "count", Name: "waf.requests"}, - {Namespace: "appsec", Type: "count", Name: "waf.updates"}, - {Namespace: "appsec", Type: "distribution", Name: "rasp.duration"}, - {Namespace: "appsec", Type: "distribution", Name: "rasp.duration_ext"}, - {Namespace: "appsec", Type: "distribution", Name: "waf.duration"}, - {Namespace: "appsec", Type: "distribution", Name: "waf.duration_ext"}, - {Namespace: "appsec", Type: "distribution", Name: "waf.truncated_value_size"}, - {Namespace: "appsec", Type: "gauge", Name: "enabled"}, - {Namespace: "civisibility", Type: "count", Name: "code_coverage.errors"}, - {Namespace: "civisibility", Type: "count", Name: "code_coverage.is_empty"}, - {Namespace: "civisibility", Type: "count", Name: "code_coverage_finished"}, - {Namespace: "civisibility", Type: "count", Name: "code_coverage_started"}, - {Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request"}, - {Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request_errors"}, - {Namespace: "civisibility", Type: "count", Name: "endpoint_payload.dropped"}, - {Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests"}, - {Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests_errors"}, - {Namespace: "civisibility", Type: "count", Name: "event_created"}, - {Namespace: "civisibility", Type: "count", Name: "event_finished"}, - {Namespace: "civisibility", Type: "count", Name: "events_enqueued_for_serialization"}, - {Namespace: "civisibility", Type: "count", Name: "flaky_tests.request"}, - {Namespace: "civisibility", Type: "count", Name: "flaky_tests.request_errors"}, - {Namespace: "civisibility", Type: "count", Name: "git.command"}, - {Namespace: "civisibility", Type: "count", Name: "git.command_errors"}, - {Namespace: "civisibility", Type: "count", Name: "git.commit_sha_discrepancy"}, - {Namespace: "civisibility", Type: "count", Name: "git.commit_sha_match"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack_errors"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits_errors"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.settings"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.settings_errors"}, - {Namespace: "civisibility", Type: "count", Name: "git_requests.settings_response"}, - {Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request"}, - {Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request_errors"}, - {Namespace: "civisibility", Type: "count", Name: "itr_forced_run"}, - {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request"}, - {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request_errors"}, - {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_suites"}, - {Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_tests"}, - {Namespace: "civisibility", Type: "count", Name: "itr_skipped"}, - {Namespace: "civisibility", Type: "count", Name: "itr_unskippable"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.dropped"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests_errors"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.logs.dropped"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.logs.submitted"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.traces.dropped"}, - {Namespace: "civisibility", Type: "count", Name: "jenkins.traces.submitted"}, - {Namespace: "civisibility", Type: "count", Name: "known_tests.request"}, - {Namespace: "civisibility", Type: "count", Name: "known_tests.request_errors"}, - {Namespace: "civisibility", Type: "count", Name: "manual_api_events"}, - {Namespace: "civisibility", Type: "count", Name: "test_management_tests.request"}, - {Namespace: "civisibility", Type: "count", Name: "test_management_tests.request_errors"}, - {Namespace: "civisibility", Type: "count", Name: "test_session"}, - {Namespace: "civisibility", Type: "distribution", Name: "code_coverage.files"}, - {Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.request_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_tests"}, - {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_count"}, - {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_serialization_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.requests_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.request_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_tests"}, - {Namespace: "civisibility", Type: "distribution", Name: "git.command_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_files"}, - {Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "git_requests.search_commits_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "git_requests.settings_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.request_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_files"}, - {Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.request_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.response_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.events_count"}, - {Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.requests_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "known_tests.request_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_tests"}, - {Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.request_ms"}, - {Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_bytes"}, - {Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_tests"}, - {Namespace: "general", Type: "count", Name: "logs_created"}, - {Namespace: "general", Type: "distribution", Name: "init_time"}, - {Namespace: "general", Type: "distribution", Name: "tracer_init_time"}, - {Namespace: "iast", Type: "count", Name: "executed.propagation"}, - {Namespace: "iast", Type: "count", Name: "executed.sink"}, - {Namespace: "iast", Type: "count", Name: "executed.source"}, - {Namespace: "iast", Type: "count", Name: "executed.tainted"}, - {Namespace: "iast", Type: "count", Name: "instrumented.propagation"}, - {Namespace: "iast", Type: "count", Name: "instrumented.sink"}, - {Namespace: "iast", Type: "count", Name: "instrumented.source"}, - {Namespace: "iast", Type: "count", Name: "json.tag.size.exceeded"}, - {Namespace: "iast", Type: "count", Name: "request.tainted"}, - {Namespace: "iast", Type: "count", Name: "suppressed.vulnerabilities"}, - {Namespace: "mlobs", Type: "count", Name: "activate_distributed_headers"}, - {Namespace: "mlobs", Type: "count", Name: "annotations"}, - {Namespace: "mlobs", Type: "count", Name: "dropped_eval_events"}, - {Namespace: "mlobs", Type: "count", Name: "dropped_span_events"}, - {Namespace: "mlobs", Type: "count", Name: "evals_submitted"}, - {Namespace: "mlobs", Type: "count", Name: "evaluators.error"}, - {Namespace: "mlobs", Type: "count", Name: "evaluators.init"}, - {Namespace: "mlobs", Type: "count", Name: "evaluators.run"}, - {Namespace: "mlobs", Type: "count", Name: "inject_distributed_headers"}, - {Namespace: "mlobs", Type: "count", Name: "product_enabled"}, - {Namespace: "mlobs", Type: "count", Name: "span.finished"}, - {Namespace: "mlobs", Type: "count", Name: "span.start"}, - {Namespace: "mlobs", Type: "count", Name: "spans_exported"}, - {Namespace: "mlobs", Type: "count", Name: "user_flush"}, - {Namespace: "mlobs", Type: "count", Name: "user_processor_called"}, - {Namespace: "mlobs", Type: "distribution", Name: "evaluators.rule_sample_rate"}, - {Namespace: "mlobs", Type: "distribution", Name: "init_time"}, - {Namespace: "mlobs", Type: "distribution", Name: "span.raw_size"}, - {Namespace: "mlobs", Type: "distribution", Name: "span.size"}, - {Namespace: "profilers", Type: "count", Name: "profile_api.errors"}, - {Namespace: "profilers", Type: "count", Name: "profile_api.requests"}, - {Namespace: "profilers", Type: "count", Name: "profile_api.responses"}, - {Namespace: "profilers", Type: "distribution", Name: "profile_api.bytes"}, - {Namespace: "profilers", Type: "distribution", Name: "profile_api.ms"}, - {Namespace: "rum", Type: "count", Name: "injection.content_security_policy"}, - {Namespace: "rum", Type: "count", Name: "injection.failed"}, - {Namespace: "rum", Type: "count", Name: "injection.initialization.failed"}, - {Namespace: "rum", Type: "count", Name: "injection.initialization.succeed"}, - {Namespace: "rum", Type: "count", Name: "injection.installation"}, - {Namespace: "rum", Type: "count", Name: "injection.skipped"}, - {Namespace: "rum", Type: "count", Name: "injection.succeed"}, - {Namespace: "rum", Type: "distribution", Name: "injection.installation.duration"}, - {Namespace: "rum", Type: "distribution", Name: "injection.ms"}, - {Namespace: "rum", Type: "distribution", Name: "injection.response.bytes"}, - {Namespace: "sidecar", Type: "count", Name: "server.submitted_payloads"}, - {Namespace: "sidecar", Type: "distribution", Name: "server.memory_usage"}, - {Namespace: "sidecar", Type: "gauge", Name: "server.active_sessions"}, - {Namespace: "telemetry", Type: "count", Name: "telemetry_api.errors"}, - {Namespace: "telemetry", Type: "count", Name: "telemetry_api.requests"}, - {Namespace: "telemetry", Type: "count", Name: "telemetry_api.responses"}, - {Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.bytes"}, - {Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.ms"}, - {Namespace: "tracers", Type: "count", Name: "context_header.truncated"}, - {Namespace: "tracers", Type: "count", Name: "context_header_style.extracted"}, - {Namespace: "tracers", Type: "count", Name: "context_header_style.injected"}, - {Namespace: "tracers", Type: "count", Name: "docker_lib_injection.failure"}, - {Namespace: "tracers", Type: "count", Name: "docker_lib_injection.success"}, - {Namespace: "tracers", Type: "count", Name: "exporter_fallback"}, - {Namespace: "tracers", Type: "count", Name: "host_lib_injection.failure"}, - {Namespace: "tracers", Type: "count", Name: "host_lib_injection.success"}, - {Namespace: "tracers", Type: "count", Name: "inject.error"}, - {Namespace: "tracers", Type: "count", Name: "inject.language_detection"}, - {Namespace: "tracers", Type: "count", Name: "inject.skip"}, - {Namespace: "tracers", Type: "count", Name: "inject.success"}, - {Namespace: "tracers", Type: "count", Name: "integration_errors"}, - {Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.failure"}, - {Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.success"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.integration"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.runtime"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.complete"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.error"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.injector.error"}, - {Namespace: "tracers", Type: "count", Name: "library_entrypoint.start"}, - {Namespace: "tracers", Type: "count", Name: "otel.env.hiding"}, - {Namespace: "tracers", Type: "count", Name: "otel.env.invalid"}, - {Namespace: "tracers", Type: "count", Name: "otel.env.unsupported"}, - {Namespace: "tracers", Type: "count", Name: "public_api.called"}, - {Namespace: "tracers", Type: "count", Name: "span_created"}, - {Namespace: "tracers", Type: "count", Name: "span_finished"}, - {Namespace: "tracers", Type: "count", Name: "span_pointer_calculation"}, - {Namespace: "tracers", Type: "count", Name: "span_pointer_calculation.issue"}, - {Namespace: "tracers", Type: "count", Name: "spans_created"}, - {Namespace: "tracers", Type: "count", Name: "spans_dropped"}, - {Namespace: "tracers", Type: "count", Name: "spans_enqueued_for_serialization"}, - {Namespace: "tracers", Type: "count", Name: "spans_finished"}, - {Namespace: "tracers", Type: "count", Name: "stats_api.errors"}, - {Namespace: "tracers", Type: "count", Name: "stats_api.requests"}, - {Namespace: "tracers", Type: "count", Name: "stats_api.responses"}, - {Namespace: "tracers", Type: "count", Name: "trace_api.errors"}, - {Namespace: "tracers", Type: "count", Name: "trace_api.requests"}, - {Namespace: "tracers", Type: "count", Name: "trace_api.responses"}, - {Namespace: "tracers", Type: "count", Name: "trace_chunks_dropped"}, - {Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued"}, - {Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued_for_serialization"}, - {Namespace: "tracers", Type: "count", Name: "trace_chunks_sent"}, - {Namespace: "tracers", Type: "count", Name: "trace_partial_flush.count"}, - {Namespace: "tracers", Type: "count", Name: "trace_segments_closed"}, - {Namespace: "tracers", Type: "count", Name: "trace_segments_created"}, - {Namespace: "tracers", Type: "distribution", Name: "inject.latency.baseline"}, - {Namespace: "tracers", Type: "distribution", Name: "inject.latency.end_to_end"}, - {Namespace: "tracers", Type: "distribution", Name: "inject.latency.init_container"}, - {Namespace: "tracers", Type: "distribution", Name: "stats_api.bytes"}, - {Namespace: "tracers", Type: "distribution", Name: "stats_api.ms"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_api.bytes"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_api.ms"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.bytes"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.ms"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_chunk_size"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_closed"}, - {Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_remaining"}, - {Namespace: "tracers", Type: "gauge", Name: "stats_buckets"}, + { Namespace: "appsec", Type: "count", Name: "api_security.missing_route" }, + { Namespace: "appsec", Type: "count", Name: "api_security.request.no_schema" }, + { Namespace: "appsec", Type: "count", Name: "api_security.request.schema" }, + { Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_id" }, + { Namespace: "appsec", Type: "count", Name: "instrum.user_auth.missing_user_login" }, + { Namespace: "appsec", Type: "count", Name: "rasp.error" }, + { Namespace: "appsec", Type: "count", Name: "rasp.rule.eval" }, + { Namespace: "appsec", Type: "count", Name: "rasp.rule.match" }, + { Namespace: "appsec", Type: "count", Name: "rasp.rule.skipped" }, + { Namespace: "appsec", Type: "count", Name: "rasp.timeout" }, + { Namespace: "appsec", Type: "count", Name: "sdk.event" }, + { Namespace: "appsec", Type: "count", Name: "waf.config_errors" }, + { Namespace: "appsec", Type: "count", Name: "waf.error" }, + { Namespace: "appsec", Type: "count", Name: "waf.init" }, + { Namespace: "appsec", Type: "count", Name: "waf.input_truncated" }, + { Namespace: "appsec", Type: "count", Name: "waf.requests" }, + { Namespace: "appsec", Type: "count", Name: "waf.updates" }, + { Namespace: "appsec", Type: "distribution", Name: "rasp.duration" }, + { Namespace: "appsec", Type: "distribution", Name: "rasp.duration_ext" }, + { Namespace: "appsec", Type: "distribution", Name: "waf.duration" }, + { Namespace: "appsec", Type: "distribution", Name: "waf.duration_ext" }, + { Namespace: "appsec", Type: "distribution", Name: "waf.truncated_value_size" }, + { Namespace: "appsec", Type: "gauge", Name: "enabled" }, + { Namespace: "civisibility", Type: "count", Name: "code_coverage.errors" }, + { Namespace: "civisibility", Type: "count", Name: "code_coverage.is_empty" }, + { Namespace: "civisibility", Type: "count", Name: "code_coverage_finished" }, + { Namespace: "civisibility", Type: "count", Name: "code_coverage_started" }, + { Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request" }, + { Namespace: "civisibility", Type: "count", Name: "early_flake_detection.request_errors" }, + { Namespace: "civisibility", Type: "count", Name: "endpoint_payload.dropped" }, + { Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests" }, + { Namespace: "civisibility", Type: "count", Name: "endpoint_payload.requests_errors" }, + { Namespace: "civisibility", Type: "count", Name: "event_created" }, + { Namespace: "civisibility", Type: "count", Name: "event_finished" }, + { Namespace: "civisibility", Type: "count", Name: "events_enqueued_for_serialization" }, + { Namespace: "civisibility", Type: "count", Name: "flaky_tests.request" }, + { Namespace: "civisibility", Type: "count", Name: "flaky_tests.request_errors" }, + { Namespace: "civisibility", Type: "count", Name: "git.command" }, + { Namespace: "civisibility", Type: "count", Name: "git.command_errors" }, + { Namespace: "civisibility", Type: "count", Name: "git.commit_sha_discrepancy" }, + { Namespace: "civisibility", Type: "count", Name: "git.commit_sha_match" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.objects_pack_errors" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.search_commits_errors" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.settings" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.settings_errors" }, + { Namespace: "civisibility", Type: "count", Name: "git_requests.settings_response" }, + { Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request" }, + { Namespace: "civisibility", Type: "count", Name: "impacted_tests_detection.request_errors" }, + { Namespace: "civisibility", Type: "count", Name: "itr_forced_run" }, + { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request" }, + { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.request_errors" }, + { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_suites" }, + { Namespace: "civisibility", Type: "count", Name: "itr_skippable_tests.response_tests" }, + { Namespace: "civisibility", Type: "count", Name: "itr_skipped" }, + { Namespace: "civisibility", Type: "count", Name: "itr_unskippable" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.dropped" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.http_endpoint.requests_errors" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.logs.dropped" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.logs.submitted" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.traces.dropped" }, + { Namespace: "civisibility", Type: "count", Name: "jenkins.traces.submitted" }, + { Namespace: "civisibility", Type: "count", Name: "known_tests.request" }, + { Namespace: "civisibility", Type: "count", Name: "known_tests.request_errors" }, + { Namespace: "civisibility", Type: "count", Name: "manual_api_events" }, + { Namespace: "civisibility", Type: "count", Name: "test_management_tests.request" }, + { Namespace: "civisibility", Type: "count", Name: "test_management_tests.request_errors" }, + { Namespace: "civisibility", Type: "count", Name: "test_session" }, + { Namespace: "civisibility", Type: "distribution", Name: "code_coverage.files" }, + { Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.request_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "early_flake_detection.response_tests" }, + { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_count" }, + { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.events_serialization_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "endpoint_payload.requests_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.request_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "flaky_tests.response_tests" }, + { Namespace: "civisibility", Type: "distribution", Name: "git.command_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_files" }, + { Namespace: "civisibility", Type: "distribution", Name: "git_requests.objects_pack_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "git_requests.search_commits_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "git_requests.settings_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.request_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "impacted_tests_detection.response_files" }, + { Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.request_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "itr_skippable_tests.response_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.events_count" }, + { Namespace: "civisibility", Type: "distribution", Name: "jenkins.http_endpoint.requests_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "known_tests.request_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "known_tests.response_tests" }, + { Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.request_ms" }, + { Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_bytes" }, + { Namespace: "civisibility", Type: "distribution", Name: "test_management_tests.response_tests" }, + { Namespace: "general", Type: "count", Name: "logs_created" }, + { Namespace: "general", Type: "distribution", Name: "init_time" }, + { Namespace: "general", Type: "distribution", Name: "tracer_init_time" }, + { Namespace: "iast", Type: "count", Name: "executed.propagation" }, + { Namespace: "iast", Type: "count", Name: "executed.sink" }, + { Namespace: "iast", Type: "count", Name: "executed.source" }, + { Namespace: "iast", Type: "count", Name: "executed.tainted" }, + { Namespace: "iast", Type: "count", Name: "instrumented.propagation" }, + { Namespace: "iast", Type: "count", Name: "instrumented.sink" }, + { Namespace: "iast", Type: "count", Name: "instrumented.source" }, + { Namespace: "iast", Type: "count", Name: "json.tag.size.exceeded" }, + { Namespace: "iast", Type: "count", Name: "request.tainted" }, + { Namespace: "iast", Type: "count", Name: "suppressed.vulnerabilities" }, + { Namespace: "mlobs", Type: "count", Name: "activate_distributed_headers" }, + { Namespace: "mlobs", Type: "count", Name: "annotations" }, + { Namespace: "mlobs", Type: "count", Name: "dropped_eval_events" }, + { Namespace: "mlobs", Type: "count", Name: "dropped_span_events" }, + { Namespace: "mlobs", Type: "count", Name: "evals_submitted" }, + { Namespace: "mlobs", Type: "count", Name: "evaluators.error" }, + { Namespace: "mlobs", Type: "count", Name: "evaluators.init" }, + { Namespace: "mlobs", Type: "count", Name: "evaluators.run" }, + { Namespace: "mlobs", Type: "count", Name: "inject_distributed_headers" }, + { Namespace: "mlobs", Type: "count", Name: "product_enabled" }, + { Namespace: "mlobs", Type: "count", Name: "span.finished" }, + { Namespace: "mlobs", Type: "count", Name: "span.start" }, + { Namespace: "mlobs", Type: "count", Name: "spans_exported" }, + { Namespace: "mlobs", Type: "count", Name: "user_flush" }, + { Namespace: "mlobs", Type: "count", Name: "user_processor_called" }, + { Namespace: "mlobs", Type: "distribution", Name: "evaluators.rule_sample_rate" }, + { Namespace: "mlobs", Type: "distribution", Name: "init_time" }, + { Namespace: "mlobs", Type: "distribution", Name: "span.raw_size" }, + { Namespace: "mlobs", Type: "distribution", Name: "span.size" }, + { Namespace: "profilers", Type: "count", Name: "profile_api.errors" }, + { Namespace: "profilers", Type: "count", Name: "profile_api.requests" }, + { Namespace: "profilers", Type: "count", Name: "profile_api.responses" }, + { Namespace: "profilers", Type: "distribution", Name: "profile_api.bytes" }, + { Namespace: "profilers", Type: "distribution", Name: "profile_api.ms" }, + { Namespace: "rum", Type: "count", Name: "injection.content_security_policy" }, + { Namespace: "rum", Type: "count", Name: "injection.failed" }, + { Namespace: "rum", Type: "count", Name: "injection.initialization.failed" }, + { Namespace: "rum", Type: "count", Name: "injection.initialization.succeed" }, + { Namespace: "rum", Type: "count", Name: "injection.installation" }, + { Namespace: "rum", Type: "count", Name: "injection.skipped" }, + { Namespace: "rum", Type: "count", Name: "injection.succeed" }, + { Namespace: "rum", Type: "distribution", Name: "injection.installation.duration" }, + { Namespace: "rum", Type: "distribution", Name: "injection.ms" }, + { Namespace: "rum", Type: "distribution", Name: "injection.response.bytes" }, + { Namespace: "sidecar", Type: "count", Name: "server.submitted_payloads" }, + { Namespace: "sidecar", Type: "distribution", Name: "server.memory_usage" }, + { Namespace: "sidecar", Type: "gauge", Name: "server.active_sessions" }, + { Namespace: "telemetry", Type: "count", Name: "telemetry_api.errors" }, + { Namespace: "telemetry", Type: "count", Name: "telemetry_api.requests" }, + { Namespace: "telemetry", Type: "count", Name: "telemetry_api.responses" }, + { Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.bytes" }, + { Namespace: "telemetry", Type: "distribution", Name: "telemetry_api.ms" }, + { Namespace: "tracers", Type: "count", Name: "context_header.truncated" }, + { Namespace: "tracers", Type: "count", Name: "context_header_style.extracted" }, + { Namespace: "tracers", Type: "count", Name: "context_header_style.injected" }, + { Namespace: "tracers", Type: "count", Name: "docker_lib_injection.failure" }, + { Namespace: "tracers", Type: "count", Name: "docker_lib_injection.success" }, + { Namespace: "tracers", Type: "count", Name: "exporter_fallback" }, + { Namespace: "tracers", Type: "count", Name: "host_lib_injection.failure" }, + { Namespace: "tracers", Type: "count", Name: "host_lib_injection.success" }, + { Namespace: "tracers", Type: "count", Name: "inject.error" }, + { Namespace: "tracers", Type: "count", Name: "inject.language_detection" }, + { Namespace: "tracers", Type: "count", Name: "inject.skip" }, + { Namespace: "tracers", Type: "count", Name: "inject.success" }, + { Namespace: "tracers", Type: "count", Name: "integration_errors" }, + { Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.failure" }, + { Namespace: "tracers", Type: "count", Name: "k8s_lib_injection.success" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.integration" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.abort.runtime" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.complete" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.error" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.injector.error" }, + { Namespace: "tracers", Type: "count", Name: "library_entrypoint.start" }, + { Namespace: "tracers", Type: "count", Name: "otel.env.hiding" }, + { Namespace: "tracers", Type: "count", Name: "otel.env.invalid" }, + { Namespace: "tracers", Type: "count", Name: "otel.env.unsupported" }, + { Namespace: "tracers", Type: "count", Name: "public_api.called" }, + { Namespace: "tracers", Type: "count", Name: "span_created" }, + { Namespace: "tracers", Type: "count", Name: "span_finished" }, + { Namespace: "tracers", Type: "count", Name: "span_pointer_calculation" }, + { Namespace: "tracers", Type: "count", Name: "span_pointer_calculation.issue" }, + { Namespace: "tracers", Type: "count", Name: "spans_created" }, + { Namespace: "tracers", Type: "count", Name: "spans_dropped" }, + { Namespace: "tracers", Type: "count", Name: "spans_enqueued_for_serialization" }, + { Namespace: "tracers", Type: "count", Name: "spans_finished" }, + { Namespace: "tracers", Type: "count", Name: "stats_api.errors" }, + { Namespace: "tracers", Type: "count", Name: "stats_api.requests" }, + { Namespace: "tracers", Type: "count", Name: "stats_api.responses" }, + { Namespace: "tracers", Type: "count", Name: "trace_api.errors" }, + { Namespace: "tracers", Type: "count", Name: "trace_api.requests" }, + { Namespace: "tracers", Type: "count", Name: "trace_api.responses" }, + { Namespace: "tracers", Type: "count", Name: "trace_chunks_dropped" }, + { Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued" }, + { Namespace: "tracers", Type: "count", Name: "trace_chunks_enqueued_for_serialization" }, + { Namespace: "tracers", Type: "count", Name: "trace_chunks_sent" }, + { Namespace: "tracers", Type: "count", Name: "trace_partial_flush.count" }, + { Namespace: "tracers", Type: "count", Name: "trace_segments_closed" }, + { Namespace: "tracers", Type: "count", Name: "trace_segments_created" }, + { Namespace: "tracers", Type: "distribution", Name: "inject.latency.baseline" }, + { Namespace: "tracers", Type: "distribution", Name: "inject.latency.end_to_end" }, + { Namespace: "tracers", Type: "distribution", Name: "inject.latency.init_container" }, + { Namespace: "tracers", Type: "distribution", Name: "stats_api.bytes" }, + { Namespace: "tracers", Type: "distribution", Name: "stats_api.ms" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_api.bytes" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_api.ms" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.bytes" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_chunk_serialization.ms" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_chunk_size" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_closed" }, + { Namespace: "tracers", Type: "distribution", Name: "trace_partial_flush.spans_remaining" }, + { Namespace: "tracers", Type: "gauge", Name: "stats_buckets" }, } From ee521b4c729eaf6911d13afaef2359af2e99de4a Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 27 Oct 2025 11:45:33 -0400 Subject: [PATCH 07/26] restore unrelated files to their status on main --- internal/env/supported_configurations.gen.go | 2 -- internal/env/supported_configurations.json | 6 ------ 2 files changed, 8 deletions(-) diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index 9ea9b93f49..2ae4274dd2 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -195,8 +195,6 @@ var SupportedConfigurations = map[string]struct{}{ "DD_TRACE_REDIS_RAW_COMMAND": {}, "DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED": {}, "DD_TRACE_REPORT_HOSTNAME": {}, - "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT": {}, - "DD_TRACE_RESOURCE_RENAMING_ENABLED": {}, "DD_TRACE_RESTFUL_ANALYTICS_ENABLED": {}, "DD_TRACE_SAMPLE_RATE": {}, "DD_TRACE_SAMPLING_RULES": {}, diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index 1659ba5e5f..e8a2e86411 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -558,12 +558,6 @@ "DD_TRACE_REPORT_HOSTNAME": [ "A" ], - "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT": [ - "A" - ], - "DD_TRACE_RESOURCE_RENAMING_ENABLED": [ - "A" - ], "DD_TRACE_RESTFUL_ANALYTICS_ENABLED": [ "A" ], From c3c69fe9ff73d000984f66f51a8d9531e07d62fc Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 27 Oct 2025 11:53:37 -0400 Subject: [PATCH 08/26] Fix file format in supported_configurations.json --- internal/env/supported_configurations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index e8a2e86411..532e91f198 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -645,4 +645,4 @@ "DD-API-KEY" ] } -} \ No newline at end of file +} From eeebfd1a9d8198e8e24121644a3143da586605b6 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 27 Oct 2025 15:40:25 -0400 Subject: [PATCH 09/26] cleanup/nits --- internal/config/config.go | 83 +++++----- internal/config/config_test.go | 173 +++++++++------------ internal/config/configprovider.go | 72 +++++++++ internal/config/configprovider_test.go | 30 ++-- internal/config/otelenvconfigsource.go | 16 ++ internal/env/supported_configurations.json | 29 +++- 6 files changed, 251 insertions(+), 152 deletions(-) create mode 100644 internal/config/otelenvconfigsource.go diff --git a/internal/config/config.go b/internal/config/config.go index 3fd4db93d1..2a35f91bb3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,7 +6,6 @@ package config import ( - "net/http" "net/url" "time" ) @@ -21,50 +20,31 @@ type Config struct { // Debug enables debug logging. Debug bool `json:"DD_TRACE_DEBUG"` // has trace in the name, but impacts all products? - // HTTPClient is the HTTP client to use for sending requests to the Datadog agent. - HTTPClient *http.Client - - FeatureFlags map[string]struct{} `json:"DD_TRACE_FEATURE_FLAGS"` - LogToStdout bool `json:"DD_TRACE_LOG_TO_STDOUT"` - SendRetries int `json:"DD_TRACE_SEND_RETRIES"` - - RetryInterval int64 `json:"DD_TRACE_RETRY_INTERVAL"` - - LogStartup bool `json:"DD_TRACE_LOG_STARTUP"` - - ServiceName string `json:"DD_TRACE_SERVICE_NAME"` + LogStartup bool `json:"DD_TRACE_STARTUP_LOGS"` - UniversalVersion bool `json:"DD_TRACE_UNIVERSAL_VERSION"` + ServiceName string `json:"DD_SERVICE"` - Version string `json:"DD_TRACE_VERSION"` + Version string `json:"DD_VERSION"` - Env string `json:"DD_TRACE_ENV"` + Env string `json:"DD_ENV"` - ServiceMappings map[string]string `json:"DD_TRACE_SERVICE_MAPPING"` + ServiceMappings map[string]string `json:"DD_SERVICE_MAPPING"` HTTPClientTimeout int64 `json:"DD_TRACE_HTTP_CLIENT_TIMEOUT"` - Hostname string `json:"DD_TRACE_HOSTNAME"` + Hostname string `json:"DD_TRACE_SOURCE_HOSTNAME"` RuntimeMetrics bool `json:"DD_TRACE_RUNTIME_METRICS"` RuntimeMetricsV2 bool `json:"DD_TRACE_RUNTIME_METRICS_V2"` - DogstatsdAddr string `json:"DD_TRACE_DOGSTATSD_ADDR"` + ProfilerHotspots bool `json:"DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED"` - TickChan <-chan time.Time `json:"DD_TRACE_TICK_CHAN"` + ProfilerEndpoints bool `json:"DD_PROFILING_ENDPOINT_COLLECTION_ENABLED"` - NoDebugStack bool `json:"DD_TRACE_NO_DEBUG_STACK"` - - ProfilerHotspots bool `json:"DD_TRACE_PROFILER_HOTSPOTS"` - - ProfilerEndpoints bool `json:"DD_TRACE_PROFILER_ENDPOINTS"` - - EnableHostnameDetection bool `json:"DD_TRACE_ENABLE_HOSTNAME_DETECTION"` - - SpanAttributeSchemaVersion int `json:"DD_TRACE_SPAN_ATTRIBUTE_SCHEMA_VERSION"` + SpanAttributeSchemaVersion int `json:"DD_TRACE_SPAN_ATTRIBUTE_SCHEMA"` PeerServiceDefaultsEnabled bool `json:"DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED"` @@ -80,23 +60,21 @@ type Config struct { StatsComputationEnabled bool `json:"DD_TRACE_STATS_COMPUTATION_ENABLED"` - DataStreamsMonitoringEnabled bool `json:"DD_TRACE_DATA_STREAMS_MONITORING_ENABLED"` + DataStreamsMonitoringEnabled bool `json:"DD_DATA_STREAMS_ENABLED"` - DynamicInstrumentationEnabled bool `json:"DD_TRACE_DYNAMIC_INSTRUMENTATION_ENABLED"` + DynamicInstrumentationEnabled bool `json:"DD_DYNAMIC_INSTRUMENTATION_ENABLED"` - GlobalSampleRate float64 `json:"DD_TRACE_GLOBAL_SAMPLE_RATE"` + GlobalSampleRate float64 `json:"DD_TRACE_SAMPLE_RATE"` - CIVisibilityEnabled bool `json:"DD_TRACE_CI_VISIBILITY_ENABLED"` + CIVisibilityEnabled bool `json:"DD_CIVISIBILITY_ENABLED"` - CIVisibilityAgentless bool `json:"DD_TRACE_CI_VISIBILITY_AGENTLESS"` + CIVisibilityAgentless bool `json:"DD_CIVISIBILITY_AGENTLESS_ENABLED"` LogDirectory string `json:"DD_TRACE_LOG_DIRECTORY"` - TracingAsTransport bool `json:"DD_TRACE_TRACING_AS_TRANSPORT"` - - TraceRateLimitPerSecond float64 `json:"DD_TRACE_TRACE_RATE_LIMIT_PER_SECOND"` + TraceRateLimitPerSecond float64 `json:"DD_TRACE_RATE_LIMIT"` - TraceProtocol float64 `json:"DD_TRACE_TRACE_PROTOCOL"` + TraceProtocol float64 `json:"DD_TRACE_AGENT_PROTOCOL_VERSION"` } func loadConfig() *Config { @@ -105,7 +83,34 @@ func loadConfig() *Config { // TODO: Use defaults from config json instead of hardcoding them here cfg.AgentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) cfg.Debug = provider.getBool("DD_TRACE_DEBUG", false) - // TODO: Initialize all fields + cfg.LogToStdout = provider.getBool("DD_TRACE_LOG_TO_STDOUT", false) + cfg.LogStartup = provider.getBool("DD_TRACE_STARTUP_LOGS", false) + cfg.ServiceName = provider.getString("DD_SERVICE", "") + cfg.Version = provider.getString("DD_VERSION", "") + cfg.Env = provider.getString("DD_ENV", "") + cfg.ServiceMappings = provider.getMap("DD_SERVICE_MAPPING", nil) + cfg.HTTPClientTimeout = provider.getInt64("DD_TRACE_HTTP_CLIENT_TIMEOUT", 0) + cfg.Hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") + cfg.RuntimeMetrics = provider.getBool("DD_TRACE_RUNTIME_METRICS", false) + cfg.RuntimeMetricsV2 = provider.getBool("DD_TRACE_RUNTIME_METRICS_V2", false) + cfg.ProfilerHotspots = provider.getBool("DD_TRACE_PROFILER_HOTSPOTS", false) + cfg.ProfilerEndpoints = provider.getBool("DD_TRACE_PROFILER_ENDPOINTS", false) + cfg.SpanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) + cfg.PeerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) + cfg.PeerServiceMappings = provider.getMap("DD_TRACE_PEER_SERVICE_MAPPING", nil) + cfg.DebugAbandonedSpans = provider.getBool("DD_TRACE_DEBUG_ABANDONED_SPANS", false) + cfg.SpanTimeout = provider.getDuration("DD_TRACE_SPAN_TIMEOUT", 0) + cfg.PartialFlushMinSpans = provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0) + cfg.PartialFlushEnabled = provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false) + cfg.StatsComputationEnabled = provider.getBool("DD_TRACE_STATS_COMPUTATION_ENABLED", false) + cfg.DataStreamsMonitoringEnabled = provider.getBool("DD_DATA_STREAMS_ENABLED", false) + cfg.DynamicInstrumentationEnabled = provider.getBool("DD_DYNAMIC_INSTRUMENTATION_ENABLED", false) + cfg.GlobalSampleRate = provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0) + cfg.CIVisibilityEnabled = provider.getBool("DD_CIVISIBILITY_ENABLED", false) + cfg.CIVisibilityAgentless = provider.getBool("DD_CIVISIBILITY_AGENTLESS_ENABLED", false) + cfg.LogDirectory = provider.getString("DD_TRACE_LOG_DIRECTORY", "") + cfg.TraceRateLimitPerSecond = provider.getFloat("DD_TRACE_RATE_LIMIT", 0.0) + cfg.TraceProtocol = provider.getFloat("DD_TRACE_AGENT_PROTOCOL_VERSION", 0.0) return cfg } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e436dcabaf..91e0cdf140 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -5,105 +5,84 @@ package config -import ( - "net/http" - "net/url" - "reflect" - "testing" - "time" +// func TestConfigHasFields(t *testing.T) { +// // TODO: Use supported configurations JSON as expectedFields instead +// expectedFields := map[string]reflect.Type{ +// "AgentURL": reflect.TypeOf((*url.URL)(nil)), +// "Debug": reflect.TypeOf(false), +// "LogToStdout": reflect.TypeOf(false), +// "LogStartup": reflect.TypeOf(false), +// "ServiceName": reflect.TypeOf(""), +// "Version": reflect.TypeOf(""), +// "Env": reflect.TypeOf(""), +// //"Sampler": reflect.TypeOf((*RateSampler)(nil)), +// // "OriginalAgentURL": reflect.TypeOf((*url.URL)(nil)), // We probably don't need this anymore +// "ServiceMappings": reflect.TypeOf((map[string]string)(nil)), +// // "GlobalTags": reflect.TypeOf((*dynamicConfig[map[string]interface{}])(nil)), +// // "Transport": reflect.TypeOf((*transport)(nil)), +// "HTTPClientTimeout": reflect.TypeOf(int64(0)), +// // "Propagator": reflect.TypeOf((*Propagator)(nil)), +// "Hostname": reflect.TypeOf(""), +// // "Logger": reflect.TypeOf((*Logger)(nil)), +// "RuntimeMetrics": reflect.TypeOf(false), +// "RuntimeMetricsV2": reflect.TypeOf(false), +// // "StatsdClient": reflect.TypeOf((*internal.StatsdClient)(nil)), +// // "SpanRules": reflect.TypeOf((*[]SamplingRule)(nil)), +// // "TraceRules": reflect.TypeOf((*[]SamplingRule)(nil)), +// "ProfilerHotspots": reflect.TypeOf(false), +// "ProfilerEndpoints": reflect.TypeOf(false), +// // "TracingEnabled": reflect.TypeOf((*dynamicConfig[bool])(nil)), +// "EnableHostnameDetection": reflect.TypeOf(false), +// "SpanAttributeSchemaVersion": reflect.TypeOf(0), +// "PeerServiceDefaultsEnabled": reflect.TypeOf(false), +// "PeerServiceMappings": reflect.TypeOf((map[string]string)(nil)), +// "DebugAbandonedSpans": reflect.TypeOf(false), +// "SpanTimeout": reflect.TypeOf(time.Duration(int64(0))), +// "PartialFlushMinSpans": reflect.TypeOf(0), +// "PartialFlushEnabled": reflect.TypeOf(false), +// "StatsComputationEnabled": reflect.TypeOf(false), +// "DataStreamsMonitoringEnabled": reflect.TypeOf(false), +// // "OrchestrionCfg": reflect.TypeOf((*orchestrionConfig)(nil)), +// // "TraceSampleRate": reflect.TypeOf((*dynamicConfig[float64])(nil)), +// // "TraceSampleRules": reflect.TypeOf((*dynamicConfig[[]SamplingRule])(nil)), +// // "HeaderAsTags": reflect.TypeOf((*dynamicConfig[[]string])(nil)), +// "DynamicInstrumentationEnabled": reflect.TypeOf(false), +// "GlobalSampleRate": reflect.TypeOf(float64(0)), +// "CIVisibilityEnabled": reflect.TypeOf(false), +// "CIVisibilityAgentless": reflect.TypeOf(false), +// "LogDirectory": reflect.TypeOf(""), +// "TracingAsTransport": reflect.TypeOf(false), +// "TraceRateLimitPerSecond": reflect.TypeOf(float64(0)), +// "TraceProtocol": reflect.TypeOf(float64(0)), +// // "LLMObsEnabled": reflect.TypeOf(false), +// // "LLMObsMLApp": reflect.TypeOf(""), +// // "LLMObsAgentlessEnabled": reflect.TypeOf(false), +// // "LLMObsProjectName": reflect.TypeOf(""), +// } - "github.com/stretchr/testify/assert" -) +// // Get the Config struct type +// configType := reflect.TypeOf(Config{}) -func TestConfigHasFields(t *testing.T) { - // Define expected fields with their types - expectedFields := map[string]reflect.Type{ - "AgentURL": reflect.TypeOf((*url.URL)(nil)), - "Debug": reflect.TypeOf(false), - "HTTPClient": reflect.TypeOf((*http.Client)(nil)), - //appsecStartOptions? - // "Agent": reflect.TypeOf((*agentFeatures)(nil)), - // "Integrations": reflect.TypeOf((*map[string]integrationConfig)(nil)), - "FeatureFlags": reflect.TypeOf((map[string]struct{})(nil)), - "LogToStdout": reflect.TypeOf(false), - "SendRetries": reflect.TypeOf(0), - "RetryInterval": reflect.TypeOf(int64(0)), - "LogStartup": reflect.TypeOf(false), - "ServiceName": reflect.TypeOf(""), - "UniversalVersion": reflect.TypeOf(false), - "Version": reflect.TypeOf(""), - "Env": reflect.TypeOf(""), - //"Sampler": reflect.TypeOf((*RateSampler)(nil)), - // "OriginalAgentURL": reflect.TypeOf((*url.URL)(nil)), // We probably don't need this anymore - "ServiceMappings": reflect.TypeOf((map[string]string)(nil)), - // "GlobalTags": reflect.TypeOf((*dynamicConfig[map[string]interface{}])(nil)), - // "Transport": reflect.TypeOf((*transport)(nil)), - "HTTPClientTimeout": reflect.TypeOf(int64(0)), - // "Propagator": reflect.TypeOf((*Propagator)(nil)), - "Hostname": reflect.TypeOf(""), - // "Logger": reflect.TypeOf((*Logger)(nil)), - "RuntimeMetrics": reflect.TypeOf(false), - "RuntimeMetricsV2": reflect.TypeOf(false), - "DogstatsdAddr": reflect.TypeOf(""), - // "StatsdClient": reflect.TypeOf((*internal.StatsdClient)(nil)), - // "SpanRules": reflect.TypeOf((*[]SamplingRule)(nil)), - // "TraceRules": reflect.TypeOf((*[]SamplingRule)(nil)), - "TickChan": reflect.TypeOf((<-chan time.Time)(nil)), - "NoDebugStack": reflect.TypeOf(false), - "ProfilerHotspots": reflect.TypeOf(false), - "ProfilerEndpoints": reflect.TypeOf(false), - // "TracingEnabled": reflect.TypeOf((*dynamicConfig[bool])(nil)), - "EnableHostnameDetection": reflect.TypeOf(false), - "SpanAttributeSchemaVersion": reflect.TypeOf(0), - "PeerServiceDefaultsEnabled": reflect.TypeOf(false), - "PeerServiceMappings": reflect.TypeOf((map[string]string)(nil)), - "DebugAbandonedSpans": reflect.TypeOf(false), - "SpanTimeout": reflect.TypeOf(time.Duration(int64(0))), - "PartialFlushMinSpans": reflect.TypeOf(0), - "PartialFlushEnabled": reflect.TypeOf(false), - "StatsComputationEnabled": reflect.TypeOf(false), - "DataStreamsMonitoringEnabled": reflect.TypeOf(false), - // "OrchestrionCfg": reflect.TypeOf((*orchestrionConfig)(nil)), - // "TraceSampleRate": reflect.TypeOf((*dynamicConfig[float64])(nil)), - // "TraceSampleRules": reflect.TypeOf((*dynamicConfig[[]SamplingRule])(nil)), - // "HeaderAsTags": reflect.TypeOf((*dynamicConfig[[]string])(nil)), - "DynamicInstrumentationEnabled": reflect.TypeOf(false), - "GlobalSampleRate": reflect.TypeOf(float64(0)), - "CIVisibilityEnabled": reflect.TypeOf(false), - "CIVisibilityAgentless": reflect.TypeOf(false), - "LogDirectory": reflect.TypeOf(""), - "TracingAsTransport": reflect.TypeOf(false), - "TraceRateLimitPerSecond": reflect.TypeOf(float64(0)), - "TraceProtocol": reflect.TypeOf(float64(0)), - // "LLMObsEnabled": reflect.TypeOf(false), - // "LLMObsMLApp": reflect.TypeOf(""), - // "LLMObsAgentlessEnabled": reflect.TypeOf(false), - // "LLMObsProjectName": reflect.TypeOf(""), - } +// // Verify the number of expected fields matches the actual number of fields +// actualFieldCount := configType.NumField() +// expectedFieldCount := len(expectedFields) +// assert.Equal(t, expectedFieldCount, actualFieldCount, +// "Expected %d fields in Config struct, but found %d. Update the test when adding/removing fields.", +// expectedFieldCount, actualFieldCount) - // Get the Config struct type - configType := reflect.TypeOf(Config{}) +// // Verify each expected field exists with the correct type +// for fieldName, expectedType := range expectedFields { +// field, found := configType.FieldByName(fieldName) +// assert.True(t, found, "Field %s should exist on Config struct", fieldName) - // Verify the number of expected fields matches the actual number of fields - actualFieldCount := configType.NumField() - expectedFieldCount := len(expectedFields) - assert.Equal(t, expectedFieldCount, actualFieldCount, - "Expected %d fields in Config struct, but found %d. Update the test when adding/removing fields.", - expectedFieldCount, actualFieldCount) +// if found { +// assert.Equal(t, expectedType, field.Type, +// "Field %s should have type %s, but has type %s", +// fieldName, expectedType, field.Type) +// } +// } - // Verify each expected field exists with the correct type - for fieldName, expectedType := range expectedFields { - field, found := configType.FieldByName(fieldName) - assert.True(t, found, "Field %s should exist on Config struct", fieldName) - - if found { - assert.Equal(t, expectedType, field.Type, - "Field %s should have type %s, but has type %s", - fieldName, expectedType, field.Type) - } - } - - // Verify we can instantiate the config - cfg := new(Config) - assert.NotNil(t, cfg, "Should be able to create new Config instance") -} +// // Verify we can instantiate the config +// cfg := new(Config) +// assert.NotNil(t, cfg, "Should be able to create new Config instance") +// } diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index e88013677d..eb359dc03d 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -9,6 +9,7 @@ import ( "net/url" "strconv" "strings" + "time" ) var provider = DefaultConfigProvider() @@ -52,6 +53,42 @@ func (p *ConfigProvider) getInt(key string, def int) int { return def } +func (p *ConfigProvider) getInt64(key string, def int64) int64 { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + v, err := strconv.ParseInt(v, 10, 64) + if err == nil { + return v + } + } + } + return def +} + +func (p *ConfigProvider) getMap(key string, def map[string]string) map[string]string { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + m := parseMapString(v) + if len(m) > 0 { + return m + } + } + } + return def +} + +func (p *ConfigProvider) getDuration(key string, def time.Duration) time.Duration { + for _, source := range p.sources { + if v := source.Get(key); v != "" { + d, err := time.ParseDuration(v) + if err == nil { + return d + } + } + } + return def +} + func (p *ConfigProvider) getFloat(key string, def float64) float64 { for _, source := range p.sources { if v := source.Get(key); v != "" { @@ -98,3 +135,38 @@ func normalizeKey(key string) string { } return "DD_" + strings.ToUpper(key) } + +// parseMapString parses a string containing key:value pairs separated by comma or space. +// Format: "key1:value1,key2:value2" or "key1:value1 key2:value2" +func parseMapString(str string) map[string]string { + result := make(map[string]string) + + // Determine separator (comma or space) + sep := " " + if strings.Contains(str, ",") { + sep = "," + } + + // Parse each key:value pair + for _, pair := range strings.Split(str, sep) { + pair = strings.TrimSpace(pair) + if pair == "" { + continue + } + + // Split on colon delimiter + kv := strings.SplitN(pair, ":", 2) + key := strings.TrimSpace(kv[0]) + if key == "" { + continue + } + + var val string + if len(kv) == 2 { + val = strings.TrimSpace(kv[1]) + } + result[key] = val + } + + return result +} diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index 778dfebfbb..d21b1b5fef 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -44,23 +44,23 @@ func TestGetMethods(t *testing.T) { provider := newTestConfigProvider(newTestConfigSource(nil)) assert.Equal(t, "value", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", true)) - assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 1)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 1)) assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 1.0)) assert.Equal(t, &url.URL{Scheme: "http", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"})) }) t.Run("non-defaults", func(t *testing.T) { // Test that non-defaults are used when the queried key exists entries := map[string]string{ - "DD_SERVICE": "string", - "DD_TRACE_DEBUG": "true", - "DD_TRACE_SEND_RETRIES": "1", - "DD_TRACE_SAMPLE_RATE": "1.0", - "DD_TRACE_AGENT_URL": "https://localhost:8126", + "DD_SERVICE": "string", + "DD_TRACE_DEBUG": "true", + "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS": "1", + "DD_TRACE_SAMPLE_RATE": "1.0", + "DD_TRACE_AGENT_URL": "https://localhost:8126", } provider := newTestConfigProvider(newTestConfigSource(entries)) assert.Equal(t, "string", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) - assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) }) @@ -71,7 +71,7 @@ func TestDefaultConfigProvider(t *testing.T) { // Setup: environment variables of each type t.Setenv("DD_SERVICE", "string") t.Setenv("DD_TRACE_DEBUG", "true") - t.Setenv("DD_TRACE_SEND_RETRIES", "1") + t.Setenv("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", "1") t.Setenv("DD_TRACE_SAMPLE_RATE", "1.0") t.Setenv("DD_TRACE_AGENT_URL", "https://localhost:8126") // TODO: Add more types as we go along @@ -81,7 +81,7 @@ func TestDefaultConfigProvider(t *testing.T) { // Configured values are returned correctly assert.Equal(t, "string", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) - assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) @@ -93,7 +93,7 @@ func TestDefaultConfigProvider(t *testing.T) { apm_configuration_default: DD_SERVICE: local DD_TRACE_DEBUG: true - DD_TRACE_SEND_RETRIES: "1" + DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: "1" DD_TRACE_SAMPLE_RATE: 1.0 DD_TRACE_AGENT_URL: https://localhost:8126 ` @@ -112,7 +112,7 @@ apm_configuration_default: assert.Equal(t, "local", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) - assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) @@ -125,7 +125,7 @@ apm_configuration_default: apm_configuration_default: DD_SERVICE: managed DD_TRACE_DEBUG: true - DD_TRACE_SEND_RETRIES: "1" + DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: "1" DD_TRACE_SAMPLE_RATE: 1.0 DD_TRACE_AGENT_URL: https://localhost:8126` @@ -143,7 +143,7 @@ apm_configuration_default: assert.Equal(t, "managed", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) - assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) @@ -156,7 +156,7 @@ apm_configuration_default: DD_SERVICE: local DD_TRACE_DEBUG: false DD_TRACE_HOSTNAME: otherhost - DD_TRACE_SEND_RETRIES: "1"` + DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: "1"` managedYaml := ` apm_configuration_default: @@ -194,7 +194,7 @@ apm_configuration_default: assert.Equal(t, "managed", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) assert.Equal(t, "otherhost", provider.getString("DD_TRACE_HOSTNAME", "value")) - assert.Equal(t, 1, provider.getInt("DD_TRACE_SEND_RETRIES", 0)) + assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) assert.Equal(t, "dev", provider.getString("DD_ENV", "value")) assert.Equal(t, "1.0.0", provider.getString("DD_VERSION", "0")) assert.Equal(t, true, provider.getBool("DD_TRACE_LOG_TO_STDOUT", false)) diff --git a/internal/config/otelenvconfigsource.go b/internal/config/otelenvconfigsource.go new file mode 100644 index 0000000000..23e224a4a5 --- /dev/null +++ b/internal/config/otelenvconfigsource.go @@ -0,0 +1,16 @@ +// 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 2025 Datadog, Inc. + +package config + +// import "github.com/DataDog/dd-trace-go/v2/internal/env" + +// type otelEnvConfigSource struct{ +// keys map[string]string +// } + +// func (o *otelEnvConfigSource) Get(key string) string { +// return o.keys[key] +// } diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index 532e91f198..9d0e9945a7 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -456,6 +456,9 @@ "DD_TRACE_HEADER_TAGS": [ "A" ], + "DD_TRACE_HOSTNAME": [ + "A" + ], "DD_TRACE_HTTPROUTER_ANALYTICS_ENABLED": [ "A" ], @@ -474,6 +477,9 @@ "DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING": [ "A" ], + "DD_TRACE_HTTP_CLIENT_TIMEOUT": [ + "A" + ], "DD_TRACE_HTTP_HANDLER_RESOURCE_NAME_QUANTIZE": [ "A" ], @@ -498,6 +504,9 @@ "DD_TRACE_LOG_DIRECTORY": [ "A" ], + "DD_TRACE_LOG_TO_STDOUT": [ + "A" + ], "DD_TRACE_MEMCACHE_ANALYTICS_ENABLED": [ "A" ], @@ -528,6 +537,12 @@ "DD_TRACE_PEER_SERVICE_MAPPING": [ "A" ], + "DD_TRACE_PROFILER_ENDPOINTS": [ + "A" + ], + "DD_TRACE_PROFILER_HOTSPOTS": [ + "A" + ], "DD_TRACE_PROPAGATION_EXTRACT_FIRST": [ "A" ], @@ -561,6 +576,12 @@ "DD_TRACE_RESTFUL_ANALYTICS_ENABLED": [ "A" ], + "DD_TRACE_RUNTIME_METRICS": [ + "A" + ], + "DD_TRACE_RUNTIME_METRICS_V2": [ + "A" + ], "DD_TRACE_SAMPLE_RATE": [ "A" ], @@ -573,12 +594,18 @@ "DD_TRACE_SARAMA_ANALYTICS_ENABLED": [ "A" ], + "DD_TRACE_SEND_RETRIES": [ + "A" + ], "DD_TRACE_SOURCE_HOSTNAME": [ "A" ], "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA": [ "A" ], + "DD_TRACE_SPAN_TIMEOUT": [ + "A" + ], "DD_TRACE_SQL_ANALYTICS_ENABLED": [ "A" ], @@ -645,4 +672,4 @@ "DD-API-KEY" ] } -} +} \ No newline at end of file From 0e2527eb963a84dc8ad6ffcd75f33550a97ea23b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 30 Oct 2025 11:42:54 -0400 Subject: [PATCH 10/26] Fix references to fake configs: DD_TRACE_HOSTNAME DD_TRACE_HTTP_CLIENT_TIMEOUT DD_TRACE_LOG_TO_STDOUT DD_TRACE_PROFILER_ENDPOINTS DD_TRACE_PROFILER_HOTSPOTS DD_TRACE_RUNTIME_METRICS DD_TRACE_RUNTIME_METRICS_V2 DD_TRACE_SEND_RETRIES DD_TRACE_SPAN_TIMEOUT --- internal/config/config.go | 20 ++++++---------- internal/config/configprovider_test.go | 12 +++++----- internal/env/supported_configurations.json | 27 ---------------------- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 2a35f91bb3..0a764b8ec1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,8 +20,6 @@ type Config struct { // Debug enables debug logging. Debug bool `json:"DD_TRACE_DEBUG"` // has trace in the name, but impacts all products? - LogToStdout bool `json:"DD_TRACE_LOG_TO_STDOUT"` - LogStartup bool `json:"DD_TRACE_STARTUP_LOGS"` ServiceName string `json:"DD_SERVICE"` @@ -32,13 +30,11 @@ type Config struct { ServiceMappings map[string]string `json:"DD_SERVICE_MAPPING"` - HTTPClientTimeout int64 `json:"DD_TRACE_HTTP_CLIENT_TIMEOUT"` - Hostname string `json:"DD_TRACE_SOURCE_HOSTNAME"` - RuntimeMetrics bool `json:"DD_TRACE_RUNTIME_METRICS"` + RuntimeMetrics bool `json:"DD_RUNTIME_METRICS"` - RuntimeMetricsV2 bool `json:"DD_TRACE_RUNTIME_METRICS_V2"` + RuntimeMetricsV2 bool `json:"DD_RUNTIME_METRICS_V2_ENABLED"` ProfilerHotspots bool `json:"DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED"` @@ -83,23 +79,21 @@ func loadConfig() *Config { // TODO: Use defaults from config json instead of hardcoding them here cfg.AgentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) cfg.Debug = provider.getBool("DD_TRACE_DEBUG", false) - cfg.LogToStdout = provider.getBool("DD_TRACE_LOG_TO_STDOUT", false) cfg.LogStartup = provider.getBool("DD_TRACE_STARTUP_LOGS", false) cfg.ServiceName = provider.getString("DD_SERVICE", "") cfg.Version = provider.getString("DD_VERSION", "") cfg.Env = provider.getString("DD_ENV", "") cfg.ServiceMappings = provider.getMap("DD_SERVICE_MAPPING", nil) - cfg.HTTPClientTimeout = provider.getInt64("DD_TRACE_HTTP_CLIENT_TIMEOUT", 0) cfg.Hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") - cfg.RuntimeMetrics = provider.getBool("DD_TRACE_RUNTIME_METRICS", false) - cfg.RuntimeMetricsV2 = provider.getBool("DD_TRACE_RUNTIME_METRICS_V2", false) - cfg.ProfilerHotspots = provider.getBool("DD_TRACE_PROFILER_HOTSPOTS", false) - cfg.ProfilerEndpoints = provider.getBool("DD_TRACE_PROFILER_ENDPOINTS", false) + cfg.RuntimeMetrics = provider.getBool("DD_RUNTIME_METRICS", false) + cfg.RuntimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", false) + cfg.ProfilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", false) + cfg.ProfilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", false) cfg.SpanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) cfg.PeerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) cfg.PeerServiceMappings = provider.getMap("DD_TRACE_PEER_SERVICE_MAPPING", nil) cfg.DebugAbandonedSpans = provider.getBool("DD_TRACE_DEBUG_ABANDONED_SPANS", false) - cfg.SpanTimeout = provider.getDuration("DD_TRACE_SPAN_TIMEOUT", 0) + cfg.SpanTimeout = provider.getDuration("DD_TRACE_ABANDONED_SPAN_TIMEOUT", 0) cfg.PartialFlushMinSpans = provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0) cfg.PartialFlushEnabled = provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false) cfg.StatsComputationEnabled = provider.getBool("DD_TRACE_STATS_COMPUTATION_ENABLED", false) diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index d21b1b5fef..6318fea963 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -155,20 +155,20 @@ apm_configuration_default: apm_configuration_default: DD_SERVICE: local DD_TRACE_DEBUG: false - DD_TRACE_HOSTNAME: otherhost + DD_TRACE_SOURCE_HOSTNAME: otherhost DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: "1"` managedYaml := ` apm_configuration_default: DD_SERVICE: managed DD_TRACE_DEBUG: true - DD_TRACE_LOG_TO_STDOUT: true + DD_TRACE_PARTIAL_FLUSH_ENABLED: true DD_VERSION: 1.0.0` t.Setenv("DD_SERVICE", "env") - t.Setenv("DD_TRACE_LOG_TO_STDOUT", "false") + t.Setenv("DD_TRACE_PARTIAL_FLUSH_ENABLED", "false") t.Setenv("DD_ENV", "dev") - t.Setenv("DD_TRACE_HOSTNAME", "otherhost") + t.Setenv("DD_TRACE_SOURCE_HOSTNAME", "otherhost") tempLocalPath := "local.yml" err := os.WriteFile(tempLocalPath, []byte(localYaml), 0644) @@ -193,11 +193,11 @@ apm_configuration_default: provider := DefaultConfigProvider() assert.Equal(t, "managed", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) - assert.Equal(t, "otherhost", provider.getString("DD_TRACE_HOSTNAME", "value")) + assert.Equal(t, "otherhost", provider.getString("DD_TRACE_SOURCE_HOSTNAME", "value")) assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) assert.Equal(t, "dev", provider.getString("DD_ENV", "value")) assert.Equal(t, "1.0.0", provider.getString("DD_VERSION", "0")) - assert.Equal(t, true, provider.getBool("DD_TRACE_LOG_TO_STDOUT", false)) + assert.Equal(t, true, provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false)) // Defaults are returned for settings that are not configured assert.Equal(t, false, provider.getBool("DD_TRACE_STARTUP_LOGS", false)) diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index 9d0e9945a7..e8a2e86411 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -456,9 +456,6 @@ "DD_TRACE_HEADER_TAGS": [ "A" ], - "DD_TRACE_HOSTNAME": [ - "A" - ], "DD_TRACE_HTTPROUTER_ANALYTICS_ENABLED": [ "A" ], @@ -477,9 +474,6 @@ "DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING": [ "A" ], - "DD_TRACE_HTTP_CLIENT_TIMEOUT": [ - "A" - ], "DD_TRACE_HTTP_HANDLER_RESOURCE_NAME_QUANTIZE": [ "A" ], @@ -504,9 +498,6 @@ "DD_TRACE_LOG_DIRECTORY": [ "A" ], - "DD_TRACE_LOG_TO_STDOUT": [ - "A" - ], "DD_TRACE_MEMCACHE_ANALYTICS_ENABLED": [ "A" ], @@ -537,12 +528,6 @@ "DD_TRACE_PEER_SERVICE_MAPPING": [ "A" ], - "DD_TRACE_PROFILER_ENDPOINTS": [ - "A" - ], - "DD_TRACE_PROFILER_HOTSPOTS": [ - "A" - ], "DD_TRACE_PROPAGATION_EXTRACT_FIRST": [ "A" ], @@ -576,12 +561,6 @@ "DD_TRACE_RESTFUL_ANALYTICS_ENABLED": [ "A" ], - "DD_TRACE_RUNTIME_METRICS": [ - "A" - ], - "DD_TRACE_RUNTIME_METRICS_V2": [ - "A" - ], "DD_TRACE_SAMPLE_RATE": [ "A" ], @@ -594,18 +573,12 @@ "DD_TRACE_SARAMA_ANALYTICS_ENABLED": [ "A" ], - "DD_TRACE_SEND_RETRIES": [ - "A" - ], "DD_TRACE_SOURCE_HOSTNAME": [ "A" ], "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA": [ "A" ], - "DD_TRACE_SPAN_TIMEOUT": [ - "A" - ], "DD_TRACE_SQL_ANALYTICS_ENABLED": [ "A" ], From 85ae2514a04751820520a7a4a48b31b72828bb38 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 30 Oct 2025 14:37:22 -0400 Subject: [PATCH 11/26] remove reference to DD_RUNTIME_METRICS --- internal/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 0a764b8ec1..14fa98b560 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -32,7 +32,7 @@ type Config struct { Hostname string `json:"DD_TRACE_SOURCE_HOSTNAME"` - RuntimeMetrics bool `json:"DD_RUNTIME_METRICS"` + RuntimeMetrics bool `json:"DD_RUNTIME_METRICS_ENABLED"` RuntimeMetricsV2 bool `json:"DD_RUNTIME_METRICS_V2_ENABLED"` @@ -85,7 +85,7 @@ func loadConfig() *Config { cfg.Env = provider.getString("DD_ENV", "") cfg.ServiceMappings = provider.getMap("DD_SERVICE_MAPPING", nil) cfg.Hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") - cfg.RuntimeMetrics = provider.getBool("DD_RUNTIME_METRICS", false) + cfg.RuntimeMetrics = provider.getBool("DD_RUNTIME_METRICS_ENABLED", false) cfg.RuntimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", false) cfg.ProfilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", false) cfg.ProfilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", false) From 2e5e8e66b5deda5a76e5d1646420338cc83c000a Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 3 Nov 2025 15:02:49 -0500 Subject: [PATCH 12/26] Introduce otelenvconfigsource, and add tests for it in configprovider_test.go --- ddtrace/tracer/option.go | 3 +- ddtrace/tracer/otel_dd_mappings.go | 3 +- internal/config/configprovider.go | 41 ++-- internal/config/configprovider_test.go | 109 ++++++++--- internal/config/otelenvconfigsource.go | 196 ++++++++++++++++++- internal/env/supported_configurations.gen.go | 2 +- internal/env/supported_configurations.json | 3 + 7 files changed, 299 insertions(+), 58 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 5d41d8943e..a48e9fcf7c 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -607,7 +607,8 @@ func newConfig(opts ...StartOption) (*config, error) { if c.logger != nil { log.UseLogger(c.logger) } - if internalconfig.GlobalConfig().IsDebugEnabled() { + globalConfig := internalconfig.GlobalConfig() + if globalConfig.IsDebugEnabled() { log.SetLevel(log.LevelDebug) } // Check if CI Visibility mode is enabled diff --git a/ddtrace/tracer/otel_dd_mappings.go b/ddtrace/tracer/otel_dd_mappings.go index 9d4e5c5a00..818ece81e0 100644 --- a/ddtrace/tracer/otel_dd_mappings.go +++ b/ddtrace/tracer/otel_dd_mappings.go @@ -93,7 +93,8 @@ var propagationMapping = map[string]string{ func getDDorOtelConfig(configName string) string { config, ok := otelDDConfigs[configName] if !ok { - panic(fmt.Sprintf("Programming Error: %v not found in supported configurations", configName)) + log.Debug("Programming Error: %v not found in supported configurations", configName) + return "" } // 1. Check managed stable config if handsOff diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index eb359dc03d..77ca5246a9 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -18,6 +18,21 @@ type ConfigProvider struct { sources []ConfigSource // In order of priority } +type ConfigSource interface { + Get(key string) string +} + +func DefaultConfigProvider() *ConfigProvider { + return &ConfigProvider{ + sources: []ConfigSource{ + ManagedDeclarativeConfig, + new(envConfigSource), + new(otelEnvConfigSource), + LocalDeclarativeConfig, + }, + } +} + func (p *ConfigProvider) getString(key string, def string) string { // TODO: Eventually, iterate over all sources and report telemetry for _, source := range p.sources { @@ -53,18 +68,6 @@ func (p *ConfigProvider) getInt(key string, def int) int { return def } -func (p *ConfigProvider) getInt64(key string, def int64) int64 { - for _, source := range p.sources { - if v := source.Get(key); v != "" { - v, err := strconv.ParseInt(v, 10, 64) - if err == nil { - return v - } - } - } - return def -} - func (p *ConfigProvider) getMap(key string, def map[string]string) map[string]string { for _, source := range p.sources { if v := source.Get(key); v != "" { @@ -113,20 +116,6 @@ func (p *ConfigProvider) getURL(key string, def *url.URL) *url.URL { return def } -func DefaultConfigProvider() *ConfigProvider { - return &ConfigProvider{ - sources: []ConfigSource{ - ManagedDeclarativeConfig, - new(envConfigSource), - LocalDeclarativeConfig, - }, - } -} - -type ConfigSource interface { - Get(key string) string -} - // normalizeKey is a helper function for ConfigSource implementations to normalize the key to a valid environment variable name. func normalizeKey(key string) string { // Try to convert key to a valid environment variable name diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index 6318fea963..fa3248cc77 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -88,6 +88,24 @@ func TestDefaultConfigProvider(t *testing.T) { // Defaults are returned for settings that are not configured assert.Equal(t, "value", provider.getString("DD_ENV", "value")) }) + + t.Run("Settings only exist in OtelEnvConfigSource", func(t *testing.T) { + t.Setenv("OTEL_SERVICE_NAME", "string") + t.Setenv("OTEL_LOG_LEVEL", "debug") + t.Setenv("OTEL_TRACES_SAMPLER", "parentbased_always_on") + t.Setenv("OTEL_TRACES_EXPORTER", "1.0") + t.Setenv("OTEL_PROPAGATORS", "https://localhost:8126") + t.Setenv("OTEL_RESOURCE_ATTRIBUTES", "key1=value1,key2=value2") + + provider := DefaultConfigProvider() + + assert.Equal(t, "string", provider.getString("DD_SERVICE", "value")) + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0)) + assert.Equal(t, 1.0, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0)) + assert.Equal(t, &url.URL{Scheme: "https", Host: "localhost:8126"}, provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "https", Host: "localhost:8126"})) + assert.Equal(t, "key1:value1,key2:value2", provider.getString("DD_TAGS", "key:value")) + }) t.Run("Settings only exist in LocalDeclarativeConfigSource", func(t *testing.T) { const localYaml = ` apm_configuration_default: @@ -151,25 +169,48 @@ apm_configuration_default: assert.Equal(t, "value", provider.getString("DD_ENV", "value")) }) t.Run("Settings exist in all ConfigSources", func(t *testing.T) { + // Priority order (highest to lowest): + // 1. ManagedDeclarativeConfig + // 2. EnvConfigSource (DD_* env vars) + // 3. OtelEnvConfigSource (OTEL_* env vars) + // 4. LocalDeclarativeConfig + + // Setup: Configure the same keys across multiple sources with different values + // to verify that the correct precedence is applied + localYaml := ` apm_configuration_default: - DD_SERVICE: local - DD_TRACE_DEBUG: false - DD_TRACE_SOURCE_HOSTNAME: otherhost - DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: "1"` + DD_SERVICE: local_service # Set in all 4 sources - should lose to Managed + DD_TRACE_DEBUG: false # Set in all 4 sources - should lose to Managed + DD_ENV: local_env # Set in 3 sources (Local, DD Env, OTEL) - should lose to DD Env + DD_VERSION: 0.1.0 # Set in 2 sources (Local, Managed) - should lose to Managed + DD_TRACE_SAMPLE_RATE: 0.1 # Set in 2 sources (Local, OTEL) - should lose to OTEL + DD_TRACE_AGENT_TIMEOUT: 5s # Only in Local - should WIN (lowest priority available) +` managedYaml := ` apm_configuration_default: - DD_SERVICE: managed - DD_TRACE_DEBUG: true - DD_TRACE_PARTIAL_FLUSH_ENABLED: true - DD_VERSION: 1.0.0` - - t.Setenv("DD_SERVICE", "env") - t.Setenv("DD_TRACE_PARTIAL_FLUSH_ENABLED", "false") - t.Setenv("DD_ENV", "dev") - t.Setenv("DD_TRACE_SOURCE_HOSTNAME", "otherhost") + DD_SERVICE: managed_service # Set in all 4 sources - should WIN (highest priority) + DD_TRACE_DEBUG: true # Set in all 4 sources - should WIN (highest priority) + DD_VERSION: 1.0.0 # Set in 2 sources (Local, Managed) - should WIN + DD_TRACE_PARTIAL_FLUSH_ENABLED: true # Set in 2 sources (Managed, DD Env) - should WIN +` + // DD Env vars - priority level 2 + t.Setenv("DD_SERVICE", "env_service") // Set in all 4 sources - should lose to Managed + t.Setenv("DD_TRACE_DEBUG", "false") // Set in all 4 sources - should lose to Managed + t.Setenv("DD_ENV", "env_environment") // Set in 3 sources - should WIN (higher than OTEL and Local) + t.Setenv("DD_TRACE_PARTIAL_FLUSH_ENABLED", "false") // Set in 2 sources - should lose to Managed + t.Setenv("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", "100") // Only in DD Env - should WIN + + // OTEL Env vars - priority level 3 + t.Setenv("OTEL_SERVICE_NAME", "otel_service") // Set in all 4 sources (maps to DD_SERVICE) - should lose to Managed + t.Setenv("OTEL_LOG_LEVEL", "debug") // Set in all 4 sources (maps to DD_TRACE_DEBUG) - should lose to Managed + t.Setenv("OTEL_RESOURCE_ATTRIBUTES", "deployment.environment=otel_env,service.version=0.5.0") // Set in 3 sources - should lose to DD Env for DD_ENV, but provide version if not in higher sources + t.Setenv("OTEL_TRACES_SAMPLER", "traceidratio") // Set in 2 sources (OTEL, Local) - should WIN over Local (maps to DD_TRACE_SAMPLE_RATE) + t.Setenv("OTEL_TRACES_SAMPLER_ARG", "0.8") // Provides sample rate value of 0.8 + + // Create config files tempLocalPath := "local.yml" err := os.WriteFile(tempLocalPath, []byte(localYaml), 0644) assert.NoError(t, err) @@ -191,15 +232,39 @@ apm_configuration_default: }() provider := DefaultConfigProvider() - assert.Equal(t, "managed", provider.getString("DD_SERVICE", "value")) - assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) - assert.Equal(t, "otherhost", provider.getString("DD_TRACE_SOURCE_HOSTNAME", "value")) - assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) - assert.Equal(t, "dev", provider.getString("DD_ENV", "value")) - assert.Equal(t, "1.0.0", provider.getString("DD_VERSION", "0")) - assert.Equal(t, true, provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false)) - // Defaults are returned for settings that are not configured - assert.Equal(t, false, provider.getBool("DD_TRACE_STARTUP_LOGS", false)) + // Assertions grouped by which source should win + + // Managed Config wins (set in all 4 sources) + assert.Equal(t, "managed_service", provider.getString("DD_SERVICE", "default"), + "DD_SERVICE: Managed should win over DD Env, OTEL, and Local") + assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false), + "DD_TRACE_DEBUG: Managed should win over DD Env, OTEL, and Local") + + // Managed Config wins (set in 2 sources: Managed + one other) + assert.Equal(t, "1.0.0", provider.getString("DD_VERSION", "default"), + "DD_VERSION: Managed should win over Local") + assert.Equal(t, true, provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false), + "DD_TRACE_PARTIAL_FLUSH_ENABLED: Managed should win over DD Env") + + // DD Env wins (set in 3 sources: DD Env, OTEL, Local) + assert.Equal(t, "env_environment", provider.getString("DD_ENV", "default"), + "DD_ENV: DD Env should win over OTEL and Local") + + // DD Env wins (only in DD Env) + assert.Equal(t, 100, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0), + "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: DD Env should win (only source)") + + // OTEL Env wins (set in 2 sources: OTEL, Local) + assert.Equal(t, 0.8, provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0), + "DD_TRACE_SAMPLE_RATE: OTEL should win over Local") + + // Local Config wins (only in Local) + assert.Equal(t, "5s", provider.getString("DD_TRACE_AGENT_TIMEOUT", "default"), + "DD_TRACE_AGENT_TIMEOUT: Local should win (only source)") + + // Defaults are returned for settings not configured anywhere + assert.Equal(t, false, provider.getBool("DD_TRACE_STARTUP_LOGS", false), + "Unconfigured setting should return default") }) } diff --git a/internal/config/otelenvconfigsource.go b/internal/config/otelenvconfigsource.go index 23e224a4a5..c8d1ff14cf 100644 --- a/internal/config/otelenvconfigsource.go +++ b/internal/config/otelenvconfigsource.go @@ -5,12 +5,194 @@ package config -// import "github.com/DataDog/dd-trace-go/v2/internal/env" +import ( + "fmt" + "strings" -// type otelEnvConfigSource struct{ -// keys map[string]string -// } + "github.com/DataDog/dd-trace-go/v2/internal" + "github.com/DataDog/dd-trace-go/v2/internal/env" + "github.com/DataDog/dd-trace-go/v2/internal/log" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" +) -// func (o *otelEnvConfigSource) Get(key string) string { -// return o.keys[key] -// } +const ( + ddPrefix = "config_datadog:" + otelPrefix = "config_opentelemetry:" +) + +type otelEnvConfigSource struct{} + +func (o *otelEnvConfigSource) Get(key string) string { + ddKey := normalizeKey(key) + entry := otelConfigs[ddKey] + if entry == nil { + return "" + } + otVal := env.Get(entry.ot) + if otVal == "" { + return "" + } + if ddVal := env.Get(ddKey); ddVal != "" { + log.Warn("Both %q and %q are set, using %s=%s", entry.ot, ddKey, entry.ot, ddVal) + telemetryTags := []string{ddPrefix + strings.ToLower(ddKey), otelPrefix + strings.ToLower(entry.ot)} + telemetry.Count(telemetry.NamespaceTracers, "otel.env.hiding", telemetryTags).Submit(1) + return ddVal + } + val, err := entry.remapper(otVal) + if err != nil { + log.Warn("%s", err.Error()) + telemetryTags := []string{ddPrefix + strings.ToLower(ddKey), otelPrefix + strings.ToLower(entry.ot)} + telemetry.Count(telemetry.NamespaceTracers, "otel.env.invalid", telemetryTags).Submit(1) + return "" + } + return val +} + +type otelDDEnv struct { + ot string + remapper func(string) (string, error) +} + +var otelConfigs = map[string]*otelDDEnv{ + "DD_SERVICE": { + ot: "OTEL_SERVICE_NAME", + remapper: mapService, + }, + "DD_RUNTIME_METRICS_ENABLED": { + ot: "OTEL_METRICS_EXPORTER", + remapper: mapMetrics, + }, + "DD_TRACE_DEBUG": { + ot: "OTEL_LOG_LEVEL", + remapper: mapLogLevel, + }, + "DD_TRACE_ENABLED": { + ot: "OTEL_TRACES_EXPORTER", + remapper: mapEnabled, + }, + "DD_TRACE_SAMPLE_RATE": { + ot: "OTEL_TRACES_SAMPLER", + remapper: mapSampleRate, + }, + "DD_TRACE_PROPAGATION_STYLE": { + ot: "OTEL_PROPAGATORS", + remapper: mapPropagationStyle, + }, + "DD_TAGS": { + ot: "OTEL_RESOURCE_ATTRIBUTES", + remapper: mapDDTags, + }, +} + +var ddTagsMapping = map[string]string{ + "service.name": "service", + "deployment.environment": "env", + "service.version": "version", +} + +var unsupportedSamplerMapping = map[string]string{ + "always_on": "parentbased_always_on", + "always_off": "parentbased_always_off", + "traceidratio": "parentbased_traceidratio", +} + +var propagationMapping = map[string]string{ + "tracecontext": "tracecontext", + "b3": "b3 single header", + "b3multi": "b3multi", + "datadog": "datadog", + "none": "none", +} + +// mapService maps OTEL_SERVICE_NAME to DD_SERVICE +func mapService(ot string) (string, error) { + return ot, nil +} + +// mapMetrics maps OTEL_METRICS_EXPORTER to DD_RUNTIME_METRICS_ENABLED +func mapMetrics(ot string) (string, error) { + ot = strings.TrimSpace(strings.ToLower(ot)) + if ot == "none" { + return "false", nil + } + return "", fmt.Errorf("the following configuration is not supported: OTEL_METRICS_EXPORTER=%v", ot) +} + +// mapLogLevel maps OTEL_LOG_LEVEL to DD_TRACE_DEBUG +func mapLogLevel(ot string) (string, error) { + if strings.TrimSpace(strings.ToLower(ot)) == "debug" { + return "true", nil + } + return "", fmt.Errorf("the following configuration is not supported: OTEL_LOG_LEVEL=%v", ot) +} + +// mapEnabled maps OTEL_TRACES_EXPORTER to DD_TRACE_ENABLED +func mapEnabled(ot string) (string, error) { + if strings.TrimSpace(strings.ToLower(ot)) == "none" { + return "false", nil + } + return "", fmt.Errorf("the following configuration is not supported: OTEL_TRACES_EXPORTER=%v", ot) +} + +// mapSampleRate maps OTEL_TRACES_SAMPLER to DD_TRACE_SAMPLE_RATE +func otelTraceIDRatio() string { + if v := env.Get("OTEL_TRACES_SAMPLER_ARG"); v != "" { + return v + } + return "1.0" +} + +// mapSampleRate maps OTEL_TRACES_SAMPLER to DD_TRACE_SAMPLE_RATE +func mapSampleRate(ot string) (string, error) { + ot = strings.TrimSpace(strings.ToLower(ot)) + if v, ok := unsupportedSamplerMapping[ot]; ok { + log.Warn("The following configuration is not supported: OTEL_TRACES_SAMPLER=%s. %s will be used", ot, v) + ot = v + } + + var samplerMapping = map[string]string{ + "parentbased_always_on": "1.0", + "parentbased_always_off": "0.0", + "parentbased_traceidratio": otelTraceIDRatio(), + } + if v, ok := samplerMapping[ot]; ok { + return v, nil + } + return "", fmt.Errorf("unknown sampling configuration %v", ot) +} + +// mapPropagationStyle maps OTEL_PROPAGATORS to DD_TRACE_PROPAGATION_STYLE +func mapPropagationStyle(ot string) (string, error) { + ot = strings.TrimSpace(strings.ToLower(ot)) + supportedStyles := make([]string, 0) + for _, otStyle := range strings.Split(ot, ",") { + otStyle = strings.TrimSpace(otStyle) + if _, ok := propagationMapping[otStyle]; ok { + supportedStyles = append(supportedStyles, propagationMapping[otStyle]) + } else { + log.Warn("Invalid configuration: %q is not supported. This propagation style will be ignored.", otStyle) + } + } + return strings.Join(supportedStyles, ","), nil +} + +// mapDDTags maps OTEL_RESOURCE_ATTRIBUTES to DD_TAGS +func mapDDTags(ot string) (string, error) { + ddTags := make([]string, 0) + internal.ForEachStringTag(ot, internal.OtelTagsDelimeter, func(key, val string) { + // replace otel delimiter with dd delimiter and normalize tag names + if ddkey, ok := ddTagsMapping[key]; ok { + // map reserved otel tag names to dd tag names + ddTags = append([]string{ddkey + internal.DDTagsDelimiter + val}, ddTags...) + } else { + ddTags = append(ddTags, key+internal.DDTagsDelimiter+val) + } + }) + + if len(ddTags) > 10 { + log.Warn("The following resource attributes have been dropped: %v. Only the first 10 resource attributes will be applied: %s", ddTags[10:], ddTags[:10]) //nolint:gocritic // Slice logging for debugging + ddTags = ddTags[:10] + } + + return strings.Join(ddTags, ","), nil +} diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index 2ae4274dd2..a30c21c95a 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -106,8 +106,8 @@ var SupportedConfigurations = map[string]struct{}{ "DD_SERVICE_EXTENSION_OBSERVABILITY_MODE": {}, "DD_SERVICE_EXTENSION_PORT": {}, "DD_SERVICE_EXTENSION_TLS": {}, - "DD_SERVICE_EXTENSION_TLS_KEY_FILE": {}, "DD_SERVICE_EXTENSION_TLS_CERT_FILE": {}, + "DD_SERVICE_EXTENSION_TLS_KEY_FILE": {}, "DD_SERVICE_MAPPING": {}, "DD_SITE": {}, "DD_SPAN_SAMPLING_RULES": {}, diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index e8a2e86411..e9bc2e7530 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -360,6 +360,9 @@ "DD_TRACE_AGENT_PROTOCOL_VERSION": [ "A" ], + "DD_TRACE_AGENT_TIMEOUT": [ + "A" + ], "DD_TRACE_AGENT_URL": [ "A" ], From 378f08d237144eb37a23d4e303aff2f149be4a42 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 3 Nov 2025 15:09:28 -0500 Subject: [PATCH 13/26] Remove DD_TRACE_AGENT_TIMEOUT; use DD_TRACE_STARTUP_LOGS in tests instead --- internal/config/configprovider_test.go | 8 ++++---- internal/env/supported_configurations.json | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index fa3248cc77..7b8cb8b30a 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -185,7 +185,7 @@ apm_configuration_default: DD_ENV: local_env # Set in 3 sources (Local, DD Env, OTEL) - should lose to DD Env DD_VERSION: 0.1.0 # Set in 2 sources (Local, Managed) - should lose to Managed DD_TRACE_SAMPLE_RATE: 0.1 # Set in 2 sources (Local, OTEL) - should lose to OTEL - DD_TRACE_AGENT_TIMEOUT: 5s # Only in Local - should WIN (lowest priority available) + DD_TRACE_STARTUP_LOGS: true # Only in Local - should WIN (lowest priority available) ` managedYaml := ` @@ -260,11 +260,11 @@ apm_configuration_default: "DD_TRACE_SAMPLE_RATE: OTEL should win over Local") // Local Config wins (only in Local) - assert.Equal(t, "5s", provider.getString("DD_TRACE_AGENT_TIMEOUT", "default"), - "DD_TRACE_AGENT_TIMEOUT: Local should win (only source)") + assert.Equal(t, true, provider.getBool("DD_TRACE_STARTUP_LOGS", false), + "DD_TRACE_STARTUP_LOGS: Local should win (only source)") // Defaults are returned for settings not configured anywhere - assert.Equal(t, false, provider.getBool("DD_TRACE_STARTUP_LOGS", false), + assert.Equal(t, "default", provider.getString("DD_TRACE_AGENT_URL", "default"), "Unconfigured setting should return default") }) } diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index e9bc2e7530..e8a2e86411 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -360,9 +360,6 @@ "DD_TRACE_AGENT_PROTOCOL_VERSION": [ "A" ], - "DD_TRACE_AGENT_TIMEOUT": [ - "A" - ], "DD_TRACE_AGENT_URL": [ "A" ], From 7d873f4d8fa365176b88ede8ac0704d2f9eb3ddc Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 3 Nov 2025 15:13:26 -0500 Subject: [PATCH 14/26] Make loadConfig threadsafe --- internal/config/config.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 14fa98b560..15481cd54e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,10 +7,14 @@ package config import ( "net/url" + "sync" "time" ) -var globalConfig *Config +var ( + globalConfig *Config + configOnce sync.Once +) // Config represents global configuration properties. type Config struct { @@ -110,9 +114,9 @@ func loadConfig() *Config { } func GlobalConfig() *Config { - if globalConfig == nil { + configOnce.Do(func() { globalConfig = loadConfig() - } + }) return globalConfig } From f1483161b268fa4b82eeeae97b40c9f4e438707f Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 3 Nov 2025 15:14:31 -0500 Subject: [PATCH 15/26] Add godocs to loadConfig() and GlobalConfig() to document thread safety behavior of the methods --- internal/config/config.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 15481cd54e..8ad63c9eea 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -77,6 +77,8 @@ type Config struct { TraceProtocol float64 `json:"DD_TRACE_AGENT_PROTOCOL_VERSION"` } +// loadConfig initializes and returns a new Config by reading from all configured sources. +// This function is NOT thread-safe and should only be called once through GlobalConfig's sync.Once. func loadConfig() *Config { cfg := new(Config) @@ -113,6 +115,10 @@ func loadConfig() *Config { return cfg } +// GlobalConfig returns the global configuration singleton. +// This function is thread-safe and can be called from multiple goroutines concurrently. +// The configuration is lazily initialized on first access using sync.Once, ensuring +// loadConfig() is called exactly once even under concurrent access. func GlobalConfig() *Config { configOnce.Do(func() { globalConfig = loadConfig() From 4057354d5e40e306405a74b4fffcda65fc9da5a1 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Mon, 3 Nov 2025 16:17:54 -0500 Subject: [PATCH 16/26] merge main and remove refs to DD_TRACE_AGENT_PROTOCOL --- internal/config/config.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 8ad63c9eea..a414a455ce 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -73,8 +73,6 @@ type Config struct { LogDirectory string `json:"DD_TRACE_LOG_DIRECTORY"` TraceRateLimitPerSecond float64 `json:"DD_TRACE_RATE_LIMIT"` - - TraceProtocol float64 `json:"DD_TRACE_AGENT_PROTOCOL_VERSION"` } // loadConfig initializes and returns a new Config by reading from all configured sources. @@ -110,7 +108,6 @@ func loadConfig() *Config { cfg.CIVisibilityAgentless = provider.getBool("DD_CIVISIBILITY_AGENTLESS_ENABLED", false) cfg.LogDirectory = provider.getString("DD_TRACE_LOG_DIRECTORY", "") cfg.TraceRateLimitPerSecond = provider.getFloat("DD_TRACE_RATE_LIMIT", 0.0) - cfg.TraceProtocol = provider.getFloat("DD_TRACE_AGENT_PROTOCOL_VERSION", 0.0) return cfg } From bfdaae762d09cd5d44e3d311c4bf92a729f08934 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Tue, 4 Nov 2025 11:55:03 -0500 Subject: [PATCH 17/26] Fix c.debug assignment in tracer config --- ddtrace/tracer/option.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index c7620a1bba..890b4b7bb7 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -476,7 +476,8 @@ func newConfig(opts ...StartOption) (*config, error) { 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", true) - c.debug = internal.BoolVal(getDDorOtelConfig("debugMode"), false) + internalConfig := internalconfig.GlobalConfig() + c.debug = internalConfig.IsDebugEnabled() c.logDirectory = env.Get("DD_TRACE_LOG_DIRECTORY") c.enabled = newDynamicConfig("tracing_enabled", internal.BoolVal(getDDorOtelConfig("enabled"), true), func(_ bool) bool { return true }, equal[bool]) if _, ok := env.Lookup("DD_TRACE_ENABLED"); ok { @@ -620,8 +621,7 @@ func newConfig(opts ...StartOption) (*config, error) { if c.logger != nil { log.UseLogger(c.logger) } - globalConfig := internalconfig.GlobalConfig() - if globalConfig.IsDebugEnabled() { + if c.debug { log.SetLevel(log.LevelDebug) } // Check if CI Visibility mode is enabled From 2595c887cf58a01cf18c74c43a9c4ec795686ed0 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 6 Nov 2025 14:25:09 -0500 Subject: [PATCH 18/26] Always reload config in testing environment --- ddtrace/tracer/option.go | 2 +- internal/config/config.go | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 890b4b7bb7..32098ae5ed 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -374,6 +374,7 @@ const partialFlushMinSpansDefault = 1000 // and passed user opts. func newConfig(opts ...StartOption) (*config, error) { c := new(config) + internalConfig := internalconfig.GlobalConfig() // If this was built with a recent-enough version of Orchestrion, force the orchestrion config to // the baked-in values. We do this early so that opts can be used to override the baked-in values, @@ -476,7 +477,6 @@ func newConfig(opts ...StartOption) (*config, error) { 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", true) - internalConfig := internalconfig.GlobalConfig() c.debug = internalConfig.IsDebugEnabled() c.logDirectory = env.Get("DD_TRACE_LOG_DIRECTORY") c.enabled = newDynamicConfig("tracing_enabled", internal.BoolVal(getDDorOtelConfig("enabled"), true), func(_ bool) bool { return true }, equal[bool]) diff --git a/internal/config/config.go b/internal/config/config.go index a414a455ce..a815aa310c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,6 +8,7 @@ package config import ( "net/url" "sync" + "testing" "time" ) @@ -117,9 +118,13 @@ func loadConfig() *Config { // The configuration is lazily initialized on first access using sync.Once, ensuring // loadConfig() is called exactly once even under concurrent access. func GlobalConfig() *Config { - configOnce.Do(func() { + if testing.Testing() { globalConfig = loadConfig() - }) + } else { + configOnce.Do(func() { + globalConfig = loadConfig() + }) + } return globalConfig } From 2fe6e1398a06774301a734dc126f2161255846d7 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 6 Nov 2025 14:28:38 -0500 Subject: [PATCH 19/26] Fix otel test that expects panic --- ddtrace/tracer/otel_dd_mappings_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddtrace/tracer/otel_dd_mappings_test.go b/ddtrace/tracer/otel_dd_mappings_test.go index f03381e05d..920ed597ef 100644 --- a/ddtrace/tracer/otel_dd_mappings_test.go +++ b/ddtrace/tracer/otel_dd_mappings_test.go @@ -15,8 +15,9 @@ import ( ) func TestAssessSource(t *testing.T) { + // regression test t.Run("invalid", func(t *testing.T) { - assert.Panics(t, func() { getDDorOtelConfig("invalid") }, "invalid config should panic") + assert.NotPanics(t, func() { getDDorOtelConfig("invalid") }, "invalid config should not panic") }) t.Run("dd", func(t *testing.T) { From d4960bb0af4d0aa064689ffff3d73900020a8669 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 6 Nov 2025 16:18:38 -0500 Subject: [PATCH 20/26] Report telemetry in configprovider --- internal/config/config.go | 114 ++++++++--------- internal/config/config_test.go | 88 ------------- internal/config/configprovider.go | 54 +++++++- internal/config/configprovider_test.go | 138 ++++++++++++++++++++- internal/config/declarativeconfigsource.go | 4 + internal/config/envconfigsource.go | 9 +- internal/config/otelenvconfigsource.go | 4 + 7 files changed, 258 insertions(+), 153 deletions(-) delete mode 100644 internal/config/config_test.go diff --git a/internal/config/config.go b/internal/config/config.go index a815aa310c..de44b45928 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,61 +19,61 @@ var ( // Config represents global configuration properties. type Config struct { - // AgentURL is the URL of the Datadog agent. - AgentURL *url.URL `json:"DD_AGENT_URL"` + // agentURL is the URL of the Datadog agent. + agentURL *url.URL - // Debug enables debug logging. - Debug bool `json:"DD_TRACE_DEBUG"` // has trace in the name, but impacts all products? + // debug enables debug logging. + debug bool - LogStartup bool `json:"DD_TRACE_STARTUP_LOGS"` + logStartup bool - ServiceName string `json:"DD_SERVICE"` + serviceName string - Version string `json:"DD_VERSION"` + version string - Env string `json:"DD_ENV"` + env string - ServiceMappings map[string]string `json:"DD_SERVICE_MAPPING"` + serviceMappings map[string]string - Hostname string `json:"DD_TRACE_SOURCE_HOSTNAME"` + hostname string - RuntimeMetrics bool `json:"DD_RUNTIME_METRICS_ENABLED"` + runtimeMetrics bool - RuntimeMetricsV2 bool `json:"DD_RUNTIME_METRICS_V2_ENABLED"` + runtimeMetricsV2 bool - ProfilerHotspots bool `json:"DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED"` + profilerHotspots bool - ProfilerEndpoints bool `json:"DD_PROFILING_ENDPOINT_COLLECTION_ENABLED"` + profilerEndpoints bool - SpanAttributeSchemaVersion int `json:"DD_TRACE_SPAN_ATTRIBUTE_SCHEMA"` + spanAttributeSchemaVersion int - PeerServiceDefaultsEnabled bool `json:"DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED"` + peerServiceDefaultsEnabled bool - PeerServiceMappings map[string]string `json:"DD_TRACE_PEER_SERVICE_MAPPING"` + peerServiceMappings map[string]string - DebugAbandonedSpans bool `json:"DD_TRACE_DEBUG_ABANDONED_SPANS"` + debugAbandonedSpans bool - SpanTimeout time.Duration `json:"DD_TRACE_SPAN_TIMEOUT"` + spanTimeout time.Duration - PartialFlushMinSpans int `json:"DD_TRACE_PARTIAL_FLUSH_MIN_SPANS"` + partialFlushMinSpans int - PartialFlushEnabled bool `json:"DD_TRACE_PARTIAL_FLUSH_ENABLED"` + partialFlushEnabled bool - StatsComputationEnabled bool `json:"DD_TRACE_STATS_COMPUTATION_ENABLED"` + statsComputationEnabled bool - DataStreamsMonitoringEnabled bool `json:"DD_DATA_STREAMS_ENABLED"` + dataStreamsMonitoringEnabled bool - DynamicInstrumentationEnabled bool `json:"DD_DYNAMIC_INSTRUMENTATION_ENABLED"` + dynamicInstrumentationEnabled bool - GlobalSampleRate float64 `json:"DD_TRACE_SAMPLE_RATE"` + globalSampleRate float64 - CIVisibilityEnabled bool `json:"DD_CIVISIBILITY_ENABLED"` + ciVisibilityEnabled bool - CIVisibilityAgentless bool `json:"DD_CIVISIBILITY_AGENTLESS_ENABLED"` + ciVisibilityAgentless bool - LogDirectory string `json:"DD_TRACE_LOG_DIRECTORY"` + logDirectory string - TraceRateLimitPerSecond float64 `json:"DD_TRACE_RATE_LIMIT"` + traceRateLimitPerSecond float64 } // loadConfig initializes and returns a new Config by reading from all configured sources. @@ -82,33 +82,33 @@ func loadConfig() *Config { cfg := new(Config) // TODO: Use defaults from config json instead of hardcoding them here - cfg.AgentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) - cfg.Debug = provider.getBool("DD_TRACE_DEBUG", false) - cfg.LogStartup = provider.getBool("DD_TRACE_STARTUP_LOGS", false) - cfg.ServiceName = provider.getString("DD_SERVICE", "") - cfg.Version = provider.getString("DD_VERSION", "") - cfg.Env = provider.getString("DD_ENV", "") - cfg.ServiceMappings = provider.getMap("DD_SERVICE_MAPPING", nil) - cfg.Hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") - cfg.RuntimeMetrics = provider.getBool("DD_RUNTIME_METRICS_ENABLED", false) - cfg.RuntimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", false) - cfg.ProfilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", false) - cfg.ProfilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", false) - cfg.SpanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) - cfg.PeerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) - cfg.PeerServiceMappings = provider.getMap("DD_TRACE_PEER_SERVICE_MAPPING", nil) - cfg.DebugAbandonedSpans = provider.getBool("DD_TRACE_DEBUG_ABANDONED_SPANS", false) - cfg.SpanTimeout = provider.getDuration("DD_TRACE_ABANDONED_SPAN_TIMEOUT", 0) - cfg.PartialFlushMinSpans = provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0) - cfg.PartialFlushEnabled = provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false) - cfg.StatsComputationEnabled = provider.getBool("DD_TRACE_STATS_COMPUTATION_ENABLED", false) - cfg.DataStreamsMonitoringEnabled = provider.getBool("DD_DATA_STREAMS_ENABLED", false) - cfg.DynamicInstrumentationEnabled = provider.getBool("DD_DYNAMIC_INSTRUMENTATION_ENABLED", false) - cfg.GlobalSampleRate = provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0) - cfg.CIVisibilityEnabled = provider.getBool("DD_CIVISIBILITY_ENABLED", false) - cfg.CIVisibilityAgentless = provider.getBool("DD_CIVISIBILITY_AGENTLESS_ENABLED", false) - cfg.LogDirectory = provider.getString("DD_TRACE_LOG_DIRECTORY", "") - cfg.TraceRateLimitPerSecond = provider.getFloat("DD_TRACE_RATE_LIMIT", 0.0) + cfg.agentURL = provider.getURL("DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:8126"}) + cfg.debug = provider.getBool("DD_TRACE_DEBUG", false) + cfg.logStartup = provider.getBool("DD_TRACE_STARTUP_LOGS", false) + cfg.serviceName = provider.getString("DD_SERVICE", "") + cfg.version = provider.getString("DD_VERSION", "") + cfg.env = provider.getString("DD_ENV", "") + cfg.serviceMappings = provider.getMap("DD_SERVICE_MAPPING", nil) + cfg.hostname = provider.getString("DD_TRACE_SOURCE_HOSTNAME", "") + cfg.runtimeMetrics = provider.getBool("DD_RUNTIME_METRICS_ENABLED", false) + cfg.runtimeMetricsV2 = provider.getBool("DD_RUNTIME_METRICS_V2_ENABLED", false) + cfg.profilerHotspots = provider.getBool("DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED", false) + cfg.profilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", false) + cfg.spanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) + cfg.peerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) + cfg.peerServiceMappings = provider.getMap("DD_TRACE_PEER_SERVICE_MAPPING", nil) + cfg.debugAbandonedSpans = provider.getBool("DD_TRACE_DEBUG_ABANDONED_SPANS", false) + cfg.spanTimeout = provider.getDuration("DD_TRACE_ABANDONED_SPAN_TIMEOUT", 0) + cfg.partialFlushMinSpans = provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0) + cfg.partialFlushEnabled = provider.getBool("DD_TRACE_PARTIAL_FLUSH_ENABLED", false) + cfg.statsComputationEnabled = provider.getBool("DD_TRACE_STATS_COMPUTATION_ENABLED", false) + cfg.dataStreamsMonitoringEnabled = provider.getBool("DD_DATA_STREAMS_ENABLED", false) + cfg.dynamicInstrumentationEnabled = provider.getBool("DD_DYNAMIC_INSTRUMENTATION_ENABLED", false) + cfg.globalSampleRate = provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0) + cfg.ciVisibilityEnabled = provider.getBool("DD_CIVISIBILITY_ENABLED", false) + cfg.ciVisibilityAgentless = provider.getBool("DD_CIVISIBILITY_AGENTLESS_ENABLED", false) + cfg.logDirectory = provider.getString("DD_TRACE_LOG_DIRECTORY", "") + cfg.traceRateLimitPerSecond = provider.getFloat("DD_TRACE_RATE_LIMIT", 0.0) return cfg } @@ -129,5 +129,5 @@ func GlobalConfig() *Config { } func (c *Config) IsDebugEnabled() bool { - return c.Debug + return c.debug } diff --git a/internal/config/config_test.go b/internal/config/config_test.go deleted file mode 100644 index 91e0cdf140..0000000000 --- a/internal/config/config_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// 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 2025 Datadog, Inc. - -package config - -// func TestConfigHasFields(t *testing.T) { -// // TODO: Use supported configurations JSON as expectedFields instead -// expectedFields := map[string]reflect.Type{ -// "AgentURL": reflect.TypeOf((*url.URL)(nil)), -// "Debug": reflect.TypeOf(false), -// "LogToStdout": reflect.TypeOf(false), -// "LogStartup": reflect.TypeOf(false), -// "ServiceName": reflect.TypeOf(""), -// "Version": reflect.TypeOf(""), -// "Env": reflect.TypeOf(""), -// //"Sampler": reflect.TypeOf((*RateSampler)(nil)), -// // "OriginalAgentURL": reflect.TypeOf((*url.URL)(nil)), // We probably don't need this anymore -// "ServiceMappings": reflect.TypeOf((map[string]string)(nil)), -// // "GlobalTags": reflect.TypeOf((*dynamicConfig[map[string]interface{}])(nil)), -// // "Transport": reflect.TypeOf((*transport)(nil)), -// "HTTPClientTimeout": reflect.TypeOf(int64(0)), -// // "Propagator": reflect.TypeOf((*Propagator)(nil)), -// "Hostname": reflect.TypeOf(""), -// // "Logger": reflect.TypeOf((*Logger)(nil)), -// "RuntimeMetrics": reflect.TypeOf(false), -// "RuntimeMetricsV2": reflect.TypeOf(false), -// // "StatsdClient": reflect.TypeOf((*internal.StatsdClient)(nil)), -// // "SpanRules": reflect.TypeOf((*[]SamplingRule)(nil)), -// // "TraceRules": reflect.TypeOf((*[]SamplingRule)(nil)), -// "ProfilerHotspots": reflect.TypeOf(false), -// "ProfilerEndpoints": reflect.TypeOf(false), -// // "TracingEnabled": reflect.TypeOf((*dynamicConfig[bool])(nil)), -// "EnableHostnameDetection": reflect.TypeOf(false), -// "SpanAttributeSchemaVersion": reflect.TypeOf(0), -// "PeerServiceDefaultsEnabled": reflect.TypeOf(false), -// "PeerServiceMappings": reflect.TypeOf((map[string]string)(nil)), -// "DebugAbandonedSpans": reflect.TypeOf(false), -// "SpanTimeout": reflect.TypeOf(time.Duration(int64(0))), -// "PartialFlushMinSpans": reflect.TypeOf(0), -// "PartialFlushEnabled": reflect.TypeOf(false), -// "StatsComputationEnabled": reflect.TypeOf(false), -// "DataStreamsMonitoringEnabled": reflect.TypeOf(false), -// // "OrchestrionCfg": reflect.TypeOf((*orchestrionConfig)(nil)), -// // "TraceSampleRate": reflect.TypeOf((*dynamicConfig[float64])(nil)), -// // "TraceSampleRules": reflect.TypeOf((*dynamicConfig[[]SamplingRule])(nil)), -// // "HeaderAsTags": reflect.TypeOf((*dynamicConfig[[]string])(nil)), -// "DynamicInstrumentationEnabled": reflect.TypeOf(false), -// "GlobalSampleRate": reflect.TypeOf(float64(0)), -// "CIVisibilityEnabled": reflect.TypeOf(false), -// "CIVisibilityAgentless": reflect.TypeOf(false), -// "LogDirectory": reflect.TypeOf(""), -// "TracingAsTransport": reflect.TypeOf(false), -// "TraceRateLimitPerSecond": reflect.TypeOf(float64(0)), -// "TraceProtocol": reflect.TypeOf(float64(0)), -// // "LLMObsEnabled": reflect.TypeOf(false), -// // "LLMObsMLApp": reflect.TypeOf(""), -// // "LLMObsAgentlessEnabled": reflect.TypeOf(false), -// // "LLMObsProjectName": reflect.TypeOf(""), -// } - -// // Get the Config struct type -// configType := reflect.TypeOf(Config{}) - -// // Verify the number of expected fields matches the actual number of fields -// actualFieldCount := configType.NumField() -// expectedFieldCount := len(expectedFields) -// assert.Equal(t, expectedFieldCount, actualFieldCount, -// "Expected %d fields in Config struct, but found %d. Update the test when adding/removing fields.", -// expectedFieldCount, actualFieldCount) - -// // Verify each expected field exists with the correct type -// for fieldName, expectedType := range expectedFields { -// field, found := configType.FieldByName(fieldName) -// assert.True(t, found, "Field %s should exist on Config struct", fieldName) - -// if found { -// assert.Equal(t, expectedType, field.Type, -// "Field %s should have type %s, but has type %s", -// fieldName, expectedType, field.Type) -// } -// } - -// // Verify we can instantiate the config -// cfg := new(Config) -// assert.NotNil(t, cfg, "Should be able to create new Config instance") -// } diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index 77ca5246a9..4e934f71ca 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -10,6 +10,8 @@ import ( "strconv" "strings" "time" + + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" ) var provider = DefaultConfigProvider() @@ -20,6 +22,7 @@ type ConfigProvider struct { type ConfigSource interface { Get(key string) string + Origin() telemetry.Origin } func DefaultConfigProvider() *ConfigProvider { @@ -37,6 +40,12 @@ func (p *ConfigProvider) getString(key string, def string) string { // TODO: Eventually, iterate over all sources and report telemetry for _, source := range p.sources { if v := source.Get(key); v != "" { + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() // TODO: Store or use this config ID for telemetry + } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) return v } } @@ -46,9 +55,16 @@ func (p *ConfigProvider) getString(key string, def string) string { func (p *ConfigProvider) getBool(key string, def bool) bool { for _, source := range p.sources { if v := source.Get(key); v != "" { + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() + } if v == "true" { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) return true } else if v == "false" { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) return false } } @@ -59,9 +75,15 @@ func (p *ConfigProvider) getBool(key string, def bool) bool { func (p *ConfigProvider) getInt(key string, def int) int { for _, source := range p.sources { if v := source.Get(key); v != "" { - v, err := strconv.Atoi(v) + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() + } + intVal, err := strconv.Atoi(v) if err == nil { - return v + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) + return intVal } } } @@ -71,8 +93,14 @@ func (p *ConfigProvider) getInt(key string, def int) int { func (p *ConfigProvider) getMap(key string, def map[string]string) map[string]string { for _, source := range p.sources { if v := source.Get(key); v != "" { + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() + } m := parseMapString(v) if len(m) > 0 { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) return m } } @@ -83,8 +111,14 @@ func (p *ConfigProvider) getMap(key string, def map[string]string) map[string]st func (p *ConfigProvider) getDuration(key string, def time.Duration) time.Duration { for _, source := range p.sources { if v := source.Get(key); v != "" { + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() + } d, err := time.ParseDuration(v) if err == nil { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) return d } } @@ -95,9 +129,15 @@ func (p *ConfigProvider) getDuration(key string, def time.Duration) time.Duratio func (p *ConfigProvider) getFloat(key string, def float64) float64 { for _, source := range p.sources { if v := source.Get(key); v != "" { - v, err := strconv.ParseFloat(v, 64) + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() + } + floatVal, err := strconv.ParseFloat(v, 64) if err == nil { - return v + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) + return floatVal } } } @@ -107,8 +147,14 @@ func (p *ConfigProvider) getFloat(key string, def float64) float64 { func (p *ConfigProvider) getURL(key string, def *url.URL) *url.URL { for _, source := range p.sources { if v := source.Get(key); v != "" { + var id string + // If source is a declarativeConfigSource, capture the config ID + if s, ok := source.(*declarativeConfigSource); ok { + id = s.GetID() + } u, err := url.Parse(v) if err == nil { + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: v, Origin: source.Origin(), ID: id}) return u } } diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index 7b8cb8b30a..6ad879c132 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -12,7 +12,9 @@ import ( "net/url" "github.com/DataDog/dd-trace-go/v2/internal/telemetry" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry/telemetrytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func newTestConfigProvider(sources ...ConfigSource) *ConfigProvider { @@ -23,14 +25,16 @@ func newTestConfigProvider(sources ...ConfigSource) *ConfigProvider { type testConfigSource struct { entries map[string]string + origin telemetry.Origin } -func newTestConfigSource(entries map[string]string) *testConfigSource { +func newTestConfigSource(entries map[string]string, origin telemetry.Origin) *testConfigSource { if entries == nil { entries = make(map[string]string) } return &testConfigSource{ entries: entries, + origin: origin, } } @@ -38,10 +42,14 @@ func (s *testConfigSource) Get(key string) string { return s.entries[key] } +func (s *testConfigSource) Origin() telemetry.Origin { + return s.origin +} + func TestGetMethods(t *testing.T) { t.Run("defaults", func(t *testing.T) { // Test that defaults are used when the queried key does not exist - provider := newTestConfigProvider(newTestConfigSource(nil)) + provider := newTestConfigProvider(newTestConfigSource(nil, telemetry.OriginEnvVar)) assert.Equal(t, "value", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", true)) assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 1)) @@ -57,7 +65,7 @@ func TestGetMethods(t *testing.T) { "DD_TRACE_SAMPLE_RATE": "1.0", "DD_TRACE_AGENT_URL": "https://localhost:8126", } - provider := newTestConfigProvider(newTestConfigSource(entries)) + provider := newTestConfigProvider(newTestConfigSource(entries, telemetry.OriginEnvVar)) assert.Equal(t, "string", provider.getString("DD_SERVICE", "value")) assert.Equal(t, true, provider.getBool("DD_TRACE_DEBUG", false)) assert.Equal(t, 1, provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0)) @@ -268,3 +276,127 @@ apm_configuration_default: "Unconfigured setting should return default") }) } + +func TestConfigProviderTelemetryRegistration(t *testing.T) { + t.Run("env source reports telemetry for all getters", func(t *testing.T) { + telemetryClient := new(telemetrytest.MockClient) + // Expectations: value is the raw string from the source; ID is empty + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE", Value: "service", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_DEBUG", Value: "true", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", Value: "100", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_SAMPLE_RATE", Value: "0.5", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_AGENT_URL", Value: "http://localhost:8126", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE_MAPPING", Value: "old:new", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_ABANDONED_SPAN_TIMEOUT", Value: "10s", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}).Return() + defer telemetry.MockClient(telemetryClient)() + + source := newTestConfigSource(map[string]string{ + "DD_SERVICE": "service", + "DD_TRACE_DEBUG": "true", + "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS": "100", + "DD_TRACE_SAMPLE_RATE": "0.5", + "DD_TRACE_AGENT_URL": "http://localhost:8126", + "DD_SERVICE_MAPPING": "old:new", + "DD_TRACE_ABANDONED_SPAN_TIMEOUT": "10s", + }, telemetry.OriginEnvVar) + provider := newTestConfigProvider(source) + + _ = provider.getString("DD_SERVICE", "default") + _ = provider.getBool("DD_TRACE_DEBUG", false) + _ = provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0) + _ = provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0) + _ = provider.getURL("DD_TRACE_AGENT_URL", nil) + _ = provider.getMap("DD_SERVICE_MAPPING", nil) + _ = provider.getDuration("DD_TRACE_ABANDONED_SPAN_TIMEOUT", 0) + + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE", Value: "service", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_DEBUG", Value: "true", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", Value: "100", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_SAMPLE_RATE", Value: "0.5", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_AGENT_URL", Value: "http://localhost:8126", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE_MAPPING", Value: "old:new", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_ABANDONED_SPAN_TIMEOUT", Value: "10s", Origin: telemetry.OriginEnvVar, ID: telemetry.EmptyID}}) + }) + + t.Run("declarative source reports telemetry with ID", func(t *testing.T) { + telemetryClient := new(telemetrytest.MockClient) + // Values expected as raw strings, with OriginLocalStableConfig and ID from file + yaml := `config_id: 123 +apm_configuration_default: + DD_SERVICE: svc + DD_TRACE_DEBUG: true + DD_TRACE_PARTIAL_FLUSH_MIN_SPANS: "7" + DD_TRACE_SAMPLE_RATE: 0.9 + DD_TRACE_AGENT_URL: http://127.0.0.1:8126 + DD_SERVICE_MAPPING: a:b + DD_TRACE_ABANDONED_SPAN_TIMEOUT: 2s +` + temp := "decl.yml" + require.NoError(t, os.WriteFile(temp, []byte(yaml), 0644)) + defer os.Remove(temp) + + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE", Value: "svc", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_DEBUG", Value: "true", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", Value: "7", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_SAMPLE_RATE", Value: "0.9", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_AGENT_URL", Value: "http://127.0.0.1:8126", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE_MAPPING", Value: "a:b", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_ABANDONED_SPAN_TIMEOUT", Value: "2s", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}).Return() + defer telemetry.MockClient(telemetryClient)() + + decl := newDeclarativeConfigSource(temp, telemetry.OriginLocalStableConfig) + provider := newTestConfigProvider(decl) + + _ = provider.getString("DD_SERVICE", "default") + _ = provider.getBool("DD_TRACE_DEBUG", false) + _ = provider.getInt("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 0) + _ = provider.getFloat("DD_TRACE_SAMPLE_RATE", 0.0) + _ = provider.getURL("DD_TRACE_AGENT_URL", nil) + _ = provider.getMap("DD_SERVICE_MAPPING", nil) + _ = provider.getDuration("DD_TRACE_ABANDONED_SPAN_TIMEOUT", 0) + + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE", Value: "svc", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_DEBUG", Value: "true", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", Value: "7", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_SAMPLE_RATE", Value: "0.9", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_AGENT_URL", Value: "http://127.0.0.1:8126", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_SERVICE_MAPPING", Value: "a:b", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: "DD_TRACE_ABANDONED_SPAN_TIMEOUT", Value: "2s", Origin: telemetry.OriginLocalStableConfig, ID: "123"}}) + }) + + t.Run("source priority with config IDs", func(t *testing.T) { + // Test that when multiple sources exist, only the winning source's + // telemetry (including its config ID) is registered + + yamlManaged := `config_id: managed-123 +apm_configuration_default: + DD_SERVICE: managed-service +` + yamlLocal := `config_id: local-456 +apm_configuration_default: + DD_SERVICE: local-service + DD_ENV: local-env +` + tempManaged := "test_managed.yml" + tempLocal := "test_local.yml" + + require.NoError(t, os.WriteFile(tempManaged, []byte(yamlManaged), 0644)) + require.NoError(t, os.WriteFile(tempLocal, []byte(yamlLocal), 0644)) + defer os.Remove(tempManaged) + defer os.Remove(tempLocal) + + managedSource := newDeclarativeConfigSource(tempManaged, telemetry.OriginManagedStableConfig) + localSource := newDeclarativeConfigSource(tempLocal, telemetry.OriginLocalStableConfig) + + // Managed has higher priority than Local + provider := newTestConfigProvider(managedSource, localSource) + + // For DD_SERVICE: managed wins, so telemetry gets ID "managed-123" + result := provider.getString("DD_SERVICE", "default") + assert.Equal(t, "managed-service", result) + + // For DD_ENV: local wins (managed doesn't have it), so telemetry gets ID "local-456" + env := provider.getString("DD_ENV", "default") + assert.Equal(t, "local-env", env) + }) +} diff --git a/internal/config/declarativeconfigsource.go b/internal/config/declarativeconfigsource.go index 42623a33da..a1bd14f9c9 100644 --- a/internal/config/declarativeconfigsource.go +++ b/internal/config/declarativeconfigsource.go @@ -44,6 +44,10 @@ func (d *declarativeConfigSource) GetID() string { return d.config.getID() } +func (d *declarativeConfigSource) Origin() telemetry.Origin { + return d.origin +} + // newDeclarativeConfigSource initializes a new declarativeConfigSource from the given file. func newDeclarativeConfigSource(filePath string, origin telemetry.Origin) *declarativeConfigSource { return &declarativeConfigSource{ diff --git a/internal/config/envconfigsource.go b/internal/config/envconfigsource.go index 874e72e66f..10a92779c5 100644 --- a/internal/config/envconfigsource.go +++ b/internal/config/envconfigsource.go @@ -5,10 +5,17 @@ package config -import "github.com/DataDog/dd-trace-go/v2/internal/env" +import ( + "github.com/DataDog/dd-trace-go/v2/internal/env" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" +) type envConfigSource struct{} func (e *envConfigSource) Get(key string) string { return env.Get(normalizeKey(key)) } + +func (e *envConfigSource) Origin() telemetry.Origin { + return telemetry.OriginEnvVar +} diff --git a/internal/config/otelenvconfigsource.go b/internal/config/otelenvconfigsource.go index c8d1ff14cf..12570ce930 100644 --- a/internal/config/otelenvconfigsource.go +++ b/internal/config/otelenvconfigsource.go @@ -48,6 +48,10 @@ func (o *otelEnvConfigSource) Get(key string) string { return val } +func (o *otelEnvConfigSource) Origin() telemetry.Origin { + return telemetry.OriginEnvVar +} + type otelDDEnv struct { ot string remapper func(string) (string, error) From 0be643e249dcfe8c62a32edd453980173ad606e1 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 6 Nov 2025 16:40:03 -0500 Subject: [PATCH 21/26] Report telemetry adn add tests --- internal/config/configprovider.go | 7 +++++ internal/config/configprovider_test.go | 42 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index 4e934f71ca..e0577a779b 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -49,6 +49,7 @@ func (p *ConfigProvider) getString(key string, def string) string { return v } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } @@ -69,6 +70,7 @@ func (p *ConfigProvider) getBool(key string, def bool) bool { } } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } @@ -87,6 +89,7 @@ func (p *ConfigProvider) getInt(key string, def int) int { } } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } @@ -105,6 +108,7 @@ func (p *ConfigProvider) getMap(key string, def map[string]string) map[string]st } } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } @@ -123,6 +127,7 @@ func (p *ConfigProvider) getDuration(key string, def time.Duration) time.Duratio } } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } @@ -141,6 +146,7 @@ func (p *ConfigProvider) getFloat(key string, def float64) float64 { } } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } @@ -159,6 +165,7 @@ func (p *ConfigProvider) getURL(key string, def *url.URL) *url.URL { } } } + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: def, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}) return def } diff --git a/internal/config/configprovider_test.go b/internal/config/configprovider_test.go index 6ad879c132..0e5f8eab28 100644 --- a/internal/config/configprovider_test.go +++ b/internal/config/configprovider_test.go @@ -10,6 +10,7 @@ import ( "testing" "net/url" + "time" "github.com/DataDog/dd-trace-go/v2/internal/telemetry" "github.com/DataDog/dd-trace-go/v2/internal/telemetry/telemetrytest" @@ -399,4 +400,45 @@ apm_configuration_default: env := provider.getString("DD_ENV", "default") assert.Equal(t, "local-env", env) }) + + t.Run("reports defaults via telemetry when key missing or invalid", func(t *testing.T) { + telemetryClient := new(telemetrytest.MockClient) + + strKey, strDef := "DD_SERVICE", "default_service" + boolKey, boolDef := "DD_TRACE_DEBUG", true + intKey, intDef := "DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", 7 + floatKey, floatDef := "DD_TRACE_SAMPLE_RATE", 0.25 + durKey, durDef := "DD_TRACE_ABANDONED_SPAN_TIMEOUT", 42*time.Second + urlKey, urlDef := "DD_TRACE_AGENT_URL", &url.URL{Scheme: "http", Host: "localhost:9000"} + mapKey, mapDef := "DD_SERVICE_MAPPING", map[string]string{"a": "b"} + + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: strKey, Value: strDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: boolKey, Value: boolDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: intKey, Value: intDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: floatKey, Value: floatDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: durKey, Value: durDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: urlKey, Value: urlDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + telemetryClient.On("RegisterAppConfigs", []telemetry.Configuration{{Name: mapKey, Value: mapDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}).Return() + defer telemetry.MockClient(telemetryClient)() + + // Use an empty test source to force defaults + provider := newTestConfigProvider(newTestConfigSource(map[string]string{}, telemetry.OriginEnvVar)) + + // Defaults should be used and reported + assert.Equal(t, strDef, provider.getString(strKey, strDef)) + assert.Equal(t, boolDef, provider.getBool(boolKey, boolDef)) + assert.Equal(t, intDef, provider.getInt(intKey, intDef)) + assert.Equal(t, floatDef, provider.getFloat(floatKey, floatDef)) + assert.Equal(t, durDef, provider.getDuration(durKey, durDef)) + assert.Equal(t, urlDef, provider.getURL(urlKey, urlDef)) + assert.Equal(t, mapDef, provider.getMap(mapKey, mapDef)) + + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: strKey, Value: strDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: boolKey, Value: boolDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: intKey, Value: intDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: floatKey, Value: floatDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: durKey, Value: durDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: urlKey, Value: urlDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + telemetryClient.AssertCalled(t, "RegisterAppConfigs", []telemetry.Configuration{{Name: mapKey, Value: mapDef, Origin: telemetry.OriginDefault, ID: telemetry.EmptyID}}) + }) } From b9fbb656d0d059858de014d8e9c628981bbe853b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 13 Nov 2025 11:13:38 -0500 Subject: [PATCH 22/26] Introduce ResetConfigForTesting to reset sync.Once in tests; rename to just Config instead of GlobalConfig --- ddtrace/tracer/option.go | 2 +- ddtrace/tracer/option_test.go | 6 ++++++ internal/config/config.go | 26 +++++++++++++++----------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 32098ae5ed..8ba7f6b7df 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -374,7 +374,7 @@ const partialFlushMinSpansDefault = 1000 // and passed user opts. func newConfig(opts ...StartOption) (*config, error) { c := new(config) - internalConfig := internalconfig.GlobalConfig() + internalConfig := internalconfig.GetConfig() // If this was built with a recent-enough version of Orchestrion, force the orchestrion config to // the baked-in values. We do this early so that opts can be used to override the baked-in values, diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 6da090aec4..8ed287a731 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -26,6 +26,7 @@ import ( "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" "github.com/DataDog/dd-trace-go/v2/internal" + internalconfig "github.com/DataDog/dd-trace-go/v2/internal/config" "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/telemetry" @@ -433,12 +434,14 @@ func TestTracerOptionsDefaults(t *testing.T) { }) t.Run("env", func(t *testing.T) { t.Setenv("DD_TRACE_DEBUG", "true") + internalconfig.ResetConfigForTesting() c, err := newTestConfig() assert.NoError(t, err) assert.True(t, c.debug) }) t.Run("otel-env-debug", func(t *testing.T) { t.Setenv("OTEL_LOG_LEVEL", "debug") + internalconfig.ResetConfigForTesting() c, err := newTestConfig() assert.NoError(t, err) assert.True(t, c.debug) @@ -446,6 +449,7 @@ func TestTracerOptionsDefaults(t *testing.T) { t.Run("otel-env-notdebug", func(t *testing.T) { // any value other than debug, does nothing t.Setenv("OTEL_LOG_LEVEL", "notdebug") + internalconfig.ResetConfigForTesting() c, err := newTestConfig() assert.NoError(t, err) assert.False(t, c.debug) @@ -454,11 +458,13 @@ func TestTracerOptionsDefaults(t *testing.T) { assert := assert.New(t) // option override otel t.Setenv("OTEL_LOG_LEVEL", "debug") + internalconfig.ResetConfigForTesting() c, err := newTestConfig(WithDebugMode(false)) assert.NoError(err) assert.False(c.debug) // env override otel t.Setenv("DD_TRACE_DEBUG", "false") + internalconfig.ResetConfigForTesting() c, err = newTestConfig() assert.NoError(err) assert.False(c.debug) diff --git a/internal/config/config.go b/internal/config/config.go index de44b45928..1470e280bc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,8 +13,8 @@ import ( ) var ( - globalConfig *Config - configOnce sync.Once + config *Config + configOnce sync.Once ) // Config represents global configuration properties. @@ -77,7 +77,7 @@ type Config struct { } // loadConfig initializes and returns a new Config by reading from all configured sources. -// This function is NOT thread-safe and should only be called once through GlobalConfig's sync.Once. +// This function is NOT thread-safe and should only be called once through GetConfig's sync.Once. func loadConfig() *Config { cfg := new(Config) @@ -113,19 +113,23 @@ func loadConfig() *Config { return cfg } -// GlobalConfig returns the global configuration singleton. +// GetConfig returns the global configuration singleton. // This function is thread-safe and can be called from multiple goroutines concurrently. // The configuration is lazily initialized on first access using sync.Once, ensuring // loadConfig() is called exactly once even under concurrent access. -func GlobalConfig() *Config { +func GetConfig() *Config { + configOnce.Do(func() { + config = loadConfig() + }) + return config +} + +// ResetConfigForTesting resets the global configuration state for testing purposes. It should only be called from tests. +func ResetConfigForTesting() { if testing.Testing() { - globalConfig = loadConfig() - } else { - configOnce.Do(func() { - globalConfig = loadConfig() - }) + config = nil + configOnce = sync.Once{} } - return globalConfig } func (c *Config) IsDebugEnabled() bool { From 02802f30d1068eac5e0e726b7aa2df210d709729 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 13 Nov 2025 15:09:22 -0500 Subject: [PATCH 23/26] Add tests for envconfigsource and otelenvconfigsource --- internal/config/config.go | 2 - internal/config/envconfigsource_test.go | 22 ++++ internal/config/otelconfigsource_test.go | 129 +++++++++++++++++++++++ internal/config/otelenvconfigsource.go | 1 - 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 internal/config/envconfigsource_test.go create mode 100644 internal/config/otelconfigsource_test.go diff --git a/internal/config/config.go b/internal/config/config.go index 1470e280bc..56e0a5ce41 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,10 +19,8 @@ var ( // Config represents global configuration properties. type Config struct { - // agentURL is the URL of the Datadog agent. agentURL *url.URL - // debug enables debug logging. debug bool logStartup bool diff --git a/internal/config/envconfigsource_test.go b/internal/config/envconfigsource_test.go new file mode 100644 index 0000000000..599dc681a6 --- /dev/null +++ b/internal/config/envconfigsource_test.go @@ -0,0 +1,22 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" +) + +func TestEnvConfigSource(t *testing.T) { + envConfigSource := &envConfigSource{} + t.Setenv("DD_SERVICE", "value") + assert.Equal(t, "value", envConfigSource.Get("DD_SERVICE")) + assert.Equal(t, telemetry.OriginEnvVar, envConfigSource.Origin()) +} + +func TestNormalizedEnvConfigSource(t *testing.T) { + envConfigSource := &envConfigSource{} + t.Setenv("DD_SERVICE", "value") + assert.Equal(t, "value", envConfigSource.Get("service")) +} diff --git a/internal/config/otelconfigsource_test.go b/internal/config/otelconfigsource_test.go new file mode 100644 index 0000000000..f428b86dde --- /dev/null +++ b/internal/config/otelconfigsource_test.go @@ -0,0 +1,129 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry/telemetrytest" +) + +func TestOtelEnvConfigSource(t *testing.T) { + t.Run("maps OTEL_SERVICE_NAME to service", func(t *testing.T) { + t.Setenv("OTEL_SERVICE_NAME", "my-service") + source := &otelEnvConfigSource{} + v := source.Get("service") + assert.Equal(t, "my-service", v) + }) + + t.Run("maps OTEL_SERVICE_NAME with DD_SERVICE key", func(t *testing.T) { + t.Setenv("OTEL_SERVICE_NAME", "my-service") + source := &otelEnvConfigSource{} + v := source.Get("DD_SERVICE") + assert.Equal(t, "my-service", v) + }) + + t.Run("returns empty when only DD var is set", func(t *testing.T) { + t.Setenv("DD_SERVICE", "my-service") + source := &otelEnvConfigSource{} + v := source.Get("service") + assert.Equal(t, "", v, "otelEnvConfigSource should not read DD vars directly") + }) + + t.Run("maps OTEL_TRACES_SAMPLER to sample rate", func(t *testing.T) { + t.Setenv("OTEL_TRACES_SAMPLER", "parentbased_always_on") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_SAMPLE_RATE") + assert.Equal(t, "1.0", v) + }) + + t.Run("maps OTEL_TRACES_SAMPLER with sampler arg", func(t *testing.T) { + t.Setenv("OTEL_TRACES_SAMPLER", "parentbased_traceidratio") + t.Setenv("OTEL_TRACES_SAMPLER_ARG", "0.5") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_SAMPLE_RATE") + assert.Equal(t, "0.5", v) + }) + + t.Run("maps OTEL_LOG_LEVEL=debug to DD_TRACE_DEBUG=true", func(t *testing.T) { + t.Setenv("OTEL_LOG_LEVEL", "debug") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_DEBUG") + assert.Equal(t, "true", v) + }) + + t.Run("returns empty for invalid OTEL_LOG_LEVEL", func(t *testing.T) { + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() + + t.Setenv("OTEL_LOG_LEVEL", "invalid") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_DEBUG") + + assert.Equal(t, "", v) + assert.NotZero(t, telemetryClient.Count(telemetry.NamespaceTracers, "otel.env.invalid", []string{"config_datadog:dd_trace_debug", "config_opentelemetry:otel_log_level"}).Get()) + }) + + t.Run("maps OTEL_TRACES_EXPORTER=none to DD_TRACE_ENABLED=false", func(t *testing.T) { + t.Setenv("OTEL_TRACES_EXPORTER", "none") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_ENABLED") + assert.Equal(t, "false", v) + }) + + t.Run("returns empty for invalid OTEL_TRACES_EXPORTER", func(t *testing.T) { + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() + + t.Setenv("OTEL_TRACES_EXPORTER", "jaeger") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_ENABLED") + + assert.Equal(t, "", v) + assert.NotZero(t, telemetryClient.Count(telemetry.NamespaceTracers, "otel.env.invalid", []string{"config_datadog:dd_trace_enabled", "config_opentelemetry:otel_traces_exporter"}).Get()) + }) + + t.Run("maps OTEL_METRICS_EXPORTER=none to DD_RUNTIME_METRICS_ENABLED=false", func(t *testing.T) { + t.Setenv("OTEL_METRICS_EXPORTER", "none") + source := &otelEnvConfigSource{} + v := source.Get("DD_RUNTIME_METRICS_ENABLED") + assert.Equal(t, "false", v) + }) + + t.Run("maps OTEL_PROPAGATORS to DD_TRACE_PROPAGATION_STYLE", func(t *testing.T) { + t.Setenv("OTEL_PROPAGATORS", "tracecontext,b3") + source := &otelEnvConfigSource{} + v := source.Get("DD_TRACE_PROPAGATION_STYLE") + assert.Equal(t, "tracecontext,b3 single header", v) + }) + + t.Run("maps OTEL_RESOURCE_ATTRIBUTES to DD_TAGS", func(t *testing.T) { + t.Setenv("OTEL_RESOURCE_ATTRIBUTES", "service.name=my-service,deployment.environment=prod,custom.key=value") + source := &otelEnvConfigSource{} + v := source.Get("DD_TAGS") + + // service.name should be mapped to "service" + assert.Contains(t, v, "service:my-service") + assert.Contains(t, v, "env:prod") + assert.Contains(t, v, "custom.key:value") + }) + + t.Run("returns empty for unsupported key", func(t *testing.T) { + t.Setenv("OTEL_SERVICE_NAME", "my-service") + source := &otelEnvConfigSource{} + v := source.Get("UNSUPPORTED_KEY") + assert.Equal(t, "", v) + }) + + t.Run("returns empty when OTEL var not set", func(t *testing.T) { + source := &otelEnvConfigSource{} + v := source.Get("DD_SERVICE") + assert.Equal(t, "", v) + }) + + t.Run("origin returns OriginEnvVar", func(t *testing.T) { + source := &otelEnvConfigSource{} + assert.Equal(t, telemetry.OriginEnvVar, source.Origin()) + }) +} diff --git a/internal/config/otelenvconfigsource.go b/internal/config/otelenvconfigsource.go index 12570ce930..91de23da98 100644 --- a/internal/config/otelenvconfigsource.go +++ b/internal/config/otelenvconfigsource.go @@ -36,7 +36,6 @@ func (o *otelEnvConfigSource) Get(key string) string { log.Warn("Both %q and %q are set, using %s=%s", entry.ot, ddKey, entry.ot, ddVal) telemetryTags := []string{ddPrefix + strings.ToLower(ddKey), otelPrefix + strings.ToLower(entry.ot)} telemetry.Count(telemetry.NamespaceTracers, "otel.env.hiding", telemetryTags).Submit(1) - return ddVal } val, err := entry.remapper(otVal) if err != nil { From ca387944992bfcf19cf6be338d80767bc0d4174c Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 13 Nov 2025 15:26:36 -0500 Subject: [PATCH 24/26] Use all stableconfigsource_test logic in new declarativeconfigsource_test --- .../config/declarativeconfigsource_test.go | 424 ++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 internal/config/declarativeconfigsource_test.go diff --git a/internal/config/declarativeconfigsource_test.go b/internal/config/declarativeconfigsource_test.go new file mode 100644 index 0000000000..4adeb08f9f --- /dev/null +++ b/internal/config/declarativeconfigsource_test.go @@ -0,0 +1,424 @@ +// 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 2025 Datadog, Inc. + +package config + +import ( + "bytes" + "os" + "runtime" + "testing" + + "github.com/DataDog/dd-trace-go/v2/internal/log" + "github.com/DataDog/dd-trace-go/v2/internal/telemetry" + "github.com/stretchr/testify/assert" +) + +const ( + validYaml = ` +config_id: 67890 +apm_configuration_default: + DD_KEY_1: value_1 + "DD_KEY_2": "value_2" +` +) + +// testLogger implements a mock Logger that captures output +type testLogger struct { + buf bytes.Buffer +} + +func (l *testLogger) Log(msg string) { + l.buf.WriteString(msg) +} + +func (l *testLogger) String() string { + return l.buf.String() +} + +func (l *testLogger) Reset() { + l.buf.Reset() +} + +// Helper function to check if declarativeConfig is empty +func isEmptyDeclarativeConfig(dc *declarativeConfig) bool { + return dc.ID == telemetry.EmptyID && len(dc.Config) == 0 +} + +func TestFileContentsToConfig(t *testing.T) { + t.Run("simple failure", func(t *testing.T) { + data := ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("simple success", func(t *testing.T) { + dc := fileContentsToConfig([]byte(validYaml), "test.yml") + assert.Equal(t, "67890", dc.ID) + assert.Equal(t, 2, len(dc.Config)) + assert.Equal(t, "value_1", dc.Config["DD_KEY_1"]) + assert.Equal(t, "value_2", dc.Config["DD_KEY_2"]) + }) + + t.Run("success without apm_configuration_default", func(t *testing.T) { + data := ` +config_id: 67890 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.Equal(t, 0, len(dc.Config)) + assert.Equal(t, "67890", dc.ID) + }) + + t.Run("success without config_id", func(t *testing.T) { + data := ` +apm_configuration_default: + DD_KEY_1: value_1 + "DD_KEY_2": "value_2" +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.Equal(t, 2, len(dc.Config)) + assert.Equal(t, "value_1", dc.Config["DD_KEY_1"]) + assert.Equal(t, "value_2", dc.Config["DD_KEY_2"]) + assert.Equal(t, "", dc.ID) + }) + + t.Run("success with empty contents", func(t *testing.T) { + dc := fileContentsToConfig([]byte(``), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("numeric values", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: + DD_KEY_1: 123 + DD_KEY_2: 3.14 + DD_KEY_3: -42 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.Equal(t, "67890", dc.ID) + assert.Equal(t, 3, len(dc.Config)) + assert.Equal(t, "123", dc.Config["DD_KEY_1"]) + assert.Equal(t, "3.14", dc.Config["DD_KEY_2"]) + assert.Equal(t, "-42", dc.Config["DD_KEY_3"]) + }) + + t.Run("boolean values", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: + DD_KEY_1: true + DD_KEY_2: false + DD_KEY_3: yes + DD_KEY_4: no +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.Equal(t, "67890", dc.ID) + assert.Equal(t, 4, len(dc.Config)) + assert.Equal(t, "true", dc.Config["DD_KEY_1"]) + assert.Equal(t, "false", dc.Config["DD_KEY_2"]) + assert.Equal(t, "yes", dc.Config["DD_KEY_3"]) + assert.Equal(t, "no", dc.Config["DD_KEY_4"]) + }) + + t.Run("malformed YAML - missing colon", func(t *testing.T) { + data := ` +config_id 67890 +apm_configuration_default + DD_KEY_1 value_1 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("malformed YAML - incorrect indentation", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: +DD_KEY_1: value_1 + DD_KEY_2: value_2 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("malformed YAML - duplicate keys", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: + DD_KEY_1: value_1 + DD_KEY_1: value_2 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) // yaml.v3 treats duplicate keys as an error + }) + + t.Run("malformed YAML - unclosed quotes", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: + DD_KEY_1: "value_1 + DD_KEY_2: value_2 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("malformed YAML - invalid nested structure", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: + nested: + - item1 + - item2 + DD_KEY_1: value_1 +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("malformed YAML - special characters in values", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default: + DD_KEY_1: "value with spaces" + DD_KEY_2: "value with \n newline" + DD_KEY_3: "value with \t tab" + DD_KEY_4: "value with \" quotes" + DD_KEY_5: "value with \\ backslash" +` + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.Equal(t, "67890", dc.ID) + assert.Equal(t, 5, len(dc.Config)) + assert.Equal(t, "value with spaces", dc.Config["DD_KEY_1"]) + assert.Equal(t, "value with \n newline", dc.Config["DD_KEY_2"]) + assert.Equal(t, "value with \t tab", dc.Config["DD_KEY_3"]) + assert.Equal(t, "value with \" quotes", dc.Config["DD_KEY_4"]) + assert.Equal(t, "value with \\ backslash", dc.Config["DD_KEY_5"]) + }) +} + +func TestParseFile(t *testing.T) { + t.Run("file doesn't exist", func(t *testing.T) { + dc := parseFile("test_nonexistent.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) + + t.Run("success", func(t *testing.T) { + err := os.WriteFile("test_declarative.yml", []byte(validYaml), 0644) + assert.NoError(t, err) + defer os.Remove("test_declarative.yml") + + dc := parseFile("test_declarative.yml") + assert.Equal(t, "67890", dc.ID) + assert.Equal(t, 2, len(dc.Config)) + assert.Equal(t, "value_1", dc.Config["DD_KEY_1"]) + assert.Equal(t, "value_2", dc.Config["DD_KEY_2"]) + }) + + t.Run("file with no read permissions", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("File permission restrictions don't work reliably on Windows - the OS often grants read access to file owners regardless of permission bits") + } + + // On Unix-like systems, create file with no read permissions + err := os.WriteFile("test_declarative_noperm.yml", []byte(validYaml), 0000) + assert.NoError(t, err) + defer os.Remove("test_declarative_noperm.yml") + + dc := parseFile("test_declarative_noperm.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + }) +} + +func TestFileSizeLimits(t *testing.T) { + t.Run("under limit", func(t *testing.T) { + data := ` +"config_id": 67890 +"apm_configuration_default": + "DD_APM_TRACING_ENABLED": "false" + "DD_RUNTIME_METRICS_ENABLED": "false" + "DD_LOGS_INJECTION": "false" + "DD_PROFILING_ENABLED": "false" + "DD_DATA_STREAMS_ENABLED": "false" + "DD_APPSEC_ENABLED": "false" + "DD_IAST_ENABLED": "false" + "DD_DYNAMIC_INSTRUMENTATION_ENABLED": "false" + "DD_DATA_JOBS_ENABLED": "false" + "DD_APPSEC_SCA_ENABLED": "false" + "DD_TRACE_DEBUG": "false" +` + err := os.WriteFile("test_declarative_small.yml", []byte(data), 0644) + assert.NoError(t, err) + defer os.Remove("test_declarative_small.yml") + + dc := parseFile("test_declarative_small.yml") + assert.False(t, isEmptyDeclarativeConfig(dc)) // file parsing succeeded + }) + + t.Run("over limit", func(t *testing.T) { + // Build a valid declarative configuration file that surpasses maxFileSize + header := `"config_id": 67890 + "apm_configuration_default": + ` + entry := ` "DD_TRACE_DEBUG": "false"` + content := header + for len(content) <= maxFileSize { + content += entry + } + + err := os.WriteFile("test_declarative_large.yml", []byte(content), 0644) + assert.NoError(t, err) + defer os.Remove("test_declarative_large.yml") + + dc := parseFile("test_declarative_large.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) // file parsing failed due to size + }) +} + +func TestParseFileLogging(t *testing.T) { + // Capture log output + tl := &testLogger{} + defer log.UseLogger(tl)() + + t.Run("non-existent file on non-Linux doesn't log on stat", func(t *testing.T) { + if runtime.GOOS == "linux" { + t.Skip("This test is for non-linux platforms") + } + + tl.Reset() + dc := parseFile("test_nonexistent_declarative.yml") + assert.NotNil(t, dc) + assert.Empty(t, dc.Config) + // Should not log warnings for non-existent files + assert.NotContains(t, tl.String(), "Failed to stat") + }) + + t.Run("directory instead of file logs warning", func(t *testing.T) { + dirPath := "test_declarative_dir" + err := os.MkdirAll(dirPath, 0755) + assert.NoError(t, err) + defer os.RemoveAll(dirPath) + + tl.Reset() + dc := parseFile(dirPath) + assert.NotNil(t, dc) + assert.Empty(t, dc.Config) + assert.Contains(t, tl.String(), "Failed to read declarative config file") + }) + + t.Run("malformed YAML logs warning", func(t *testing.T) { + data := ` +config_id: 67890 +apm_configuration_default + DD_KEY_1 value_1 +` + tl.Reset() + dc := fileContentsToConfig([]byte(data), "test.yml") + assert.True(t, isEmptyDeclarativeConfig(dc)) + assert.Contains(t, tl.String(), "Parsing declarative config file test.yml failed") + }) +} + +func TestDeclarativeConfigSource(t *testing.T) { + t.Run("Get returns value from config", func(t *testing.T) { + err := os.WriteFile("test_source.yml", []byte(validYaml), 0644) + assert.NoError(t, err) + defer os.Remove("test_source.yml") + + source := newDeclarativeConfigSource("test_source.yml", telemetry.OriginLocalStableConfig) + assert.Equal(t, "value_1", source.Get("DD_KEY_1")) + assert.Equal(t, "value_2", source.Get("DD_KEY_2")) + }) + + t.Run("Get normalizes key", func(t *testing.T) { + err := os.WriteFile("test_source_normalize.yml", []byte(validYaml), 0644) + assert.NoError(t, err) + defer os.Remove("test_source_normalize.yml") + + source := newDeclarativeConfigSource("test_source_normalize.yml", telemetry.OriginLocalStableConfig) + // Should normalize "key_1" to "DD_KEY_1" + assert.Equal(t, "value_1", source.Get("key_1")) + }) + + t.Run("Get returns empty for missing key", func(t *testing.T) { + err := os.WriteFile("test_source_missing.yml", []byte(validYaml), 0644) + assert.NoError(t, err) + defer os.Remove("test_source_missing.yml") + + source := newDeclarativeConfigSource("test_source_missing.yml", telemetry.OriginLocalStableConfig) + assert.Equal(t, "", source.Get("DD_NONEXISTENT_KEY")) + }) + + t.Run("GetID returns config ID", func(t *testing.T) { + err := os.WriteFile("test_source_id.yml", []byte(validYaml), 0644) + assert.NoError(t, err) + defer os.Remove("test_source_id.yml") + + source := newDeclarativeConfigSource("test_source_id.yml", telemetry.OriginLocalStableConfig) + assert.Equal(t, "67890", source.GetID()) + }) + + t.Run("GetID returns empty for missing ID", func(t *testing.T) { + data := ` +apm_configuration_default: + DD_KEY_1: value_1 +` + err := os.WriteFile("test_source_noid.yml", []byte(data), 0644) + assert.NoError(t, err) + defer os.Remove("test_source_noid.yml") + + source := newDeclarativeConfigSource("test_source_noid.yml", telemetry.OriginLocalStableConfig) + assert.Equal(t, "", source.GetID()) + }) + + t.Run("Origin returns correct origin", func(t *testing.T) { + err := os.WriteFile("test_source_origin.yml", []byte(validYaml), 0644) + assert.NoError(t, err) + defer os.Remove("test_source_origin.yml") + + localSource := newDeclarativeConfigSource("test_source_origin.yml", telemetry.OriginLocalStableConfig) + assert.Equal(t, telemetry.OriginLocalStableConfig, localSource.Origin()) + + managedSource := newDeclarativeConfigSource("test_source_origin.yml", telemetry.OriginManagedStableConfig) + assert.Equal(t, telemetry.OriginManagedStableConfig, managedSource.Origin()) + }) + + t.Run("non-existent file creates empty config", func(t *testing.T) { + source := newDeclarativeConfigSource("nonexistent_config.yml", telemetry.OriginLocalStableConfig) + assert.Equal(t, "", source.Get("DD_ANY_KEY")) + assert.Equal(t, telemetry.EmptyID, source.GetID()) + assert.Equal(t, telemetry.OriginLocalStableConfig, source.Origin()) + }) +} + +func TestDeclarativeConfigConstants(t *testing.T) { + t.Run("file paths are defined", func(t *testing.T) { + assert.Equal(t, "/etc/datadog-agent/application_monitoring.yaml", localFilePath) + assert.Equal(t, "/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml", managedFilePath) + }) + + t.Run("max file size is 4KB", func(t *testing.T) { + assert.Equal(t, 4*1024, maxFileSize) + }) +} + +func TestGlobalDeclarativeConfigSources(t *testing.T) { + t.Run("LocalDeclarativeConfig is initialized", func(t *testing.T) { + assert.NotNil(t, LocalDeclarativeConfig) + assert.Equal(t, telemetry.OriginLocalStableConfig, LocalDeclarativeConfig.Origin()) + }) + + t.Run("ManagedDeclarativeConfig is initialized", func(t *testing.T) { + assert.NotNil(t, ManagedDeclarativeConfig) + assert.Equal(t, telemetry.OriginManagedStableConfig, ManagedDeclarativeConfig.Origin()) + }) +} + From 9e109e8b3426360837eee3ba2e821ddd12857a95 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 13 Nov 2025 15:37:27 -0500 Subject: [PATCH 25/26] Add missing cpwright headers; run go fmt --- internal/config/declarativeconfigsource_test.go | 1 - internal/config/envconfigsource_test.go | 5 +++++ internal/config/otelconfigsource_test.go | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/config/declarativeconfigsource_test.go b/internal/config/declarativeconfigsource_test.go index 4adeb08f9f..08642112d2 100644 --- a/internal/config/declarativeconfigsource_test.go +++ b/internal/config/declarativeconfigsource_test.go @@ -421,4 +421,3 @@ func TestGlobalDeclarativeConfigSources(t *testing.T) { assert.Equal(t, telemetry.OriginManagedStableConfig, ManagedDeclarativeConfig.Origin()) }) } - diff --git a/internal/config/envconfigsource_test.go b/internal/config/envconfigsource_test.go index 599dc681a6..0f85d3351d 100644 --- a/internal/config/envconfigsource_test.go +++ b/internal/config/envconfigsource_test.go @@ -1,3 +1,8 @@ +// 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 2025 Datadog, Inc. + package config import ( diff --git a/internal/config/otelconfigsource_test.go b/internal/config/otelconfigsource_test.go index f428b86dde..05be7cfbb9 100644 --- a/internal/config/otelconfigsource_test.go +++ b/internal/config/otelconfigsource_test.go @@ -1,3 +1,8 @@ +// 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 2025 Datadog, Inc. + package config import ( From c5c7d30240f9e7509755a9e96f402bab2ca3fb34 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 14 Nov 2025 13:12:54 -0500 Subject: [PATCH 26/26] nits --- internal/config/configprovider.go | 1 - internal/config/declarativeconfig.go | 5 ----- 2 files changed, 6 deletions(-) diff --git a/internal/config/configprovider.go b/internal/config/configprovider.go index e0577a779b..1ffb704709 100644 --- a/internal/config/configprovider.go +++ b/internal/config/configprovider.go @@ -171,7 +171,6 @@ func (p *ConfigProvider) getURL(key string, def *url.URL) *url.URL { // normalizeKey is a helper function for ConfigSource implementations to normalize the key to a valid environment variable name. func normalizeKey(key string) string { - // Try to convert key to a valid environment variable name if strings.HasPrefix(key, "DD_") || strings.HasPrefix(key, "OTEL_") { return key } diff --git a/internal/config/declarativeconfig.go b/internal/config/declarativeconfig.go index 67efd4e9b2..b25cce3bab 100644 --- a/internal/config/declarativeconfig.go +++ b/internal/config/declarativeconfig.go @@ -21,11 +21,6 @@ func (d *declarativeConfig) getID() string { return d.ID } -// To be used by tests -// func (d *declarativeConfig) isEmpty() bool { -// return d.ID == telemetry.EmptyID && len(d.Config) == 0 -// } - // emptyDeclarativeConfig creates and returns a new, empty declarativeConfig instance. func emptyDeclarativeConfig() *declarativeConfig { return &declarativeConfig{