From b108750bc95df610b086ae3e5a45ca0dcaa552d9 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Fri, 3 May 2024 10:54:44 -0700 Subject: [PATCH 01/17] Add OTel span support --- .../com/newrelic/agent/bridge/ExitTracer.java | 11 + .../agent/bridge/Instrumentation.java | 5 +- .../newrelic/agent/bridge/TracedMethod.java | 6 + .../bridge/datastore/SqlQueryConverter.java | 19 ++ .../build.gradle | 2 + .../opentelemetry/context/ContextHelper.java | 42 +++ .../context/Context_Instrumentation.java | 16 ++ .../AutoConfiguredOpenTelemetrySdk.java | 6 +- ...r.java => OpenTelemetrySDKCustomizer.java} | 44 +++- .../sdk/autoconfigure/ResourceCustomer.java | 31 --- .../sdk/trace/ExitTracerSpan.java | 241 ++++++++++++++++++ .../sdk/trace/NRSpanBuilder.java | 156 ++++++++++++ .../sdk/trace/NRTracerBuilder.java | 32 +++ .../SdkTracerProvider_Instrumentation.java | 15 ++ .../io/opentelemetry/context/SpanTest.java | 139 ++++++++++ .../OpenTelemetrySDKCustomizerTest.java | 54 ++++ .../sdk/trace/ExitTracerSpanTest.java | 101 ++++++++ .../sdk/trace/bad-client-span.json | 56 ++++ .../io/opentelemetry/sdk/trace/db-span.json | 51 ++++ .../sdk/trace/external-http-span.json | 47 ++++ .../sdk/trace/external-rpc-span.json | 64 +++++ .../java/com/newrelic/agent/Transaction.java | 6 +- .../database/DatabaseStatementParser.java | 5 +- .../instrumentation/InstrumentationImpl.java | 20 +- .../pointcuts/XmlRpcPointCut.java | 6 +- .../agent/tracers/AbstractTracer.java | 16 ++ .../newrelic/agent/tracers/DefaultTracer.java | 22 +- .../agent/tracers/DefaultTracerTest.java | 19 ++ .../newrelic/api/agent/weaver/MatchType.java | 3 + 29 files changed, 1182 insertions(+), 53 deletions(-) create mode 100644 agent-bridge/src/main/java/com/newrelic/agent/bridge/datastore/SqlQueryConverter.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/ContextHelper.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/Context_Instrumentation.java rename instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/{PropertiesCustomizer.java => OpenTelemetrySDKCustomizer.java} (50%) delete mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceCustomer.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/ExitTracerSpan.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRSpanBuilder.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRTracerBuilder.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider_Instrumentation.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/context/SpanTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizerTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/ExitTracerSpanTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/bad-client-span.json create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/db-span.json create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-http-span.json create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/external-rpc-span.json 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..a7786af6f4 --- /dev/null +++ b/agent-bridge/src/main/java/com/newrelic/agent/bridge/datastore/SqlQueryConverter.java @@ -0,0 +1,19 @@ +package com.newrelic.agent.bridge.datastore; + +import com.newrelic.api.agent.QueryConverter; + +public 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/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle index b0964a733c..9dd073129f 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,8 @@ 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") } 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..46996efbc7 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/ContextHelper.java @@ -0,0 +1,42 @@ +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.api.trace.SpanKind; +import io.opentelemetry.sdk.trace.ExitTracerSpan; + +import java.util.Collections; + +class ContextHelper { + private ContextHelper() {} + + 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(new ExitTracerSpan((ExitTracer) tracedMethod, SpanKind.INTERNAL, + Collections.emptyMap())); + } + } + } + return context; + } + + 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..bab1d45bdb --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/context/Context_Instrumentation.java @@ -0,0 +1,16 @@ +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; + +@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..a5e22a7ba1 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 @@ -12,11 +12,11 @@ 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/PropertiesCustomizer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizer.java similarity index 50% rename from instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/PropertiesCustomizer.java rename to instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizer.java index cc8496c246..bcb978e64f 100644 --- 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/OpenTelemetrySDKCustomizer.java @@ -1,20 +1,32 @@ 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.function.Function; +import java.util.UUID; import java.util.logging.Level; -public final class PropertiesCustomizer implements Function> { - @Override - public Map apply(ConfigProperties configProperties) { +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) { 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"; @@ -37,4 +49,26 @@ public Map apply(ConfigProperties configProperties) { } 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/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/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..7c1cebf4c5 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/ExitTracerSpan.java @@ -0,0 +1,241 @@ +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.GenericParameters; +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.Context; +import io.opentelemetry.context.Scope; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +public class ExitTracerSpan implements Span { + 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); + + final ExitTracer tracer; + private final SpanKind spanKind; + private final Map attributes; + private final SpanContext spanContext; + + public ExitTracerSpan(ExitTracer tracer, SpanKind spanKind, Map attributes) { + this.tracer = tracer; + this.spanKind = spanKind; + this.attributes = attributes; + this.spanContext = SpanContext.create(tracer.getTraceId(), tracer.getSpanId(), TraceFlags.getDefault(), TraceState.getDefault()); + } + + @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) { + tracer.setMetricName("Span", name); + return this; + } + + @Override + public void end() { + if (SpanKind.CLIENT == spanKind) { + reportClientSpan(); + } + tracer.addCustomAttributes(attributes); + tracer.finish(); + } + + @Override + public void end(long timestamp, TimeUnit unit) { + this.end(); + } + + @Override + public SpanContext getSpanContext() { + return spanContext; + } + + @Override + public boolean isRecording() { + return true; + } + + @Override + public Context storeInContext(Context context) { + return Span.super.storeInContext(context); + } + + private 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); + GenericParameters genericParameters = GenericParameters.library(libraryName).uri(uri) + .procedure(getProcedure()).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(); + }; + } +} 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..79a3b2b94e --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRSpanBuilder.java @@ -0,0 +1,156 @@ +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.ExitTracer; +import com.newrelic.agent.bridge.Instrumentation; +import com.newrelic.agent.tracers.TracerFlags; +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.api.trace.StatusCode; +import io.opentelemetry.context.Context; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +class NRSpanBuilder implements SpanBuilder { + private final Instrumentation instrumentation; + private final String spanName; + private final Map attributes = new HashMap<>(); + private SpanKind spanKind= SpanKind.INTERNAL; + + public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScopeName, String instrumentationScopeVersion, String spanName) { + this.instrumentation = instrumentation; + this.spanName = spanName; + attributes.put(ExitTracerSpan.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); + attributes.put(ExitTracerSpan.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); + } + + @Override + public SpanBuilder setParent(Context context) { + 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; + } + + @Override + public Span startSpan() { + final ExitTracer tracer = instrumentation.createTracer("Span/" + spanName, + TracerFlags.GENERATE_SCOPED_METRIC + | TracerFlags.TRANSACTION_TRACER_SEGMENT + | TracerFlags.CUSTOM); + if (tracer == null) { + return NO_OP_SPAN; + } + tracer.addCustomAttribute("span.kind", spanKind.name()); + return new ExitTracerSpan(tracer, spanKind, attributes); + } + + private static final Span NO_OP_SPAN = new Span() { + @Override + public Span setAttribute(AttributeKey key, T 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, Attributes additionalAttributes) { + return this; + } + + @Override + public Span updateName(String name) { + return this; + } + + @Override + public void end() { + } + + @Override + public void end(long timestamp, TimeUnit unit) { + + } + + @Override + public SpanContext getSpanContext() { + return SpanContext.getInvalid(); + } + + @Override + public boolean isRecording() { + return false; + } + }; +} 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..b97b218658 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/NRTracerBuilder.java @@ -0,0 +1,32 @@ +package io.opentelemetry.sdk.trace; + +import com.newrelic.agent.bridge.AgentBridge; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; + +class NRTracerBuilder implements TracerBuilder { + private final String instrumentationScopeName; + private String schemaUrl; + private String instrumentationScopeVersion; + + public NRTracerBuilder(String instrumentationScopeName) { + this.instrumentationScopeName = instrumentationScopeName; + } + + @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() { + return spanName -> new NRSpanBuilder(AgentBridge.instrumentation, instrumentationScopeName, instrumentationScopeVersion, 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..def0c1c502 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider_Instrumentation.java @@ -0,0 +1,15 @@ +package io.opentelemetry.sdk.trace; + +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; + +@Weave(type = MatchType.ExactClass, originalName = "io.opentelemetry.sdk.trace.SdkTracerProvider") +public final class SdkTracerProvider_Instrumentation { + public TracerBuilder tracerBuilder(String instrumentationScopeName) { + // ignore otel builder + Weaver.callOriginal(); + return new NRTracerBuilder(instrumentationScopeName); + } +} 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..f07b0f2cb0 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/context/SpanTest.java @@ -0,0 +1,139 @@ +package io.opentelemetry.context; + +import com.newrelic.agent.introspec.InstrumentationTestConfig; +import com.newrelic.agent.introspec.InstrumentationTestRunner; +import com.newrelic.agent.introspec.Introspector; +import com.newrelic.agent.introspec.TracedMetricData; +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 org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "io.opentelemetry" }) +public class SpanTest { + static { + System.setProperty("otel.java.global-autoconfigure.enabled", "true"); + } + static Tracer otelTracer = GlobalOpenTelemetry.get().getTracer("test"); + + @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() throws InterruptedException { + final ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + asyncSpans(executor); + CountDownLatch latch = new CountDownLatch(1); + executor.execute(latch::countDown); + latch.await(1, TimeUnit.MINUTES); + + 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 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")); + } + + @Trace(dispatcher = true) + static void databaseSpan() { + Span span = otelTracer.spanBuilder("owners select").setSpanKind(SpanKind.CLIENT) + .setAttribute("db.system", "mysql") + .setAttribute("db.operation", "select") + .setAttribute("db.sql.table", "owners") + .startSpan(); + span.end(); + } + + @Trace(dispatcher = true) + static void simpleSpans() { + Span span = otelTracer.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 = otelTracer.spanBuilder("kid").setParent(Context.current()).startSpan(); + kid.end(); + scope.close(); + span.end(); + + withSpan(); + } + + @Trace(dispatcher = true) + static void asyncSpans(Executor executor) { + executor.execute(Context.current().wrap(SpanTest::asyncWork)); + } + + static void asyncWork() { + Span span = otelTracer.spanBuilder("MyCustomAsyncSpan").startSpan(); + span.makeCurrent().close(); + span.end(); + } + + @WithSpan + static void withSpan() { + Span span = otelTracer.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..20d27761bd --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySDKCustomizerTest.java @@ -0,0 +1,54 @@ +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..9364104ef3 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/ExitTracerSpanTest.java @@ -0,0 +1,101 @@ +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.GenericParameters; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +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 { + @Test + public void testReportDatabaseClientSpan() throws Exception { + ExitTracer tracer = mock(ExitTracer.class); + new ExitTracerSpan(tracer, SpanKind.CLIENT, readSpanAttributes("db-span.json")).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, SpanKind.CLIENT, attributes).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, SpanKind.CLIENT, readSpanAttributes("external-rpc-span.json")).end(); + final ArgumentCaptor externalParams = ArgumentCaptor.forClass(GenericParameters.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, SpanKind.CLIENT, readSpanAttributes("external-http-span.json")).end(); + final ArgumentCaptor externalParams = ArgumentCaptor.forClass(GenericParameters.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, SpanKind.CLIENT, readSpanAttributes("external-http-span.json")) + .setAttribute(AttributeKey.stringKey("code.function"), "execute").end(); + final ArgumentCaptor externalParams = ArgumentCaptor.forClass(GenericParameters.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, SpanKind.CLIENT, readSpanAttributes("bad-client-span.json")).end(); + verify(tracer, times(0)).reportAsExternal(any(ExternalParameters.class)); + } + + 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/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/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/instrumentation/InstrumentationImpl.java b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/InstrumentationImpl.java index ff1fbf13e3..d72090a4ac 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; @@ -360,12 +363,17 @@ public ExitTracer createSqlTracer(Object invocationTarget, int signatureId, Stri } } - @Override - public ExitTracer createScalaTxnTracer() { - return createTracer(null, SCALA_API_TXN_CLASS_SIGNATURE_ID, null, SCALA_API_TRACER_FLAGS); - } + @Override + 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) { + private boolean overSegmentLimit(TransactionActivity transactionActivity) { Transaction transaction; if (transactionActivity == null) { transaction = Transaction.getTransaction(false); 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/tracers/AbstractTracer.java b/newrelic-agent/src/main/java/com/newrelic/agent/tracers/AbstractTracer.java index 34f690dfff..acffa82c86 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; @@ -371,6 +372,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) { @@ -389,6 +395,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); 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 19278de084..8a6b13b331 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; @@ -723,8 +725,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()); @@ -747,6 +750,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/tracers/DefaultTracerTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/tracers/DefaultTracerTest.java index d9e87e1fb1..0228c1aae1 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; @@ -545,6 +546,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); From 53a0a96fd18be22017dc500b2f8b7588f62d47a5 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Fri, 3 May 2024 11:53:11 -0700 Subject: [PATCH 02/17] Documentation --- .../main/java/io/opentelemetry/context/ContextHelper.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 index 46996efbc7..c0c3ff9875 100644 --- 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 @@ -13,6 +13,9 @@ 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()) { @@ -28,6 +31,11 @@ public static Context current(Context context) { 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) { From 50ada7a0c3fdc694e6016acd086cb3dad487cfad Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Fri, 3 May 2024 11:59:07 -0700 Subject: [PATCH 03/17] Add static constructor --- .../main/java/io/opentelemetry/context/ContextHelper.java | 6 +----- .../java/io/opentelemetry/sdk/trace/ExitTracerSpan.java | 7 ++++++- 2 files changed, 7 insertions(+), 6 deletions(-) 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 index c0c3ff9875..a2ff83f9fd 100644 --- 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 @@ -5,11 +5,8 @@ import com.newrelic.agent.bridge.Transaction; import com.newrelic.api.agent.TracedMethod; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.ExitTracerSpan; -import java.util.Collections; - class ContextHelper { private ContextHelper() {} @@ -23,8 +20,7 @@ public static Context current(Context context) { if (transaction != null) { TracedMethod tracedMethod = transaction.getTracedMethod(); if (tracedMethod instanceof ExitTracer) { - return context.with(new ExitTracerSpan((ExitTracer) tracedMethod, SpanKind.INTERNAL, - Collections.emptyMap())); + return context.with(ExitTracerSpan.wrap((ExitTracer) tracedMethod)); } } } 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 index 7c1cebf4c5..aeda11cdf5 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -54,13 +55,17 @@ public class ExitTracerSpan implements Span { private final Map attributes; private final SpanContext spanContext; - public ExitTracerSpan(ExitTracer tracer, SpanKind spanKind, Map attributes) { + ExitTracerSpan(ExitTracer tracer, SpanKind spanKind, Map attributes) { this.tracer = tracer; this.spanKind = spanKind; this.attributes = attributes; this.spanContext = SpanContext.create(tracer.getTraceId(), tracer.getSpanId(), TraceFlags.getDefault(), TraceState.getDefault()); } + public static ExitTracerSpan wrap(ExitTracer tracer) { + return new ExitTracerSpan(tracer, SpanKind.INTERNAL, Collections.emptyMap()); + } + @Override public Span setAttribute(AttributeKey key, T value) { attributes.put(key.getKey(), value); From fe873d713b80fd852d9fa6bd09cea6a939625c8b Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Fri, 3 May 2024 14:06:15 -0700 Subject: [PATCH 04/17] Add test showing the SpanBuilder.setParent doesn't link async work --- .../sdk/trace/NRSpanBuilder.java | 4 +- .../io/opentelemetry/context/SpanTest.java | 57 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) 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 index 79a3b2b94e..e7c061128d 100644 --- 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 @@ -26,7 +26,9 @@ public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScop this.instrumentation = instrumentation; this.spanName = spanName; attributes.put(ExitTracerSpan.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); - attributes.put(ExitTracerSpan.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); + if (instrumentationScopeVersion != null) { + attributes.put(ExitTracerSpan.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); + } } @Override 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 index f07b0f2cb0..89c283dfee 100644 --- 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 @@ -1,9 +1,11 @@ package io.opentelemetry.context; +import com.newrelic.agent.bridge.ExitTracer; import com.newrelic.agent.introspec.InstrumentationTestConfig; import com.newrelic.agent.introspec.InstrumentationTestRunner; import com.newrelic.agent.introspec.Introspector; 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; @@ -11,15 +13,15 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.instrumentation.annotations.WithSpan; +import io.opentelemetry.sdk.trace.ExitTracerSpan; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -32,7 +34,7 @@ public class SpanTest { static { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); } - static Tracer otelTracer = GlobalOpenTelemetry.get().getTracer("test"); + static Tracer otelTracer = GlobalOpenTelemetry.get().getTracer("test", "1.0"); @Test public void testSimpleSpans() { @@ -52,13 +54,11 @@ public void testSimpleSpans() { } @Test - public void testAsyncSpans() throws InterruptedException { + public void testAsyncSpans() { final ExecutorService executor = Executors.newSingleThreadExecutor(); try { - asyncSpans(executor); - CountDownLatch latch = new CountDownLatch(1); - executor.execute(latch::countDown); - latch.await(1, TimeUnit.MINUTES); + asyncSpans(executor, SpanTest::asyncWork); + LatchingRunnable.drain(executor); Introspector introspector = InstrumentationTestRunner.getIntrospector(); assertEquals(1, introspector.getFinishedTransactionCount()); @@ -76,6 +76,40 @@ public void testAsyncSpans() throws InterruptedException { } } + @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 = otelTracer.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(); @@ -121,11 +155,12 @@ static void simpleSpans() { } @Trace(dispatcher = true) - static void asyncSpans(Executor executor) { - executor.execute(Context.current().wrap(SpanTest::asyncWork)); + static void asyncSpans(Executor executor, Consumer consumer) { + Context context = Context.current(); + executor.execute(Context.current().wrap(() -> consumer.accept(context))); } - static void asyncWork() { + static void asyncWork(Context context) { Span span = otelTracer.spanBuilder("MyCustomAsyncSpan").startSpan(); span.makeCurrent().close(); span.end(); From 8faee602e2936b458201fb874a3f1bac4e453590 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Mon, 6 May 2024 10:40:49 -0700 Subject: [PATCH 05/17] Implement more of the OTel API for spans --- .../sdk/trace/AttributesHelper.java | 24 +++ .../sdk/trace/ExitTracerSpan.java | 166 +++++++++++++++++- .../sdk/trace/NRSpanBuilder.java | 159 ++++++++++++++++- .../sdk/trace/NRTracerBuilder.java | 6 +- .../SdkTracerProvider_Instrumentation.java | 4 +- .../io/opentelemetry/context/SpanTest.java | 55 +++++- .../sdk/trace/ExitTracerSpanTest.java | 64 ++++++- .../sdk/trace/TestTracerBuilder.java | 62 +++++++ .../opentelemetry/sdk/trace/server-span.json | 54 ++++++ 9 files changed, 568 insertions(+), 26 deletions(-) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/AttributesHelper.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/TestTracerBuilder.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/io/opentelemetry/sdk/trace/server-span.json 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..c9761d200f --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/AttributesHelper.java @@ -0,0 +1,24 @@ +package io.opentelemetry.sdk.trace; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; + +import java.util.Map; + +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 index aeda11cdf5..0193a29854 100644 --- 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 @@ -17,8 +17,13 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.context.Context; 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; @@ -28,9 +33,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.logging.Level; -public class ExitTracerSpan implements 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"); @@ -52,18 +58,33 @@ public class ExitTracerSpan implements Span { 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, SpanKind spanKind, Map attributes) { + 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()); } public static ExitTracerSpan wrap(ExitTracer tracer) { - return new ExitTracerSpan(tracer, SpanKind.INTERNAL, Collections.emptyMap()); + return new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.INTERNAL, tracer.getMetricName(), SpanContext.getInvalid(), + Resource.empty(), Collections.emptyMap(), span -> {}); } @Override @@ -116,7 +137,7 @@ static Map toMap(Attributes attributes) { @Override public Span updateName(String name) { - tracer.setMetricName("Span", name); + this.spanName = name; return this; } @@ -125,8 +146,12 @@ public void end() { if (SpanKind.CLIENT == spanKind) { reportClientSpan(); } + tracer.setMetricName("Span", spanName); tracer.addCustomAttributes(attributes); tracer.finish(); + endEpochNanos = System.nanoTime(); + ended = true; + onEnd.accept(this); } @Override @@ -145,11 +170,42 @@ public boolean isRecording() { } @Override - public Context storeInContext(Context context) { - return Span.super.storeInContext(context); + public SpanContext getParentSpanContext() { + return parentSpanContext; } - private T getAttribute(AttributeKey key) { + @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(); @@ -243,4 +299,98 @@ public Scope createScope(Scope scope) { 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 index e7c061128d..8b3edbcce6 100644 --- 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 @@ -1,8 +1,14 @@ 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.ExtendedRequest; +import com.newrelic.api.agent.ExtendedResponse; +import com.newrelic.api.agent.HeaderType; +import com.newrelic.api.agent.TracedMethod; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -11,28 +17,44 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; 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; class NRSpanBuilder implements SpanBuilder { 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, String spanName) { + 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 -> {}; + } } @Override public SpanBuilder setParent(Context context) { + parentSpanContext = Span.fromContext(context).getSpanContext(); return this; } @@ -94,15 +116,138 @@ public SpanBuilder setStartTimestamp(long startTimestamp, TimeUnit unit) { @Override public Span startSpan() { - final ExitTracer tracer = instrumentation.createTracer("Span/" + spanName, - TracerFlags.GENERATE_SCOPED_METRIC - | TracerFlags.TRANSACTION_TRACER_SEGMENT - | TracerFlags.CUSTOM); + 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; } - tracer.addCustomAttribute("span.kind", spanKind.name()); - return new ExitTracerSpan(tracer, spanKind, attributes); + if (SpanKind.INTERNAL != spanKind) { + tracer.addCustomAttribute("span.kind", spanKind.name()); + } + // 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; } private static final Span NO_OP_SPAN = new Span() { 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 index b97b218658..bc924fa329 100644 --- 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 @@ -6,11 +6,13 @@ class NRTracerBuilder implements TracerBuilder { private final String instrumentationScopeName; + private final TracerSharedState sharedState; private String schemaUrl; private String instrumentationScopeVersion; - public NRTracerBuilder(String instrumentationScopeName) { + public NRTracerBuilder(String instrumentationScopeName, TracerSharedState sharedState) { this.instrumentationScopeName = instrumentationScopeName; + this.sharedState = sharedState; } @Override @@ -27,6 +29,6 @@ public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersio @Override public Tracer build() { - return spanName -> new NRSpanBuilder(AgentBridge.instrumentation, instrumentationScopeName, instrumentationScopeVersion, spanName); + 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 index def0c1c502..d64cb93dcb 100644 --- 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 @@ -7,9 +7,11 @@ @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) { // ignore otel builder Weaver.callOriginal(); - return new NRTracerBuilder(instrumentationScopeName); + return new NRTracerBuilder(instrumentationScopeName, sharedState); } } 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 index 89c283dfee..0335667f66 100644 --- 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 @@ -1,6 +1,5 @@ package io.opentelemetry.context; -import com.newrelic.agent.bridge.ExitTracer; import com.newrelic.agent.introspec.InstrumentationTestConfig; import com.newrelic.agent.introspec.InstrumentationTestRunner; import com.newrelic.agent.introspec.Introspector; @@ -13,16 +12,19 @@ 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.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.assertSame; @@ -36,6 +38,57 @@ public class SpanTest { } static Tracer otelTracer = GlobalOpenTelemetry.get().getTracer("test", "1.0"); + @Test + public void testInternalSpansNoTransaction() { + Span span = otelTracer.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 = otelTracer.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 = otelTracer.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(); 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 index 9364104ef3..ff34bdb48c 100644 --- 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 @@ -6,13 +6,22 @@ import com.newrelic.api.agent.ExternalParameters; import com.newrelic.api.agent.GenericParameters; 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; @@ -22,10 +31,51 @@ 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); - new ExitTracerSpan(tracer, SpanKind.CLIENT, readSpanAttributes("db-span.json")).end(); + 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()); @@ -41,7 +91,7 @@ public void testReportDatabaseClientSpanMissingSqlTable() throws Exception { ExitTracer tracer = mock(ExitTracer.class); Map attributes = readSpanAttributes("db-span.json"); attributes.remove("db.sql.table"); - new ExitTracerSpan(tracer, SpanKind.CLIENT, attributes).end(); + 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()); @@ -55,7 +105,7 @@ public void testReportDatabaseClientSpanMissingSqlTable() throws Exception { @Test public void testReportRpcClientSpan() throws Exception { ExitTracer tracer = mock(ExitTracer.class); - new ExitTracerSpan(tracer, SpanKind.CLIENT, readSpanAttributes("external-rpc-span.json")).end(); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(), readSpanAttributes("external-rpc-span.json"), END_HANDLER).end(); final ArgumentCaptor externalParams = ArgumentCaptor.forClass(GenericParameters.class); verify(tracer, times(1)).reportAsExternal(externalParams.capture()); assertEquals("io.opentelemetry.grpc-1.6", externalParams.getValue().getLibrary()); @@ -66,7 +116,7 @@ public void testReportRpcClientSpan() throws Exception { @Test public void testReportHttpClientSpan() throws Exception { ExitTracer tracer = mock(ExitTracer.class); - new ExitTracerSpan(tracer, SpanKind.CLIENT, readSpanAttributes("external-http-span.json")).end(); + new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.CLIENT, "", SpanContext.getInvalid(), Resource.empty(),readSpanAttributes("external-http-span.json"), END_HANDLER).end(); final ArgumentCaptor externalParams = ArgumentCaptor.forClass(GenericParameters.class); verify(tracer, times(1)).reportAsExternal(externalParams.capture()); assertEquals("io.opentelemetry.java-http-client", externalParams.getValue().getLibrary()); @@ -77,7 +127,7 @@ public void testReportHttpClientSpan() throws Exception { @Test public void testReportHttpClientSpanWithCodeFunction() throws Exception { ExitTracer tracer = mock(ExitTracer.class); - new ExitTracerSpan(tracer, SpanKind.CLIENT, readSpanAttributes("external-http-span.json")) + 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(GenericParameters.class); verify(tracer, times(1)).reportAsExternal(externalParams.capture()); @@ -89,11 +139,11 @@ public void testReportHttpClientSpanWithCodeFunction() throws Exception { @Test public void testBadClientSpan() throws Exception { ExitTracer tracer = mock(ExitTracer.class); - new ExitTracerSpan(tracer, SpanKind.CLIENT, readSpanAttributes("bad-client-span.json")).end(); + 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)); } - static Map readSpanAttributes(String fileName) throws IOException { + public static Map readSpanAttributes(String fileName) throws IOException { try (InputStream in = ExitTracerSpanTest.class.getResourceAsStream(fileName)) { return new ObjectMapper().readValue(in, Map.class); } 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..677e4f3432 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/TestTracerBuilder.java @@ -0,0 +1,62 @@ +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/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 From 583a36a12d2cbf508bf1430013c074c84d010d9a Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Tue, 7 May 2024 12:56:35 -0700 Subject: [PATCH 06/17] Add a switch to disable custom otel span builder --- .../sdk/trace/ExitTracerSpan.java | 1 + .../sdk/trace/NRSpanBuilder.java | 10 +++++ .../SdkTracerProvider_Instrumentation.java | 10 +++-- .../sdk/trace/NRSpanBuilderTest.java | 41 +++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRSpanBuilderTest.java 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 index 0193a29854..9a85cba3d4 100644 --- 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 @@ -80,6 +80,7 @@ public class ExitTracerSpan implements ReadWriteSpan { 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) { 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 index 8b3edbcce6..d2b56eabb6 100644 --- 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 @@ -5,6 +5,7 @@ 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; @@ -52,6 +53,15 @@ public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScop } } + 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(); 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 index d64cb93dcb..9266f0c8e6 100644 --- 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 @@ -1,5 +1,6 @@ package io.opentelemetry.sdk.trace; +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; @@ -10,8 +11,11 @@ public final class SdkTracerProvider_Instrumentation { private final TracerSharedState sharedState = Weaver.callOriginal(); public TracerBuilder tracerBuilder(String instrumentationScopeName) { - // ignore otel builder - Weaver.callOriginal(); - return new NRTracerBuilder(instrumentationScopeName, sharedState); + final TracerBuilder tracerBuilder = Weaver.callOriginal(); + if (NRSpanBuilder.isSpanBuilderEnabled(NewRelic.getAgent().getConfig())) { + // return our tracer builder instead of the OTel instance + return new NRTracerBuilder(instrumentationScopeName, sharedState); + } + return tracerBuilder; } } 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..bd2a2c8384 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRSpanBuilderTest.java @@ -0,0 +1,41 @@ +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 From c7bdc0aba23a5ec78a06d1d984917c8595d775f6 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Wed, 8 May 2024 11:38:22 -0700 Subject: [PATCH 07/17] Fix test --- .../tracing/FlyweightTraceMethodVisitorTest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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); From 4563453c6453f86ea00de102e5e4ec9f1b3fa550 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Wed, 8 May 2024 11:38:58 -0700 Subject: [PATCH 08/17] Log if `otel.exporter.otlp.endpoint` is set --- .../sdk/autoconfigure/OpenTelemetrySDKCustomizer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 index bcb978e64f..46a4edba16 100644 --- 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 @@ -26,7 +26,8 @@ static Map applyProperties(ConfigProperties configProperties) { * Configure OpenTelemetry exporters to send data to the New Relic backend. */ static Map applyProperties(ConfigProperties configProperties, Agent agent) { - if (configProperties.getString("otel.exporter.otlp.endpoint") == null) { + 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"; @@ -46,6 +47,10 @@ static Map applyProperties(ConfigProperties configProperties, Ag 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(); } From 224947cd33b4fc920c4ed5035864d67c1c5478c9 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Wed, 8 May 2024 15:02:35 -0700 Subject: [PATCH 09/17] Update DefaultTracerTest.java --- .../test/java/com/newrelic/agent/tracers/DefaultTracerTest.java | 1 + 1 file changed, 1 insertion(+) 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 0228c1aae1..674de3bd86 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 @@ -112,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 From 6041d136d8e8d59c37e44817fd2641efa1c95f78 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Thu, 9 May 2024 17:24:15 -0700 Subject: [PATCH 10/17] Allow individual opentelemetry instrumentation scopes to be disabled --- .../sdk/trace/NRTracerBuilder.java | 14 ++++++-- .../SdkTracerProvider_Instrumentation.java | 2 +- .../io/opentelemetry/context/SpanTest.java | 20 +++++------ .../sdk/trace/NRTracerBuilderTest.java | 35 +++++++++++++++++++ 4 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRTracerBuilderTest.java 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 index bc924fa329..a1622d2382 100644 --- 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 @@ -1,16 +1,21 @@ 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; class NRTracerBuilder implements TracerBuilder { private final String instrumentationScopeName; private final TracerSharedState sharedState; + private final Config config; private String schemaUrl; private String instrumentationScopeVersion; - public NRTracerBuilder(String instrumentationScopeName, TracerSharedState sharedState) { + public NRTracerBuilder(Config config, String instrumentationScopeName, TracerSharedState sharedState) { + this.config = config; this.instrumentationScopeName = instrumentationScopeName; this.sharedState = sharedState; } @@ -29,6 +34,11 @@ public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersio @Override public Tracer build() { - return spanName -> new NRSpanBuilder(AgentBridge.instrumentation, instrumentationScopeName, instrumentationScopeVersion, sharedState, spanName); + 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 index 9266f0c8e6..4734f536a5 100644 --- 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 @@ -14,7 +14,7 @@ public TracerBuilder tracerBuilder(String instrumentationScopeName) { final TracerBuilder tracerBuilder = Weaver.callOriginal(); if (NRSpanBuilder.isSpanBuilderEnabled(NewRelic.getAgent().getConfig())) { // return our tracer builder instead of the OTel instance - return new NRTracerBuilder(instrumentationScopeName, sharedState); + return new NRTracerBuilder(NewRelic.getAgent().getConfig(), 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 index 0335667f66..a60d971447 100644 --- 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 @@ -36,11 +36,11 @@ public class SpanTest { static { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); } - static Tracer otelTracer = GlobalOpenTelemetry.get().getTracer("test", "1.0"); + static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer("test", "1.0"); @Test public void testInternalSpansNoTransaction() { - Span span = otelTracer.spanBuilder("MyCustomSpan").startSpan(); + Span span = OTEL_TRACER.spanBuilder("MyCustomSpan").startSpan(); span.makeCurrent().close(); span.end(); @@ -51,7 +51,7 @@ public void testInternalSpansNoTransaction() { @Test public void testConsumerSpan() { - Span span = otelTracer.spanBuilder("consume").setSpanKind(SpanKind.CONSUMER).startSpan(); + Span span = OTEL_TRACER.spanBuilder("consume").setSpanKind(SpanKind.CONSUMER).startSpan(); span.makeCurrent().close(); span.end(); @@ -72,7 +72,7 @@ public void testServerSpan() throws IOException { Map attributes = readSpanAttributes("server-span.json"); final String spanName = (String) attributes.remove("name"); - Span span = otelTracer.spanBuilder(spanName).setSpanKind(SpanKind.SERVER).startSpan(); + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(SpanKind.SERVER).startSpan(); span.setAllAttributes(AttributesHelper.toAttributes(attributes)); span.makeCurrent().close(); span.end(); @@ -141,7 +141,7 @@ public void testAsyncSpansWithParentNotWorking() { // 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 = otelTracer.spanBuilder("OrphanedSpan").setParent(context).startSpan(); + Span span = OTEL_TRACER.spanBuilder("OrphanedSpan").setParent(context).startSpan(); span.makeCurrent().close(); span.end(); }); @@ -181,7 +181,7 @@ public void testDatabaseSpan() { @Trace(dispatcher = true) static void databaseSpan() { - Span span = otelTracer.spanBuilder("owners select").setSpanKind(SpanKind.CLIENT) + Span span = OTEL_TRACER.spanBuilder("owners select").setSpanKind(SpanKind.CLIENT) .setAttribute("db.system", "mysql") .setAttribute("db.operation", "select") .setAttribute("db.sql.table", "owners") @@ -191,7 +191,7 @@ static void databaseSpan() { @Trace(dispatcher = true) static void simpleSpans() { - Span span = otelTracer.spanBuilder("MyCustomSpan").startSpan(); + Span span = OTEL_TRACER.spanBuilder("MyCustomSpan").startSpan(); Scope scope = span.makeCurrent(); SpanContext spanContext = span.getSpanContext(); assertNotNull(spanContext.getTraceId()); @@ -199,7 +199,7 @@ static void simpleSpans() { assertSame(spanContext, span.getSpanContext()); Span current = Span.current(); assertEquals(span, current); - Span kid = otelTracer.spanBuilder("kid").setParent(Context.current()).startSpan(); + Span kid = OTEL_TRACER.spanBuilder("kid").setParent(Context.current()).startSpan(); kid.end(); scope.close(); span.end(); @@ -214,14 +214,14 @@ static void asyncSpans(Executor executor, Consumer consumer) { } static void asyncWork(Context context) { - Span span = otelTracer.spanBuilder("MyCustomAsyncSpan").startSpan(); + Span span = OTEL_TRACER.spanBuilder("MyCustomAsyncSpan").startSpan(); span.makeCurrent().close(); span.end(); } @WithSpan static void withSpan() { - Span span = otelTracer.spanBuilder("kid").startSpan(); + 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/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..f12cb5abef --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/trace/NRTracerBuilderTest.java @@ -0,0 +1,35 @@ +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$$Lambda$")); + } + + 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 From a9029cc062edc859e348ecc25eca48dbfa71b750 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Fri, 10 May 2024 09:31:24 -0700 Subject: [PATCH 11/17] Try to fix test --- .../java/io/opentelemetry/sdk/trace/NRTracerBuilderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index f12cb5abef..919880b55e 100644 --- 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 @@ -22,7 +22,7 @@ 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$$Lambda$")); + "io.opentelemetry.sdk.trace.NRTracerBuilder")); } public void testBuildDisabled() { From 67efb93229589d919e0941a881a3077612a0c0f0 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Thu, 16 May 2024 09:55:43 -0700 Subject: [PATCH 12/17] Use OpenTelemetry no op Span instance --- .../sdk/trace/NRSpanBuilder.java | 54 +------------------ 1 file changed, 2 insertions(+), 52 deletions(-) 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 index d2b56eabb6..9b2f584fce 100644 --- 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 @@ -10,13 +10,13 @@ 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.api.trace.StatusCode; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; @@ -28,6 +28,7 @@ import java.util.function.Consumer; 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<>(); @@ -259,55 +260,4 @@ static int getTracerFlags(boolean dispatcher) { } return flags; } - - private static final Span NO_OP_SPAN = new Span() { - @Override - public Span setAttribute(AttributeKey key, T 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, Attributes additionalAttributes) { - return this; - } - - @Override - public Span updateName(String name) { - return this; - } - - @Override - public void end() { - } - - @Override - public void end(long timestamp, TimeUnit unit) { - - } - - @Override - public SpanContext getSpanContext() { - return SpanContext.getInvalid(); - } - - @Override - public boolean isRecording() { - return false; - } - }; } From 509ce8a7cac854bd2fa5e35dda1a75d1e2f2ee85 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Tue, 21 May 2024 13:44:53 -0700 Subject: [PATCH 13/17] Fix sql obfuscation of QueryConverter used by otel spans --- .../bridge/datastore/SqlQueryConverter.java | 2 +- .../com/newrelic/agent/introspec/SpanEvent.java | 2 ++ .../agent/introspec/internal/SpanEventImpl.java | 5 +++++ .../opentelemetry/sdk/trace/ExitTracerSpan.java | 15 ++++++++++++++- .../java/io/opentelemetry/context/SpanTest.java | 17 ++++++++++++++++- .../src/test/resources/distributed_tracing.yml | 3 +++ .../newrelic/agent/database/SqlObfuscator.java | 16 ++++++++++++++++ .../service/analytics/SpanEventFactory.java | 15 ++++++++++++--- 8 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/distributed_tracing.yml 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 index a7786af6f4..5a48cd6846 100644 --- 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 @@ -2,7 +2,7 @@ import com.newrelic.api.agent.QueryConverter; -public class SqlQueryConverter implements QueryConverter { +public final class SqlQueryConverter implements QueryConverter { public static final QueryConverter INSTANCE = new SqlQueryConverter(); private SqlQueryConverter() {} 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/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 index 9a85cba3d4..748f5d1c8f 100644 --- 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 @@ -32,9 +32,12 @@ 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; public class ExitTracerSpan implements ReadWriteSpan { static final String OTEL_LIBRARY_VERSION = "otel.library.version"; @@ -55,6 +58,11 @@ public class ExitTracerSpan implements ReadWriteSpan { 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).map(AttributeKey::getKey) + .collect(Collectors.toSet())); final ExitTracer tracer; private final SpanKind spanKind; @@ -148,7 +156,12 @@ public void end() { reportClientSpan(); } tracer.setMetricName("Span", spanName); - tracer.addCustomAttributes(attributes); + // 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; 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 index a60d971447..96e43e5824 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -18,6 +19,8 @@ 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; @@ -27,11 +30,12 @@ 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" }) +@InstrumentationTestConfig(includePrefixes = { "io.opentelemetry" }, configName = "distributed_tracing.yml") public class SpanTest { static { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); @@ -177,6 +181,16 @@ public void testDatabaseSpan() { 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) @@ -185,6 +199,7 @@ static void databaseSpan() { .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(); } 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/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/service/analytics/SpanEventFactory.java b/newrelic-agent/src/main/java/com/newrelic/agent/service/analytics/SpanEventFactory.java index c7ae11defc..af5c6cd778 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,9 +10,9 @@ 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.config.TransactionEventsConfig; import com.newrelic.agent.database.SqlObfuscator; import com.newrelic.agent.model.AttributeFilter; import com.newrelic.agent.model.SpanCategory; @@ -25,6 +25,7 @@ import com.newrelic.api.agent.DatastoreParameters; import com.newrelic.api.agent.ExternalParameters; import com.newrelic.api.agent.HttpParameters; +import com.newrelic.api.agent.QueryConverter; import com.newrelic.api.agent.SlowQueryDatastoreParameters; import java.net.URI; @@ -410,12 +411,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(); From 4911fc172b9f997af2e006a3332a48f1d8210e32 Mon Sep 17 00:00:00 2001 From: Saxon D'Aubin Date: Tue, 21 May 2024 15:20:17 -0700 Subject: [PATCH 14/17] Add another external span test --- .../build.gradle | 1 + .../sdk/trace/ExitTracerSpan.java | 9 ++-- .../io/opentelemetry/context/SpanTest.java | 42 +++++++++++++++++++ .../sdk/trace/ExitTracerSpanTest.java | 8 ++-- 4 files changed, 52 insertions(+), 8 deletions(-) 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 9dd073129f..030006b2bd 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle @@ -10,6 +10,7 @@ dependencies { 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/sdk/trace/ExitTracerSpan.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/ExitTracerSpan.java index 748f5d1c8f..a18da4065c 100644 --- 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 @@ -5,7 +5,7 @@ import com.newrelic.agent.bridge.datastore.SqlQueryConverter; import com.newrelic.agent.tracers.TracerFlags; import com.newrelic.api.agent.DatastoreParameters; -import com.newrelic.api.agent.GenericParameters; +import com.newrelic.api.agent.HttpParameters; import com.newrelic.api.agent.NewRelic; import com.newrelic.api.agent.Token; import io.opentelemetry.api.common.AttributeKey; @@ -61,7 +61,8 @@ public class ExitTracerSpan implements ReadWriteSpan { // 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).map(AttributeKey::getKey) + Stream.of(DB_STATEMENT, DB_SQL_TABLE, DB_SYSTEM, DB_OPERATION, SERVER_ADDRESS, SERVER_PORT) + .map(AttributeKey::getKey) .collect(Collectors.toSet())); final ExitTracer tracer; @@ -262,8 +263,8 @@ private void reportClientSpan() { final URI uri = getUri(); if (uri != null) { final String libraryName = getAttribute(OTEL_LIBRARY_NAME); - GenericParameters genericParameters = GenericParameters.library(libraryName).uri(uri) - .procedure(getProcedure()).build(); + HttpParameters genericParameters = HttpParameters.library(libraryName).uri(uri) + .procedure(getProcedure()).noInboundHeaders().build(); tracer.reportAsExternal(genericParameters); } } catch (URISyntaxException e) { 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 index 96e43e5824..db895438b2 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -204,6 +205,47 @@ static void databaseSpan() { 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(); 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 index ff34bdb48c..60b96725f2 100644 --- 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 @@ -4,7 +4,7 @@ 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.GenericParameters; +import com.newrelic.api.agent.HttpParameters; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; @@ -106,7 +106,7 @@ public void testReportDatabaseClientSpanMissingSqlTable() throws Exception { 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(GenericParameters.class); + 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()); @@ -117,7 +117,7 @@ public void testReportRpcClientSpan() throws Exception { 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(GenericParameters.class); + 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()); @@ -129,7 +129,7 @@ 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(GenericParameters.class); + 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()); From 972a16c8ce550c204ad33a886f9d5e65ad312f3c Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Mon, 1 Jul 2024 10:11:57 -0700 Subject: [PATCH 15/17] Add copyright header and formatting --- .../bridge/datastore/SqlQueryConverter.java | 10 +++++++++- .../opentelemetry/context/ContextHelper.java | 12 +++++++++-- .../context/Context_Instrumentation.java | 9 ++++++++- .../AutoConfiguredOpenTelemetrySdk.java | 7 +++++++ .../OpenTelemetrySDKCustomizer.java | 7 +++++++ .../sdk/trace/AttributesHelper.java | 10 +++++++++- .../sdk/trace/ExitTracerSpan.java | 19 +++++++++++++----- .../sdk/trace/NRSpanBuilder.java | 20 ++++++++++++++----- .../sdk/trace/NRTracerBuilder.java | 7 +++++++ .../SdkTracerProvider_Instrumentation.java | 7 +++++++ .../io/opentelemetry/context/SpanTest.java | 8 ++++++++ .../OpenTelemetrySDKCustomizerTest.java | 7 +++++++ .../sdk/trace/ExitTracerSpanTest.java | 7 +++++++ .../sdk/trace/NRSpanBuilderTest.java | 8 +++++++- .../sdk/trace/NRTracerBuilderTest.java | 7 +++++++ .../sdk/trace/TestTracerBuilder.java | 10 +++++++++- 16 files changed, 138 insertions(+), 17 deletions(-) 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 index 5a48cd6846..ad8938f341 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -5,7 +12,8 @@ public final class SqlQueryConverter implements QueryConverter { public static final QueryConverter INSTANCE = new SqlQueryConverter(); - private SqlQueryConverter() {} + private SqlQueryConverter() { + } @Override public String toRawQueryString(String rawQuery) { 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 index a2ff83f9fd..3e8bdb209e 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + package io.opentelemetry.context; import com.newrelic.agent.bridge.AgentBridge; @@ -8,7 +15,8 @@ import io.opentelemetry.sdk.trace.ExitTracerSpan; class ContextHelper { - private 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. @@ -38,7 +46,7 @@ public static Scope makeCurrent(Context context, Scope scope) { Span currentSpan = Span.fromContext(context); if (currentSpan instanceof ExitTracerSpan) { - return ((ExitTracerSpan)currentSpan).createScope(scope); + 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 index bab1d45bdb..67430bf51a 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -11,6 +18,6 @@ public static Context current() { } public Scope makeCurrent() { - return ContextHelper.makeCurrent((Context)this, Weaver.callOriginal()); + 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 a5e22a7ba1..dd1478ec5d 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; 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 index 46a4edba16..1c2774be74 100644 --- 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 @@ -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.agent.bridge.AgentBridge; 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 index c9761d200f..305018e958 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -6,7 +13,8 @@ import java.util.Map; public class AttributesHelper { - private AttributesHelper() {} + private AttributesHelper() { + } public static Attributes toAttributes(Map attributes) { AttributesBuilder builder = Attributes.builder(); 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 index a18da4065c..ba51f49d4a 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -61,9 +68,9 @@ public class ExitTracerSpan implements ReadWriteSpan { // 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())); + 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; @@ -78,7 +85,8 @@ public class ExitTracerSpan implements ReadWriteSpan { private long endEpochNanos; private final Resource resource; - ExitTracerSpan(ExitTracer tracer, InstrumentationLibraryInfo instrumentationLibraryInfo, SpanKind spanKind, String spanName, SpanContext parentSpanContext, Resource resource, Map attributes, Consumer onEnd) { + 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; @@ -94,7 +102,8 @@ public class ExitTracerSpan implements ReadWriteSpan { public static ExitTracerSpan wrap(ExitTracer tracer) { return new ExitTracerSpan(tracer, InstrumentationLibraryInfo.empty(), SpanKind.INTERNAL, tracer.getMetricName(), SpanContext.getInvalid(), - Resource.empty(), Collections.emptyMap(), span -> {}); + Resource.empty(), Collections.emptyMap(), span -> { + }); } @Override 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 index 9b2f584fce..1d23074cc2 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -35,10 +42,11 @@ class NRSpanBuilder implements SpanBuilder { private final TracerSharedState sharedState; private final Consumer endHandler; private final InstrumentationLibraryInfo instrumentationLibraryInfo; - private SpanKind spanKind= SpanKind.INTERNAL; + private SpanKind spanKind = SpanKind.INTERNAL; private SpanContext parentSpanContext; - public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScopeName, String instrumentationScopeVersion, TracerSharedState sharedState, String spanName) { + public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScopeName, String instrumentationScopeVersion, TracerSharedState sharedState, + String spanName) { this.instrumentation = instrumentation; this.spanName = spanName; this.sharedState = sharedState; @@ -50,7 +58,8 @@ public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScop if (sharedState.getActiveSpanProcessor().isEndRequired()) { endHandler = sharedState.getActiveSpanProcessor()::onEnd; } else { - endHandler = span -> {}; + endHandler = span -> { + }; } } @@ -144,7 +153,8 @@ public Span startSpan() { tracer.addCustomAttribute("span.kind", spanKind.name()); } // REVIEW - we're not picking up the global resources - return onStart(new ExitTracerSpan(tracer, instrumentationLibraryInfo, spanKind, spanName, parentSpanContext, sharedState.getResource(), attributes, endHandler)); + return onStart(new ExitTracerSpan(tracer, instrumentationLibraryInfo, spanKind, spanName, parentSpanContext, sharedState.getResource(), attributes, + endHandler)); } private Span startServerSpan(SpanContext parentSpanContext) { @@ -208,7 +218,7 @@ public String getMethod() { @Override public int getStatus() throws Exception { Object statusCode = attributes.get("http.response.status_code"); - return statusCode instanceof Number ? ((Number)statusCode).intValue() : 0; + return statusCode instanceof Number ? ((Number) statusCode).intValue() : 0; } @Override 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 index a1622d2382..571db8341a 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; 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 index 4734f536a5..03ae55e766 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + package io.opentelemetry.sdk.trace; import com.newrelic.api.agent.NewRelic; 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 index db895438b2..8150b46485 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + package io.opentelemetry.context; import com.google.common.collect.ImmutableMap; @@ -41,6 +48,7 @@ public class SpanTest { static { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); } + static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer("test", "1.0"); @Test 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 index 20d27761bd..44625ce3b3 100644 --- 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 @@ -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.Agent; 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 index 60b96725f2..9da3c5dd52 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; 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 index bd2a2c8384..c8bba060d8 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -37,5 +44,4 @@ private Config createConfig(Boolean autoconfigureEnabled, Boolean 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 index 919880b55e..a5ff86f305 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; 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 index 677e4f3432..a432c1d0b0 100644 --- 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 @@ -1,3 +1,10 @@ +/* + * + * * 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; @@ -56,7 +63,8 @@ public TracerBuilder withTracer(ExitTracer tracer) { @Override public Tracer build() { Supplier spanLimitsSupplier = () -> SpanLimits.getDefault(); - TracerSharedState sharedState = new TracerSharedState(Clock.getDefault(), IdGenerator.random(), resource, spanLimitsSupplier, Sampler.alwaysOn(), spanProcessors); + TracerSharedState sharedState = new TracerSharedState(Clock.getDefault(), IdGenerator.random(), resource, spanLimitsSupplier, Sampler.alwaysOn(), + spanProcessors); return spanName -> new NRSpanBuilder(instrumentation, instrumentationScopeName, instrumentationScopeVersion, sharedState, spanName); } } From 35c91f84eb2f85143cd44be629cc652de971d812 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Tue, 29 Oct 2024 16:40:41 -0700 Subject: [PATCH 16/17] Add some comments --- .../io/opentelemetry/context/ContextHelper.java | 3 +++ .../context/Context_Instrumentation.java | 3 +++ .../AutoConfiguredOpenTelemetrySdk.java | 4 ++++ .../OpenTelemetrySDKCustomizer.java | 4 ++++ .../sdk/trace/AttributesHelper.java | 3 +++ .../opentelemetry/sdk/trace/ExitTracerSpan.java | 3 +++ .../opentelemetry/sdk/trace/NRSpanBuilder.java | 17 ++++++++++++++++- .../sdk/trace/NRTracerBuilder.java | 5 +++++ .../SdkTracerProvider_Instrumentation.java | 9 +++++++-- 9 files changed, 48 insertions(+), 3 deletions(-) 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 index 3e8bdb209e..b9fb3e87d7 100644 --- 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 @@ -14,6 +14,9 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.trace.ExitTracerSpan; +/** + * Helper class for managing the OpenTelemetry Context + */ class ContextHelper { private ContextHelper() { } 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 index 67430bf51a..7c6c83fa54 100644 --- 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 @@ -11,6 +11,9 @@ 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() { 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 dd1478ec5d..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 @@ -14,6 +14,10 @@ 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 { 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 index 1c2774be74..ca7d87f792 100644 --- 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 @@ -22,6 +22,10 @@ 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"); 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 index 305018e958..a41b49ef62 100644 --- 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 @@ -12,6 +12,9 @@ import java.util.Map; +/** + * Helper class for adding attributes to Spans + */ public class AttributesHelper { private AttributesHelper() { } 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 index ba51f49d4a..67b0e479a1 100644 --- 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 @@ -46,6 +46,9 @@ 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"); 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 index 1d23074cc2..67a866bea3 100644 --- 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 @@ -34,6 +34,13 @@ 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; @@ -134,6 +141,13 @@ 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 ? @@ -152,7 +166,7 @@ public Span startSpan() { if (SpanKind.INTERNAL != spanKind) { tracer.addCustomAttribute("span.kind", spanKind.name()); } - // REVIEW - we're not picking up the global resources + // TODO REVIEW - we're not picking up the global resources return onStart(new ExitTracerSpan(tracer, instrumentationLibraryInfo, spanKind, spanName, parentSpanContext, sharedState.getResource(), attributes, endHandler)); } @@ -213,6 +227,7 @@ public String getMethod() { return (String) attributes.get("http.request.method"); } }; + final ExtendedResponse response = new ExtendedResponse() { @Override 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 index 571db8341a..791e1b20cf 100644 --- 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 @@ -14,6 +14,11 @@ 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; 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 index 03ae55e766..b41c343f78 100644 --- 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 @@ -7,21 +7,26 @@ 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(); - if (NRSpanBuilder.isSpanBuilderEnabled(NewRelic.getAgent().getConfig())) { + Config config = NewRelic.getAgent().getConfig(); + if (NRSpanBuilder.isSpanBuilderEnabled(config)) { // return our tracer builder instead of the OTel instance - return new NRTracerBuilder(NewRelic.getAgent().getConfig(), instrumentationScopeName, sharedState); + return new NRTracerBuilder(config, instrumentationScopeName, sharedState); } return tracerBuilder; } From c308a60d7007e71c54a074a94a13a027b46b4c9d Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Mon, 2 Dec 2024 14:32:15 -0800 Subject: [PATCH 17/17] Add readme for OTel functionality --- .../README.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md 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)