diff --git a/agent-bridge/src/main/java/com/newrelic/agent/bridge/ExitTracer.java b/agent-bridge/src/main/java/com/newrelic/agent/bridge/ExitTracer.java index e72c22284a..5be047bb27 100644 --- a/agent-bridge/src/main/java/com/newrelic/agent/bridge/ExitTracer.java +++ b/agent-bridge/src/main/java/com/newrelic/agent/bridge/ExitTracer.java @@ -7,6 +7,8 @@ package com.newrelic.agent.bridge; +import com.newrelic.api.agent.Token; + import java.lang.reflect.InvocationHandler; @@ -19,10 +21,19 @@ public interface ExitTracer extends InvocationHandler, TracedMethod { */ void finish(int opcode, Object returnValue); + default void finish() { + // 177 is Opcodes.RETURN + finish(177, null); + } + /** * Called when a method invocation throws an exception. * * @param throwable */ void finish(Throwable throwable); + + default Token getToken() { + return NoOpToken.INSTANCE; + } } diff --git a/agent-bridge/src/main/java/com/newrelic/agent/bridge/Instrumentation.java b/agent-bridge/src/main/java/com/newrelic/agent/bridge/Instrumentation.java index 907026fa75..a3593b7cae 100644 --- a/agent-bridge/src/main/java/com/newrelic/agent/bridge/Instrumentation.java +++ b/agent-bridge/src/main/java/com/newrelic/agent/bridge/Instrumentation.java @@ -12,7 +12,6 @@ import com.newrelic.api.agent.NewRelic; import com.newrelic.api.agent.Token; -import com.newrelic.api.agent.Trace; public interface Instrumentation { @@ -59,6 +58,10 @@ ExitTracer createTracer(Object invocationTarget, int signatureId, boolean dispat ExitTracer createScalaTxnTracer(); + default ExitTracer createTracer(String metricName, int flags) { + return null; + } + /** * Returns the current transaction. This should not be called directly - instead use {@link Agent#getTransaction()}. * diff --git a/agent-bridge/src/main/java/com/newrelic/agent/bridge/TracedMethod.java b/agent-bridge/src/main/java/com/newrelic/agent/bridge/TracedMethod.java index e6c6d13c58..575876f8c0 100644 --- a/agent-bridge/src/main/java/com/newrelic/agent/bridge/TracedMethod.java +++ b/agent-bridge/src/main/java/com/newrelic/agent/bridge/TracedMethod.java @@ -16,6 +16,12 @@ */ public interface TracedMethod extends com.newrelic.api.agent.TracedMethod { + default String getTraceId() { + return "0000000000000000"; + } + default String getSpanId() { + return "0000000000000000"; + } /** * Returns the parent of this traced method, or null if this is the root tracer. * diff --git a/agent-bridge/src/main/java/com/newrelic/agent/bridge/datastore/SqlQueryConverter.java b/agent-bridge/src/main/java/com/newrelic/agent/bridge/datastore/SqlQueryConverter.java new file mode 100644 index 0000000000..ad8938f341 --- /dev/null +++ b/agent-bridge/src/main/java/com/newrelic/agent/bridge/datastore/SqlQueryConverter.java @@ -0,0 +1,27 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.newrelic.agent.bridge.datastore; + +import com.newrelic.api.agent.QueryConverter; + +public final class SqlQueryConverter implements QueryConverter { + public static final QueryConverter INSTANCE = new SqlQueryConverter(); + + private SqlQueryConverter() { + } + + @Override + public String toRawQueryString(String rawQuery) { + return rawQuery; + } + + @Override + public String toObfuscatedQueryString(String rawQuery) { + return null; + } +} diff --git a/instrumentation-test/src/main/java/com/newrelic/agent/introspec/SpanEvent.java b/instrumentation-test/src/main/java/com/newrelic/agent/introspec/SpanEvent.java index 3e30b34fc4..fc1e95a3b1 100644 --- a/instrumentation-test/src/main/java/com/newrelic/agent/introspec/SpanEvent.java +++ b/instrumentation-test/src/main/java/com/newrelic/agent/introspec/SpanEvent.java @@ -33,4 +33,6 @@ public interface SpanEvent { String getStatusText(); Map getAgentAttributes(); + + Map getUserAttributes(); } diff --git a/instrumentation-test/src/main/java/com/newrelic/agent/introspec/internal/SpanEventImpl.java b/instrumentation-test/src/main/java/com/newrelic/agent/introspec/internal/SpanEventImpl.java index fcaa743ee5..37a17a47e3 100644 --- a/instrumentation-test/src/main/java/com/newrelic/agent/introspec/internal/SpanEventImpl.java +++ b/instrumentation-test/src/main/java/com/newrelic/agent/introspec/internal/SpanEventImpl.java @@ -79,4 +79,9 @@ public String getStatusText() { public Map getAgentAttributes() { return spanEvent.getAgentAttributes(); } + + @Override + public Map getUserAttributes() { + return spanEvent.getUserAttributesCopy(); + } } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md new file mode 100644 index 0000000000..c061e39ea1 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md @@ -0,0 +1,93 @@ +# OpenTelemetry Instrumentation + +This instrumentation module weaves parts of the OpenTelemetry SDK to incorporate bits of OpenTelemetry functionality into the New Relic Java agent. + +Specifically it can: +* Detect OpenTelemetry Spans and include them in New Relic Java agent traces. +* Detect OpenTelemetry dimensional metrics and report them to the APM entity being monitored by the Java agent. +* Autoconfigure the OpenTelemetry SDK so that OpenTelemetry data is sent to New Relic and properly associated with an APM entity guid. + +## New Relic Java Agent Configuration + +To use the OpenTelemetry Span and dimensional metric functionality incorporated into the New Relic Java agent you must enable the following config options: + +Configuration via yaml: +``` +opentelemetry: + sdk: + autoconfigure: + enabled: true + spans: + enabled: true +``` + +Configuration via system property: +``` +-Dopentelemetry.sdk.autoconfigure.enabled=true +-Dopentelemetry.sdk.spans.enabled=true +``` + +Configuration via environment variable: +``` +NEW_RELIC_OPENTELEMETRY_SDK_AUTOCONFIGURE_ENABLED=true +NEW_RELIC_OPENTELEMETRY_SDK_SPANS_ENABLED=true +``` + +## OpenTelemetry Dimensional Metrics + +OpenTelemetry APIs can be used to create dimensional metrics which will be detected by the New Relic Java agent and reported to the APM entity being monitored by the New Relic Java agent. + +To use this functionality, enable the feature as documented above, add the required `opentelemetry` dependencies to your application: +```groovy + implementation(platform("io.opentelemetry:opentelemetry-bom:1.44.1")) + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation("io.opentelemetry:opentelemetry-exporter-otlp") +``` + +Then utilize the OpenTelemetry APIs to record dimensional metrics: +```java +LongCounter longCounter = GlobalOpenTelemetry.get().getMeterProvider().get("my-application").counterBuilder("my.application.counter").build(); +longCounter.add(1, Attributes.of(AttributeKey.stringKey("foo"), "bar")); +``` + +Any recorded dimensional metrics can be found in the Metrics Explorer for the associated APM entity and can be used to build custom dashboards. + +## OpenTelemetry Spans + +Documented below are several approaches for incorporating OpenTelemetry Spans into New Relic Java agent traces. + +### `@WithSpan` Annotation + +The New Relic Java agent will detect usage of the OpenTelemetry [@WithSpan](https://opentelemetry.io/docs/zero-code/java/agent/annotations/) annotation. The `@WithSpan` annotation can be used as an alternative to the `@Trace` annotation. + +This does not currently support the following config options: +* [Suppressing @WithSpan instrumentation](https://opentelemetry.io/docs/zero-code/java/agent/annotations/#suppressing-withspan-instrumentation) +* [Creating spans around methods with otel.instrumentation.methods.include](https://opentelemetry.io/docs/zero-code/java/agent/annotations/#creating-spans-around-methods-with-otelinstrumentationmethodsinclude) + +Note that OpenTelemetry config properties can be set through environment or system properties, like our agent, and eventually through a config file. We can use our existing OpenTelemetry instrumentation model to get access to the normalized version of the instrumentation settings to include and exclude methods and pass those to the core agent through the bridge. + +See `ClassTransformerConfigImpl.java` for implementation details of the `@WithSpan` annotation. + +### Spans Emitted From OpenTelemetry Instrumentation + +The New Relic Java agent will detect Spans emitted by [OpenTelemetry instrumentation](https://opentelemetry.io/docs/languages/java/instrumentation/). It does this by weaving the `io.opentelemetry.sdk.trace.SdkTracerProvider` so that it will create a New Relic Tracer each time an OpenTelemetry Span is started and weaving the `io.opentelemetry.context.Context` to propagate context between New Relic and OpenTelemetry Spans. + +Currently, the New Relic Java agent does not load any OpenTelemetry instrumentation it simply detects Spans emitted by OpenTelemetry manual instrumentation, native instrumentation, library instrumentation, or zero code instrumentation (i.e. bytecode instrumentation that would also require running the OpenTelemetry Java agent). + +Depending on the OpenTelemetry Span `SpanKind`, it may result in the New Relic Java agent starting a transaction (when one doesn't already exist). + +* `SpanKind.INTERNAL` + * Creating a span with no `SpanKind`, which defaults to `SpanKind.INTERNAL`, will not start a transaction + * If `SpanKind.INTERNAL` spans occur within an already existing New Relic transaction they will be included in the trace +* `SpanKind.CLIENT` + * Creating a span with `SpanKind.CLIENT` will not start a transaction. If a `CLIENT` span has certain db attributes it will be treated as a DB span, and other specific attributes will cause it to be treated as an external span + * If `SpanKind.CLIENT` spans occur within an already existing New Relic transaction they will be included in the trace +* `SpanKind.SERVER` + * Creating a span with `SpanKind.SERVER` will start a `WebTransaction/Uri/*` transaction. + * If `SpanKind.SERVER` spans occur within an already existing New Relic transaction they will be included in the trace +* `SpanKind.CONSUMER` + * Creating a span with `SpanKind.CONSUMER` will start a `OtherTransaction/*` transaction. + * If `SpanKind.CONSUMER` spans occur within an already existing New Relic transaction they will be included in the trace +* `SpanKind.PRODUCER` + * Creating a span with `SpanKind.PRODUCER` will not start a transaction. There is no explicit processing for `PRODUCER` spans currently. + * If `SpanKind.PRODUCER` spans occur within an already existing New Relic transaction they will be included in the trace (though it's effectively no different from a `SpanKind.INTERNAL` span) diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle index b0964a733c..030006b2bd 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle @@ -8,6 +8,9 @@ dependencies { implementation(project(":newrelic-weaver-api")) implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.28.0") testImplementation("junit:junit:4.12") + testImplementation("io.opentelemetry:opentelemetry-exporter-otlp:1.28.0") + testImplementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:1.28.0") + testImplementation("com.google.guava:guava:30.1.1-jre") } jar { diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/ContextHelper.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/ContextHelper.java new file mode 100644 index 0000000000..b9fb3e87d7 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/ContextHelper.java @@ -0,0 +1,57 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.context; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.ExitTracer; +import com.newrelic.agent.bridge.Transaction; +import com.newrelic.api.agent.TracedMethod; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.sdk.trace.ExitTracerSpan; + +/** + * Helper class for managing the OpenTelemetry Context + */ +class ContextHelper { + private ContextHelper() { + } + + /** + * If there's no span on the context, but there is a NR tracer on the stack, return a context with our span. + */ + public static Context current(Context context) { + Span currentSpan = Span.fromContext(context); + if (currentSpan == Span.getInvalid()) { + Transaction transaction = AgentBridge.getAgent().getTransaction(false); + if (transaction != null) { + TracedMethod tracedMethod = transaction.getTracedMethod(); + if (tracedMethod instanceof ExitTracer) { + return context.with(ExitTracerSpan.wrap((ExitTracer) tracedMethod)); + } + } + } + return context; + } + + /** + * If there's currently no NR transaction but the current contains a NR span, create a + * {@link com.newrelic.api.agent.Token} related to that span's transaction and hook it into + * the returned {@link Scope}. + */ + public static Scope makeCurrent(Context context, Scope scope) { + final Transaction currentTransaction = AgentBridge.getAgent().getTransaction(false); + if (currentTransaction == null) { + Span currentSpan = Span.fromContext(context); + + if (currentSpan instanceof ExitTracerSpan) { + return ((ExitTracerSpan) currentSpan).createScope(scope); + } + } + return scope; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/Context_Instrumentation.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/Context_Instrumentation.java new file mode 100644 index 0000000000..7c6c83fa54 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/Context_Instrumentation.java @@ -0,0 +1,26 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.context; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +/** + * Weaved to manage the OpenTelemetry Context + */ +@Weave(type = MatchType.Interface, originalName = "io.opentelemetry.context.Context") +public abstract class Context_Instrumentation { + public static Context current() { + return ContextHelper.current(Weaver.callOriginal()); + } + + public Scope makeCurrent() { + return ContextHelper.makeCurrent((Context) this, Weaver.callOriginal()); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdk.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdk.java index faeb02645e..814c054d40 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdk.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdk.java @@ -1,3 +1,10 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + package io.opentelemetry.sdk.autoconfigure; import com.newrelic.api.agent.NewRelic; @@ -7,16 +14,20 @@ import java.util.logging.Level; +/** + * Weaved to autoconfigure the OpenTelemetrySDK properties + * and resources for compatability with New Relic. + */ @Weave(type = MatchType.ExactClass) public class AutoConfiguredOpenTelemetrySdk { public static AutoConfiguredOpenTelemetrySdkBuilder builder() { final AutoConfiguredOpenTelemetrySdkBuilder builder = Weaver.callOriginal(); - Boolean autoConfigure = NewRelic.getAgent().getConfig().getValue("opentelemetry.sdk.autoconfigure.enabled"); + final Boolean autoConfigure = NewRelic.getAgent().getConfig().getValue("opentelemetry.sdk.autoconfigure.enabled"); if (autoConfigure == null || autoConfigure) { NewRelic.getAgent().getLogger().log(Level.INFO, "Appending OpenTelemetry SDK customizers"); - builder.addPropertiesCustomizer(new PropertiesCustomizer()); - builder.addResourceCustomizer(new ResourceCustomer()); + builder.addPropertiesCustomizer(OpenTelemetrySDKCustomizer::applyProperties); + builder.addResourceCustomizer(OpenTelemetrySDKCustomizer::applyResources); } return builder; } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizer.java new file mode 100644 index 0000000000..ca7d87f792 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizer.java @@ -0,0 +1,90 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.autoconfigure; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Agent; +import com.newrelic.api.agent.Logger; +import com.newrelic.api.agent.NewRelic; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +/** + * Helper class for customizing OpenTelemetrySDK properties + * and resources for compatability with New Relic. + */ +final class OpenTelemetrySDKCustomizer { + static final AttributeKey SERVICE_INSTANCE_ID_ATTRIBUTE_KEY = AttributeKey.stringKey("service.instance.id"); + + static Map applyProperties(ConfigProperties configProperties) { + return applyProperties(configProperties, NewRelic.getAgent()); + } + + /** + * Configure OpenTelemetry exporters to send data to the New Relic backend. + */ + static Map applyProperties(ConfigProperties configProperties, Agent agent) { + final String existingEndpoint = configProperties.getString("otel.exporter.otlp.endpoint"); + if (existingEndpoint == null) { + agent.getLogger().log(Level.INFO, "Auto-initializing OpenTelemetry SDK"); + final String host = agent.getConfig().getValue("host"); + final String endpoint = "https://" + host + ":443"; + final String licenseKey = agent.getConfig().getValue("license_key"); + final Map properties = new HashMap<>(); + properties.put("otel.exporter.otlp.headers", "api-key=" + licenseKey); + properties.put("otel.exporter.otlp.endpoint", endpoint); + properties.put("otel.exporter.otlp.protocol", "http/protobuf"); + properties.put("otel.span.attribute.value.length.limit", "4095"); + properties.put("otel.exporter.otlp.compression", "gzip"); + properties.put("otel.exporter.otlp.metrics.temporality.preference", "DELTA"); + properties.put("otel.exporter.otlp.metrics.default.histogram.aggregation", "BASE2_EXPONENTIAL_BUCKET_HISTOGRAM"); + properties.put("otel.experimental.exporter.otlp.retry.enabled", "true"); + properties.put("otel.experimental.resource.disabled.keys", "process.command_line"); + + final Object appName = agent.getConfig().getValue("app_name"); + properties.put("otel.service.name", appName.toString()); + + return properties; + } else { + agent.getLogger().log(Level.WARNING, + "The OpenTelemetry exporter endpoint is set to {0}, the agent will not autoconfigure the SDK", + existingEndpoint); + } + return Collections.emptyMap(); + } + + static Resource applyResources(Resource resource, ConfigProperties configProperties) { + return applyResources(resource, AgentBridge.getAgent(), NewRelic.getAgent().getLogger()); + } + + /** + * Add the monitored service's entity.guid to resources. + */ + static Resource applyResources(Resource resource, com.newrelic.agent.bridge.Agent agent, Logger logger) { + logger.log(Level.FINE, "Appending OpenTelemetry resources"); + final ResourceBuilder builder = new ResourceBuilder().putAll(resource); + final String instanceId = resource.getAttribute(SERVICE_INSTANCE_ID_ATTRIBUTE_KEY); + if (instanceId == null) { + builder.put(SERVICE_INSTANCE_ID_ATTRIBUTE_KEY, UUID.randomUUID().toString()); + } + + final String entityGuid = agent.getEntityGuid(true); + if (entityGuid != null) { + builder.put("entity.guid", entityGuid); + } + return builder.build(); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/PropertiesCustomizer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/PropertiesCustomizer.java deleted file mode 100644 index cc8496c246..0000000000 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/PropertiesCustomizer.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.opentelemetry.sdk.autoconfigure; - -import com.newrelic.api.agent.Agent; -import com.newrelic.api.agent.NewRelic; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; -import java.util.logging.Level; - -public final class PropertiesCustomizer implements Function> { - @Override - public Map apply(ConfigProperties configProperties) { - if (configProperties.getString("otel.exporter.otlp.endpoint") == null) { - final Agent agent = NewRelic.getAgent(); - agent.getLogger().log(Level.INFO, "Auto-initializing OpenTelemetry SDK"); - final String host = agent.getConfig().getValue("host"); - final String endpoint = "https://" + host + ":443"; - final String licenseKey = agent.getConfig().getValue("license_key"); - final Map properties = new HashMap<>(); - properties.put("otel.exporter.otlp.headers", "api-key=" + licenseKey); - properties.put("otel.exporter.otlp.endpoint", endpoint); - properties.put("otel.exporter.otlp.protocol", "http/protobuf"); - properties.put("otel.span.attribute.value.length.limit", "4095"); - properties.put("otel.exporter.otlp.compression", "gzip"); - properties.put("otel.exporter.otlp.metrics.temporality.preference", "DELTA"); - properties.put("otel.exporter.otlp.metrics.default.histogram.aggregation", "BASE2_EXPONENTIAL_BUCKET_HISTOGRAM"); - properties.put("otel.experimental.exporter.otlp.retry.enabled", "true"); - properties.put("otel.experimental.resource.disabled.keys", "process.command_line"); - - final Object appName = agent.getConfig().getValue("app_name"); - properties.put("otel.service.name", appName.toString()); - - return properties; - } - return Collections.emptyMap(); - } -} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceCustomer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceCustomer.java deleted file mode 100644 index 33868c9071..0000000000 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceCustomer.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.opentelemetry.sdk.autoconfigure; - -import com.newrelic.agent.bridge.AgentBridge; -import com.newrelic.api.agent.NewRelic; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.ResourceBuilder; - -import java.util.UUID; -import java.util.function.BiFunction; -import java.util.logging.Level; - -public final class ResourceCustomer implements BiFunction { - @Override - public Resource apply(Resource resource, ConfigProperties configProperties) { - NewRelic.getAgent().getLogger().log(Level.FINE, "Appending OpenTelemetry resources"); - final ResourceBuilder builder = new ResourceBuilder().putAll(resource); - final AttributeKey instanceIdKey = AttributeKey.stringKey("service.instance.id"); - final String instanceId = resource.getAttribute(instanceIdKey); - if (instanceId == null) { - builder.put(instanceIdKey, UUID.randomUUID().toString()); - } - - final String entityGuid = AgentBridge.getAgent().getEntityGuid(true); - if (entityGuid != null) { - builder.put("entity.guid", entityGuid); - } - return builder.build(); - } -} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/AttributesHelper.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/AttributesHelper.java new file mode 100644 index 0000000000..a41b49ef62 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/AttributesHelper.java @@ -0,0 +1,35 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; + +import java.util.Map; + +/** + * Helper class for adding attributes to Spans + */ +public class AttributesHelper { + private AttributesHelper() { + } + + public static Attributes toAttributes(Map attributes) { + AttributesBuilder builder = Attributes.builder(); + attributes.forEach((key, value) -> { + if (value instanceof String) { + builder.put(key, (String) value); + } else if (value instanceof Float || value instanceof Double) { + builder.put(key, ((Number) value).doubleValue()); + } else if (value instanceof Number) { + builder.put(key, ((Number) value).longValue()); + } + }); + return builder.build(); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/ExitTracerSpan.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/ExitTracerSpan.java new file mode 100644 index 0000000000..67b0e479a1 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/ExitTracerSpan.java @@ -0,0 +1,423 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.ExitTracer; +import com.newrelic.agent.bridge.datastore.SqlQueryConverter; +import com.newrelic.agent.tracers.TracerFlags; +import com.newrelic.api.agent.DatastoreParameters; +import com.newrelic.api.agent.HttpParameters; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.data.EventData; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Representation of a Span + */ +public class ExitTracerSpan implements ReadWriteSpan { + static final String OTEL_LIBRARY_VERSION = "otel.library.version"; + static final AttributeKey OTEL_LIBRARY_NAME = AttributeKey.stringKey("otel.library.name"); + private static final AttributeKey DB_SYSTEM = AttributeKey.stringKey("db.system"); + private static final AttributeKey DB_STATEMENT = AttributeKey.stringKey("db.statement"); + private static final AttributeKey DB_OPERATION = AttributeKey.stringKey("db.operation"); + private static final AttributeKey DB_SQL_TABLE = AttributeKey.stringKey("db.sql.table"); + private static final AttributeKey DB_NAME = AttributeKey.stringKey("db.name"); + + private static final AttributeKey SERVER_ADDRESS = AttributeKey.stringKey("server.address"); + private static final AttributeKey SERVER_PORT = AttributeKey.longKey("server.port"); + private static final AttributeKey URL_FULL = AttributeKey.stringKey("url.full"); + private static final AttributeKey URL_SCHEME = AttributeKey.stringKey("url.scheme"); + private static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); + private static final AttributeKey CODE_FUNCTION = AttributeKey.stringKey("code.function"); + private static final AttributeKey HTTP_REQUEST_METHOD = AttributeKey.stringKey("http.request.method"); + + private static final List> PROCEDURE_KEYS = + Arrays.asList(CODE_FUNCTION, RPC_METHOD, HTTP_REQUEST_METHOD); + // these attributes are reported as agent attributes, we don't want to duplicate them in user attributes + private static final Set AGENT_ATTRIBUTE_KEYS = + Collections.unmodifiableSet( + Stream.of(DB_STATEMENT, DB_SQL_TABLE, DB_SYSTEM, DB_OPERATION, SERVER_ADDRESS, SERVER_PORT) + .map(AttributeKey::getKey) + .collect(Collectors.toSet())); + + final ExitTracer tracer; + private final SpanKind spanKind; + private final InstrumentationLibraryInfo instrumentationLibraryInfo; + private final Map attributes; + private final SpanContext spanContext; + private final Consumer onEnd; + private final SpanContext parentSpanContext; + private final long startEpochNanos; + private boolean ended; + private String spanName; + private long endEpochNanos; + private final Resource resource; + + ExitTracerSpan(ExitTracer tracer, InstrumentationLibraryInfo instrumentationLibraryInfo, SpanKind spanKind, String spanName, SpanContext parentSpanContext, + Resource resource, Map attributes, Consumer onEnd) { + this.tracer = tracer; + this.spanKind = spanKind; + this.spanName = spanName; + this.parentSpanContext = parentSpanContext; + this.attributes = attributes; + this.onEnd = onEnd; + this.resource = resource; + this.instrumentationLibraryInfo = instrumentationLibraryInfo; + this.startEpochNanos = System.nanoTime(); + this.spanContext = SpanContext.create(tracer.getTraceId(), tracer.getSpanId(), TraceFlags.getDefault(), TraceState.getDefault()); + this.setAllAttributes(resource.getAttributes()); + } + + public static ExitTracerSpan wrap(ExitTracer tracer) { + return new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.INTERNAL, tracer.getMetricName(), SpanContext.getInvalid(), + Resource.empty(), Collections.emptyMap(), span -> { + }); + } + + @Override + public Span setAttribute(AttributeKey key, T value) { + attributes.put(key.getKey(), value); + return this; + } + + @Override + public Span addEvent(String name, Attributes attributes) { + return this; + } + + @Override + public Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { + return this; + } + + @Override + public Span setStatus(StatusCode statusCode, String description) { + return this; + } + + @Override + public Span recordException(Throwable exception) { + NewRelic.noticeError(exception); + return this; + } + + @Override + public Span recordException(Throwable exception, Attributes additionalAttributes) { + NewRelic.noticeError(exception, toMap(additionalAttributes)); + return this; + } + + static Map toMap(Attributes attributes) { + final Map map = new HashMap<>(attributes.size()); + attributes.forEach((key, value) -> { + switch (key.getType()) { + case STRING: + case LONG: + case DOUBLE: + case BOOLEAN: + map.put(key.getKey(), value); + break; + } + }); + return map; + } + + @Override + public Span updateName(String name) { + this.spanName = name; + return this; + } + + @Override + public void end() { + if (SpanKind.CLIENT == spanKind) { + reportClientSpan(); + } + tracer.setMetricName("Span", spanName); + // db.statement is reported through DatastoreParameters.SlowQueryParameter. That code path + // will correctly obfuscate the sql based on agent settings. + Map filteredAttributes = attributes.entrySet().stream() + .filter(entry -> !AGENT_ATTRIBUTE_KEYS.contains(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + tracer.addCustomAttributes(filteredAttributes); + tracer.finish(); + endEpochNanos = System.nanoTime(); + ended = true; + onEnd.accept(this); + } + + @Override + public void end(long timestamp, TimeUnit unit) { + this.end(); + } + + @Override + public SpanContext getSpanContext() { + return spanContext; + } + + @Override + public boolean isRecording() { + return true; + } + + @Override + public SpanContext getParentSpanContext() { + return parentSpanContext; + } + + @Override + public String getName() { + return spanName; + } + + @Override + public SpanData toSpanData() { + return new BasicSpanData(spanName, endEpochNanos, AttributesHelper.toAttributes(attributes), ended); + } + + @Override + public InstrumentationLibraryInfo getInstrumentationLibraryInfo() { + return InstrumentationLibraryInfo.empty(); + } + + @Override + public boolean hasEnded() { + return ended; + } + + @Override + public long getLatencyNanos() { + long endEpochNanos = ended ? this.endEpochNanos : System.nanoTime(); + return endEpochNanos - startEpochNanos; + } + + @Override + public SpanKind getKind() { + return spanKind; + } + + public T getAttribute(AttributeKey key) { + Object value = attributes.get(key.getKey()); + if (key.getType() == AttributeType.LONG && value instanceof Number) { + value = ((Number) value).longValue(); + } else if (key.getType() == AttributeType.DOUBLE && value instanceof Number) { + value = ((Number) value).doubleValue(); + } + return (T) value; + } + + private void reportClientSpan() { + final String dbSystem = getAttribute(DB_SYSTEM); + if (dbSystem != null) { + String operation = getAttribute(DB_OPERATION); + DatastoreParameters.InstanceParameter builder = DatastoreParameters + .product(dbSystem) + .collection(getAttribute(DB_SQL_TABLE)) + .operation(operation == null ? "unknown" : operation); + String serverAddress = getAttribute(SERVER_ADDRESS); + Long serverPort = getAttribute(SERVER_PORT); + + DatastoreParameters.DatabaseParameter instance = serverAddress == null ? builder.noInstance() : + builder.instance(serverAddress, (serverPort == null ? Long.valueOf(0L) : serverPort).intValue()); + + String dbName = getAttribute(DB_NAME); + DatastoreParameters.SlowQueryParameter slowQueryParameter = + dbName == null ? instance.noDatabaseName() : instance.databaseName(dbName); + final String dbStatement = getAttribute(DB_STATEMENT); + final DatastoreParameters datastoreParameters; + if (dbStatement == null) { + datastoreParameters = slowQueryParameter.build(); + } else { + datastoreParameters = slowQueryParameter.slowQuery(dbStatement, SqlQueryConverter.INSTANCE).build(); + } + + tracer.reportAsExternal(datastoreParameters); + } + // Only support the current otel spec. Ignore client spans with old attribute names + else { + try { + final URI uri = getUri(); + if (uri != null) { + final String libraryName = getAttribute(OTEL_LIBRARY_NAME); + HttpParameters genericParameters = HttpParameters.library(libraryName).uri(uri) + .procedure(getProcedure()).noInboundHeaders().build(); + tracer.reportAsExternal(genericParameters); + } + } catch (URISyntaxException e) { + NewRelic.getAgent().getLogger().log(Level.FINER, "Error parsing client span uri", e); + } + } + } + + String getProcedure() { + for (AttributeKey key : PROCEDURE_KEYS) { + String value = getAttribute(key); + if (value != null) { + return value; + } + } + return "unknown"; + } + + URI getUri() throws URISyntaxException { + final String urlFull = getAttribute(URL_FULL); + if (urlFull != null) { + return URI.create(urlFull); + } else { + final String serverAddress = getAttribute(SERVER_ADDRESS); + if (serverAddress != null) { + final String scheme = getAttribute(URL_SCHEME); + final Long serverPort = getAttribute(SERVER_PORT); + return new URI(scheme == null ? "http" : scheme, null, serverAddress, + serverPort == null ? 0 : serverPort.intValue(), null, null, null); + } + } + return null; + } + + public Scope createScope(Scope scope) { + final Token token = tracer.getToken(); + // we can't link a known transaction from one thread to another unless there is + // a transaction with at least one tracer on the new thread. + AgentBridge.getAgent().getTransaction(true); + final ExitTracer tracer = AgentBridge.instrumentation.createTracer(null, + TracerFlags.CUSTOM | TracerFlags.ASYNC); + tracer.setMetricName("Java", "OpenTelemetry", "AsyncScope"); + token.link(); + return () -> { + token.expire(); + tracer.finish(); + scope.close(); + }; + } + + public class BasicSpanData implements SpanData { + private final String spanName; + private final long endEpochNanos; + private final Attributes attributes; + private final boolean ended; + + public BasicSpanData(String spanName, long endEpochNanos, Attributes attributes, boolean ended) { + this.spanName = spanName; + this.endEpochNanos = endEpochNanos; + this.attributes = attributes; + this.ended = ended; + } + + @Override + public String getName() { + return spanName; + } + + @Override + public SpanKind getKind() { + return spanKind; + } + + @Override + public SpanContext getSpanContext() { + return spanContext; + } + + @Override + public SpanContext getParentSpanContext() { + return parentSpanContext; + } + + @Override + public StatusData getStatus() { + return StatusData.ok(); + } + + @Override + public long getStartEpochNanos() { + return startEpochNanos; + } + + @Override + public Attributes getAttributes() { + return attributes; + } + + @Override + public List getEvents() { + return Collections.emptyList(); + } + + @Override + public List getLinks() { + return Collections.emptyList(); + } + + @Override + public long getEndEpochNanos() { + return endEpochNanos; + } + + @Override + public boolean hasEnded() { + return ended; + } + + @Override + public int getTotalRecordedEvents() { + return 0; + } + + @Override + public int getTotalRecordedLinks() { + return 0; + } + + @Override + public int getTotalAttributeCount() { + return 0; + } + + @Override + public InstrumentationLibraryInfo getInstrumentationLibraryInfo() { + return instrumentationLibraryInfo; + } + + @Override + public Resource getResource() { + return resource; + } + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRSpanBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRSpanBuilder.java new file mode 100644 index 0000000000..67a866bea3 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRSpanBuilder.java @@ -0,0 +1,288 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.ExitTracer; +import com.newrelic.agent.bridge.Instrumentation; +import com.newrelic.agent.bridge.Transaction; +import com.newrelic.agent.tracers.TracerFlags; +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.ExtendedRequest; +import com.newrelic.api.agent.ExtendedResponse; +import com.newrelic.api.agent.HeaderType; +import com.newrelic.api.agent.TracedMethod; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * New Relic Java agent implementation of an OpenTelemetry SpanBuilder, + * which is used to construct Span instances. Instead of starting an OpenTelemetry + * Span, this implementation will create a New Relic Java agent Tracer to time + * the executing code and will potentially start a New Relic Java agent Transaction + * based on the detected SpanKind type. + */ +class NRSpanBuilder implements SpanBuilder { + private static final Span NO_OP_SPAN = OpenTelemetry.noop().getTracer("").spanBuilder("").startSpan(); + private final Instrumentation instrumentation; + private final String spanName; + private final Map attributes = new HashMap<>(); + private final TracerSharedState sharedState; + private final Consumer endHandler; + private final InstrumentationLibraryInfo instrumentationLibraryInfo; + private SpanKind spanKind = SpanKind.INTERNAL; + private SpanContext parentSpanContext; + + public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScopeName, String instrumentationScopeVersion, TracerSharedState sharedState, + String spanName) { + this.instrumentation = instrumentation; + this.spanName = spanName; + this.sharedState = sharedState; + instrumentationLibraryInfo = InstrumentationLibraryInfo.create(instrumentationScopeName, instrumentationScopeVersion); + attributes.put(ExitTracerSpan.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); + if (instrumentationScopeVersion != null) { + attributes.put(ExitTracerSpan.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); + } + if (sharedState.getActiveSpanProcessor().isEndRequired()) { + endHandler = sharedState.getActiveSpanProcessor()::onEnd; + } else { + endHandler = span -> { + }; + } + } + + static boolean isSpanBuilderEnabled(Config config) { + final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled"); + if (autoConfigure == null || autoConfigure) { + final Boolean spansEnabled = config.getValue("opentelemetry.sdk.spans.enabled"); + return spansEnabled == null || spansEnabled; + } + return false; + } + + @Override + public SpanBuilder setParent(Context context) { + parentSpanContext = Span.fromContext(context).getSpanContext(); + return this; + } + + @Override + public SpanBuilder setNoParent() { + return this; + } + + @Override + public SpanBuilder addLink(SpanContext spanContext) { + return this; + } + + @Override + public SpanBuilder addLink(SpanContext spanContext, Attributes attributes) { + return this; + } + + @Override + public SpanBuilder setAttribute(String key, String value) { + attributes.put(key, value); + return this; + } + + @Override + public SpanBuilder setAttribute(String key, long value) { + attributes.put(key, value); + return this; + } + + @Override + public SpanBuilder setAttribute(String key, double value) { + attributes.put(key, value); + return this; + } + + @Override + public SpanBuilder setAttribute(String key, boolean value) { + attributes.put(key, value); + return this; + } + + @Override + public SpanBuilder setAttribute(AttributeKey key, T value) { + attributes.put(key.getKey(), value); + return this; + } + + @Override + public SpanBuilder setSpanKind(SpanKind spanKind) { + this.spanKind = spanKind; + return this; + } + + @Override + public SpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) { + return this; + } + + /** + * Called when starting an OpenTelemetry Span and will result in a New Relic + * Java agent Tracer being created for each OpenTelemetry Span. Depending on + * the SpanKind type, this method may start a New Relic Java agent Transaction. + * + * @return OpenTelemetry Span + */ + @Override + public Span startSpan() { + SpanContext parentSpanContext = this.parentSpanContext == null ? + Span.fromContext(Context.current()).getSpanContext() : this.parentSpanContext; + if (SpanKind.SERVER == spanKind) { + return startServerSpan(parentSpanContext); + } + final boolean dispatcher = SpanKind.CONSUMER.equals(spanKind); + if (dispatcher) { + AgentBridge.getAgent().getTransaction(true); + } + final ExitTracer tracer = instrumentation.createTracer(spanName, getTracerFlags(dispatcher)); + if (tracer == null) { + return NO_OP_SPAN; + } + if (SpanKind.INTERNAL != spanKind) { + tracer.addCustomAttribute("span.kind", spanKind.name()); + } + // TODO REVIEW - we're not picking up the global resources + return onStart(new ExitTracerSpan(tracer, instrumentationLibraryInfo, spanKind, spanName, parentSpanContext, sharedState.getResource(), attributes, + endHandler)); + } + + private Span startServerSpan(SpanContext parentSpanContext) { + Transaction transaction = AgentBridge.getAgent().getTransaction(true); + final ExtendedRequest request = new ExtendedRequest() { + + @Override + public String getRequestURI() { + Object httpRoute = attributes.get("http.route"); + if (httpRoute != null) { + return httpRoute.toString(); + } + return (String) attributes.get("url.path"); + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public Enumeration getParameterNames() { + return Collections.emptyEnumeration(); + } + + @Override + public String[] getParameterValues(String name) { + return new String[0]; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public String getCookieValue(String name) { + return null; + } + + @Override + public HeaderType getHeaderType() { + return HeaderType.HTTP; + } + + @Override + public String getHeader(String name) { + if ("User-Agent".equals(name)) { + return (String) attributes.get("user_agent.original"); + } + return null; + } + + @Override + public String getMethod() { + return (String) attributes.get("http.request.method"); + } + }; + + final ExtendedResponse response = new ExtendedResponse() { + + @Override + public int getStatus() throws Exception { + Object statusCode = attributes.get("http.response.status_code"); + return statusCode instanceof Number ? ((Number) statusCode).intValue() : 0; + } + + @Override + public String getStatusMessage() throws Exception { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public HeaderType getHeaderType() { + return HeaderType.HTTP; + } + + @Override + public void setHeader(String name, String value) { + + } + + @Override + public long getContentLength() { + return 0; + } + }; + transaction.requestInitialized(request, response); + TracedMethod tracedMethod = transaction.getTracedMethod(); + return onStart(new ExitTracerSpan((ExitTracer) tracedMethod, instrumentationLibraryInfo, spanKind, spanName, + parentSpanContext, sharedState.getResource(), attributes, endHandler)); + } + + Span onStart(ReadWriteSpan span) { + // FIXME + Context parent = Context.current(); + if (sharedState.getActiveSpanProcessor().isStartRequired()) { + sharedState.getActiveSpanProcessor().onStart(parent, span); + } + return span; + } + + static int getTracerFlags(boolean dispatcher) { + int flags = TracerFlags.GENERATE_SCOPED_METRIC + | TracerFlags.TRANSACTION_TRACER_SEGMENT + | TracerFlags.CUSTOM; + if (dispatcher) { + flags |= TracerFlags.DISPATCHER; + } + return flags; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRTracerBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRTracerBuilder.java new file mode 100644 index 0000000000..791e1b20cf --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRTracerBuilder.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; + +/** + * New Relic Java agent implementation of an OpenTelemetry + * TracerBuilder, which is a factory for building OpenTelemetry Tracers. + * An OpenTelemetry Tracer can then be used to create OpenTelemetry Spans. + */ +class NRTracerBuilder implements TracerBuilder { + private final String instrumentationScopeName; + private final TracerSharedState sharedState; + private final Config config; + private String schemaUrl; + private String instrumentationScopeVersion; + + public NRTracerBuilder(Config config, String instrumentationScopeName, TracerSharedState sharedState) { + this.config = config; + this.instrumentationScopeName = instrumentationScopeName; + this.sharedState = sharedState; + } + + @Override + public TracerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + this.instrumentationScopeVersion = instrumentationScopeVersion; + return this; + } + + @Override + public Tracer build() { + Boolean enabled = config.getValue("opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); + if (enabled != null && !enabled) { + return OpenTelemetry.noop().getTracer(instrumentationScopeName); + } else { + return spanName -> new NRSpanBuilder(AgentBridge.instrumentation, instrumentationScopeName, instrumentationScopeVersion, sharedState, spanName); + } + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider_Instrumentation.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider_Instrumentation.java new file mode 100644 index 0000000000..b41c343f78 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider_Instrumentation.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import io.opentelemetry.api.trace.TracerBuilder; + +/** + * Weaved to inject a New Relic Java agent implementation of an OpenTelemetry TracerBuilder + */ +@Weave(type = MatchType.ExactClass, originalName = "io.opentelemetry.sdk.trace.SdkTracerProvider") +public final class SdkTracerProvider_Instrumentation { + private final TracerSharedState sharedState = Weaver.callOriginal(); + + public TracerBuilder tracerBuilder(String instrumentationScopeName) { + final TracerBuilder tracerBuilder = Weaver.callOriginal(); + Config config = NewRelic.getAgent().getConfig(); + if (NRSpanBuilder.isSpanBuilderEnabled(config)) { + // return our tracer builder instead of the OTel instance + return new NRTracerBuilder(config, instrumentationScopeName, sharedState); + } + return tracerBuilder; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/context/SpanTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/context/SpanTest.java new file mode 100644 index 0000000000..8150b46485 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/context/SpanTest.java @@ -0,0 +1,292 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.context; + +import com.google.common.collect.ImmutableMap; +import com.newrelic.agent.introspec.InstrumentationTestConfig; +import com.newrelic.agent.introspec.InstrumentationTestRunner; +import com.newrelic.agent.introspec.Introspector; +import com.newrelic.agent.introspec.SpanEvent; +import com.newrelic.agent.introspec.TracedMetricData; +import com.newrelic.agent.util.LatchingRunnable; +import com.newrelic.api.agent.Trace; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.sdk.trace.AttributesHelper; +import io.opentelemetry.sdk.trace.ExitTracerSpan; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +import static io.opentelemetry.sdk.trace.ExitTracerSpanTest.readSpanAttributes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "io.opentelemetry" }, configName = "distributed_tracing.yml") +public class SpanTest { + static { + System.setProperty("otel.java.global-autoconfigure.enabled", "true"); + } + + static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer("test", "1.0"); + + @Test + public void testInternalSpansNoTransaction() { + Span span = OTEL_TRACER.spanBuilder("MyCustomSpan").startSpan(); + span.makeCurrent().close(); + span.end(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + // no transactions because there was no dispatcher trace around the spans + assertEquals(0, introspector.getFinishedTransactionCount()); + } + + @Test + public void testConsumerSpan() { + Span span = OTEL_TRACER.spanBuilder("consume").setSpanKind(SpanKind.CONSUMER).startSpan(); + span.makeCurrent().close(); + span.end(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + assertEquals(1, introspector.getFinishedTransactionCount()); + + final String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("OtherTransaction/consume", txName); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(1, metricsForTransaction.size()); + assertTrue(metricsForTransaction.keySet().toString(), metricsForTransaction.containsKey("Span/consume")); + } + + @Test + public void testServerSpan() throws IOException { + Map attributes = readSpanAttributes("server-span.json"); + final String spanName = (String) attributes.remove("name"); + + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(SpanKind.SERVER).startSpan(); + span.setAllAttributes(AttributesHelper.toAttributes(attributes)); + span.makeCurrent().close(); + span.end(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + assertEquals(1, introspector.getFinishedTransactionCount()); + + final String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("WebTransaction/Uri/owners", txName); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(1, metricsForTransaction.size()); + assertTrue(metricsForTransaction.keySet().toString(), metricsForTransaction.containsKey("Span/GET /owners")); + } + + @Test + public void testSimpleSpans() { + simpleSpans(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + assertEquals(1, introspector.getFinishedTransactionCount()); + final String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("OtherTransaction/Custom/io.opentelemetry.context.SpanTest/simpleSpans", txName); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(3, metricsForTransaction.size()); + assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/simpleSpans")); + assertTrue(metricsForTransaction.containsKey("Span/MyCustomSpan")); + assertTrue(metricsForTransaction.containsKey("Span/kid")); + } + + @Test + public void testAsyncSpans() { + final ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + asyncSpans(executor, SpanTest::asyncWork); + LatchingRunnable.drain(executor); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + assertEquals(1, introspector.getFinishedTransactionCount()); + final String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("OtherTransaction/Custom/io.opentelemetry.context.SpanTest/asyncSpans", txName); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(3, metricsForTransaction.size()); + assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/asyncSpans")); + assertTrue(metricsForTransaction.containsKey("Java/OpenTelemetry/AsyncScope")); + assertTrue(metricsForTransaction.containsKey("Span/MyCustomAsyncSpan")); + } finally { + executor.shutdown(); + } + } + + @Test + public void testAsyncSpansWithParentNotWorking() { + final ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + asyncSpans(executor, context -> { + // this is the correct parent, but it's transaction has already finished, so + // we can't link it together + Span parent = Span.fromContext(context); + assertTrue(parent instanceof ExitTracerSpan); + + // however, we could use the trace id from the parent transaction. We'd have two metric + // transactions, but the distributed trace would display the relationship between the spans + Span span = OTEL_TRACER.spanBuilder("OrphanedSpan").setParent(context).startSpan(); + span.makeCurrent().close(); + span.end(); + }); + LatchingRunnable.drain(executor); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + // we have two transactions because the async activity isn't linked together + assertEquals(2, introspector.getFinishedTransactionCount()); + final String txName = "OtherTransaction/Custom/io.opentelemetry.context.SpanTest/asyncSpans"; + assertTrue(introspector.getTransactionNames().contains("OtherTransaction/Custom/io.opentelemetry.context.SpanTest/asyncSpans")); + assertTrue(introspector.getTransactionNames().contains("OtherTransaction/Custom")); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(1, metricsForTransaction.size()); + assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/asyncSpans")); + } finally { + executor.shutdown(); + } + } + + @Test + public void testDatabaseSpan() { + databaseSpan(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + assertEquals(1, introspector.getFinishedTransactionCount()); + final String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("OtherTransaction/Custom/io.opentelemetry.context.SpanTest/databaseSpan", txName); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(2, metricsForTransaction.size()); + assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/databaseSpan")); + assertTrue(metricsForTransaction.containsKey("Datastore/statement/mysql/owners/select")); + + Collection spanEvents = introspector.getSpanEvents(); + assertEquals(2, spanEvents.size()); + SpanEvent dbSpan = spanEvents.stream() + .filter(span -> "datastore".equals(span.category())).findFirst().get(); + assertEquals("owners", dbSpan.getAgentAttributes().get("db.collection")); + assertEquals("SELECT * FROM owners WHERE ssn = ?", dbSpan.getAgentAttributes().get("db.statement")); + Arrays.asList("db.collection", "db.sql.table", "db.system", "db.operation").forEach(key -> { + assertNull(key, dbSpan.getUserAttributes().get(key)); + }); + } + + @Trace(dispatcher = true) + static void databaseSpan() { + Span span = OTEL_TRACER.spanBuilder("owners select").setSpanKind(SpanKind.CLIENT) + .setAttribute("db.system", "mysql") + .setAttribute("db.operation", "select") + .setAttribute("db.sql.table", "owners") + .setAttribute("db.statement", "SELECT * FROM owners WHERE ssn = 4566661792") + .startSpan(); + span.end(); + } + + @Test + public void testExternalSpan() { + externalSpan(); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + assertEquals(1, introspector.getFinishedTransactionCount()); + final String txName = introspector.getTransactionNames().iterator().next(); + assertEquals("OtherTransaction/Custom/io.opentelemetry.context.SpanTest/externalSpan", txName); + + Map metricsForTransaction = InstrumentationTestRunner.getIntrospector().getMetricsForTransaction(txName); + + assertEquals(2, metricsForTransaction.size()); + assertTrue(metricsForTransaction.toString(), metricsForTransaction.containsKey("External/www.foo.bar/test/GET")); + assertTrue(metricsForTransaction.toString(), metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/externalSpan")); + + Collection spanEvents = introspector.getSpanEvents(); + assertEquals(2, spanEvents.size()); + SpanEvent httpSpan = spanEvents.stream() + .filter(span -> "http".equals(span.category())).findFirst().get(); + Map agentAttributes = ImmutableMap.of( + "server.address", "www.foo.bar", + "server.port", 8080, + "http.url", "https://www.foo.bar:8080/search", + "peer.hostname", "www.foo.bar", + "http.method", "GET"); + assertEquals(agentAttributes.size(), httpSpan.getAgentAttributes().size()); + agentAttributes.forEach((key, value) -> assertEquals(value, httpSpan.getAgentAttributes().get(key))); + agentAttributes.forEach((key, value) -> assertNull(key, httpSpan.getUserAttributes().get(key))); + } + + @Trace(dispatcher = true) + static void externalSpan() { + Span span = OTEL_TRACER.spanBuilder("example.com").setSpanKind(SpanKind.CLIENT) + .setAttribute("server.address", "www.foo.bar") + .setAttribute("url.full", "https://www.foo.bar:8080/search?q=OpenTelemetry#SemConv") + .setAttribute("server.port", 8080) + .setAttribute("http.request.method", "GET") + .startSpan(); + span.end(); + } + + @Trace(dispatcher = true) + static void simpleSpans() { + Span span = OTEL_TRACER.spanBuilder("MyCustomSpan").startSpan(); + Scope scope = span.makeCurrent(); + SpanContext spanContext = span.getSpanContext(); + assertNotNull(spanContext.getTraceId()); + assertNotNull(spanContext.getSpanId()); + assertSame(spanContext, span.getSpanContext()); + Span current = Span.current(); + assertEquals(span, current); + Span kid = OTEL_TRACER.spanBuilder("kid").setParent(Context.current()).startSpan(); + kid.end(); + scope.close(); + span.end(); + + withSpan(); + } + + @Trace(dispatcher = true) + static void asyncSpans(Executor executor, Consumer consumer) { + Context context = Context.current(); + executor.execute(Context.current().wrap(() -> consumer.accept(context))); + } + + static void asyncWork(Context context) { + Span span = OTEL_TRACER.spanBuilder("MyCustomAsyncSpan").startSpan(); + span.makeCurrent().close(); + span.end(); + } + + @WithSpan + static void withSpan() { + Span span = OTEL_TRACER.spanBuilder("kid").startSpan(); + span.end(); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizerTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizerTest.java new file mode 100644 index 0000000000..44625ce3b3 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizerTest.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.autoconfigure; + +import com.newrelic.api.agent.Agent; +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.Logger; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; +import junit.framework.TestCase; + +import java.util.Map; + +import static io.opentelemetry.sdk.autoconfigure.OpenTelemetrySDKCustomizer.SERVICE_INSTANCE_ID_ATTRIBUTE_KEY; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OpenTelemetrySDKCustomizerTest extends TestCase { + + public void testApplyProperties() { + Agent agent = mock(Agent.class); + Logger logger = mock(Logger.class); + when(agent.getLogger()).thenReturn(logger); + Config config = mock(Config.class); + when(agent.getConfig()).thenReturn(config); + when(config.getValue("app_name")).thenReturn("Test"); + when(config.getValue("host")).thenReturn("mylaptop"); + when(config.getValue("license_key")).thenReturn("12345"); + + Map properties = OpenTelemetrySDKCustomizer.applyProperties(mock(ConfigProperties.class), agent); + assertEquals("api-key=12345", properties.get("otel.exporter.otlp.headers")); + assertEquals("https://mylaptop:443", properties.get("otel.exporter.otlp.endpoint")); + assertEquals("http/protobuf", properties.get("otel.exporter.otlp.protocol")); + assertEquals("Test", properties.get("otel.service.name")); + assertEquals("gzip", properties.get("otel.exporter.otlp.compression")); + } + + public void testApplyResourcesServiceInstanceIdSet() { + com.newrelic.agent.bridge.Agent agent = mock(com.newrelic.agent.bridge.Agent.class); + Resource resource = OpenTelemetrySDKCustomizer.applyResources( + Resource.create(Attributes.of(SERVICE_INSTANCE_ID_ATTRIBUTE_KEY, "7fjjr")), agent, mock(Logger.class)); + assertEquals("7fjjr", resource.getAttribute(SERVICE_INSTANCE_ID_ATTRIBUTE_KEY)); + assertNull(resource.getAttribute(AttributeKey.stringKey("entity.guid"))); + } + + public void testApplyResources() { + com.newrelic.agent.bridge.Agent agent = mock(com.newrelic.agent.bridge.Agent.class); + when(agent.getEntityGuid(true)).thenReturn("myguid"); + Resource resource = OpenTelemetrySDKCustomizer.applyResources( + Resource.empty(), agent, mock(Logger.class)); + assertNotNull(resource.getAttribute(SERVICE_INSTANCE_ID_ATTRIBUTE_KEY)); + assertEquals("myguid", resource.getAttribute(AttributeKey.stringKey("entity.guid"))); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/ExitTracerSpanTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/ExitTracerSpanTest.java new file mode 100644 index 0000000000..9da3c5dd52 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/ExitTracerSpanTest.java @@ -0,0 +1,158 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.ExitTracer; +import com.newrelic.agent.security.deps.com.fasterxml.jackson.databind.ObjectMapper; +import com.newrelic.api.agent.DatastoreParameters; +import com.newrelic.api.agent.ExternalParameters; +import com.newrelic.api.agent.HttpParameters; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.data.SpanData; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ExitTracerSpanTest { + private static final Consumer END_HANDLER = span -> {}; + + @Test + public void testReportDatabaseClientSpan() throws Exception { + final Map attributes = readSpanAttributes("db-span.json"); + + ExitTracer tracer = mock(ExitTracer.class); + final List started = new ArrayList<>(); + final List ended = new ArrayList<>(); + SpanProcessor processor = new SpanProcessor() { + @Override + public void onStart(Context parentContext, ReadWriteSpan span) { + started.add(span); + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan span) { + ended.add(span); + } + + @Override + public boolean isEndRequired() { + return true; + } + }; + SpanBuilder spanBuilder = new TestTracerBuilder("test").addSpanProcessor(processor) + .setResource(Resource.getDefault()) + .withTracer(tracer).build().spanBuilder((String) attributes.remove("name")); + spanBuilder.setSpanKind(SpanKind.CLIENT).setAllAttributes(AttributesHelper.toAttributes(attributes)).startSpan().end(); + + assertEquals(1, started.size()); + assertEquals(1, ended.size()); + ReadWriteSpan startedSpan = started.get(0); + assertEquals("SELECT petclinic", startedSpan.getName()); + SpanData spanData = startedSpan.toSpanData(); + assertEquals(48, spanData.getAttributes().size()); + assertEquals(4, spanData.getResource().getAttributes().size()); + assertEquals("opentelemetry", spanData.getResource().getAttributes().get(AttributeKey.stringKey("telemetry.sdk.name"))); + + //new ExitTracerSpan(tracer, SpanKind.CLIENT, attributes, END_HANDLER).end(); + final ArgumentCaptor dbParams = ArgumentCaptor.forClass(DatastoreParameters.class); + verify(tracer, times(1)).reportAsExternal(dbParams.capture()); + assertEquals("mysql", dbParams.getValue().getProduct()); + assertEquals("owners", dbParams.getValue().getCollection()); + assertEquals("mysqlserver", dbParams.getValue().getHost()); + assertEquals(3306, dbParams.getValue().getPort().intValue()); + assertEquals("SELECT", dbParams.getValue().getOperation()); + assertEquals("petclinic", dbParams.getValue().getDatabaseName()); + } + + @Test + public void testReportDatabaseClientSpanMissingSqlTable() throws Exception { + ExitTracer tracer = mock(ExitTracer.class); + Map attributes = readSpanAttributes("db-span.json"); + attributes.remove("db.sql.table"); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(),attributes, END_HANDLER).end(); + final ArgumentCaptor dbParams = ArgumentCaptor.forClass(DatastoreParameters.class); + verify(tracer, times(1)).reportAsExternal(dbParams.capture()); + assertEquals("mysql", dbParams.getValue().getProduct()); + assertNull(dbParams.getValue().getCollection()); + assertEquals("mysqlserver", dbParams.getValue().getHost()); + assertEquals(3306, dbParams.getValue().getPort().intValue()); + assertEquals("SELECT", dbParams.getValue().getOperation()); + assertEquals("petclinic", dbParams.getValue().getDatabaseName()); + } + + @Test + public void testReportRpcClientSpan() throws Exception { + ExitTracer tracer = mock(ExitTracer.class); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(), readSpanAttributes("external-rpc-span.json"), END_HANDLER).end(); + final ArgumentCaptor externalParams = ArgumentCaptor.forClass(HttpParameters.class); + verify(tracer, times(1)).reportAsExternal(externalParams.capture()); + assertEquals("io.opentelemetry.grpc-1.6", externalParams.getValue().getLibrary()); + assertEquals("ResolveBoolean", externalParams.getValue().getProcedure()); + assertEquals("http://opentelemetry-demo-flagd:8013", externalParams.getValue().getUri().toString()); + } + + @Test + public void testReportHttpClientSpan() throws Exception { + ExitTracer tracer = mock(ExitTracer.class); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(),readSpanAttributes("external-http-span.json"), END_HANDLER).end(); + final ArgumentCaptor externalParams = ArgumentCaptor.forClass(HttpParameters.class); + verify(tracer, times(1)).reportAsExternal(externalParams.capture()); + assertEquals("io.opentelemetry.java-http-client", externalParams.getValue().getLibrary()); + assertEquals("https://google.com", externalParams.getValue().getUri().toString()); + assertEquals("GET", externalParams.getValue().getProcedure()); + } + + @Test + public void testReportHttpClientSpanWithCodeFunction() throws Exception { + ExitTracer tracer = mock(ExitTracer.class); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(),readSpanAttributes("external-http-span.json"), END_HANDLER) + .setAttribute(AttributeKey.stringKey("code.function"), "execute").end(); + final ArgumentCaptor externalParams = ArgumentCaptor.forClass(HttpParameters.class); + verify(tracer, times(1)).reportAsExternal(externalParams.capture()); + assertEquals("io.opentelemetry.java-http-client", externalParams.getValue().getLibrary()); + assertEquals("https://google.com", externalParams.getValue().getUri().toString()); + assertEquals("execute", externalParams.getValue().getProcedure()); + } + + @Test + public void testBadClientSpan() throws Exception { + ExitTracer tracer = mock(ExitTracer.class); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(),readSpanAttributes("bad-client-span.json"), END_HANDLER).end(); + verify(tracer, times(0)).reportAsExternal(any(ExternalParameters.class)); + } + + public static Map readSpanAttributes(String fileName) throws IOException { + try (InputStream in = ExitTracerSpanTest.class.getResourceAsStream(fileName)) { + return new ObjectMapper().readValue(in, Map.class); + } + } +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRSpanBuilderTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRSpanBuilderTest.java new file mode 100644 index 0000000000..c8bba060d8 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRSpanBuilderTest.java @@ -0,0 +1,47 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.api.agent.Config; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NRSpanBuilderTest { + + @Test + public void testIsSpanBuilderEnabled() { + assertTrue(NRSpanBuilder.isSpanBuilderEnabled(createConfig(null, null))); + assertTrue(NRSpanBuilder.isSpanBuilderEnabled(createConfig(true, null))); + assertTrue(NRSpanBuilder.isSpanBuilderEnabled(createConfig(null, true))); + assertTrue(NRSpanBuilder.isSpanBuilderEnabled(createConfig(true, true))); + } + + @Test + public void testIsSpanBuilderDisabled() { + assertFalse(NRSpanBuilder.isSpanBuilderEnabled(createConfig(false, true))); + assertFalse(NRSpanBuilder.isSpanBuilderEnabled(createConfig(false, null))); + assertFalse(NRSpanBuilder.isSpanBuilderEnabled(createConfig(null, false))); + assertFalse(NRSpanBuilder.isSpanBuilderEnabled(createConfig(true, false))); + } + + private Config createConfig(Boolean autoconfigureEnabled, Boolean spansEnabled) { + Config config = mock(Config.class); + if (autoconfigureEnabled != null) { + when(config.getValue("opentelemetry.sdk.autoconfigure.enabled")).thenReturn(autoconfigureEnabled); + } + if (spansEnabled != null) { + when(config.getValue("opentelemetry.sdk.spans.enabled")).thenReturn(spansEnabled); + } + return config; + } + +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRTracerBuilderTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRTracerBuilderTest.java new file mode 100644 index 0000000000..a5ff86f305 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRTracerBuilderTest.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import junit.framework.TestCase; + +import java.util.Collections; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NRTracerBuilderTest extends TestCase { + final TracerSharedState TRACER_SHARED_STATE = new TracerSharedState(Clock.getDefault(), IdGenerator.random(), + Resource.empty(), () -> SpanLimits.getDefault(), Sampler.alwaysOn(), Collections.emptyList()); + + public void testBuild() { + Tracer tracer = new NRTracerBuilder(NewRelic.getAgent().getConfig(), "test-lib", + TRACER_SHARED_STATE).build(); + assertTrue(tracer.getClass().getName(), tracer.getClass().getName().startsWith( + "io.opentelemetry.sdk.trace.NRTracerBuilder")); + } + + public void testBuildDisabled() { + Config config = mock(Config.class); + when(config.getValue("opentelemetry.instrumentation.test-lib.enabled")).thenReturn(false); + Tracer tracer = new NRTracerBuilder(config, "test-lib", + TRACER_SHARED_STATE).build(); + assertSame(OpenTelemetry.noop().getTracer("dude"), tracer); + } +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/TestTracerBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/TestTracerBuilder.java new file mode 100644 index 0000000000..a432c1d0b0 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/TestTracerBuilder.java @@ -0,0 +1,70 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.ExitTracer; +import com.newrelic.agent.bridge.Instrumentation; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.samplers.Sampler; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TestTracerBuilder implements TracerBuilder { + private final String instrumentationScopeName; + private String instrumentationScopeVersion; + private final Instrumentation instrumentation = mock(Instrumentation.class); + private final List spanProcessors = new ArrayList<>(); + private Resource resource = Resource.empty(); + + public TestTracerBuilder(String instrumentationScopeName) { + this.instrumentationScopeName = instrumentationScopeName; + } + + public TestTracerBuilder addSpanProcessor(SpanProcessor processor) { + this.spanProcessors.add(processor); + return this; + } + + public TestTracerBuilder setResource(Resource resource) { + this.resource = resource; + return this; + } + + @Override + public TracerBuilder setSchemaUrl(String schemaUrl) { + return this; + } + + @Override + public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + return this; + } + + public TracerBuilder withTracer(ExitTracer tracer) { + when(instrumentation.createTracer(anyString(), anyInt())).thenReturn(tracer); + return this; + } + + @Override + public Tracer build() { + Supplier spanLimitsSupplier = () -> SpanLimits.getDefault(); + TracerSharedState sharedState = new TracerSharedState(Clock.getDefault(), IdGenerator.random(), resource, spanLimitsSupplier, Sampler.alwaysOn(), + spanProcessors); + return spanName -> new NRSpanBuilder(instrumentation, instrumentationScopeName, instrumentationScopeVersion, sharedState, spanName); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/distributed_tracing.yml b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/distributed_tracing.yml new file mode 100644 index 0000000000..acdf6d1a3d --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/distributed_tracing.yml @@ -0,0 +1,3 @@ +common: &default_settings + distributed_tracing: + enabled: true \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/bad-client-span.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/bad-client-span.json new file mode 100644 index 0000000000..fa79d74b83 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/bad-client-span.json @@ -0,0 +1,56 @@ +{ + "cloud.account.id": "opentracing-265818", + "cloud.availability_zone": "us-central1-a", + "cloud.platform": "gcp_kubernetes_engine", + "cloud.provider": "gcp", + "container.id": "237ba1f2fbce91b54da664202ed8c3154c3ed65992eb08823bdae8a173718404", + "duration.ms": 1.881907, + "entity.guid": "MTA5Mzk5NzR8RVhUfFNFUlZJQ0V8MTE5NzIxNzM2MDk3MzYyODQ0MA", + "entity.name": "adservice", + "entity.type": "SERVICE", + "entityGuid": "MTA5Mzk5NzR8RVhUfFNFUlZJQ0V8MTE5NzIxNzM2MDk3MzYyODQ0MA", + "feature_flag.key": "adServiceFailure", + "feature_flag.provider_name": "flagd", + "host.arch": "amd64", + "host.id": "gcp-243707756122848312", + "host.name": "gke-teddy-opentelemetry--default-pool-22bb6e2e-dpc6", + "id": "68599eb449c78da5", + "instrumentation.provider": "opentelemetry", + "k8s.cluster.name": "teddy-opentelemetry-demo", + "k8s.deployment.name": "opentelemetry-demo-adservice", + "k8s.namespace.name": "otel-demo", + "k8s.node.name": "gke-teddy-opentelemetry--default-pool-22bb6e2e-dpc6", + "k8s.pod.ip": "10.104.1.6", + "k8s.pod.name": "opentelemetry-demo-adservice-8b4f49c74-vh7r2", + "k8s.pod.start_time": "2024-04-11T15:44:13Z", + "k8s.pod.uid": "ea4cc312-dc6f-43fb-bf42-380eb94dde64", + "name": "resolve", + "newRelic.ingestPoint": "api.traces.otlp", + "newrelic.source": "api.traces.otlp", + "nr.invalidAttributeCount": 1, + "os.description": "Linux 5.15.146+", + "os.type": "linux", + "otel.library.name": "OpenFeature/dev.openfeature.contrib.providers.flagd", + "otel.library.version": "", + "parent.id": "df0e1f82cd58105d", + "process.command_line": "/opt/java/openjdk/bin/java -javaagent:/usr/src/app/opentelemetry-javaagent.jar oteldemo.AdService", + "process.executable.path": "/opt/java/openjdk/bin/java", + "process.id": "df0e1f82cd58105d", + "process.pid": 1, + "process.runtime.description": "Eclipse Adoptium OpenJDK 64-Bit Server VM 21.0.2+13-LTS", + "process.runtime.name": "OpenJDK Runtime Environment", + "process.runtime.version": "21.0.2+13-LTS", + "service.instance.id": "ea4cc312-dc6f-43fb-bf42-380eb94dde64", + "service.name": "adservice", + "service.namespace": "opentelemetry-demo", + "span.kind": "client", + "telemetry.distro.name": "opentelemetry-java-instrumentation", + "telemetry.distro.version": "2.0.0", + "telemetry.sdk.language": "java", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": "1.34.1", + "thread.id": 1386, + "thread.name": "grpc-default-executor-670", + "timestamp": 1714497468129, + "trace.id": "30f08e412c7d612577b7b8d617879698" +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/db-span.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/db-span.json new file mode 100644 index 0000000000..394e1d3533 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/db-span.json @@ -0,0 +1,51 @@ +{ + "category": "datastore", + "container.id": "c4fe17ff1aa22c9e00df7885bdaa2f2d856ec7c528537b1f265b843f05c78010", + "db.connection_string": "mysql://mysqlserver:3306", + "db.name": "petclinic", + "db.operation": "SELECT", + "db.sql.table": "owners", + "db.statement": "select count(distinct o1_0.id) from owners o1_0 left join pets p1_0 on o1_0.id=p1_0.owner_id where o1_0.last_name like ? escape ?", + "db.system": "mysql", + "db.user": "petclinic", + "duration.ms": 0.475583, + "entity.guid": "MTEzMTk5MDd8RVhUfFNFUlZJQ0V8LTExODgyMzk4OTM0MTY1Nzk1MjQ", + "entity.name": "PetClinic-saxon", + "entity.type": "SERVICE", + "entityGuid": "MTEzMTk5MDd8RVhUfFNFUlZJQ0V8LTExODgyMzk4OTM0MTY1Nzk1MjQ", + "host.arch": "aarch64", + "host.name": "c4fe17ff1aa2", + "id": "8ef69c1ad32c0b17", + "instrumentation.provider": "opentelemetry", + "name": "SELECT petclinic", + "newRelic.ingestPoint": "api.traces.otlp", + "newrelic.source": "api.traces.otlp", + "nr.categories": ":datastore:", + "nr.invalidAttributeCount": 1, + "os.description": "Linux 6.5.11-linuxkit", + "os.type": "linux", + "otel.library.name": "io.opentelemetry.jdbc", + "otel.library.version": "1.33.0-alpha", + "parent.id": "b47b915d6cc4d819", + "process.command_args": "", + "process.executable.path": "/opt/java/openjdk/bin/java", + "process.id": "59370658d7b61ec9", + "process.pid": 1, + "process.runtime.description": "Eclipse Adoptium OpenJDK 64-Bit Server VM 17.0.10+7", + "process.runtime.name": "OpenJDK Runtime Environment", + "process.runtime.version": "17.0.10+7", + "server.address": "mysqlserver", + "server.port": 3306, + "service.instance.id": "itAAmTeOuJ7pFVo", + "service.name": "PetClinic-saxon", + "service.version": "3.2.0-SNAPSHOT", + "span.kind": "client", + "telemetry.auto.version": "1.33.0", + "telemetry.sdk.language": "java", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": "1.35.0", + "thread.id": 45, + "thread.name": "http-nio-8080-exec-5", + "timestamp": 1714427973075, + "trace.id": "07c5d2f8972e783b33f5b6fa57308638" +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-http-span.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-http-span.json new file mode 100644 index 0000000000..e312ae9177 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-http-span.json @@ -0,0 +1,47 @@ +{ + "category": "http", + "container.id": "c4fe17ff1aa22c9e00df7885bdaa2f2d856ec7c528537b1f265b843f05c78010", + "duration.ms": 304.125292, + "entity.guid": "MTEzMTk5MDd8RVhUfFNFUlZJQ0V8LTExODgyMzk4OTM0MTY1Nzk1MjQ", + "entity.name": "PetClinic-saxon", + "entity.type": "SERVICE", + "entityGuid": "MTEzMTk5MDd8RVhUfFNFUlZJQ0V8LTExODgyMzk4OTM0MTY1Nzk1MjQ", + "host.arch": "aarch64", + "host.name": "c4fe17ff1aa2", + "http.request.method": "GET", + "http.response.status_code": 301, + "id": "5aeb8ccf27d29d66", + "instrumentation.provider": "opentelemetry", + "name": "GET", + "network.protocol.version": "2", + "newRelic.ingestPoint": "api.traces.otlp", + "newrelic.source": "api.traces.otlp", + "nr.categories": ":http:", + "nr.invalidAttributeCount": 1, + "os.description": "Linux 6.5.11-linuxkit", + "os.type": "linux", + "otel.library.name": "io.opentelemetry.java-http-client", + "otel.library.version": "1.33.0-alpha", + "parent.id": "935b92e59644b6bf", + "process.command_args": "", + "process.executable.path": "/opt/java/openjdk/bin/java", + "process.id": "55f2d1f8c6d40e1f", + "process.pid": 1, + "process.runtime.description": "Eclipse Adoptium OpenJDK 64-Bit Server VM 17.0.10+7", + "process.runtime.name": "OpenJDK Runtime Environment", + "process.runtime.version": "17.0.10+7", + "server.address": "google.com", + "service.instance.id": "itAAmTeOuJ7pFVo", + "service.name": "PetClinic-saxon", + "service.version": "3.2.0-SNAPSHOT", + "span.kind": "client", + "telemetry.auto.version": "1.33.0", + "telemetry.sdk.language": "java", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": "1.35.0", + "thread.id": 44, + "thread.name": "http-nio-8080-exec-4", + "timestamp": 1714428373323, + "trace.id": "8ce187a446c21ab4159a5a3ece7e54ad", + "url.full": "https://google.com" +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-rpc-span.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-rpc-span.json new file mode 100644 index 0000000000..08afe7c189 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-rpc-span.json @@ -0,0 +1,64 @@ +{ + "cloud.account.id": "opentracing-265818", + "cloud.availability_zone": "us-central1-a", + "cloud.platform": "gcp_kubernetes_engine", + "cloud.provider": "gcp", + "container.id": "237ba1f2fbce91b54da664202ed8c3154c3ed65992eb08823bdae8a173718404", + "duration.ms": 1.731308, + "entity.guid": "MTA5Mzk5NzR8RVhUfFNFUlZJQ0V8MTE5NzIxNzM2MDk3MzYyODQ0MA", + "entity.name": "adservice", + "entity.type": "SERVICE", + "entityGuid": "MTA5Mzk5NzR8RVhUfFNFUlZJQ0V8MTE5NzIxNzM2MDk3MzYyODQ0MA", + "host.arch": "amd64", + "host.id": "gcp-243707756122848312", + "host.name": "gke-teddy-opentelemetry--default-pool-22bb6e2e-dpc6", + "id": "977ab2fb6909a327", + "instrumentation.provider": "opentelemetry", + "k8s.cluster.name": "teddy-opentelemetry-demo", + "k8s.deployment.name": "opentelemetry-demo-adservice", + "k8s.namespace.name": "otel-demo", + "k8s.node.name": "gke-teddy-opentelemetry--default-pool-22bb6e2e-dpc6", + "k8s.pod.ip": "10.104.1.6", + "k8s.pod.name": "opentelemetry-demo-adservice-8b4f49c74-vh7r2", + "k8s.pod.start_time": "2024-04-11T15:44:13Z", + "k8s.pod.uid": "ea4cc312-dc6f-43fb-bf42-380eb94dde64", + "name": "flagd.evaluation.v1.Service/ResolveBoolean", + "network.peer.address": "10.121.67.191", + "network.peer.port": 8013, + "network.type": "ipv4", + "newRelic.ingestPoint": "api.traces.otlp", + "newrelic.source": "api.traces.otlp", + "nr.invalidAttributeCount": 1, + "nr.spanEventCount": 2, + "os.description": "Linux 5.15.146+", + "os.type": "linux", + "otel.library.name": "io.opentelemetry.grpc-1.6", + "otel.library.version": "2.0.0-alpha", + "parent.id": "41c73413b4146f8a", + "process.command_line": "/opt/java/openjdk/bin/java -javaagent:/usr/src/app/opentelemetry-javaagent.jar oteldemo.AdService", + "process.executable.path": "/opt/java/openjdk/bin/java", + "process.id": "a46c5733c4028806", + "process.pid": 1, + "process.runtime.description": "Eclipse Adoptium OpenJDK 64-Bit Server VM 21.0.2+13-LTS", + "process.runtime.name": "OpenJDK Runtime Environment", + "process.runtime.version": "21.0.2+13-LTS", + "rpc.grpc.status_code": 0, + "rpc.method": "ResolveBoolean", + "rpc.service": "flagd.evaluation.v1.Service", + "rpc.system": "grpc", + "server.address": "opentelemetry-demo-flagd", + "server.port": 8013, + "service.instance.id": "ea4cc312-dc6f-43fb-bf42-380eb94dde64", + "service.name": "adservice", + "service.namespace": "opentelemetry-demo", + "span.kind": "client", + "telemetry.distro.name": "opentelemetry-java-instrumentation", + "telemetry.distro.version": "2.0.0", + "telemetry.sdk.language": "java", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": "1.34.1", + "thread.id": 1322, + "thread.name": "grpc-default-executor-632", + "timestamp": 1714415451412, + "trace.id": "65a744b6cd40e6b98152b853d9dfab2b" +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/server-span.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/server-span.json new file mode 100644 index 0000000000..592ff245fb --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/server-span.json @@ -0,0 +1,54 @@ +{ + "category": "http", + "container.id": "c4fe17ff1aa22c9e00df7885bdaa2f2d856ec7c528537b1f265b843f05c78010", + "duration.ms": 9.47825, + "entity.guid": "MTEzMTk5MDd8RVhUfFNFUlZJQ0V8LTExODgyMzk4OTM0MTY1Nzk1MjQ", + "entity.name": "PetClinic-saxon", + "entity.type": "SERVICE", + "entityGuid": "MTEzMTk5MDd8RVhUfFNFUlZJQ0V8LTExODgyMzk4OTM0MTY1Nzk1MjQ", + "host.arch": "aarch64", + "host.name": "c4fe17ff1aa2", + "http.request.method": "GET", + "http.response.status_code": 200, + "http.route": "/owners", + "id": "03aca13482b8ebac", + "instrumentation.provider": "opentelemetry", + "name": "GET /owners", + "network.peer.address": "192.168.65.1", + "network.peer.port": 50130, + "network.protocol.version": "1.1", + "newRelic.ingestPoint": "api.traces.otlp", + "newrelic.source": "api.traces.otlp", + "nr.categories": ":http:", + "nr.invalidAttributeCount": 1, + "os.description": "Linux 6.5.11-linuxkit", + "os.type": "linux", + "otel.library.name": "io.opentelemetry.tomcat-10.0", + "otel.library.version": "1.33.0-alpha", + "process.command_args": "", + "process.executable.path": "/opt/java/openjdk/bin/java", + "process.id": "03aca13482b8ebac", + "process.pid": 1, + "process.runtime.description": "Eclipse Adoptium OpenJDK 64-Bit Server VM 17.0.10+7", + "process.runtime.name": "OpenJDK Runtime Environment", + "process.runtime.version": "17.0.10+7", + "server.address": "localhost", + "server.port": 8082, + "service.instance.id": "itAAmTeOuJ7pFVo", + "service.name": "PetClinic-saxon", + "service.version": "3.2.0-SNAPSHOT", + "span.kind": "server", + "telemetry.auto.version": "1.33.0", + "telemetry.sdk.language": "java", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": "1.35.0", + "thread.id": 47, + "thread.name": "http-nio-8080-exec-7", + "timestamp": 1714429208409, + "trace.id": "940efa58589ecce9122c79bcb00e2f16", + "transaction.name": "WebTransaction/server/GET /owners", + "url.path": "/owners", + "url.query": "lastName=", + "url.scheme": "http", + "user_agent.original": "Apache-HttpClient/4.5.14 (Java/17.0.8.1)" +} \ No newline at end of file diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java b/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java index 3829699352..ea93adff52 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/Transaction.java @@ -129,7 +129,11 @@ public class Transaction { static final ClassMethodSignature SCALA_API_TXN_CLASS_SIGNATURE = new ClassMethodSignature( "newrelic.scala.api.TraceOps$", "txn", null); public static final int SCALA_API_TXN_CLASS_SIGNATURE_ID = - ClassMethodSignatures.get().add(SCALA_API_TXN_CLASS_SIGNATURE); + ClassMethodSignatures.get().add(SCALA_API_TXN_CLASS_SIGNATURE); + + public static final int GENERIC_TXN_CLASS_SIGNATURE_ID = + ClassMethodSignatures.get().add(new ClassMethodSignature("", "", null)); + private static final String THREAD_ASSERTION_FAILURE = "Thread assertion failed!"; private static final ThreadLocal transactionHolder = new ThreadLocal<>(); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/database/DatabaseStatementParser.java b/newrelic-agent/src/main/java/com/newrelic/agent/database/DatabaseStatementParser.java index a376ffd623..396ada4eca 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/database/DatabaseStatementParser.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/database/DatabaseStatementParser.java @@ -11,6 +11,8 @@ import com.newrelic.agent.bridge.datastore.DatabaseVendor; +import javax.annotation.Nullable; + /** * Parses a sql string and returns a {@link ParsedDatabaseStatement}. * @@ -37,12 +39,11 @@ public interface DatabaseStatementParser { /** * Returns a parsed statement even if the statement is unparseable. Must not return null. - * * * @param databaseVendor * @param statement * @param resultSetMetaData */ ParsedDatabaseStatement getParsedDatabaseStatement(DatabaseVendor databaseVendor, String statement, - ResultSetMetaData resultSetMetaData); + @Nullable ResultSetMetaData resultSetMetaData); } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/database/SqlObfuscator.java b/newrelic-agent/src/main/java/com/newrelic/agent/database/SqlObfuscator.java index 5bcdf5a205..dcad33f6c4 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/database/SqlObfuscator.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/database/SqlObfuscator.java @@ -8,6 +8,7 @@ package com.newrelic.agent.database; import com.google.common.base.Joiner; +import com.newrelic.api.agent.QueryConverter; import jregex.Pattern; import java.util.HashMap; @@ -32,6 +33,17 @@ public abstract class SqlObfuscator { public static final String OBFUSCATED_SETTING = "obfuscated"; public static final String RAW_SETTING = "raw"; public static final String OFF_SETTING = "off"; + private final QueryConverter queryConverter = new QueryConverter() { + @Override + public String toRawQueryString(String rawQuery) { + return rawQuery; + } + + @Override + public String toObfuscatedQueryString(String rawQuery) { + return obfuscateSql(rawQuery); + } + }; private SqlObfuscator() { } @@ -61,6 +73,10 @@ public boolean isObfuscating() { return false; } + public QueryConverter getQueryConverter() { + return queryConverter; + } + static class DefaultSqlObfuscator extends SqlObfuscator { private static final Pattern ALL_DIALECTS_PATTERN; private static final Pattern ALL_UNMATCHED_PATTERN; diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/InstrumentationImpl.java b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/InstrumentationImpl.java index 0c6f8839da..ddbcf43017 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/InstrumentationImpl.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/InstrumentationImpl.java @@ -34,6 +34,7 @@ import com.newrelic.agent.profile.v2.TransactionProfileSession; import com.newrelic.agent.reinstrument.PeriodicRetransformer; import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.agent.trace.TransactionGuidFactory; import com.newrelic.agent.tracers.ClassMethodSignature; import com.newrelic.agent.tracers.ClassMethodSignatures; import com.newrelic.agent.tracers.DefaultSqlTracer; @@ -50,6 +51,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import javax.annotation.Nullable; import java.io.Closeable; import java.lang.instrument.UnmodifiableClassException; import java.lang.reflect.Method; @@ -64,6 +66,7 @@ import static com.newrelic.agent.Transaction.SCALA_API_TRACER_FLAGS; import static com.newrelic.agent.Transaction.SCALA_API_TXN_CLASS_SIGNATURE_ID; +import static com.newrelic.agent.Transaction.GENERIC_TXN_CLASS_SIGNATURE_ID; public class InstrumentationImpl implements Instrumentation { @@ -135,7 +138,7 @@ public ExitTracer createTracer(Object invocationTarget, int signatureId, boolean * a Transaction is present on the thread. If present, we do not know if the Transaction has been started. */ @Override - public ExitTracer createTracer(Object invocationTarget, int signatureId, String metricName, int flags) { + public @Nullable ExitTracer createTracer(Object invocationTarget, int signatureId, String metricName, int flags) { try { if (ServiceFactory.getServiceManager().isStopped()) { return null; @@ -365,6 +368,11 @@ public ExitTracer createScalaTxnTracer() { return createTracer(null, SCALA_API_TXN_CLASS_SIGNATURE_ID, null, SCALA_API_TRACER_FLAGS); } + @Override + public @Nullable ExitTracer createTracer(String metricName, int flags) { + return createTracer(null, GENERIC_TXN_CLASS_SIGNATURE_ID, metricName, flags); + } + private boolean overSegmentLimit(TransactionActivity transactionActivity) { Transaction transaction; if (transactionActivity == null) { diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/pointcuts/XmlRpcPointCut.java b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/pointcuts/XmlRpcPointCut.java index 09237cb75e..9077ff1903 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/pointcuts/XmlRpcPointCut.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/pointcuts/XmlRpcPointCut.java @@ -72,7 +72,7 @@ private XmlRpcTracer(PointCut pc, Transaction transaction, ClassMethodSignature this.library = library; } - private void finish() { + private void doFinish() { try { NewRelic.getAgent().getTracedMethod().reportAsExternal(HttpParameters .library(library) @@ -92,13 +92,13 @@ private void finish() { @Override public void finish(int opcode, Object returnValue) { - finish(); + doFinish(); super.finish(opcode, returnValue); } @Override public void finish(Throwable throwable) { - finish(); + doFinish(); super.finish(throwable); } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java index 73368f8921..a70e2f5c50 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java @@ -10,6 +10,7 @@ import com.google.common.base.Joiner; import com.newrelic.agent.attributes.AttributeNames; import com.newrelic.agent.attributes.AttributeValidator; +import com.newrelic.agent.bridge.datastore.SqlQueryConverter; import com.newrelic.agent.config.AgentConfig; import com.newrelic.agent.config.AttributesConfig; import com.newrelic.agent.database.SqlObfuscator; @@ -25,6 +26,7 @@ import com.newrelic.api.agent.ExternalParameters; import com.newrelic.api.agent.CloudParameters; import com.newrelic.api.agent.HttpParameters; +import com.newrelic.api.agent.QueryConverter; import com.newrelic.api.agent.MessageConsumeParameters; import com.newrelic.api.agent.MessageProduceParameters; import com.newrelic.api.agent.SlowQueryDatastoreParameters; @@ -486,12 +488,20 @@ private String determineObfuscationLevel(SlowQueryDatastoreParameters slo if (config.isHighSecurity() || config.getTransactionTracerConfig().getRecordSql().equals(SqlObfuscator.OFF_SETTING)) { return null; } else if (config.getTransactionTracerConfig().getRecordSql().equals(SqlObfuscator.RAW_SETTING)) { - return slowQueryDatastoreParameters.getQueryConverter().toRawQueryString(slowQueryDatastoreParameters.getRawQuery()); + return getQueryConverter(slowQueryDatastoreParameters).toRawQueryString(slowQueryDatastoreParameters.getRawQuery()); } else { - return slowQueryDatastoreParameters.getQueryConverter().toObfuscatedQueryString(slowQueryDatastoreParameters.getRawQuery()); + return getQueryConverter(slowQueryDatastoreParameters).toObfuscatedQueryString(slowQueryDatastoreParameters.getRawQuery()); } } + private static QueryConverter getQueryConverter(SlowQueryDatastoreParameters slowQueryDatastoreParameters) { + final QueryConverter queryConverter = slowQueryDatastoreParameters.getQueryConverter(); + if (queryConverter == SqlQueryConverter.INSTANCE) { + return (QueryConverter) ServiceFactory.getDatabaseService().getDefaultSqlObfuscator().getQueryConverter(); + } + return queryConverter; + } + public SpanEvent build() { builder.timestamp(timestampSupplier.get()); return builder.build(); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracers/AbstractTracer.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracers/AbstractTracer.java index ea0f62ca6e..e6b283120a 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/tracers/AbstractTracer.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracers/AbstractTracer.java @@ -21,6 +21,7 @@ import com.newrelic.api.agent.ExternalParameters; import com.newrelic.api.agent.InboundHeaders; import com.newrelic.api.agent.OutboundHeaders; +import com.newrelic.api.agent.Token; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -379,6 +380,11 @@ public void setAttribute(String key, Object value, boolean checkLimits, boolean } } + @Override + public Token getToken() { + return getTransaction().getToken(); + } + static int sizeof(Object value) { int size = 0; if (value == null) { @@ -397,6 +403,16 @@ static int sizeof(Object value) { return size; } + @Override + public String getTraceId() { + return getTransaction().getSpanProxy().getOrCreateTraceId(); + } + + @Override + public String getSpanId() { + return getGuid(); + } + @Override public void setAgentAttribute(String key, Object value) { setAttribute(key, value, true, false, false); diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/tracers/DefaultTracer.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracers/DefaultTracer.java index 6879311229..544bbb65f4 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/tracers/DefaultTracer.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/tracers/DefaultTracer.java @@ -13,11 +13,13 @@ import com.newrelic.agent.TransactionActivity; import com.newrelic.agent.attributes.AttributeNames; import com.newrelic.agent.attributes.AttributeValidator; +import com.newrelic.agent.bridge.datastore.UnknownDatabaseVendor; import com.newrelic.agent.bridge.external.ExternalMetrics; import com.newrelic.agent.config.AgentConfigImpl; import com.newrelic.agent.config.DatastoreConfig; import com.newrelic.agent.config.TransactionTracerConfig; import com.newrelic.agent.database.DatastoreMetrics; +import com.newrelic.agent.database.ParsedDatabaseStatement; import com.newrelic.agent.database.SqlObfuscator; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.stats.ResponseTimeStats; @@ -729,8 +731,9 @@ private void recordExternalMetricsHttp(HttpParameters externalParameters) { private void recordExternalMetricsDatastore(DatastoreParameters datastoreParameters) { Transaction tx = getTransactionActivity().getTransaction(); if (tx != null && datastoreParameters != null) { + final String collection = getCollection(datastoreParameters); DatastoreMetrics.collectDatastoreMetrics(datastoreParameters.getProduct(), tx, this, - datastoreParameters.getCollection(), datastoreParameters.getOperation(), + collection, datastoreParameters.getOperation(), datastoreParameters.getHost(), datastoreParameters.getPort(), datastoreParameters.getPathOrId(), datastoreParameters.getDatabaseName()); @@ -758,6 +761,23 @@ private void recordExternalMetricsDatastore(DatastoreParameters datastoreParamet } } + private String getCollection(DatastoreParameters datastoreParameters) { + final String collection = datastoreParameters.getCollection(); + if (collection == null && datastoreParameters instanceof SlowQueryDatastoreParameters) { + final Object rawQuery = ((SlowQueryDatastoreParameters)datastoreParameters).getRawQuery(); + if (rawQuery != null) { + ParsedDatabaseStatement databaseStatement = ServiceFactory.getDatabaseService(). + getDatabaseStatementParser().getParsedDatabaseStatement( + UnknownDatabaseVendor.INSTANCE, rawQuery.toString(), null); + if (databaseStatement.recordMetric()) { + return databaseStatement.getModel(); + } + } + return "unknown"; + } + return collection; + } + private void catForMessaging(MessageProduceParameters produceParameters) { OutboundHeaders outboundHeaders = produceParameters.getOutboundHeaders(); if (outboundHeaders == null) { diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/tracing/FlyweightTraceMethodVisitorTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/tracing/FlyweightTraceMethodVisitorTest.java index 754e54701b..8c1ec31a48 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/tracing/FlyweightTraceMethodVisitorTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/tracing/FlyweightTraceMethodVisitorTest.java @@ -24,9 +24,15 @@ public class FlyweightTraceMethodVisitorTest { @Test public void verifyTracedMethodStitching() { - // these methods are overridden in the bridge with a different signature. ignore them - Set excludes = ImmutableSet.of(new Method("getParentTracedMethod", - "()Lcom/newrelic/api/agent/TracedMethod;")); + Set excludes = ImmutableSet.of( + // this method is overridden in the bridge with a different signature. ignore it + new Method("getParentTracedMethod", + "()Lcom/newrelic/api/agent/TracedMethod;"), + // these methods have default implementations which are fine because flyweight tracers are leaves + new Method("getTraceId", + "()Ljava/lang/String;"), + new Method("getSpanId", + "()Ljava/lang/String;")); TraceDetails trace = TraceDetailsBuilder.newBuilder().build(); FlyweightTraceMethodVisitor mv = new FlyweightTraceMethodVisitor("", null, 0, "go", "()V", trace, null); diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/tracers/DefaultTracerTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracers/DefaultTracerTest.java index b63228b971..7289a01759 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/tracers/DefaultTracerTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/tracers/DefaultTracerTest.java @@ -25,6 +25,7 @@ import com.newrelic.agent.bridge.Token; import com.newrelic.agent.bridge.TransactionNamePriority; import com.newrelic.agent.bridge.datastore.DatastoreVendor; +import com.newrelic.agent.bridge.datastore.SqlQueryConverter; import com.newrelic.agent.config.AgentConfigFactory; import com.newrelic.agent.config.TransactionTracerConfig; import com.newrelic.agent.database.SqlObfuscator; @@ -111,6 +112,7 @@ public void before() throws Exception { APP_NAME = ServiceFactory.getConfigService().getDefaultAgentConfig().getApplicationName(); SamplingPriorityQueue eventPool = spanEventService.getOrCreateDistributedSamplingReservoir(APP_NAME); eventPool.clear(); + ServiceFactory.getStatsService().getStatsEngineForHarvest("Unit Test").clear(); } @Test @@ -545,6 +547,24 @@ public void testDatastoreParametersNoHost() { assertClmAbsent(tracer); } + @Test + public void testDatastoreParametersNullCollection() { + DefaultTracer tracer = prepareTracer(); + TransactionStats stats = tracer.getTransactionActivity().getTransactionStats(); + + tracer.reportAsExternal(DatastoreParameters + .product("Product") + .collection(null) + .operation("operation") + .noInstance() + .noDatabaseName().slowQuery("SELECT * FROM users", SqlQueryConverter.INSTANCE) + .build()); + tracer.recordMetrics(stats); + // verify that collection is parsed from SQL + assertEquals("Datastore/statement/Product/users/operation", tracer.getMetricName()); + assertClmAbsent(tracer); + } + @Test public void testNoParametersInUri() { DefaultTracer tracer = prepareTracer(); diff --git a/newrelic-weaver-api/src/main/java/com/newrelic/api/agent/weaver/MatchType.java b/newrelic-weaver-api/src/main/java/com/newrelic/api/agent/weaver/MatchType.java index b4251471a5..be2fc652df 100644 --- a/newrelic-weaver-api/src/main/java/com/newrelic/api/agent/weaver/MatchType.java +++ b/newrelic-weaver-api/src/main/java/com/newrelic/api/agent/weaver/MatchType.java @@ -24,6 +24,9 @@ public enum MatchType { /** * The weave instrumentation will be injected into all classes which implement an interface with the exact same name * as the weave class. + * + * To instrument a `default` method on an interface, define the instrumentation + * class as `public abstract` and define the target method as `public`. */ Interface(false);