diff --git a/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java b/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java index 00d9cfcf863..904a1260842 100644 --- a/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java +++ b/dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java @@ -84,6 +84,8 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[ + "datadog.trace.api.ConfigDefaults:build_time," + "datadog.trace.api.ConfigOrigin:build_time," + "datadog.trace.api.ConfigSetting:build_time," + + "datadog.trace.api.config.OtlpConfig$Protocol:build_time," + + "datadog.trace.api.config.OtlpConfig$Temporality:build_time," + "datadog.trace.api.EventTracker:build_time," + "datadog.trace.api.InstrumenterConfig:build_time," + "datadog.trace.api.Functions:build_time," diff --git a/dd-trace-api/build.gradle.kts b/dd-trace-api/build.gradle.kts index f191c71be0e..43637568c03 100644 --- a/dd-trace-api/build.gradle.kts +++ b/dd-trace-api/build.gradle.kts @@ -63,6 +63,8 @@ val excludedClassesCoverage by extra( "datadog.trace.payloadtags.PayloadTagsData", "datadog.trace.payloadtags.PayloadTagsData.PathAndValue", "datadog.trace.api.llmobs.LLMObsTags", + "datadog.trace.api.config.OtlpConfig.Protocol", + "datadog.trace.api.config.OtlpConfig.Temporality", ) ) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 91151166b62..6205504fd1c 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -101,6 +101,16 @@ public final class ConfigDefaults { static final boolean DEFAULT_JMX_FETCH_MULTIPLE_RUNTIME_SERVICES_ENABLED = false; static final int DEFAULT_JMX_FETCH_MULTIPLE_RUNTIME_SERVICES_LIMIT = 10; + public static final boolean DEFAULT_METRICS_OTEL_ENABLED = false; + // Default recommended by Datadog; it differs from Otel’s default of 60000 (60s) + static final int DEFAULT_METRICS_OTEL_INTERVAL = 10000; // ms + // Default recommended by Datadog; it differs from Otel’s default of 30000 (30s) + static final int DEFAULT_METRICS_OTEL_TIMEOUT = 7500; // ms + + static final String DEFAULT_OTLP_HTTP_METRIC_ENDPOINT = "v1/metrics"; + static final String DEFAULT_OTLP_HTTP_PORT = "4318"; + static final String DEFAULT_OTLP_GRPC_PORT = "4317"; + static final int DEFAULT_DOGSTATSD_START_DELAY = 15; // seconds static final boolean DEFAULT_HEALTH_METRICS_ENABLED = true; diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/OtlpConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/OtlpConfig.java new file mode 100644 index 00000000000..29d7e144ca4 --- /dev/null +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/OtlpConfig.java @@ -0,0 +1,29 @@ +package datadog.trace.api.config; + +public final class OtlpConfig { + + public static final String METRICS_OTEL_ENABLED = "metrics.otel.enabled"; + public static final String METRICS_OTEL_INTERVAL = "metrics.otel.interval"; + public static final String METRICS_OTEL_TIMEOUT = "metrics.otel.timeout"; + + public static final String OTLP_METRICS_ENDPOINT = "otlp.metrics.endpoint"; + public static final String OTLP_METRICS_HEADERS = "otlp.metrics.headers"; + public static final String OTLP_METRICS_PROTOCOL = "otlp.metrics.protocol"; + public static final String OTLP_METRICS_TIMEOUT = "otlp.metrics.timeout"; + public static final String OTLP_METRICS_TEMPORALITY_PREFERENCE = + "otlp.metrics.temporality.preference"; + + public enum Protocol { + GRPC, + HTTP_PROTOBUF, + HTTP_JSON + } + + public enum Temporality { + CUMULATIVE, + DELTA, + LOWMEMORY + } + + private OtlpConfig() {} +} diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index d8410864868..a28a1729919 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -114,6 +114,12 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_JMX_FETCH_MULTIPLE_RUNTIME_SERVICES_LIMIT; import static datadog.trace.api.ConfigDefaults.DEFAULT_LLM_OBS_AGENTLESS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_LOGS_INJECTION_ENABLED; +import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_ENABLED; +import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_INTERVAL; +import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_TIMEOUT; +import static datadog.trace.api.ConfigDefaults.DEFAULT_OTLP_GRPC_PORT; +import static datadog.trace.api.ConfigDefaults.DEFAULT_OTLP_HTTP_METRIC_ENDPOINT; +import static datadog.trace.api.ConfigDefaults.DEFAULT_OTLP_HTTP_PORT; import static datadog.trace.api.ConfigDefaults.DEFAULT_PARTIAL_FLUSH_MIN_SPANS; import static datadog.trace.api.ConfigDefaults.DEFAULT_PERF_METRICS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_PRIORITY_SAMPLING_ENABLED; @@ -437,6 +443,14 @@ import static datadog.trace.api.config.JmxFetchConfig.JMX_TAGS; import static datadog.trace.api.config.LlmObsConfig.LLMOBS_AGENTLESS_ENABLED; import static datadog.trace.api.config.LlmObsConfig.LLMOBS_ML_APP; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_ENABLED; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_INTERVAL; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_TIMEOUT; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_ENDPOINT; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_HEADERS; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_PROTOCOL; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_TEMPORALITY_PREFERENCE; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_TIMEOUT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_AGENTLESS; import static datadog.trace.api.config.ProfilingConfig.PROFILING_AGENTLESS_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_API_KEY_FILE_OLD; @@ -654,7 +668,6 @@ import static datadog.trace.api.config.TracerConfig.TRACE_X_DATADOG_TAGS_MAX_LENGTH; import static datadog.trace.api.config.TracerConfig.WRITER_BAGGAGE_INJECT; import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; -import static datadog.trace.api.iast.IastDetectionMode.DEFAULT; import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; import static datadog.trace.util.CollectionUtils.tryMakeImmutableList; import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet; @@ -665,6 +678,7 @@ import datadog.environment.SystemProperties; import datadog.trace.api.civisibility.CiVisibilityWellKnownTags; import datadog.trace.api.config.GeneralConfig; +import datadog.trace.api.config.OtlpConfig; import datadog.trace.api.config.ProfilingConfig; import datadog.trace.api.config.TracerConfig; import datadog.trace.api.iast.IastContext; @@ -889,6 +903,15 @@ public static String getHostName() { private final boolean jmxFetchMultipleRuntimeServicesEnabled; private final int jmxFetchMultipleRuntimeServicesLimit; + private final boolean metricsOtelEnabled; + private final int metricsOtelInterval; + private final int metricsOtelTimeout; + private final String otlpMetricsEndpoint; + private final Map otlpMetricsHeaders; + private final OtlpConfig.Protocol otlpMetricsProtocol; + private final int otlpMetricsTimeout; + private final OtlpConfig.Temporality otlpMetricsTemporalityPreference; + // These values are default-ed to those of jmx fetch values as needed private final boolean healthMetricsEnabled; private final String healthMetricsStatsdHost; @@ -1453,7 +1476,7 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins } } - if (agentHostFromEnvironment == null) { + if (agentHostFromEnvironment == null || agentHostFromEnvironment.isEmpty()) { agentHost = DEFAULT_AGENT_HOST; } else if (agentHostFromEnvironment.charAt(0) == '[') { agentHost = agentHostFromEnvironment.substring(1, agentHostFromEnvironment.length() - 1); @@ -1832,6 +1855,61 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins statsDClientSocketBuffer = configProvider.getInteger(STATSD_CLIENT_SOCKET_BUFFER); statsDClientSocketTimeout = configProvider.getInteger(STATSD_CLIENT_SOCKET_TIMEOUT); + metricsOtelEnabled = + configProvider.getBoolean(METRICS_OTEL_ENABLED, DEFAULT_METRICS_OTEL_ENABLED); + + int otelInterval = + configProvider.getInteger(METRICS_OTEL_INTERVAL, DEFAULT_METRICS_OTEL_INTERVAL); + if (otelInterval < 0) { + log.warn("Invalid OTel metrics interval: {}. The value must be positive", otelInterval); + otelInterval = DEFAULT_METRICS_OTEL_INTERVAL; + } + metricsOtelInterval = otelInterval; + + int otelTimeout = configProvider.getInteger(METRICS_OTEL_TIMEOUT, DEFAULT_METRICS_OTEL_TIMEOUT); + if (otelTimeout < 0) { + log.warn("Invalid OTel metrics timeout: {}. The value must be positive", otelTimeout); + otelTimeout = DEFAULT_METRICS_OTEL_TIMEOUT; + } + metricsOtelTimeout = otelTimeout; + + // keep OTLP default timeout below the overall export timeout + int defaultOtlpTimeout = Math.min(metricsOtelTimeout, DEFAULT_METRICS_OTEL_TIMEOUT); + int otlpTimeout = configProvider.getInteger(OTLP_METRICS_TIMEOUT, defaultOtlpTimeout); + if (otlpTimeout < 0) { + log.warn("Invalid OTLP metrics timeout: {}. The value must be positive", otlpTimeout); + otlpTimeout = defaultOtlpTimeout; + } + otlpMetricsTimeout = otlpTimeout; + + otlpMetricsHeaders = configProvider.getMergedMap(OTLP_METRICS_HEADERS, '='); + otlpMetricsProtocol = + configProvider.getEnum( + OTLP_METRICS_PROTOCOL, OtlpConfig.Protocol.class, OtlpConfig.Protocol.HTTP_PROTOBUF); + + String otlpMetricsEndpointFromEnvironment = configProvider.getString(OTLP_METRICS_ENDPOINT); + if (otlpMetricsEndpointFromEnvironment == null) { + if (otlpMetricsProtocol == OtlpConfig.Protocol.GRPC) { + otlpMetricsEndpointFromEnvironment = "http://" + agentHost + ':' + DEFAULT_OTLP_GRPC_PORT; + } else { + otlpMetricsEndpointFromEnvironment = + "http://" + + agentHost + + ':' + + DEFAULT_OTLP_HTTP_PORT + + '/' + + DEFAULT_OTLP_HTTP_METRIC_ENDPOINT; + } + } + otlpMetricsEndpoint = otlpMetricsEndpointFromEnvironment; + + otlpMetricsTemporalityPreference = + configProvider.getEnum( + OTLP_METRICS_TEMPORALITY_PREFERENCE, + OtlpConfig.Temporality.class, + OtlpConfig.Temporality.DELTA); + + // Runtime metrics are disabled if Otel metrics are enabled and the metrics exporter is none runtimeMetricsEnabled = configProvider.getBoolean(RUNTIME_METRICS_ENABLED, true); jmxFetchEnabled = @@ -2156,7 +2234,8 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) iastContextMode = configProvider.getEnum(IAST_CONTEXT_MODE, IastContext.Mode.class, IastContext.Mode.REQUEST); iastDetectionMode = - configProvider.getEnum(IAST_DETECTION_MODE, IastDetectionMode.class, DEFAULT); + configProvider.getEnum( + IAST_DETECTION_MODE, IastDetectionMode.class, IastDetectionMode.DEFAULT); iastMaxConcurrentRequests = iastDetectionMode.getIastMaxConcurrentRequests(configProvider); iastVulnerabilitiesPerRequest = iastDetectionMode.getIastVulnerabilitiesPerRequest(configProvider); @@ -4970,6 +5049,38 @@ public boolean isJmxFetchIntegrationEnabled( return configProvider.isEnabled(integrationNames, "jmxfetch.", ".enabled", defaultEnabled); } + public boolean isMetricsOtelEnabled() { + return metricsOtelEnabled; + } + + public int getMetricsOtelInterval() { + return metricsOtelInterval; + } + + public int getMetricsOtelTimeout() { + return metricsOtelTimeout; + } + + public String getOtlpMetricsEndpoint() { + return otlpMetricsEndpoint; + } + + public Map getOtlpMetricsHeaders() { + return otlpMetricsHeaders; + } + + public OtlpConfig.Protocol getOtlpMetricsProtocol() { + return otlpMetricsProtocol; + } + + public int getOtlpMetricsTimeout() { + return otlpMetricsTimeout; + } + + public OtlpConfig.Temporality getOtlpMetricsTemporalityPreference() { + return otlpMetricsTemporalityPreference; + } + public boolean isRuleEnabled(final String name) { return isRuleEnabled(name, true); } @@ -5958,6 +6069,22 @@ public String toString() { + aiGuardEnabled + ", aiGuardEndpoint=" + aiGuardEndpoint + + ", metricsOtelEnabled=" + + metricsOtelEnabled + + ", metricsOtelInterval=" + + metricsOtelInterval + + ", metricsOtelTimeout=" + + metricsOtelTimeout + + ", otlpMetricsEndpoint=" + + otlpMetricsEndpoint + + ", otlpMetricsHeaders=" + + otlpMetricsHeaders + + ", otlpMetricsProtocol=" + + otlpMetricsProtocol + + ", otlpMetricsTimeout=" + + otlpMetricsTimeout + + ", otlpMetricsTemporalityPreference=" + + otlpMetricsTemporalityPreference + ", serviceDiscoveryEnabled=" + serviceDiscoveryEnabled + '}'; diff --git a/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy index 85a3c80974e..164278d81d7 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/ConfigTest.groovy @@ -6,7 +6,6 @@ import datadog.trace.bootstrap.config.provider.ConfigConverter import datadog.trace.bootstrap.config.provider.ConfigProvider import datadog.trace.test.util.DDSpecification import datadog.trace.util.throwable.FatalAgentMisconfigurationError - import static datadog.trace.api.ConfigDefaults.DEFAULT_HTTP_CLIENT_ERROR_STATUSES import static datadog.trace.api.ConfigDefaults.DEFAULT_HTTP_SERVER_ERROR_STATUSES import static datadog.trace.api.ConfigDefaults.DEFAULT_PARTIAL_FLUSH_MIN_SPANS @@ -137,6 +136,18 @@ import static datadog.trace.api.config.TracerConfig.TRACE_SAMPLING_OPERATION_RUL import static datadog.trace.api.config.TracerConfig.TRACE_SAMPLING_SERVICE_RULES import static datadog.trace.api.config.TracerConfig.TRACE_X_DATADOG_TAGS_MAX_LENGTH import static datadog.trace.api.config.TracerConfig.WRITER_TYPE +import static datadog.trace.api.config.OtlpConfig.Protocol.HTTP_PROTOBUF +import static datadog.trace.api.config.OtlpConfig.Protocol.HTTP_JSON +import static datadog.trace.api.config.OtlpConfig.Temporality.CUMULATIVE +import static datadog.trace.api.config.OtlpConfig.Temporality.DELTA +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_ENABLED +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_INTERVAL +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_TIMEOUT +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_ENDPOINT +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_HEADERS +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_PROTOCOL +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_TIMEOUT +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_TEMPORALITY_PREFERENCE import datadog.trace.config.inversion.ConfigHelper class ConfigTest extends DDSpecification { @@ -174,6 +185,37 @@ class ConfigTest extends DDSpecification { private static final DD_LLMOBS_ML_APP_ENV = "DD_LLMOBS_ML_APP" private static final DD_LLMOBS_AGENTLESS_ENABLED_ENV = "DD_LLMOBS_AGENTLESS_ENABLED" + private static final DD_TRACE_OTEL_ENABLED_ENV = "DD_TRACE_OTEL_ENABLED" + private static final DD_TRACE_OTEL_ENABLED_PROP = "dd.trace.otel.enabled" + + private static final DD_METRICS_OTEL_ENABLED_ENV = "DD_METRICS_OTEL_ENABLED" + private static final DD_METRICS_OTEL_ENABLED_PROP = "dd.metrics.otel.enabled" + + private static final OTEL_RESOURCE_ATTRIBUTES_ENV = "OTEL_RESOURCE_ATTRIBUTES" + private static final OTEL_RESOURCE_ATTRIBUTES_PROP = "otel.resource.attributes" + + private static final OTEL_METRIC_EXPORT_TIMEOUT_ENV = "OTEL_METRIC_EXPORT_TIMEOUT" + private static final OTEL_METRIC_EXPORT_TIMEOUT_PROP = "otel.metric.export.timeout" + private static final OTEL_METRIC_EXPORT_INTERVAL_ENV = "OTEL_METRIC_EXPORT_INTERVAL" + private static final OTEL_METRIC_EXPORT_INTERVAL_PROP = "otel.metric.export.interval" + + private static final OTEL_EXPORTER_OTLP_ENDPOINT_PROP = "otel.exporter.otlp.endpoint" + private static final OTEL_EXPORTER_OTLP_HEADERS_PROP = "otel.exporter.otlp.headers" + private static final OTEL_EXPORTER_OTLP_PROTOCOL_PROP = "otel.exporter.otlp.protocol" + private static final OTEL_EXPORTER_OTLP_TIMEOUT_PROP = "otel.exporter.otlp.timeout" + + private static final OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" + private static final OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_PROP = "otel.exporter.otlp.metrics.endpoint" + private static final OTEL_EXPORTER_OTLP_METRICS_HEADERS_ENV = "OTEL_EXPORTER_OTLP_METRICS_HEADERS" + private static final OTEL_EXPORTER_OTLP_METRICS_HEADERS_PROP = "otel.exporter.otlp.metrics.headers" + private static final OTEL_EXPORTER_OTLP_METRICS_PROTOCOL_ENV = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" + private static final OTEL_EXPORTER_OTLP_METRICS_PROTOCOL_PROP = "otel.exporter.otlp.metrics.protocol" + private static final OTEL_EXPORTER_OTLP_METRICS_TIMEOUT_ENV = "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT" + private static final OTEL_EXPORTER_OTLP_METRICS_TIMEOUT_PROP = "otel.exporter.otlp.metrics.timeout" + + private static final OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE_ENV = "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" + private static final OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE_PROP = "otel.exporter.otlp.metrics.temporality.preference" + def setup() { FixedCapturedEnvironment.useFixedEnv([:]) } @@ -271,6 +313,15 @@ class ConfigTest extends DDSpecification { prop.setProperty(TRACE_X_DATADOG_TAGS_MAX_LENGTH, "128") prop.setProperty(JDK_SOCKET_ENABLED, "false") + prop.setProperty(METRICS_OTEL_ENABLED, "True") + prop.setProperty(METRICS_OTEL_INTERVAL, "11000") + prop.setProperty(METRICS_OTEL_TIMEOUT, "9000") + prop.setProperty(OTLP_METRICS_ENDPOINT, "http://localhost:4333/v1/metrics") + prop.setProperty(OTLP_METRICS_HEADERS, "api-key=key,other-config-value=value") + prop.setProperty(OTLP_METRICS_PROTOCOL, "http/protobuf") + prop.setProperty(OTLP_METRICS_TIMEOUT, "5000") + prop.setProperty(OTLP_METRICS_TEMPORALITY_PREFERENCE, "cumulative") + when: Config config = Config.get(prop) @@ -364,11 +415,208 @@ class ConfigTest extends DDSpecification { config.dynamicInstrumentationInstrumentTheWorld == "method" config.dynamicInstrumentationExcludeFiles == "exclude file" config.debuggerExceptionEnabled == true + config.xDatadogTagsMaxLength == 128 config.jdkSocketEnabled == false - config.xDatadogTagsMaxLength == 128 + config.metricsOtelEnabled + config.metricsOtelInterval == 11000 + config.metricsOtelTimeout == 9000 + config.otlpMetricsEndpoint == "http://localhost:4333/v1/metrics" + config.otlpMetricsHeaders["api-key"] == "key" + config.otlpMetricsHeaders["other-config-value"] == "value" + config.otlpMetricsProtocol == HTTP_PROTOBUF + config.otlpMetricsTimeout == 5000 + config.otlpMetricsTemporalityPreference == CUMULATIVE + + } + + def "otel metrics: default values when configured incorrectly"() { + setup: + def prop = new Properties() + + prop.setProperty(METRICS_OTEL_ENABLED, "youhou") + prop.setProperty(METRICS_OTEL_INTERVAL, "-1") + prop.setProperty(METRICS_OTEL_TIMEOUT, "invalid") + prop.setProperty(OTLP_METRICS_ENDPOINT, "invalid") + prop.setProperty(OTLP_METRICS_HEADERS, "11") + prop.setProperty(OTLP_METRICS_PROTOCOL, "invalid") + prop.setProperty(OTLP_METRICS_TIMEOUT, "-34") + prop.setProperty(OTLP_METRICS_TEMPORALITY_PREFERENCE, "invalid") + + when: + Config config = Config.get(prop) + + then: + !config.metricsOtelEnabled + config.metricsOtelInterval == 10000 + config.metricsOtelTimeout == 7500 + config.otlpMetricsEndpoint == "invalid" + config.otlpMetricsHeaders == [:] + config.otlpMetricsProtocol == HTTP_PROTOBUF + config.otlpMetricsTimeout == 7500 + config.otlpMetricsTemporalityPreference == DELTA + } + + def "otel metrics: default values when not set"() { + setup: + def prop = new Properties() + + when: + Config config = Config.get(prop) + + then: + !config.metricsOtelEnabled + config.metricsOtelInterval == 10000 + config.metricsOtelTimeout == 7500 + config.otlpMetricsEndpoint == "http://localhost:4318/v1/metrics" + config.otlpMetricsHeaders == [:] + config.otlpMetricsProtocol == HTTP_PROTOBUF + config.otlpMetricsTimeout == 7500 + config.otlpMetricsTemporalityPreference == DELTA + } + + + def "otel metrics: check syntax for attributes and headers"() { + setup: + def prop = new Properties() + prop.setProperty(OTLP_METRICS_HEADERS, "api,key=key") + + when: + Config config = Config.get(prop) + + then: + config.otlpMetricsHeaders.size() == 0 + } + + def "otel generic config via system properties - metrics enabled"() { + setup: + System.setProperty(DD_METRICS_OTEL_ENABLED_PROP, "true") + System.setProperty(OTEL_RESOURCE_ATTRIBUTES_PROP, "service.name=my=app,service.version=1.0.0,deployment.environment=production, message=blahblah") + System.setProperty("otel.log.level", "warning") + + when: + Config config = new Config() + + then: + config.serviceName == "my=app" + config.version == "1.0.0" + config.env == "production" + config.tags.size() == 3 + config.tags["message"] == "blahblah" + config.tags["env"] == "production" + config.tags["version"] == "1.0.0" + config.logLevel == "warning" + } + + def "otel generic config via system properties - trace enabled"() { + setup: + System.setProperty(DD_TRACE_OTEL_ENABLED_PROP, "true") + System.setProperty(OTEL_RESOURCE_ATTRIBUTES_PROP, "service.name=my=app,service.version=1.0.0,deployment.environment=production, message=blahblah") + System.setProperty("otel.log.level", "warning") + + when: + Config config = new Config() + + then: + config.serviceName == "my=app" + config.version == "1.0.0" + config.env == "production" + config.tags.size() == 3 + config.tags["message"] == "blahblah" + config.tags["env"] == "production" + config.tags["version"] == "1.0.0" + config.logLevel == "warning" + } + + + def "otel generic config via env var - metrics enabled"() { + setup: + environmentVariables.set(DD_METRICS_OTEL_ENABLED_ENV, "true") + environmentVariables.set(OTEL_RESOURCE_ATTRIBUTES_ENV, "service.name=my=app,service.version=1.0.0,deployment.environment=production, message=blahblah") + environmentVariables.set("OTEL_LOG_LEVEL", "error") + when: + Config config = new Config() + + then: + config.serviceName == "my=app" + config.version == "1.0.0" + config.env == "production" + config.tags.size() == 3 + config.tags["message"] == "blahblah" + config.tags["env"] == "production" + config.tags["version"] == "1.0.0" + config.logLevel == "error" } + def "otel generic config via env var - traces enabled"() { + setup: + environmentVariables.set(DD_TRACE_OTEL_ENABLED_ENV, "true") + environmentVariables.set(OTEL_RESOURCE_ATTRIBUTES_ENV, "service.name=my=app,service.version=1.0.0,deployment.environment=production, message=blahblah") + environmentVariables.set("OTEL_LOG_LEVEL", "error") + when: + Config config = new Config() + + then: + config.serviceName == "my=app" + config.version == "1.0.0" + config.env == "production" + config.tags.size() == 3 + config.tags["message"] == "blahblah" + config.tags["env"] == "production" + config.tags["version"] == "1.0.0" + config.logLevel == "error" + } + + def "otel metrics: fallback keys"() { + setup: + System.setProperty(DD_METRICS_OTEL_ENABLED_PROP, "true") + System.setProperty(OTEL_EXPORTER_OTLP_PROTOCOL_PROP, "http/json") + System.setProperty(OTEL_EXPORTER_OTLP_ENDPOINT_PROP,"http://localhost:4319") + System.setProperty(OTEL_EXPORTER_OTLP_HEADERS_PROP,"api-key=key,other-config-value=value") + System.setProperty(OTEL_EXPORTER_OTLP_TIMEOUT_PROP,"1000") + + when: + Config config = new Config() + + then: + config.otlpMetricsProtocol == HTTP_JSON + config.otlpMetricsEndpoint == "http://localhost:4319/v1/metrics" + config.otlpMetricsHeaders.size() == 2 + config.otlpMetricsHeaders["api-key"] == "key" + config.otlpMetricsHeaders["other-config-value"] == "value" + config.otlpMetricsTimeout == 1000 + } + + def "otel metrics: fallback key endpoint"() { + setup: + System.setProperty(DD_METRICS_OTEL_ENABLED_PROP, "true") + System.setProperty(OTEL_EXPORTER_OTLP_PROTOCOL_PROP, "http/json") + System.setProperty(PREFIX + TRACE_AGENT_URL,"http://192.168.0.3:8126") + + when: + Config config = new Config() + + then: + config.agentHost == "192.168.0.3" + config.otlpMetricsProtocol == HTTP_JSON + config.otlpMetricsEndpoint == "http://192.168.0.3:4318/v1/metrics" + } + + def "otel metrics: fallback key endpoint 2"() { + setup: + System.setProperty(DD_METRICS_OTEL_ENABLED_PROP, "true") + System.setProperty(PREFIX + TRACE_AGENT_URL,"'/tmp/ddagent/trace.sock'") + + when: + Config config = new Config() + + then: + config.agentHost == "localhost" + config.otlpMetricsProtocol == HTTP_PROTOBUF + config.otlpMetricsEndpoint == "http://localhost:4318/v1/metrics" + } + + def "specify overrides via system properties"() { setup: System.setProperty(PREFIX + API_KEY, "new api key") @@ -460,6 +708,16 @@ class ConfigTest extends DDSpecification { System.setProperty(PREFIX + DYNAMIC_INSTRUMENTATION_EXCLUDE_FILES, "exclude file") System.setProperty(PREFIX + TRACE_X_DATADOG_TAGS_MAX_LENGTH, "128") + System.setProperty(DD_METRICS_OTEL_ENABLED_PROP, "True") + System.setProperty(OTEL_METRIC_EXPORT_INTERVAL_PROP, "11000") + System.setProperty(OTEL_METRIC_EXPORT_TIMEOUT_PROP, "9000") + System.setProperty(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_PROP, "http://localhost:4333/v1/metrics") + System.setProperty(OTEL_EXPORTER_OTLP_METRICS_HEADERS_PROP, "api-key=key,other-config-value=value") + System.setProperty(OTEL_EXPORTER_OTLP_METRICS_PROTOCOL_PROP, "http/protobuf") + System.setProperty(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT_PROP, "5000") + System.setProperty(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE_PROP, "cumulative") + System.setProperty(OTEL_RESOURCE_ATTRIBUTES_PROP, "service.name=my=app,service.version=1.0.0,deployment.environment=production") + when: Config config = new Config() @@ -476,8 +734,8 @@ class ConfigTest extends DDSpecification { config.prioritySamplingEnabled == false config.traceResolverEnabled == false config.serviceMapping == [a: "1"] - config.mergedSpanTags == [b: "2", c: "3"] - config.mergedJmxTags == [b: "2", d: "4", (RUNTIME_ID_TAG): config.getRuntimeId(), (SERVICE_TAG): config.serviceName] + config.mergedSpanTags == [b: "2", c: "3", env: "production", version:"1.0.0"] + config.mergedJmxTags == [b: "2", d: "4", (RUNTIME_ID_TAG): config.getRuntimeId(), (SERVICE_TAG): config.serviceName, env: "production", version:"1.0.0"] config.requestHeaderTags == [e: "five"] config.baggageMapping == [f: "six", g: "g"] config.httpServerErrorStatuses == toBitSet((122..457)) @@ -517,7 +775,7 @@ class ConfigTest extends DDSpecification { config.profilingEnabled == true config.profilingUrl == "new url" - config.mergedProfilingTags == [b: "2", f: "6", (HOST_TAG): "test-host", (RUNTIME_ID_TAG): config.getRuntimeId(), (RUNTIME_VERSION_TAG): config.getRuntimeVersion(), (SERVICE_TAG): config.serviceName, (LANGUAGE_TAG_KEY): LANGUAGE_TAG_VALUE] + config.mergedProfilingTags == [b: "2", f: "6", (HOST_TAG): "test-host", (RUNTIME_ID_TAG): config.getRuntimeId(), (RUNTIME_VERSION_TAG): config.getRuntimeVersion(), (SERVICE_TAG): config.serviceName, (LANGUAGE_TAG_KEY): LANGUAGE_TAG_VALUE, env: "production", version:"1.0.0"] config.profilingStartDelay == 1111 config.profilingStartForceFirst == true config.profilingUploadPeriod == 1112 @@ -552,6 +810,18 @@ class ConfigTest extends DDSpecification { config.dynamicInstrumentationExcludeFiles == "exclude file" config.xDatadogTagsMaxLength == 128 + + config.metricsOtelEnabled + config.version == "1.0.0" + config.env == "production" + config.metricsOtelInterval == 11000 + config.metricsOtelTimeout == 9000 + config.otlpMetricsEndpoint == "http://localhost:4333/v1/metrics" + config.otlpMetricsHeaders["api-key"] == "key" + config.otlpMetricsHeaders["other-config-value"] == "value" + config.otlpMetricsProtocol == HTTP_PROTOBUF + config.otlpMetricsTimeout == 5000 + config.otlpMetricsTemporalityPreference == CUMULATIVE } def "specify overrides via env vars"() { @@ -570,6 +840,15 @@ class ConfigTest extends DDSpecification { environmentVariables.set(DD_TRACE_LONG_RUNNING_ENABLED, "true") environmentVariables.set(DD_TRACE_LONG_RUNNING_FLUSH_INTERVAL, "81") environmentVariables.set(DD_TRACE_HEADER_TAGS, "*") + environmentVariables.set(DD_METRICS_OTEL_ENABLED_ENV, "True") + environmentVariables.set(OTEL_RESOURCE_ATTRIBUTES_ENV, "service.name=my=app,service.version=1.0.0,deployment.environment=production") + environmentVariables.set(OTEL_METRIC_EXPORT_INTERVAL_ENV, "11000") + environmentVariables.set(OTEL_METRIC_EXPORT_TIMEOUT_ENV, "9000") + environmentVariables.set(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_ENV, "http://localhost:4333/v1/metrics") + environmentVariables.set(OTEL_EXPORTER_OTLP_METRICS_HEADERS_ENV, "api-key=key,other-config-value=value") + environmentVariables.set(OTEL_EXPORTER_OTLP_METRICS_PROTOCOL_ENV, "http/protobuf") + environmentVariables.set(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT_ENV, "5000") + environmentVariables.set(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE_ENV, "cumulative") when: def config = new Config() @@ -591,6 +870,15 @@ class ConfigTest extends DDSpecification { config.getLongRunningTraceFlushInterval() == 81 config.requestHeaderTags == ["*":"http.request.headers."] config.responseHeaderTags == ["*":"http.response.headers."] + config.metricsOtelEnabled + config.metricsOtelInterval == 11000 + config.metricsOtelTimeout == 9000 + config.otlpMetricsEndpoint == "http://localhost:4333/v1/metrics" + config.otlpMetricsHeaders["api-key"] == "key" + config.otlpMetricsHeaders["other-config-value"] == "value" + config.otlpMetricsProtocol == HTTP_PROTOBUF + config.otlpMetricsTimeout == 5000 + config.otlpMetricsTemporalityPreference == CUMULATIVE } def "sys props override env vars"() { diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 634ab20cf98..f003bba5b71 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -284,8 +284,16 @@ "DD_LOG_LEVEL": ["A"], "DD_MEASURE_METHODS": ["A"], "DD_MESSAGE_BROKER_SPLIT_BY_DESTINATION": ["A"], + "DD_METRICS_OTEL_ENABLED": ["A"], + "DD_METRICS_OTEL_INTERVAL": ["A"], + "DD_METRICS_OTEL_TIMEOUT": ["A"], "DD_OBFUSCATION_QUERY_STRING_REGEXP": ["A"], "DD_OPTIMIZED_MAP_ENABLED": ["A"], + "DD_OTLP_METRICS_ENDPOINT": ["A"], + "DD_OTLP_METRICS_HEADERS": ["A"], + "DD_OTLP_METRICS_PROTOCOL": ["A"], + "DD_OTLP_METRICS_TIMEOUT": ["A"], + "DD_OTLP_METRICS_TEMPORALITY_PREFERENCE": ["A"], "DD_PIPELINE_EXECUTION_ID": ["A"], "DD_PRIMARY_TAG": ["A"], "DD_PRIORITIZATION_TYPE": ["A"], @@ -1358,7 +1366,18 @@ "OTEL_SERVICE_NAME": ["A"], "OTEL_TRACES_EXPORTER": ["A"], "OTEL_TRACES_SAMPLER_ARG": ["A"], - "OTEL_TRACES_SAMPLER": ["A"] + "OTEL_TRACES_SAMPLER": ["A"], + "OTEL_METRIC_EXPORT_INTERVAL": ["A"], + "OTEL_METRIC_EXPORT_TIMEOUT": ["A"], + "OTEL_EXPORTER_OTLP_ENDPOINT": ["A"], + "OTEL_EXPORTER_OTLP_HEADERS": ["A"], + "OTEL_EXPORTER_OTLP_PROTOCOL": ["A"], + "OTEL_EXPORTER_OTLP_TIMEOUT": ["A"], + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": ["A"], + "OTEL_EXPORTER_OTLP_METRICS_HEADERS": ["A"], + "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": ["A"], + "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": ["A"], + "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": ["A"] }, "aliases": { "DD_APPLICATION_KEY": ["DD_APP_KEY"], diff --git a/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java b/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java index 0f25c548f5b..ea8172ce66d 100644 --- a/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java +++ b/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/ConfigProvider.java @@ -21,6 +21,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Properties; @@ -111,7 +112,8 @@ public > T getEnum(String key, Class enumType, T defaultVal String value = getStringInternal(key); if (null != value) { try { - return Enum.valueOf(enumType, value); + // replace invalid characters with _ and make sure it's upper-case before converting + return Enum.valueOf(enumType, value.replace('/', '_').toUpperCase(Locale.ROOT)); } catch (Exception ignoreAndUseDefault) { log.debug("failed to parse {} for {}, defaulting to {}", value, key, defaultValue); } @@ -328,6 +330,10 @@ public List getSpacedList(String key) { } public Map getMergedMap(String key, String... aliases) { + return getMergedMap(key, ':', aliases); + } + + public Map getMergedMap(String key, char keyValueDelimiter, String... aliases) { ConfigMergeResolver mergeResolver = new ConfigMergeResolver(new HashMap<>()); int seqId = NON_DEFAULT_SEQ_ID; @@ -337,7 +343,7 @@ public Map getMergedMap(String key, String... aliases) { // We reverse iterate to allow overrides for (int i = sources.length - 1; 0 <= i; i--) { String value = sources[i].get(key, aliases); - Map parsedMap = ConfigConverter.parseMap(value, key); + Map parsedMap = ConfigConverter.parseMap(value, key, keyValueDelimiter); if (!parsedMap.isEmpty()) { if (collectConfig) { diff --git a/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/OtelEnvironmentConfigSource.java b/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/OtelEnvironmentConfigSource.java index 566c5ac5dc4..d8b12c81280 100644 --- a/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/OtelEnvironmentConfigSource.java +++ b/utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/OtelEnvironmentConfigSource.java @@ -1,5 +1,6 @@ package datadog.trace.bootstrap.config.provider; +import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_TRACE_OTEL_ENABLED; import static datadog.trace.api.config.GeneralConfig.ENV; import static datadog.trace.api.config.GeneralConfig.LOG_LEVEL; @@ -7,6 +8,14 @@ import static datadog.trace.api.config.GeneralConfig.SERVICE_NAME; import static datadog.trace.api.config.GeneralConfig.TAGS; import static datadog.trace.api.config.GeneralConfig.VERSION; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_ENABLED; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_INTERVAL; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_TIMEOUT; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_ENDPOINT; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_HEADERS; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_PROTOCOL; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_TEMPORALITY_PREFERENCE; +import static datadog.trace.api.config.OtlpConfig.OTLP_METRICS_TIMEOUT; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_ENABLED; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_EXTENSIONS_PATH; import static datadog.trace.api.config.TraceInstrumentationConfig.TRACE_OTEL_ENABLED; @@ -71,32 +80,26 @@ public ConfigOrigin origin() { } OtelEnvironmentConfigSource(Properties datadogConfigFile) { - this.enabled = traceOtelEnabled(); this.datadogConfigFile = datadogConfigFile; + this.enabled = traceOtelEnabled() || metricsOtelEnabled(); if (enabled) { - setupOteEnvironment(); + setupOtelEnvironment(); } } - private void setupOteEnvironment() { + private void setupOtelEnvironment() { - // only applies when OTEL is enabled by default (otherwise TRACE_OTEL_ENABLED takes precedence) + // only applies when OTEL is enabled by default String sdkDisabled = getOtelProperty("otel.sdk.disabled", "dd." + TRACE_OTEL_ENABLED); if ("true".equalsIgnoreCase(sdkDisabled)) { capture(TRACE_OTEL_ENABLED, "false"); + capture(METRICS_OTEL_ENABLED, "false"); return; } - - String serviceName = getOtelProperty("otel.service.name", "dd." + SERVICE_NAME); String logLevel = getOtelProperty("otel.log.level", "dd." + LOG_LEVEL); - String propagators = getOtelProperty("otel.propagators", "dd." + TRACE_PROPAGATION_STYLE); - String tracesSampler = getOtelProperty("otel.traces.sampler", "dd." + TRACE_SAMPLE_RATE); String resourceAttributes = getOtelProperty("otel.resource.attributes", "dd." + TAGS); - String requestHeaders = getOtelHeaders("request-headers", "dd." + REQUEST_HEADER_TAGS); - String responseHeaders = getOtelHeaders("response-headers", "dd." + RESPONSE_HEADER_TAGS); - String extensions = getOtelProperty("otel.javaagent.extensions", "dd." + TRACE_EXTENSIONS_PATH); - + String serviceName = getOtelProperty("otel.service.name", "dd." + SERVICE_NAME); if (null != resourceAttributes) { Map attributeMap = parseOtelMap(resourceAttributes); capture(SERVICE_NAME, attributeMap.remove("service.name")); @@ -104,22 +107,68 @@ private void setupOteEnvironment() { capture(ENV, attributeMap.remove("deployment.environment")); capture(TAGS, renderDatadogMap(attributeMap, 10)); } - capture(LOG_LEVEL, logLevel); capture(SERVICE_NAME, serviceName); - capture(TRACE_PROPAGATION_STYLE, mapPropagationStyle(propagators)); - capture(TRACE_SAMPLE_RATE, mapSampleRate(tracesSampler)); - capture(TRACE_ENABLED, mapDataCollection("traces")); - capture(RUNTIME_METRICS_ENABLED, mapDataCollection("metrics")); + if (traceOtelEnabled()) { + setupTraceOtelEnvironment(); + } + + if (metricsOtelEnabled()) { + setupMetricsOtelEnvironment(); + } else { + capture(RUNTIME_METRICS_ENABLED, mapDataCollection("metrics")); + } + mapDataCollection("logs"); // check setting, but no need to capture it + } + private void setupTraceOtelEnvironment() { + String propagators = getOtelProperty("otel.propagators", "dd." + TRACE_PROPAGATION_STYLE); + String tracesSampler = getOtelProperty("otel.traces.sampler", "dd." + TRACE_SAMPLE_RATE); + String requestHeaders = getOtelHeaders("request-headers", "dd." + REQUEST_HEADER_TAGS); + String responseHeaders = getOtelHeaders("response-headers", "dd." + RESPONSE_HEADER_TAGS); + String extensions = getOtelProperty("otel.javaagent.extensions", "dd." + TRACE_EXTENSIONS_PATH); + capture(TRACE_PROPAGATION_STYLE, mapPropagationStyle(propagators)); + capture(TRACE_SAMPLE_RATE, mapSampleRate(tracesSampler)); + capture(TRACE_ENABLED, mapDataCollection("traces")); capture(REQUEST_HEADER_TAGS, mapHeaderTags("http.request.header.", requestHeaders)); capture(RESPONSE_HEADER_TAGS, mapHeaderTags("http.response.header.", responseHeaders)); - capture(TRACE_EXTENSIONS_PATH, extensions); } + private void setupMetricsOtelEnvironment() { + capture( + METRICS_OTEL_INTERVAL, + getOtelProperty("otel.metric.export.interval", "dd." + METRICS_OTEL_INTERVAL)); + capture( + METRICS_OTEL_TIMEOUT, + getOtelProperty("otel.metric.export.timeout", "dd." + METRICS_OTEL_TIMEOUT)); + + String exporter = getOtelProperty("otel.metrics.exporter"); + if (exporter == null || "otlp".equalsIgnoreCase(exporter)) { + capture( + OTLP_METRICS_HEADERS, + getOtelOtlpProperty("metrics", "headers", "dd." + OTLP_METRICS_HEADERS)); + capture( + OTLP_METRICS_PROTOCOL, + getOtelOtlpProperty("metrics", "protocol", "dd." + OTLP_METRICS_PROTOCOL)); + capture( + OTLP_METRICS_TIMEOUT, + getOtelOtlpProperty("metrics", "timeout", "dd." + OTLP_METRICS_TIMEOUT)); + capture( + OTLP_METRICS_ENDPOINT, + getOtelOtlpProperty("metrics", "endpoint", "dd." + OTLP_METRICS_ENDPOINT)); + capture( + OTLP_METRICS_TEMPORALITY_PREFERENCE, + getOtelProperty( + "otel.exporter.otlp.metrics.temporality.preference", + "dd." + OTLP_METRICS_TEMPORALITY_PREFERENCE)); + } else { + capture(RUNTIME_METRICS_ENABLED, mapDataCollection("metrics")); + } + } + private boolean traceOtelEnabled() { String enabled = getDatadogProperty("dd." + TRACE_OTEL_ENABLED); if (null != enabled) { @@ -129,6 +178,15 @@ private boolean traceOtelEnabled() { } } + private boolean metricsOtelEnabled() { + String enabled = getDatadogProperty("dd." + METRICS_OTEL_ENABLED); + if (null != enabled) { + return Boolean.parseBoolean(enabled); + } else { + return DEFAULT_METRICS_OTEL_ENABLED; + } + } + /** * Gets an OpenTelemetry property. * @@ -164,6 +222,41 @@ private String getOtelProperty(String sysProp) { return value; } + /** + * Gets an OpenTelemetry OTLP property, checking the signal specific key before the general one. + * + *

Checks system properties, environment variables, and the optional OpenTelemetry config file. + * If the equivalent Datadog property is also set then log a warning and return {@code null}. + */ + private String getOtelOtlpProperty(String signal, String subkey, String ddSysProp) { + String otelKey = "otel.exporter.otlp." + signal + "." + subkey; + String otelValue = getOtelProperty(otelKey); + if (null == otelValue) { + // fall back to general configuration + otelKey = "otel.exporter.otlp." + subkey; + otelValue = getOtelProperty(otelKey); + // special case when using general endpoint as fallback: append appropriate metric suffix + if ("metrics".equals(signal) + && "endpoint".equals(subkey) + && otelValue != null + && !"grpc".equalsIgnoreCase(otelEnvironment.get(OTLP_METRICS_PROTOCOL))) { + otelValue = otelValue + "/v1/metrics"; + } + } + if (null == otelValue) { + return null; + } + String ddValue = getDatadogProperty(ddSysProp); + if (null != ddValue) { + String otelEnvVar = toEnvVar(otelKey); + log.warn("Both {} and {} are set, ignoring {}", toEnvVar(ddSysProp), otelEnvVar, otelEnvVar); + OtelEnvMetricCollectorProvider.get() + .setHidingOtelEnvVarMetric(toEnvVarLowerCase(otelKey), toEnvVarLowerCase(ddSysProp)); + return null; + } + return otelValue; + } + /** * Gets a Datadog property. * @@ -330,8 +423,8 @@ private String mapSampleRate(String tracesSampler) { } /** Maps an OpenTelemetry exporter setting to the equivalent Datadog collection setting. */ - private String mapDataCollection(String type) { - String exporter = getOtelProperty("otel." + type + ".exporter"); + private String mapDataCollection(String signal) { + String exporter = getOtelProperty("otel." + signal + ".exporter"); if (null == exporter) { return null; } @@ -340,9 +433,9 @@ private String mapDataCollection(String type) { return "false"; // currently we only accept "none" which maps to disable data collection } - log.warn("OTEL_{}_EXPORTER={} is not supported", type, exporter.toUpperCase(Locale.ROOT)); + log.warn("OTEL_{}_EXPORTER={} is not supported", signal, exporter.toUpperCase(Locale.ROOT)); OtelEnvMetricCollectorProvider.get() - .setUnsupportedOtelEnvVarMetric("otel_" + type + "_exporter"); + .setUnsupportedOtelEnvVarMetric("otel_" + signal + "_exporter"); return null; }