diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeter.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeter.java new file mode 100644 index 00000000000..994ceb10ab4 --- /dev/null +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeter.java @@ -0,0 +1,67 @@ +package datadog.opentelemetry.shim.metrics; + +import io.opentelemetry.api.metrics.BatchCallback; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableMeasurement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-api/1.47.0/io/opentelemetry/api/metrics/Meter.html +public class OtelMeter implements Meter { + private static final Logger LOGGER = LoggerFactory.getLogger(OtelMeter.class); + + private final String instrumentationScopeName; + private final String schemaUrl; + private final String instrumentationVersion; + + public OtelMeter( + String instrumentationScopeName, String schemaUrl, String instrumentationVersion) { + this.instrumentationScopeName = instrumentationScopeName; + this.schemaUrl = schemaUrl; + this.instrumentationVersion = instrumentationVersion; + } + + public boolean match( + String instrumentationScopeName, String instrumentationVersion, String schemaUrl) { + return instrumentationScopeName.equals(this.instrumentationScopeName) + && schemaUrl.equals(this.schemaUrl) + && instrumentationVersion.equals(this.instrumentationVersion); + } + + @Override + public LongCounterBuilder counterBuilder(String instrumentName) { + LOGGER.info("CounterBuilder is not yet supported"); + return null; + } + + @Override + public LongUpDownCounterBuilder upDownCounterBuilder(String instrumentName) { + LOGGER.info("upDownCounterBuilder is not yet supported"); + return null; + } + + @Override + public DoubleHistogramBuilder histogramBuilder(String instrumentName) { + LOGGER.info("histogramBuilder is not yet supported"); + return null; + } + + @Override + public DoubleGaugeBuilder gaugeBuilder(String instrumentName) { + LOGGER.info("gaugeBuilder is not yet supported"); + return null; + } + + @Override + public BatchCallback batchCallback( + Runnable callback, + ObservableMeasurement observableMeasurement, + ObservableMeasurement... additionalMeasurements) { + LOGGER.info("batchCallback is not yet supported"); + return Meter.super.batchCallback(callback, observableMeasurement, additionalMeasurements); + } +} diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeterBuilder.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeterBuilder.java new file mode 100644 index 00000000000..a489b034495 --- /dev/null +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeterBuilder.java @@ -0,0 +1,37 @@ +package datadog.opentelemetry.shim.metrics; + +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterBuilder; +import javax.annotation.ParametersAreNonnullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OtelMeterBuilder implements MeterBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(OtelMeterBuilder.class); + private final String instrumentationScopeName; + private String schemaUrl; + private String instrumentationVersion; + + public OtelMeterBuilder(String instrumentationScopeName) { + this.instrumentationScopeName = instrumentationScopeName; + } + + @Override + @ParametersAreNonnullByDefault + public MeterBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + @ParametersAreNonnullByDefault + public MeterBuilder setInstrumentationVersion(String instrumentationVersion) { + this.instrumentationVersion = instrumentationVersion; + return this; + } + + @Override + public Meter build() { + return new OtelMeter(instrumentationScopeName, instrumentationVersion, schemaUrl); + } +} diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeterProvider.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeterProvider.java new file mode 100644 index 00000000000..578c98f6130 --- /dev/null +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/OtelMeterProvider.java @@ -0,0 +1,62 @@ +package datadog.opentelemetry.shim.metrics; + +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.metrics.MeterProvider; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.ParametersAreNonnullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OtelMeterProvider implements MeterProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(OtelMeterProvider.class); + private static final String DEFAULT_METER_NAME = ""; + public static final MeterProvider INSTANCE = new OtelMeterProvider(); + /** Meter instances, indexed by instrumentation scope name. */ + private final Map> scopedMeters = new ConcurrentHashMap<>(); + + @Override + @ParametersAreNonnullByDefault + public Meter get(String instrumentationScopeName) { + return get(instrumentationScopeName, null); + } + + public Meter get(String instrumentationScopeName, String instrumentationVersion) { + return get(instrumentationScopeName, instrumentationVersion, null); + } + + public Meter get( + String instrumentationScopeName, String instrumentationVersion, String urlSchema) { + List meters = this.scopedMeters.get(instrumentationScopeName); + if (meters != null) { + for (Meter meter : meters) { + if ((meter instanceof OtelMeter) + && ((OtelMeter) meter) + .match(instrumentationScopeName, instrumentationVersion, urlSchema)) { + return meter; + } + } + } + Meter meter = + meterBuilder(instrumentationScopeName) + .setInstrumentationVersion(instrumentationVersion) + .setSchemaUrl(urlSchema) + .build(); + this.scopedMeters.put(instrumentationScopeName, new ArrayList<>()); + this.scopedMeters.get(instrumentationScopeName).add(meter); + + return meter; + } + + @Override + public MeterBuilder meterBuilder(String instrumentationScopeName) { + if (instrumentationScopeName.trim().isEmpty()) { + LOGGER.debug("Meter requested without instrumentation scope name."); + instrumentationScopeName = DEFAULT_METER_NAME; + } + return new OtelMeterBuilder(instrumentationScopeName); + } +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/build.gradle b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/build.gradle new file mode 100644 index 00000000000..22add70e45c --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/build.gradle @@ -0,0 +1,20 @@ +def openTelemetryVersion = '1.47.0' + +muzzle { + pass { + module = 'opentelemetry-api' + group = 'io.opentelemetry' + versions = "[$openTelemetryVersion,)" + } +} + +apply from: "$rootDir/gradle/java.gradle" + +addTestSuiteForDir('latestDepTest', 'test') + +dependencies { + compileOnly group: 'io.opentelemetry', name: 'opentelemetry-api', version: openTelemetryVersion + compileOnly group: 'com.google.auto.value', name: 'auto-value-annotations', version: '1.6.6' + + implementation project(':dd-java-agent:agent-otel:otel-shim') +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/main/java/datadog/trace/instrumentation/opentelemetry147/OpenTelemetryInstrumentation.java b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/main/java/datadog/trace/instrumentation/opentelemetry147/OpenTelemetryInstrumentation.java new file mode 100644 index 00000000000..2f6c4b71960 --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/main/java/datadog/trace/instrumentation/opentelemetry147/OpenTelemetryInstrumentation.java @@ -0,0 +1,82 @@ +package datadog.trace.instrumentation.opentelemetry147; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import com.google.auto.service.AutoService; +import datadog.opentelemetry.shim.metrics.OtelMeterProvider; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import io.opentelemetry.api.metrics.MeterProvider; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumenterModule.class) +public class OpenTelemetryInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.CanShortcutTypeMatching, Instrumenter.HasMethodAdvice { + + public OpenTelemetryInstrumentation() { + super("opentelemetry.metrics", "opentelemetry-147"); + } + + @Override + protected boolean defaultEnabled() { + // Not activated yet to prevent NPE + // return InstrumenterConfig.get().isMetricsOtelEnabled(); + return false; + } + + @Override + public String hierarchyMarkerType() { + return "io.opentelemetry.api.OpenTelemetry"; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return implementsInterface(named(hierarchyMarkerType())); + } + + @Override + public String[] knownMatchingTypes() { + return new String[] { + "io.opentelemetry.api.DefaultOpenTelemetry", + "io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry" + }; + } + + @Override + public boolean onlyMatchKnownTypes() { + return isShortcutMatchingEnabled(false); + } + + @Override + public String[] helperClassNames() { + return new String[] { + "datadog.opentelemetry.shim.metrics.OtelMeter", + "datadog.opentelemetry.shim.metrics.OtelMeterBuilder", + "datadog.opentelemetry.shim.metrics.OtelMeterProvider", + }; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + // MeterProvider OpenTelemetry.getMeterProvider() + transformer.applyAdvice( + isMethod() + .and(named("getMeterProvider")) + .and(takesNoArguments()) + .and(returns(named("io.opentelemetry.api.metrics.MeterProvider"))), + OpenTelemetryInstrumentation.class.getName() + "$MeterProviderAdvice"); + } + + public static class MeterProviderAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void returnProvider(@Advice.Return(readOnly = false) MeterProvider result) { + result = OtelMeterProvider.INSTANCE; + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java index a2d2ae83169..ec965cb499e 100644 --- a/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java +++ b/internal-api/src/main/java/datadog/trace/api/InstrumenterConfig.java @@ -7,6 +7,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_INTEGRATIONS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_LLM_OBS_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_MEASURE_METHODS; +import static datadog.trace.api.ConfigDefaults.DEFAULT_METRICS_OTEL_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_RESOLVER_RESET_INTERVAL; import static datadog.trace.api.ConfigDefaults.DEFAULT_RUM_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_RUNTIME_CONTEXT_FIELD_INJECTION; @@ -29,6 +30,7 @@ import static datadog.trace.api.config.GeneralConfig.TRIAGE_REPORT_TRIGGER; import static datadog.trace.api.config.IastConfig.IAST_ENABLED; import static datadog.trace.api.config.LlmObsConfig.LLMOBS_ENABLED; +import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DIRECT_ALLOCATION_ENABLED; import static datadog.trace.api.config.ProfilingConfig.PROFILING_DIRECT_ALLOCATION_ENABLED_DEFAULT; import static datadog.trace.api.config.ProfilingConfig.PROFILING_ENABLED; @@ -116,6 +118,7 @@ public class InstrumenterConfig { private final boolean codeOriginEnabled; private final boolean traceEnabled; private final boolean traceOtelEnabled; + private final boolean metricsOtelEnabled; private final ProfilingEnablement profilingEnabled; private final boolean ciVisibilityEnabled; private final ProductActivation appSecActivation; @@ -209,6 +212,8 @@ private InstrumenterConfig() { CODE_ORIGIN_FOR_SPANS_ENABLED, DEFAULT_CODE_ORIGIN_FOR_SPANS_ENABLED); traceEnabled = configProvider.getBoolean(TRACE_ENABLED, DEFAULT_TRACE_ENABLED); traceOtelEnabled = configProvider.getBoolean(TRACE_OTEL_ENABLED, DEFAULT_TRACE_OTEL_ENABLED); + metricsOtelEnabled = + configProvider.getBoolean(METRICS_OTEL_ENABLED, DEFAULT_METRICS_OTEL_ENABLED); profilingEnabled = ProfilingEnablement.of( @@ -373,6 +378,10 @@ public boolean isTraceOtelEnabled() { return traceOtelEnabled; } + public boolean isMetricsOtelEnabled() { + return metricsOtelEnabled; + } + public boolean isProfilingEnabled() { return profilingEnabled.isActive(); } @@ -626,6 +635,8 @@ public String toString() { + traceEnabled + ", traceOtelEnabled=" + traceOtelEnabled + + ", metricsOtelEnabled=" + + metricsOtelEnabled + ", profilingEnabled=" + profilingEnabled + ", ciVisibilityEnabled=" diff --git a/settings.gradle.kts b/settings.gradle.kts index 92aba9c108e..66e2627986a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -484,6 +484,7 @@ include( ":dd-java-agent:instrumentation:opensearch:transport", ":dd-java-agent:instrumentation:opentelemetry:opentelemetry-0.3", ":dd-java-agent:instrumentation:opentelemetry:opentelemetry-1.4", + ":dd-java-agent:instrumentation:opentelemetry:opentelemetry-1.47", ":dd-java-agent:instrumentation:opentelemetry:opentelemetry-annotations-1.20", ":dd-java-agent:instrumentation:opentelemetry:opentelemetry-annotations-1.26", ":dd-java-agent:instrumentation:opentracing",