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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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/36] 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) From 36a4c8f158bb08ca82a411be3d3237b5954a61fc Mon Sep 17 00:00:00 2001 From: Jerry Duffy Date: Fri, 28 Mar 2025 07:57:57 -0400 Subject: [PATCH 18/36] Additional unit tests --- .../agent/otelhybrid/AssertionEvaluator.java | 45 ++++ .../agent/otelhybrid/HybridAgentTest.java | 195 ++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java new file mode 100644 index 0000000000..f86d647a9d --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.opentelemetry.agent.otelhybrid; + +import com.newrelic.agent.introspec.Introspector; +import com.newrelic.agent.introspec.SpanEvent; + +import java.util.Map; + +import static org.junit.Assert.*; + +public class AssertionEvaluator { + public static void assertNoTxnExists(Introspector introspector) { + assertEquals(0, introspector.getFinishedTransactionCount()); + } + + public static void assertNoNewRelicSpanExists(Introspector introspector) { + assertEquals(0, introspector.getSpanEvents().size()); + } + + public static void assertSpanCount(Introspector introspector, int count) { + assertEquals(count, introspector.getSpanEvents().size()); + } + + public static void assertTxnExists(Introspector introspector, String name) { + assertEquals(name, introspector.getTransactionNames().iterator().next()); + } + + public static void assertUserAttributeExists(String key, Object val, Map attributes) { + assertEquals(attributes.get(key), val); + } + + public static void assertExceptionExistsOnSpan(SpanEvent spanEvent, String errorMessage, String errorClass) { + assertEquals(errorMessage, spanEvent.getAgentAttributes().get("error.message")); + assertEquals(errorClass, spanEvent.getAgentAttributes().get("error.class")); + } + + public static void assertCarrierContainsW3CTraceParent(Map carrier) { + assertNotNull(carrier.get("traceparent")); + } +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java new file mode 100644 index 0000000000..d256193068 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java @@ -0,0 +1,195 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package io.opentelemetry.agent.otelhybrid; + +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.api.agent.Trace; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@RunWith(InstrumentationTestRunner.class) +@InstrumentationTestConfig(includePrefixes = { "io.opentelemetry" }, configName = "distributed_tracing.yml") +public class HybridAgentTest { + static { + System.setProperty("otel.java.global-autoconfigure.enabled", "true"); + } + + static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer("test", "1.0"); + + @Test + public void doesNotCreateSegmentWithoutATransaction() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + doWorkInSpanWithoutTxn("Bar", SpanKind.INTERNAL); + + AssertionEvaluator.assertNoTxnExists(introspector); + AssertionEvaluator.assertNoNewRelicSpanExists(introspector); + } + + @Test + public void createTxnWhenServerSpanCreated() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + createTransactionWhenServerSpanCreated("Foo"); + AssertionEvaluator.assertTxnExists(introspector, "WebTransaction/Uri/Unknown"); + } + + @Test + public void createsOtelSegmentInTxn() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + createOtelSegmentInTxn("Foo", SpanKind.INTERNAL); + + AssertionEvaluator.assertSpanCount(introspector, 2); + AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/createOtelSegmentInTxn"); + } + + @Test + public void createsNewRelicSpanAsChildOfOtelSpan() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + newRelicSpanAsChildOfOtelSpan("foo", SpanKind.INTERNAL); + + AssertionEvaluator.assertSpanCount(introspector, 3); + AssertionEvaluator.assertTxnExists(introspector, + "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/newRelicSpanAsChildOfOtelSpan"); + } + + @Test + public void otelSpansCanAddAttributes() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + addAttributesToOtelSpan("foo", SpanKind.INTERNAL); + + AssertionEvaluator.assertSpanCount(introspector, 2); + AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/addAttributesToOtelSpan"); + + Collection spanEvents = introspector.getSpanEvents(); + SpanEvent[] eventArray = spanEvents.toArray(new SpanEvent[0]); + AssertionEvaluator.assertUserAttributeExists("key1", "val1", eventArray[1].getUserAttributes()); + AssertionEvaluator.assertUserAttributeExists("key2", "val2", eventArray[1].getUserAttributes()); + } + + @Test + public void exceptionsAreRecordedOnOtelSpan() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + otelSpanRecordsException("foo", SpanKind.INTERNAL); + + AssertionEvaluator.assertSpanCount(introspector, 2); + AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/otelSpanRecordsException"); + + Collection spanEvents = introspector.getSpanEvents(); + SpanEvent[] eventArray = spanEvents.toArray(new SpanEvent[0]); + AssertionEvaluator.assertExceptionExistsOnSpan(eventArray[1], "oops", "java.lang.Exception"); + } + + @Test + public void externalCallWithW3CHeaderInjection() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Map carrier = externalCallInjectsW3CHeaders("foo", SpanKind.CLIENT); + + AssertionEvaluator.assertSpanCount(introspector, 2); + AssertionEvaluator.assertTxnExists(introspector, + "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/externalCallInjectsW3CHeaders"); + AssertionEvaluator.assertCarrierContainsW3CTraceParent(carrier); + } + + static void doWorkInSpanWithoutTxn(String spanName, SpanKind spanKind) { + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Scope scope = span.makeCurrent(); + scope.close(); + span.end(); + } + + @Trace(dispatcher = true) + static void createOtelSegmentInTxn(String spanName, SpanKind spanKind) { + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Scope scope = span.makeCurrent(); + scope.close(); + span.end(); + } + + @Trace(dispatcher = true) + static void newRelicSpanAsChildOfOtelSpan(String spanName, SpanKind spanKind) { + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Scope scope = span.makeCurrent(); + + newRelicWorkTracer(); + + scope.close(); + span.end(); + } + + @Trace + static void newRelicWorkTracer() { + // Do something + } + + @Trace(dispatcher = true) + static void addAttributesToOtelSpan(String spanName, SpanKind spanKind) { + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Scope scope = span.makeCurrent(); + + span.setAttribute("key1", "val1"); + span.setAttribute("key2", "val2"); + + scope.close(); + span.end(); + } + + @Trace(dispatcher = true) + static void otelSpanRecordsException(String spanName, SpanKind spanKind) { + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Scope scope = span.makeCurrent(); + + try { + throw new Exception("oops"); + } catch (Exception e) { + span.recordException(e); + span.setAttribute(AttributeKey.stringKey("error.type"), e.getClass().getCanonicalName()); + } finally { + scope.close(); + span.end(); + } + } + + @Trace(dispatcher = true) + static Map externalCallInjectsW3CHeaders(String spanName, SpanKind spanKind) { + final TextMapPropagator propagator = W3CTraceContextPropagator.getInstance(); + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Context context = Context.current().with(span); + Scope scope = span.makeCurrent(); + + Map carrier = new HashMap<>(); + TextMapSetter> setter = (carrier1, key, value) -> carrier1.put(key, value); + propagator.inject(context, carrier, setter); + + scope.close(); + span.end(); + + return carrier; + } + + static void createTransactionWhenServerSpanCreated(String spanName) { + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(SpanKind.SERVER).startSpan(); + Scope scope = span.makeCurrent(); + scope.close(); + span.end(); + } +} From 9a1c6a419b21a007f05c73e199c4d8b8badf7c29 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Fri, 4 Apr 2025 15:56:33 -0700 Subject: [PATCH 19/36] Fix broken trace propagation with w3c headers --- .../header/utils/HeaderType.java | 14 ++ .../header/utils/W3CTraceParentHeader.java | 38 ++++++ .../header/utils/W3CTraceParentValidator.java | 126 ++++++++++++++++++ .../sdk/trace/NRSpanBuilder.java | 45 ++++++- 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java new file mode 100644 index 0000000000..4ec225db38 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java @@ -0,0 +1,14 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.header.utils; + +public class HeaderType { + public static final String NEWRELIC = "newrelic"; + public static final String W3C_TRACEPARENT = "traceparent"; + public static final String W3C_TRACESTATE = "tracestate"; +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java new file mode 100644 index 0000000000..9a69262047 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.header.utils; + +import io.opentelemetry.api.trace.SpanContext; + +public class W3CTraceParentHeader { + + static final String W3C_VERSION = "00"; + static final String W3C_TRACE_PARENT_DELIMITER = "-"; + + public static String create(SpanContext parentSpanContext) { + final String traceId = parentSpanContext.getTraceId(); + final String spanId = parentSpanContext.getSpanId(); + final boolean sampled = parentSpanContext.isSampled(); + + String traceParentHeader = + W3C_VERSION + W3C_TRACE_PARENT_DELIMITER + traceId + W3C_TRACE_PARENT_DELIMITER + spanId + W3C_TRACE_PARENT_DELIMITER + sampledToFlags(sampled); + + boolean valid = W3CTraceParentValidator.forHeader(traceParentHeader) + .version(W3C_VERSION) + .traceId(traceId) + .parentId(spanId) + .flags(parentSpanContext.getTraceFlags().asHex()) + .isValid(); + + return valid ? traceParentHeader : ""; + } + + private static String sampledToFlags(boolean sampled) { + return sampled ? "01" : "00"; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java new file mode 100644 index 0000000000..2d7af0c930 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java @@ -0,0 +1,126 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.header.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.nr.agent.instrumentation.header.utils.W3CTraceParentHeader.W3C_VERSION; +import static java.util.regex.Pattern.compile; + +public class W3CTraceParentValidator { + + private static final String INVALID_VERSION = "ff"; + private static final String INVALID_TRACE_ID = "00000000000000000000000000000000"; // 32 characters + private static final String INVALID_PARENT_ID = "0000000000000000"; // 16 characters + private static final Pattern HEXADECIMAL_PATTERN = compile("\\p{XDigit}+"); + + private final String traceParentHeader; + private final String version; + private final String traceId; + private final String parentId; + private final String flags; + + private W3CTraceParentValidator(Builder builder) { + this.traceParentHeader = builder.traceParentHeader; + this.version = builder.version; + this.traceId = builder.traceId; + this.parentId = builder.parentId; + this.flags = builder.flags; + } + + private boolean isValid() { + return isValidVersion() && isValidTraceId() && isValidParentId() && isValidFlags(); + } + + /** + * Version can only be 2 hexadecimal characters, `ff` is not allowed and if it matches our expected version the length must be 55 characters + */ + boolean isValidVersion() { + return version.length() == 2 && isHexadecimal(version.charAt(0)) && isHexadecimal(version.charAt(1)) && !version.equals(INVALID_VERSION) && + !(version.equals(W3C_VERSION) && traceParentHeaderLengthIsInvalid()); + } + + private boolean traceParentHeaderLengthIsInvalid() { + return traceParentHeader.length() != 55; + } + + boolean isHexadecimal(char character) { + return Character.digit(character, 16) != -1; + } + + boolean isHexadecimal(String input) { + final Matcher matcher = HEXADECIMAL_PATTERN.matcher(input); + return matcher.matches(); + } + + /** + * TraceId must be 32 characters, not all zeros and must be hexadecimal + */ + boolean isValidTraceId() { + return traceId.length() == 32 && !traceId.equals(INVALID_TRACE_ID) && isHexadecimal(traceId); + } + + /** + * ParentId must be 16 characters, not all zeros and must be hexadecimal + */ + boolean isValidParentId() { + return parentId.length() == 16 && !parentId.equals(INVALID_PARENT_ID) && isHexadecimal(parentId); + } + + /** + * Flags must be 2 characters and must be hexadecimal + */ + boolean isValidFlags() { + return flags.length() == 2 && isHexadecimal(flags); + } + + static Builder forHeader(String traceParentHeader) { + return new Builder(traceParentHeader); + } + + static class Builder { + private final String traceParentHeader; + private String version; + private String traceId; + private String parentId; + private String flags; + + Builder(String traceParentHeader) { + this.traceParentHeader = traceParentHeader; + } + + public Builder version(String version) { + this.version = version; + return this; + } + + public Builder traceId(String traceId) { + this.traceId = traceId; + return this; + } + + public Builder parentId(String parentId) { + this.parentId = parentId; + return this; + } + + public Builder flags(String flags) { + this.flags = flags; + return this; + } + + W3CTraceParentValidator build() { + return new W3CTraceParentValidator(this); + } + + public boolean isValid() { + return build().isValid(); + } + } +} 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 67a866bea3..20eade7edb 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 @@ -17,6 +17,7 @@ import com.newrelic.api.agent.ExtendedResponse; import com.newrelic.api.agent.HeaderType; import com.newrelic.api.agent.TracedMethod; +import com.nr.agent.instrumentation.header.utils.W3CTraceParentHeader; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -27,13 +28,19 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; +import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import static com.nr.agent.instrumentation.header.utils.HeaderType.NEWRELIC; +import static com.nr.agent.instrumentation.header.utils.HeaderType.W3C_TRACEPARENT; +import static com.nr.agent.instrumentation.header.utils.HeaderType.W3C_TRACESTATE; + /** * New Relic Java agent implementation of an OpenTelemetry SpanBuilder, * which is used to construct Span instances. Instead of starting an OpenTelemetry @@ -216,12 +223,48 @@ public HeaderType getHeaderType() { @Override public String getHeader(String name) { - if ("User-Agent".equals(name)) { + if ("User-Agent".equalsIgnoreCase(name)) { return (String) attributes.get("user_agent.original"); } + // TODO is it possible to get the newrelic DT header from OTel??? + if (NEWRELIC.equalsIgnoreCase(name)) { + return null; + } return null; } + @Override + public List getHeaders(String name) { + if (name.isEmpty()) { + return Collections.emptyList(); + } + String nameLowerCase = name.toLowerCase(); + List headers = new ArrayList<>(); + + if (W3C_TRACESTATE.equals(nameLowerCase)) { + Map traceState = parentSpanContext.getTraceState().asMap(); + StringBuilder tracestateStringBuilder = new StringBuilder(); + // Build full tracestate header incase there are multiple vendors + for (Map.Entry entry : traceState.entrySet()) { + if (tracestateStringBuilder.length() == 0) { + tracestateStringBuilder.append(entry.toString()); + } else { + tracestateStringBuilder.append(",").append(entry.toString()); + } + } + headers.add(tracestateStringBuilder.toString()); + return headers; + } + if (W3C_TRACEPARENT.equals(nameLowerCase)) { + String traceParent = W3CTraceParentHeader.create(parentSpanContext); + if (!traceParent.isEmpty()) { + headers.add(traceParent); + return headers; + } + } + return headers; + } + @Override public String getMethod() { return (String) attributes.get("http.request.method"); From a7bd25260e0af75bec985f22acd38b7278cf5ef7 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Fri, 4 Apr 2025 16:09:15 -0700 Subject: [PATCH 20/36] Cleanup --- .../main/java/io/opentelemetry/sdk/trace/NRSpanBuilder.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 20eade7edb..00aa4740aa 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 @@ -238,10 +238,9 @@ public List getHeaders(String name) { if (name.isEmpty()) { return Collections.emptyList(); } - String nameLowerCase = name.toLowerCase(); List headers = new ArrayList<>(); - if (W3C_TRACESTATE.equals(nameLowerCase)) { + if (W3C_TRACESTATE.equalsIgnoreCase(name)) { Map traceState = parentSpanContext.getTraceState().asMap(); StringBuilder tracestateStringBuilder = new StringBuilder(); // Build full tracestate header incase there are multiple vendors @@ -255,7 +254,7 @@ public List getHeaders(String name) { headers.add(tracestateStringBuilder.toString()); return headers; } - if (W3C_TRACEPARENT.equals(nameLowerCase)) { + if (W3C_TRACEPARENT.equalsIgnoreCase(name)) { String traceParent = W3CTraceParentHeader.create(parentSpanContext); if (!traceParent.isEmpty()) { headers.add(traceParent); From d7ab6b61cfd4a63f5dbcf6e57d05c3fbf5cd019d Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Mon, 28 Apr 2025 09:11:30 -0700 Subject: [PATCH 21/36] Add assertions on OTel span data --- .../newrelic/agent/introspec/SpanEvent.java | 2 + .../introspec/internal/SpanEventImpl.java | 11 +- .../agent/otelhybrid/AssertionEvaluator.java | 41 ++- .../agent/otelhybrid/HybridAgentTest.java | 291 +++++++++++++++++- 4 files changed, 328 insertions(+), 17 deletions(-) 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 fc1e95a3b1..fac4c19f82 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 @@ -10,6 +10,8 @@ import java.util.Map; public interface SpanEvent { + String getGuid(); + String getName(); float duration(); 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 37a17a47e3..922b6d5552 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 @@ -19,6 +19,16 @@ public SpanEventImpl(com.newrelic.agent.model.SpanEvent spanEvent) { this.spanEvent = spanEvent; } + /** + * This method is just to facilitate testing + * + * @return String representing Span ID + */ + @Override + public String getGuid() { + return spanEvent.getGuid(); + } + @Override public String getName() { return spanEvent.getName(); @@ -74,7 +84,6 @@ public String getStatusText() { return (String) spanEvent.getAgentAttributes().get("http.statusText"); } - @Override public Map getAgentAttributes() { return spanEvent.getAgentAttributes(); diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java index f86d647a9d..e4f47c2e91 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/AssertionEvaluator.java @@ -9,9 +9,16 @@ import com.newrelic.agent.introspec.Introspector; import com.newrelic.agent.introspec.SpanEvent; +import java.util.Collection; import java.util.Map; +import java.util.Optional; -import static org.junit.Assert.*; +import static io.opentelemetry.agent.otelhybrid.HybridAgentTest.PARENT_SPAN_ID; +import static io.opentelemetry.agent.otelhybrid.HybridAgentTest.PARENT_TRACE_ID; +import static io.opentelemetry.agent.otelhybrid.HybridAgentTest.SPAN_ID; +import static io.opentelemetry.agent.otelhybrid.HybridAgentTest.TRACE_ID; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class AssertionEvaluator { public static void assertNoTxnExists(Introspector introspector) { @@ -26,6 +33,38 @@ public static void assertSpanCount(Introspector introspector, int count) { assertEquals(count, introspector.getSpanEvents().size()); } + public static void assertSpanDetails(Introspector introspector, Map otelSpanDetails) { + Collection spanEvents = introspector.getSpanEvents(); + if (!spanEvents.isEmpty() && !otelSpanDetails.isEmpty()) { + String otelSpanId = otelSpanDetails.get(SPAN_ID); + String otelSpanTraceId = otelSpanDetails.get(TRACE_ID); + String otelParentSpanId = otelSpanDetails.get(PARENT_SPAN_ID); + String otelParentTraceId = otelSpanDetails.get(PARENT_TRACE_ID); + + if (otelSpanId != null && otelSpanTraceId != null) { + Optional spanEventOptional = spanEvents.stream().filter(span -> otelSpanId.equals(span.getGuid())).findFirst(); + if (spanEventOptional.isPresent()) { + SpanEvent nrSpan = spanEventOptional.get(); + // OpenTelemetry API and New Relic API report the same traceId + assertEquals(otelSpanTraceId, nrSpan.traceId()); + // OpenTelemetry API and New Relic API report the same spanId + assertEquals(otelSpanId, nrSpan.getGuid()); + } + } + + if (otelParentSpanId != null && otelParentTraceId != null) { + Optional spanEventOptional = spanEvents.stream().filter(span -> otelParentSpanId.equals(span.getGuid())).findFirst(); + if (spanEventOptional.isPresent()) { + SpanEvent nrParentSpan = spanEventOptional.get(); + // OpenTelemetry API and New Relic API report the same traceId + assertEquals(otelParentTraceId, nrParentSpan.traceId()); + // OpenTelemetry API and New Relic API report the same spanId + assertEquals(otelParentSpanId, nrParentSpan.getGuid()); + } + } + } + } + public static void assertTxnExists(Introspector introspector, String name) { assertEquals(name, introspector.getTransactionNames().iterator().next()); } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java index d256193068..226587a2b6 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java @@ -14,13 +14,18 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.sdk.trace.ExitTracerSpan; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +33,8 @@ import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.assertFalse; + @RunWith(InstrumentationTestRunner.class) @InstrumentationTestConfig(includePrefixes = { "io.opentelemetry" }, configName = "distributed_tracing.yml") public class HybridAgentTest { @@ -36,46 +43,83 @@ public class HybridAgentTest { } static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer("test", "1.0"); + public static final String SPAN_ID = "spanId"; + public static final String TRACE_ID = "traceId"; + public static final String PARENT_SPAN_ID = "parentSpanId"; + public static final String PARENT_TRACE_ID = "parentTraceId"; + // OpenTelemetry API and New Relic API can inject outbound trace context @Test public void doesNotCreateSegmentWithoutATransaction() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - doWorkInSpanWithoutTxn("Bar", SpanKind.INTERNAL); + Map spanDetails = doWorkInSpanWithoutTxn("Bar", SpanKind.INTERNAL); AssertionEvaluator.assertNoTxnExists(introspector); AssertionEvaluator.assertNoNewRelicSpanExists(introspector); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // Starting transaction tests @Test public void createTxnWhenServerSpanCreated() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - createTransactionWhenServerSpanCreated("Foo"); + Map spanDetails = createTransactionWhenServerSpanCreated("Foo"); + AssertionEvaluator.assertTxnExists(introspector, "WebTransaction/Uri/Unknown"); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // Starting transaction tests + @Test + public void createTxnWhenServerSpanCreatedFromRemoteParent() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Map spanDetails = createTransactionWhenServerSpanCreatedFromRemoteContext("Bar"); + + AssertionEvaluator.assertSpanCount(introspector, 1); + AssertionEvaluator.assertTxnExists(introspector, "WebTransaction/Uri/Unknown"); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); + } + + // Starting transaction tests + @Test + public void createTxnWithServerSpanCreatedFromRemoteParent() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Map spanDetails = createTransactionWithServerSpanCreatedFromRemoteContext("EdgeCase"); + + AssertionEvaluator.assertSpanCount(introspector, 1); + AssertionEvaluator.assertTxnExists(introspector, + "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/createTransactionWithServerSpanCreatedFromRemoteContext"); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); + } + + // Creates OpenTelemetry segment in a transaction @Test public void createsOtelSegmentInTxn() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - createOtelSegmentInTxn("Foo", SpanKind.INTERNAL); + Map spanDetails = createOtelSegmentInTxn("Foo", SpanKind.INTERNAL); AssertionEvaluator.assertSpanCount(introspector, 2); AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/createOtelSegmentInTxn"); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // Creates New Relic span as child of OpenTelemetry span @Test public void createsNewRelicSpanAsChildOfOtelSpan() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - newRelicSpanAsChildOfOtelSpan("foo", SpanKind.INTERNAL); + Map spanDetails = newRelicSpanAsChildOfOtelSpan("foo", SpanKind.INTERNAL); AssertionEvaluator.assertSpanCount(introspector, 3); AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/newRelicSpanAsChildOfOtelSpan"); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // OpenTelemetry API can add custom attributes to spans @Test public void otelSpansCanAddAttributes() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - addAttributesToOtelSpan("foo", SpanKind.INTERNAL); + Map spanDetails = addAttributesToOtelSpan("foo", SpanKind.INTERNAL); AssertionEvaluator.assertSpanCount(introspector, 2); AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/addAttributesToOtelSpan"); @@ -84,12 +128,14 @@ public void otelSpansCanAddAttributes() { SpanEvent[] eventArray = spanEvents.toArray(new SpanEvent[0]); AssertionEvaluator.assertUserAttributeExists("key1", "val1", eventArray[1].getUserAttributes()); AssertionEvaluator.assertUserAttributeExists("key2", "val2", eventArray[1].getUserAttributes()); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // OpenTelemetry API can record errors @Test public void exceptionsAreRecordedOnOtelSpan() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - otelSpanRecordsException("foo", SpanKind.INTERNAL); + Map spanDetails = otelSpanRecordsException("foo", SpanKind.INTERNAL); AssertionEvaluator.assertSpanCount(introspector, 2); AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/otelSpanRecordsException"); @@ -97,43 +143,149 @@ public void exceptionsAreRecordedOnOtelSpan() { Collection spanEvents = introspector.getSpanEvents(); SpanEvent[] eventArray = spanEvents.toArray(new SpanEvent[0]); AssertionEvaluator.assertExceptionExistsOnSpan(eventArray[1], "oops", "java.lang.Exception"); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // Inbound distributed tracing tests @Test public void externalCallWithW3CHeaderInjection() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); - Map carrier = externalCallInjectsW3CHeaders("foo", SpanKind.CLIENT); + Map> mapOfMaps = externalCallInjectsW3CHeaders("foo", SpanKind.CLIENT); + + Map carrier = mapOfMaps.get("carrier"); + Map spanDetails = mapOfMaps.get("spanDetails"); AssertionEvaluator.assertSpanCount(introspector, 2); AssertionEvaluator.assertTxnExists(introspector, "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/externalCallInjectsW3CHeaders"); AssertionEvaluator.assertCarrierContainsW3CTraceParent(carrier); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); + } + + // Inbound distributed tracing tests + @Test + public void distributedTracingSpanWithInboundContext() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Map> mapOfMaps = doWorkInSpanWithInboundContext("foo", SpanKind.SERVER); + + Map carrier = mapOfMaps.get("carrier"); + Map spanDetails = mapOfMaps.get("spanDetails"); + + AssertionEvaluator.assertSpanCount(introspector, 1); + AssertionEvaluator.assertTxnExists(introspector, "WebTransaction/Uri/Unknown"); + AssertionEvaluator.assertCarrierContainsW3CTraceParent(carrier); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } - static void doWorkInSpanWithoutTxn(String spanName, SpanKind spanKind) { + // OpenTelemetry API and New Relic API can inject outbound trace context + @Test + public void apisInjectOutboundTraceContext() { + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Map spanDetails = testApisInTxn(); + + AssertionEvaluator.assertTxnExists(introspector, + "OtherTransaction/Custom/io.opentelemetry.agent.otelhybrid.HybridAgentTest/testApisInTxn"); + AssertionEvaluator.assertSpanCount(introspector, 3); + AssertionEvaluator.assertSpanDetails(introspector, spanDetails); + } + + @Trace(dispatcher = true) + static Map testApisInTxn() { + Span span = OTEL_TRACER.spanBuilder("OTelSpan1").setSpanKind(SpanKind.CLIENT).startSpan(); + Map spanDetails = new HashMap<>(); + try (Scope scope = span.makeCurrent()) { + + Map fakeExternalHeaders = new HashMap<>(); + final TextMapPropagator propagator = W3CTraceContextPropagator.getInstance(); + TextMapSetter> setter = (carrier1, key, value) -> carrier1.put(key, value); + Context context = Context.current().with(span); + propagator.inject(context, fakeExternalHeaders, setter); + + // extract inbound trace context and make it the current scope + Context extractedContext = propagator.extract(Context.current(), fakeExternalHeaders, getter); + + try (Scope extractedScope = extractedContext.makeCurrent()) { + // create a new span using the extracted context/scope + Span fooSpan = OTEL_TRACER.spanBuilder("foo").setSpanKind(SpanKind.CLIENT).startSpan(); + Context fooContext = Context.current().with(span); + try (Scope fooScope = span.makeCurrent()) { + spanDetails = createOTelSpanDetailsMap(fooSpan); + } catch (Throwable t) { + fooSpan.recordException(t); + } finally { + fooSpan.end(); + } + } catch (Throwable ignored) { + } + } catch (Throwable t) { + span.recordException(t); + } finally { + span.end(); + } + + return spanDetails; + } + + static Map createOTelSpanDetailsMap(Span span) { + Map spanDetails = new HashMap<>(); + if (span instanceof ExitTracerSpan) { + SpanContext spanContext = span.getSpanContext(); + if (spanContext != null && spanContext.isValid()) { + spanDetails.put(SPAN_ID, spanContext.getSpanId()); + spanDetails.put(TRACE_ID, spanContext.getTraceId()); + } + + Span parentSpan = Span.wrap(((ExitTracerSpan) span).getParentSpanContext()); + if (parentSpan != null) { + SpanContext parentSpanContext = parentSpan.getSpanContext(); + if (parentSpanContext != null && parentSpanContext.isValid()) { + spanDetails.put(PARENT_SPAN_ID, parentSpanContext.getSpanId()); + spanDetails.put(PARENT_TRACE_ID, parentSpanContext.getTraceId()); + } + } + } + return spanDetails; + } + + static Map doWorkInSpanWithoutTxn(String spanName, SpanKind spanKind) { Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); Scope scope = span.makeCurrent(); + assertFalse(span.getSpanContext().isValid()); + + Map spanDetails = createOTelSpanDetailsMap(span); + scope.close(); span.end(); + + return spanDetails; } @Trace(dispatcher = true) - static void createOtelSegmentInTxn(String spanName, SpanKind spanKind) { + static Map createOtelSegmentInTxn(String spanName, SpanKind spanKind) { Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); Scope scope = span.makeCurrent(); + + Map spanDetails = createOTelSpanDetailsMap(span); + scope.close(); span.end(); + + return spanDetails; } @Trace(dispatcher = true) - static void newRelicSpanAsChildOfOtelSpan(String spanName, SpanKind spanKind) { + static Map newRelicSpanAsChildOfOtelSpan(String spanName, SpanKind spanKind) { Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); Scope scope = span.makeCurrent(); newRelicWorkTracer(); + Map spanDetails = createOTelSpanDetailsMap(span); + scope.close(); span.end(); + + return spanDetails; } @Trace @@ -142,22 +294,28 @@ static void newRelicWorkTracer() { } @Trace(dispatcher = true) - static void addAttributesToOtelSpan(String spanName, SpanKind spanKind) { + static Map addAttributesToOtelSpan(String spanName, SpanKind spanKind) { Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); Scope scope = span.makeCurrent(); span.setAttribute("key1", "val1"); span.setAttribute("key2", "val2"); + Map spanDetails = createOTelSpanDetailsMap(span); + scope.close(); span.end(); + + return spanDetails; } @Trace(dispatcher = true) - static void otelSpanRecordsException(String spanName, SpanKind spanKind) { + static Map otelSpanRecordsException(String spanName, SpanKind spanKind) { Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); Scope scope = span.makeCurrent(); + Map spanDetails = createOTelSpanDetailsMap(span); + try { throw new Exception("oops"); } catch (Exception e) { @@ -167,10 +325,12 @@ static void otelSpanRecordsException(String spanName, SpanKind spanKind) { scope.close(); span.end(); } + + return spanDetails; } @Trace(dispatcher = true) - static Map externalCallInjectsW3CHeaders(String spanName, SpanKind spanKind) { + static Map> externalCallInjectsW3CHeaders(String spanName, SpanKind spanKind) { final TextMapPropagator propagator = W3CTraceContextPropagator.getInstance(); Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); Context context = Context.current().with(span); @@ -180,16 +340,117 @@ static Map externalCallInjectsW3CHeaders(String spanName, SpanKi TextMapSetter> setter = (carrier1, key, value) -> carrier1.put(key, value); propagator.inject(context, carrier, setter); + Map spanDetails = createOTelSpanDetailsMap(span); + + Map> mapOfMaps = new HashMap<>(); + mapOfMaps.put("carrier", carrier); + mapOfMaps.put("spanDetails", spanDetails); + scope.close(); span.end(); - return carrier; + return mapOfMaps; } - static void createTransactionWhenServerSpanCreated(String spanName) { + static Map createTransactionWhenServerSpanCreated(String spanName) { Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(SpanKind.SERVER).startSpan(); Scope scope = span.makeCurrent(); + + Map spanDetails = createOTelSpanDetailsMap(span); + scope.close(); span.end(); + + return spanDetails; + } + + static Map createTransactionWhenServerSpanCreatedFromRemoteContext(String spanName) { + SpanContext remoteContext = SpanContext.createFromRemoteParent("da8bc8cc6d062849b0efcf3c169afb5a", "7d3efb1b173fecfa", TraceFlags.getSampled(), + TraceState.getDefault()); + + Span spanFromRemoteContext = OTEL_TRACER.spanBuilder(spanName).setSpanKind(SpanKind.SERVER) + .setParent(Context.current().with(Span.wrap(remoteContext))) + .startSpan(); + + Scope scope = spanFromRemoteContext.makeCurrent(); + + Map spanDetails = createOTelSpanDetailsMap(spanFromRemoteContext); + + scope.close(); + spanFromRemoteContext.end(); + + return spanDetails; + } + + @Trace(dispatcher = true) + static Map createTransactionWithServerSpanCreatedFromRemoteContext(String spanName) { + SpanContext remoteContext = SpanContext.createFromRemoteParent("da8bc8cc6d062849b0efcf3c169afb5a", "7d3efb1b173fecfa", TraceFlags.getSampled(), + TraceState.getDefault()); + + Span spanFromRemoteContext = OTEL_TRACER.spanBuilder(spanName).setSpanKind(SpanKind.SERVER) + .setParent(Context.current().with(Span.wrap(remoteContext))) + .startSpan(); + + Scope scope = spanFromRemoteContext.makeCurrent(); + + Map spanDetails = createOTelSpanDetailsMap(spanFromRemoteContext); + + scope.close(); + spanFromRemoteContext.end(); + + return spanDetails; } + + static Map> doWorkInSpanWithInboundContext(String spanName, SpanKind spanKind) { + final TextMapPropagator propagator = W3CTraceContextPropagator.getInstance(); + + // mock out span with incoming w3c header propagation + TraceFlags traceFlags = TraceFlags.getDefault(); + TraceState traceState = TraceState.getDefault(); + SpanContext incomingSpanContext = SpanContext.create("da8bc8cc6d062849b0efcf3c169afb5a", "7d3efb1b173fecfa", traceFlags, traceState); + Span incomingSpan = Span.wrap(incomingSpanContext); + Context incomingContext = Context.current().with(incomingSpan); + Scope incomingScope = incomingSpan.makeCurrent(); + + Map carrier = new HashMap<>(); + TextMapSetter> setter = (carrier1, key, value) -> carrier1.put(key, value); + propagator.inject(incomingContext, carrier, setter); + + incomingScope.close(); + incomingSpan.end(); + + // extract inbound trace context and make it the current scope + Context extractedContext = propagator.extract(Context.current(), carrier, getter); + Scope extractedScope = extractedContext.makeCurrent(); + + // create a new span using the extracted context/scope + Span span = OTEL_TRACER.spanBuilder(spanName).setSpanKind(spanKind).startSpan(); + Context context = Context.current().with(span); + Scope scope = span.makeCurrent(); + + Map spanDetails = createOTelSpanDetailsMap(span); + + Map> mapOfMaps = new HashMap<>(); + mapOfMaps.put("carrier", carrier); + mapOfMaps.put("spanDetails", spanDetails); + + extractedScope.close(); + scope.close(); + span.end(); + + return mapOfMaps; + } + + private static final TextMapGetter> getter = new TextMapGetter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; + } From f685382d7d578dab2f1dd73cbd60755d99472631 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Fri, 1 Aug 2025 20:54:45 -0700 Subject: [PATCH 22/36] initial test of excluding meters by name --- .../AutoConfiguredOpenTelemetrySdk.java | 1 + .../MeterProviderCustomizer.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java 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..fa44688d42 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 @@ -17,6 +17,7 @@ public static AutoConfiguredOpenTelemetrySdkBuilder builder() { NewRelic.getAgent().getLogger().log(Level.INFO, "Appending OpenTelemetry SDK customizers"); builder.addPropertiesCustomizer(new PropertiesCustomizer()); builder.addResourceCustomizer(new ResourceCustomer()); + builder.addMeterProviderCustomizer(new MeterProviderCustomizer()); } return builder; } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java new file mode 100644 index 0000000000..b6e4ecedd7 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java @@ -0,0 +1,29 @@ +package io.opentelemetry.sdk.autoconfigure; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.View; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiFunction; + +public class MeterProviderCustomizer implements BiFunction { + + @Override + public SdkMeterProviderBuilder apply(SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProperties) { + for (String meterName : getExcludedMeters()){ + sdkMeterProviderBuilder.registerView( + InstrumentSelector.builder().setMeterName(meterName).build(), + View.builder().setAggregation(Aggregation.drop()).build() + ); + } + return sdkMeterProviderBuilder; + } + + private List getExcludedMeters(){ + return Arrays.asList("otel.demo"); + } +} From 9190f61b2761d8b1cbe178eda135eb55a8499477 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Wed, 6 Aug 2025 17:02:21 -0700 Subject: [PATCH 23/36] Initial prototype of OTel Logs API support --- .../utils}/AttributesHelper.java | 4 +- .../utils => utils/header}/HeaderType.java | 2 +- .../header}/W3CTraceParentHeader.java | 2 +- .../header}/W3CTraceParentValidator.java | 4 +- .../utils/logs/ExceptionUtil.java | 37 +++ .../utils/logs/LogEventUtil.java | 174 ++++++++++ .../opentelemetry/sdk/logs/NRLogRecord.java | 300 ++++++++++++++++++ .../sdk/logs/NRLogRecordBuilder.java | 217 +++++++++++++ .../sdk/logs/NRLoggerBuilder.java | 68 ++++ .../SdkLoggerProvider_Instrumentation.java | 44 +++ .../sdk/trace/ExitTracerSpan.java | 1 + .../sdk/trace/NRSpanBuilder.java | 8 +- .../sdk/trace/NRTracerBuilder.java | 5 +- .../io/opentelemetry/context/SpanTest.java | 2 +- .../sdk/trace/ExitTracerSpanTest.java | 1 + 15 files changed, 857 insertions(+), 12 deletions(-) rename instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/{io/opentelemetry/sdk/trace => com/nr/agent/instrumentation/utils}/AttributesHelper.java (88%) rename instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/{header/utils => utils/header}/HeaderType.java (86%) rename instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/{header/utils => utils/header}/W3CTraceParentHeader.java (95%) rename instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/{header/utils => utils/header}/W3CTraceParentValidator.java (97%) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/ExceptionUtil.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java 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/com/nr/agent/instrumentation/utils/AttributesHelper.java similarity index 88% rename from instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/trace/AttributesHelper.java rename to instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/AttributesHelper.java index a41b49ef62..c82bdc208c 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/com/nr/agent/instrumentation/utils/AttributesHelper.java @@ -5,7 +5,7 @@ * */ -package io.opentelemetry.sdk.trace; +package com.nr.agent.instrumentation.utils; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -13,7 +13,7 @@ import java.util.Map; /** - * Helper class for adding attributes to Spans + * Helper class for turning a map of attributes into an OTel Attributes object. */ public class AttributesHelper { private AttributesHelper() { diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/HeaderType.java similarity index 86% rename from instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java rename to instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/HeaderType.java index 4ec225db38..432cda42bd 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/HeaderType.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/HeaderType.java @@ -5,7 +5,7 @@ * */ -package com.nr.agent.instrumentation.header.utils; +package com.nr.agent.instrumentation.utils.header; public class HeaderType { public static final String NEWRELIC = "newrelic"; diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/W3CTraceParentHeader.java similarity index 95% rename from instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java rename to instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/W3CTraceParentHeader.java index 9a69262047..bc1b1962fd 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentHeader.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/W3CTraceParentHeader.java @@ -5,7 +5,7 @@ * */ -package com.nr.agent.instrumentation.header.utils; +package com.nr.agent.instrumentation.utils.header; import io.opentelemetry.api.trace.SpanContext; diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/W3CTraceParentValidator.java similarity index 97% rename from instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java rename to instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/W3CTraceParentValidator.java index 2d7af0c930..0d627c8e9e 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/header/utils/W3CTraceParentValidator.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/header/W3CTraceParentValidator.java @@ -5,12 +5,12 @@ * */ -package com.nr.agent.instrumentation.header.utils; +package com.nr.agent.instrumentation.utils.header; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.nr.agent.instrumentation.header.utils.W3CTraceParentHeader.W3C_VERSION; +import static com.nr.agent.instrumentation.utils.header.W3CTraceParentHeader.W3C_VERSION; import static java.util.regex.Pattern.compile; public class W3CTraceParentValidator { diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/ExceptionUtil.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/ExceptionUtil.java new file mode 100644 index 0000000000..0f9ca3ddab --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/ExceptionUtil.java @@ -0,0 +1,37 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.utils.logs; + +public class ExceptionUtil { + public static final int MAX_STACK_SIZE = 300; + + public static String getErrorStack(String errorStack) { + if (validateString(errorStack) == null) { + return null; + } + if (errorStack.length() <= MAX_STACK_SIZE) { + return errorStack; + } + return errorStack.substring(0, MAX_STACK_SIZE); + } + + public static String getErrorMessage(String errorMessage) { + return validateString(errorMessage); + } + + public static String getErrorClass(String errorClass) { + return validateString(errorClass); + } + + public static String validateString(String s) { + if (s == null || s.isEmpty()) { + return null; + } + return s; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java new file mode 100644 index 0000000000..02ab975ece --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java @@ -0,0 +1,174 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.utils.logs; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.logging.AppLoggingUtils; +import com.newrelic.agent.bridge.logging.LogAttributeKey; +import com.newrelic.agent.bridge.logging.LogAttributeType; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.sdk.logs.NRLogRecord; +import io.opentelemetry.sdk.logs.data.Body; +import io.opentelemetry.sdk.logs.data.LogRecordData; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_CLASS; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_MESSAGE; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_STACK; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.INSTRUMENTATION; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LEVEL; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LOGGER_FQCN; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LOGGER_NAME; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.MESSAGE; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.THREAD_ID; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.THREAD_NAME; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.TIMESTAMP; +import static com.newrelic.agent.bridge.logging.AppLoggingUtils.UNKNOWN; +import static io.opentelemetry.sdk.logs.NRLogRecord.BasicLogRecordData; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_EXCEPTION_MESSAGE; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_EXCEPTION_STACKTRACE; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_EXCEPTION_TYPE; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_LIBRARY_NAME; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_LIBRARY_VERSION; + +public class LogEventUtil { + private static final Set OTEL_ATTRIBUTES = new HashSet<>(Arrays.asList( + OTEL_LIBRARY_NAME.getKey(), + OTEL_LIBRARY_VERSION.getKey(), + OTEL_EXCEPTION_MESSAGE.getKey(), + OTEL_EXCEPTION_TYPE.getKey(), + OTEL_EXCEPTION_STACKTRACE.getKey()) + ); + + /** + * Record a LogEvent to be sent to New Relic. + * + * @param logRecordData to parse + */ + public static void recordNewRelicLogEvent(LogRecordData logRecordData) { + if (logRecordData != null) { + Body body = logRecordData.getBody(); + Attributes contextAttributes = logRecordData.getAttributes(); + String errorClass = contextAttributes.get(OTEL_EXCEPTION_TYPE); + String errorMessage = contextAttributes.get(OTEL_EXCEPTION_MESSAGE); + + if (shouldCreateLogEvent(body, errorClass, errorMessage)) { + Map logEventMap = new HashMap<>(calculateInitialMapSize(contextAttributes)); + logEventMap.put(INSTRUMENTATION, "opentelemetry-sdk-extension-autoconfigure-1.28.0"); + if (body != null && body.getType() == Body.Type.STRING) { + String bodyString = body.asString(); + if (bodyString != null && !bodyString.isEmpty()) { + logEventMap.put(MESSAGE, bodyString); + } + } + logEventMap.put(TIMESTAMP, logRecordData.getTimestampEpochNanos()); + + if (AppLoggingUtils.isAppLoggingContextDataEnabled()) { + for (Map.Entry, Object> entry : contextAttributes.asMap().entrySet()) { + String key = entry.getKey().getKey(); + // Don't add the context prefix to OTel attributes that are already defined by the OTel spec. + if (!OTEL_ATTRIBUTES.contains(key)) { + String value = entry.getValue().toString(); + LogAttributeKey logAttrKey = new LogAttributeKey(key, LogAttributeType.CONTEXT); + logEventMap.put(logAttrKey, value); + } + } + } + + // These attributes come from the attribute map, but they should not be prefixed with context since they are defined in the OTel semantic conventions. + if (!contextAttributes.isEmpty()) { + String otelLibraryName = contextAttributes.get(OTEL_LIBRARY_NAME); + if (otelLibraryName != null) { + LogAttributeKey logAttrKey = new LogAttributeKey(OTEL_LIBRARY_NAME.getKey(), LogAttributeType.AGENT); + logEventMap.put(logAttrKey, otelLibraryName); + } + + String otelLibraryVersion = contextAttributes.get(OTEL_LIBRARY_VERSION); + if (otelLibraryVersion != null) { + LogAttributeKey logAttrKey = new LogAttributeKey(OTEL_LIBRARY_VERSION.getKey(), LogAttributeType.AGENT); + logEventMap.put(logAttrKey, otelLibraryVersion); + } + + // Exceptions are captured in the attributes map for OTel logs. + // https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-logs/ + if (ExceptionUtil.getErrorMessage(errorMessage) != null) { + logEventMap.put(ERROR_MESSAGE, errorMessage); + } + + if (ExceptionUtil.getErrorClass(errorClass) != null) { + logEventMap.put(ERROR_CLASS, errorClass); + } + + String errorStack = ExceptionUtil.getErrorStack(contextAttributes.get(OTEL_EXCEPTION_STACKTRACE)); + if (errorStack != null) { + logEventMap.put(ERROR_STACK, errorStack); + } + } + + Severity severity = logRecordData.getSeverity(); + if (severity != null) { + String severityName = severity.name(); + if (severityName.isEmpty()) { + logEventMap.put(LEVEL, UNKNOWN); + } else { + logEventMap.put(LEVEL, severityName); + } + } + + String threadName = ((NRLogRecord.BasicLogRecordData) logRecordData).getThreadName(); + if (threadName != null) { + logEventMap.put(THREAD_NAME, threadName); + } + + long threadId = ((BasicLogRecordData) logRecordData).getThreadId(); + logEventMap.put(THREAD_ID, threadId); + + String loggerName = logRecordData.getInstrumentationScopeInfo() + .getName(); // FIXME doesn't seem to map to otel semantic conventions, should we add this? + if (loggerName != null) { + logEventMap.put(LOGGER_NAME, loggerName); + } + + String loggerFqcn = logRecordData.getInstrumentationScopeInfo() + .getName(); // FIXME doesn't seem to map to otel semantic conventions, should we add this? + if (loggerFqcn != null) { + logEventMap.put(LOGGER_FQCN, loggerFqcn); + } + + AgentBridge.getAgent().getLogSender().recordLogEvent(logEventMap); + } + } + } + + /** + * A LogEvent should be created if a log message or an error is logged. + * + * @param body Message to validate + * @param errorClass String to validate from OTel exception.type + * @param errorMessage String to validate from OTel exception.message + * @return true if a LogEvent should be created, otherwise false + */ + private static boolean shouldCreateLogEvent(Body body, String errorClass, String errorMessage) { + return (body != null) || (ExceptionUtil.getErrorClass(errorClass) != null) || (ExceptionUtil.getErrorMessage(errorMessage) != null); + } + + private static int calculateInitialMapSize(Attributes attributes) { + return AppLoggingUtils.isAppLoggingContextDataEnabled() && attributes != null + ? attributes.size() + DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES + : DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES; + } + +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java new file mode 100644 index 0000000000..05234f2a55 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java @@ -0,0 +1,300 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import com.nr.agent.instrumentation.utils.AttributesHelper; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.Body; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; + +import java.util.Map; + +/** + * New Relic Java representation of an OpenTelemetry LogRecord. + */ +public class NRLogRecord implements ReadWriteLogRecord { + public static final AttributeKey OTEL_LIBRARY_VERSION = AttributeKey.stringKey("otel.library.version"); + public static final AttributeKey OTEL_LIBRARY_NAME = AttributeKey.stringKey("otel.library.name"); + public static final AttributeKey OTEL_EXCEPTION_MESSAGE = AttributeKey.stringKey("exception.message"); + public static final AttributeKey OTEL_EXCEPTION_TYPE = AttributeKey.stringKey("exception.type"); + public static final AttributeKey OTEL_EXCEPTION_STACKTRACE = AttributeKey.stringKey("exception.stacktrace"); + + private final LogLimits logLimits; + private final Resource resource; + private final InstrumentationScopeInfo instrumentationScopeInfo; + private final long timestampEpochNanos; + private final long observedTimestampEpochNanos; + private final SpanContext spanContext; + private final Severity severity; + private final String severityText; + private final Body body; + private final Object lock = new Object(); + // TODO use Map or AttributesMap? seems to break the weaver, causes instrumentation to not apply + private final Map attributes; +// private AttributesMap attributes; + + private NRLogRecord( + LogLimits logLimits, // TODO: can we use this without AttributesMap? + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + long timestampEpochNanos, + long observedTimestampEpochNanos, + SpanContext spanContext, + Severity severity, + String severityText, + Body body, +// AttributesMap attributes + Map attributes + ) { + this.logLimits = logLimits; + this.resource = resource; + this.instrumentationScopeInfo = instrumentationScopeInfo; + this.timestampEpochNanos = timestampEpochNanos; + this.observedTimestampEpochNanos = observedTimestampEpochNanos; + this.spanContext = spanContext; + this.severity = severity; + this.severityText = severityText; + this.body = body; + this.attributes = attributes; + } + + /** + * Create the OTel LogRecord. + */ + static NRLogRecord create( + LogLimits logLimits, + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + long timestampEpochNanos, + long observedTimestampEpochNanos, + SpanContext spanContext, + Severity severity, + String severityText, + Body body, +// AttributesMap attributes + Map attributes) { + return new NRLogRecord( + logLimits, + resource, + instrumentationScopeInfo, + timestampEpochNanos, + observedTimestampEpochNanos, + spanContext, + severity, + severityText, + body, + attributes); + } + + @Override + public ReadWriteLogRecord setAttribute(AttributeKey key, T value) { + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } + synchronized (lock) { +// if (attributes == null) { +// attributes = +// AttributesMap.create( +// logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); +// } + attributes.put(key.getKey(), value); + } + return this; + } + + /** + * @return an immutable LogRecordData instance representing this log record. + */ + @Override + public LogRecordData toLogRecordData() { + synchronized (lock) { + return BasicLogRecordData.create( + resource, + instrumentationScopeInfo, + timestampEpochNanos, + observedTimestampEpochNanos, + spanContext, + severity, + severityText, + body, + AttributesHelper.toAttributes(attributes), +// getImmutableAttributes(), // FIXME + attributes.size() // FIXME any way to get attributes.getTotalAddedValues() without AttributesMap? +// attributes == null ? 0 : attributes.getTotalAddedValues() + ); + } + } + +// private Attributes getImmutableAttributes() { +// synchronized (lock) { +// if (attributes == null || attributes.isEmpty()) { +// return Attributes.empty(); +// } +// return attributes.immutableCopy(); +// } +// } + + public static class BasicLogRecordData implements LogRecordData { + private final Resource resource; + private final InstrumentationScopeInfo instrumentationScopeInfo; + private final long timestampEpochNanos; + private final long observedTimestampEpochNanos; + private final SpanContext spanContext; + private final Severity severity; + private final String severityText; + private final Body body; + private final Attributes attributes; + private final int totalAttributeCount; + + private final long threadId; + private final String threadName; + + private BasicLogRecordData( + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + long timestampEpochNanos, + long observedTimestampEpochNanos, + SpanContext spanContext, + Severity severity, + String severityText, + Body body, + Attributes attributes, + int totalAttributeCount) { + this.resource = resource; + this.instrumentationScopeInfo = instrumentationScopeInfo; + this.timestampEpochNanos = timestampEpochNanos; + this.observedTimestampEpochNanos = observedTimestampEpochNanos; + this.spanContext = spanContext; + this.severity = severity; + this.severityText = severityText; + this.body = body; + this.attributes = attributes; + this.totalAttributeCount = totalAttributeCount; + // Capture thread information at the time of log record creation, OTel does not provide this + this.threadId = Thread.currentThread().getId(); + this.threadName = Thread.currentThread().getName(); + } + + static BasicLogRecordData create( + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo, + long timestampEpochNanos, + long observedTimestampEpochNanos, + SpanContext spanContext, + Severity severity, + String severityText, + Body body, + Attributes attributes, + int totalAttributeCount) { + return new BasicLogRecordData( + resource, + instrumentationScopeInfo, + timestampEpochNanos, + observedTimestampEpochNanos, + spanContext, + severity, + severityText, + body, + attributes, + totalAttributeCount + ); + } + + public long getThreadId() { + return threadId; + } + + public String getThreadName() { + return threadName; + } + + @Override + public Resource getResource() { + return resource; + } + + @Override + public InstrumentationScopeInfo getInstrumentationScopeInfo() { + return instrumentationScopeInfo; + } + + @Override + public long getTimestampEpochNanos() { + return timestampEpochNanos; + } + + @Override + public long getObservedTimestampEpochNanos() { + return observedTimestampEpochNanos; + } + + @Override + public SpanContext getSpanContext() { + return spanContext; + } + + @Override + public Severity getSeverity() { + return severity; + } + + @Override + public String getSeverityText() { + return severityText; + } + + @Override + public Body getBody() { + return body; + } + + @Override + public Attributes getAttributes() { + return attributes; + } + + @Override + public int getTotalAttributeCount() { + return totalAttributeCount; + } + + @Override + public String toString() { + return "BasicLogRecordData{resource=" + + this.resource + + ", instrumentationScopeInfo=" + + this.instrumentationScopeInfo + + ", timestampEpochNanos=" + + this.timestampEpochNanos + + ", observedTimestampEpochNanos=" + + this.observedTimestampEpochNanos + + ", spanContext=" + + this.spanContext + + ", severity=" + + this.severity + + ", severityText=" + + this.severityText + + ", body=" + + this.body + + ", attributes=" + + this.attributes + + ", totalAttributeCount=" + + this.totalAttributeCount + + ", threadId=" + + this.threadId + + ", threadName=" + + this.threadName + + "}"; + } + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java new file mode 100644 index 0000000000..ff1bfd228c --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java @@ -0,0 +1,217 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import com.newrelic.agent.bridge.logging.AppLoggingUtils; +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; +import com.nr.agent.instrumentation.utils.logs.LogEventUtil; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.logs.LogRecordBuilder; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.Body; +import io.opentelemetry.sdk.logs.data.LogRecordData; + +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * New Relic Java agent implementation of an OpenTelemetry LogRecordBuilder, + * which is used to build and emit OTel LogRecord instances. In addition to + * emitting an OpenTelemetry LogRecord, this implementation will create a + * New Relic LogEvent. + */ +public class NRLogRecordBuilder implements LogRecordBuilder { + private final Map attributes = new HashMap<>(); + // private final AttributesMap attributes; + private final LoggerSharedState loggerSharedState; + private final InstrumentationScopeInfo instrumentationScopeInfo; +// private LogLimits logLimits; // TODO: is this needed? Looks like it could be used in setAttribute to configure an AttributesMap + + private long timestampEpochNanos; + private long observedTimestampEpochNanos; + private Context context; + private Severity severity = Severity.UNDEFINED_SEVERITY_NUMBER; + private String severityText; + private Body body = Body.empty(); + + public NRLogRecordBuilder(String instrumentationScopeName, String instrumentationScopeVersion, String schemaUrl, LoggerSharedState loggerSharedState) { + this.loggerSharedState = loggerSharedState; + +// this.logLimits = loggerSharedState.getLogLimits(); +// if (this.logLimits == null) { +// this.logLimits = LogLimits.getDefault(); +// } + + this.instrumentationScopeInfo = InstrumentationScopeInfo + .builder(instrumentationScopeName) + .setVersion(instrumentationScopeVersion) + .setSchemaUrl(schemaUrl) + .build(); + +// this.attributes = initalizeAttributesMap(this.logLimits); +// +// this.attributes.put(NRLogRecord.OTEL_LIBRARY_NAME, instrumentationScopeName); +// +// if (instrumentationScopeVersion != null) { +// this.attributes.put(NRLogRecord.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); +// } + + attributes.put(NRLogRecord.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); + + if (instrumentationScopeVersion != null) { + attributes.put(NRLogRecord.OTEL_LIBRARY_VERSION.getKey(), instrumentationScopeVersion); + } + } + + // Create an AttributesMap with the limits defined in logLimits +// public AttributesMap initalizeAttributesMap(LogLimits logLimits) { +// return AttributesMap.create( +// logLimits.getMaxNumberOfAttributes(), +// logLimits.getMaxAttributeValueLength() +// ); +// } + + @Override + public LogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { + this.timestampEpochNanos = unit.toNanos(timestamp); + return this; + } + + @Override + public LogRecordBuilder setTimestamp(Instant instant) { + this.timestampEpochNanos = TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano(); + return this; + } + + @Override + public LogRecordBuilder setObservedTimestamp(long timestamp, TimeUnit unit) { + this.observedTimestampEpochNanos = unit.toNanos(timestamp); + return this; + } + + @Override + public LogRecordBuilder setObservedTimestamp(Instant instant) { + this.observedTimestampEpochNanos = TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano(); + return this; + } + + @Override + public LogRecordBuilder setContext(Context context) { + this.context = context; + return this; + } + + @Override + public LogRecordBuilder setSeverity(Severity severity) { + this.severity = severity; + return this; + } + + @Override + public LogRecordBuilder setSeverityText(String severityText) { + this.severityText = severityText; + return this; + } + + @Override + public LogRecordBuilder setBody(String body) { + this.body = Body.string(body); + return this; + } + + @Override + public LogRecordBuilder setAttribute(AttributeKey key, T value) { + // TODO implement this properly? + if (key == null || key.getKey().isEmpty() || value == null) { + return this; + } +// if (this.attributes == null) { +// this.attributes = +// AttributesMap.create( +// logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); +// } +// this.attributes.put(key, value); + + this.attributes.put(key.getKey(), value); + return this; + } + + /** + * Intercept here to create a NR LogEvent from the OpenTelemetry + * LogRecord that is being emitted. The OpenTelemetry LogRecord + * will still be emitted to its configured destination. + */ + @Override + public void emit() { + if (loggerSharedState.hasBeenShutdown()) { + return; + } + Context context = this.context == null ? Context.current() : this.context; + + long observedTimestampEpochNanos = + this.observedTimestampEpochNanos == 0 + ? this.loggerSharedState.getClock().now() + : this.observedTimestampEpochNanos; + + NRLogRecord nrLogRecord = NRLogRecord.create( + loggerSharedState.getLogLimits(), + loggerSharedState.getResource(), + instrumentationScopeInfo, + timestampEpochNanos, + observedTimestampEpochNanos, + Span.fromContext(context).getSpanContext(), + severity, + severityText, + body, +// attributes + Collections.unmodifiableMap(new HashMap<>(attributes)) + ); + + // Pass NRLogRecord through to user configured logRecordProcessor + // so that the LogRecords get written to the expected destination. + loggerSharedState.getLogRecordProcessor().onEmit(context, nrLogRecord); + + LogRecordData logRecordData = nrLogRecord.toLogRecordData(); + + if (AppLoggingUtils.isApplicationLoggingEnabled()) { + if (AppLoggingUtils.isApplicationLoggingMetricsEnabled()) { + // Generate log level metrics + NewRelic.incrementCounter("Logging/lines"); + NewRelic.incrementCounter("Logging/lines/" + severity.name()); + } + + if (AppLoggingUtils.isApplicationLoggingForwardingEnabled()) { + // Generate New Relic LogEvents + LogEventUtil.recordNewRelicLogEvent(logRecordData); + + System.out.println(); // TODO remove logging + System.out.println(logRecordData); // TODO remove logging + System.out.println(); // TODO remove logging + } + } + } + + static boolean isLogRecordBuilderEnabled(Config config) { + final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled"); + if (autoConfigure == null || autoConfigure) { + // When opentelemetry.sdk.logs.enabled=false, this + // setting will prevent NR LogEvents from being created, + // without preventing OTel LogRecords from being emitted. + final Boolean logsEnabled = config.getValue("opentelemetry.sdk.logs.enabled"); + return logsEnabled == null || logsEnabled; + } + return false; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java new file mode 100644 index 0000000000..e20c8e67b8 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java @@ -0,0 +1,68 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import com.newrelic.api.agent.Config; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerBuilder; + +/** + * New Relic Java agent implementation of an OpenTelemetry + * LoggerBuilder, which is a factory for building OpenTelemetry Loggers. + * An OpenTelemetry Logger can then be used to build and emit OpenTelemetry LogRecords. + */ +class NRLoggerBuilder implements LoggerBuilder { + private final String instrumentationScopeName; + private final LoggerSharedState sharedState; + private final Config config; + private String schemaUrl; + private String instrumentationScopeVersion; + + public NRLoggerBuilder(Config config, String instrumentationScopeName, LoggerSharedState sharedState) { + this.config = config; + this.instrumentationScopeName = instrumentationScopeName; + this.sharedState = sharedState; + } + + @Override + public LoggerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public LoggerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + this.instrumentationScopeVersion = instrumentationScopeVersion; + return this; + } + + @Override + public Logger build() { + Boolean enabled = config.getValue("opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); + if (enabled != null && !enabled) { + /* + * This will return a no-op Logger, which results in + * no OTel LogRecords being emitted, meaning that the + * customer's logs from OTel APIs will not be written anywhere. + * + * If the goal is to prevent NR LogEvents from being created, + * but continue to emit OTel LogRecords, then that should be + * done by disabling the logs functionality in agent config: + * + * opentelemetry: + * sdk: + * logs: + * enabled: false + */ + return OpenTelemetry.noop().getLogsBridge().get(instrumentationScopeName); + } else { + return () -> new NRLogRecordBuilder(instrumentationScopeName, instrumentationScopeVersion, schemaUrl, sharedState); + } + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java new file mode 100644 index 0000000000..bdd1635e49 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +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.logs.LoggerBuilder; + +/** + * Weaved to inject a New Relic Java agent implementation of an OpenTelemetry LoggerBuilder + */ +@Weave(type = MatchType.ExactClass, originalName = "io.opentelemetry.sdk.logs.SdkLoggerProvider") +public final class SdkLoggerProvider_Instrumentation { + private final LoggerSharedState sharedState = Weaver.callOriginal(); + + public LoggerBuilder loggerBuilder(String instrumentationScopeName) { + final LoggerBuilder loggerBuilder = Weaver.callOriginal(); + Config config = NewRelic.getAgent().getConfig(); + if (NRLogRecordBuilder.isLogRecordBuilderEnabled(config)) { + // return our logger builder instead of the OTel instance + return new NRLoggerBuilder(config, instrumentationNameOrDefault(instrumentationScopeName), sharedState); + } + return loggerBuilder; + } + + /** + * Returns the instrumentation name or the default value of "unknown" if not set. + * + * @param instrumentationScopeName the name of the instrumentation scope + * @return the instrumentation name or a default value + */ + private static String instrumentationNameOrDefault(String instrumentationScopeName) { + return Weaver.callOriginal(); + } + +} 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 67b0e479a1..49b80ab746 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 @@ -15,6 +15,7 @@ import com.newrelic.api.agent.HttpParameters; import com.newrelic.api.agent.NewRelic; import com.newrelic.api.agent.Token; +import com.nr.agent.instrumentation.utils.AttributesHelper; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributeType; import io.opentelemetry.api.common.Attributes; 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 00aa4740aa..e77af8d25c 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 @@ -17,7 +17,7 @@ import com.newrelic.api.agent.ExtendedResponse; import com.newrelic.api.agent.HeaderType; import com.newrelic.api.agent.TracedMethod; -import com.nr.agent.instrumentation.header.utils.W3CTraceParentHeader; +import com.nr.agent.instrumentation.utils.header.W3CTraceParentHeader; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -37,9 +37,9 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static com.nr.agent.instrumentation.header.utils.HeaderType.NEWRELIC; -import static com.nr.agent.instrumentation.header.utils.HeaderType.W3C_TRACEPARENT; -import static com.nr.agent.instrumentation.header.utils.HeaderType.W3C_TRACESTATE; +import static com.nr.agent.instrumentation.utils.header.HeaderType.NEWRELIC; +import static com.nr.agent.instrumentation.utils.header.HeaderType.W3C_TRACEPARENT; +import static com.nr.agent.instrumentation.utils.header.HeaderType.W3C_TRACESTATE; /** * New Relic Java agent implementation of an OpenTelemetry SpanBuilder, 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 791e1b20cf..f53a4a83fe 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 @@ -9,7 +9,6 @@ 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; @@ -44,6 +43,10 @@ public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersio return this; } + /** + * Builds a new OpenTelemetry Tracer. + * If the instrumentation is disabled in the configuration, returns a noop tracer. + */ @Override public Tracer build() { Boolean enabled = config.getValue("opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); 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 8150b46485..465b49c890 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 @@ -21,7 +21,7 @@ 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 com.nr.agent.instrumentation.utils.AttributesHelper; import io.opentelemetry.sdk.trace.ExitTracerSpan; import org.junit.Test; import org.junit.runner.RunWith; 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 9da3c5dd52..534dba4b80 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 @@ -12,6 +12,7 @@ import com.newrelic.api.agent.DatastoreParameters; import com.newrelic.api.agent.ExternalParameters; import com.newrelic.api.agent.HttpParameters; +import com.nr.agent.instrumentation.utils.AttributesHelper; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; From 66c895c53a86d744b25647dbec29ec6b3d1e6cd8 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Tue, 12 Aug 2025 14:12:31 -0700 Subject: [PATCH 24/36] Add basic configuration for otel meter excludes --- .../build.gradle | 1 + .../sdk/autoconfigure/MeterProviderCustomizer.java | 9 ++++----- .../java/com/newrelic/agent/config/AgentConfig.java | 2 ++ .../com/newrelic/agent/config/AgentConfigImpl.java | 13 +++++++++++++ 4 files changed, 20 insertions(+), 5 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 b0964a733c..08e9169733 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle @@ -6,6 +6,7 @@ dependencies { implementation(project(":agent-bridge")) implementation(project(":newrelic-api")) implementation(project(":newrelic-weaver-api")) + implementation(project(":newrelic-agent")) implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.28.0") testImplementation("junit:junit:4.12") } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java index b6e4ecedd7..8ceb3a5b76 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java @@ -6,15 +6,17 @@ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.View; -import java.util.Arrays; import java.util.List; import java.util.function.BiFunction; +import com.newrelic.agent.service.ServiceFactory; public class MeterProviderCustomizer implements BiFunction { + private final List excludedMeters = ServiceFactory.getConfigService().getDefaultAgentConfig().getOtelConfig().getExcludedMeters(); + @Override public SdkMeterProviderBuilder apply(SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProperties) { - for (String meterName : getExcludedMeters()){ + for (String meterName : excludedMeters) { sdkMeterProviderBuilder.registerView( InstrumentSelector.builder().setMeterName(meterName).build(), View.builder().setAggregation(Aggregation.drop()).build() @@ -23,7 +25,4 @@ public SdkMeterProviderBuilder apply(SdkMeterProviderBuilder sdkMeterProviderBui return sdkMeterProviderBuilder; } - private List getExcludedMeters(){ - return Arrays.asList("otel.demo"); - } } diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java index d43267ce20..7560877b10 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfig.java @@ -155,6 +155,8 @@ public interface AgentConfig extends com.newrelic.api.agent.Config, DataSenderCo */ JarCollectorConfig getJarCollectorConfig(); + OtelConfig getOtelConfig(); + /** * Gets the Reinstrumentation configuration settings. * diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigImpl.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigImpl.java index f3d8606fca..3458f209d8 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigImpl.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/AgentConfigImpl.java @@ -120,6 +120,7 @@ public class AgentConfigImpl extends BaseConfig implements AgentConfig { public static final String JAR_COLLECTOR = "jar_collector"; public static final String JMX = "jmx"; public static final String JFR = "jfr"; + public static final String OTEL = "opentelemetry"; public static final String REINSTRUMENT = "reinstrument"; public static final String SLOW_SQL = "slow_sql"; public static final String SPAN_EVENTS = "span_events"; @@ -265,6 +266,7 @@ public class AgentConfigImpl extends BaseConfig implements AgentConfig { private final KeyTransactionConfig keyTransactionConfig; private final LabelsConfig labelsConfig; private final NormalizationRuleConfig normalizationRuleConfig; + private final OtelConfig otelConfig; private final ReinstrumentConfig reinstrumentConfig; private final TransactionTracerConfigImpl requestTransactionTracerConfig; private final SlowTransactionsConfig slowTransactionsConfig; @@ -375,6 +377,7 @@ private AgentConfigImpl(Map props) { normalizationRuleConfig = new NormalizationRuleConfig(props); slowTransactionsConfig = initSlowTransactionsConfig(); obfuscateJvmPropsConfig = initObfuscateJvmPropsConfig(); + otelConfig = initOtelConfig(); agentControlIntegrationConfig = initAgentControlHealthCheckConfig(); Map flattenedProps = new HashMap<>(); @@ -795,6 +798,11 @@ private ObfuscateJvmPropsConfig initObfuscateJvmPropsConfig() { return new ObfuscateJvmPropsConfigImpl(props); } + private OtelConfig initOtelConfig() { + Map props = nestedProps(OTEL); + return new OtelConfig(props); + } + private CircuitBreakerConfig initCircuitBreakerConfig() { Map props = nestedProps(CircuitBreakerConfig.PROPERTY_NAME); return new CircuitBreakerConfig(props); @@ -1255,6 +1263,11 @@ public ObfuscateJvmPropsConfig getObfuscateJvmPropsConfig() { return obfuscateJvmPropsConfig; } + @Override + public OtelConfig getOtelConfig() { + return otelConfig; + } + @Override public ReinstrumentConfig getReinstrumentConfig() { return reinstrumentConfig; From 25ef8d080ae9af60c915fe63eba903a22058f611 Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Tue, 12 Aug 2025 15:36:08 -0700 Subject: [PATCH 25/36] Add otel config class --- .../com/newrelic/agent/config/OtelConfig.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java new file mode 100644 index 0000000000..d49952b832 --- /dev/null +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java @@ -0,0 +1,25 @@ +package com.newrelic.agent.config; + +import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.api.agent.NewRelic; + +import java.util.List; +import java.util.Map; + +public class OtelConfig extends BaseConfig{ + + public static final String EXCLUDE_METERS = "meters.exclude"; + + public static final String SYSTEM_PROPERTY_ROOT = "newrelic.config.opentelemetry."; + + private final List excludedMeters; + + public OtelConfig(Map props) { + super(props, SYSTEM_PROPERTY_ROOT); + excludedMeters = getUniqueStrings(EXCLUDE_METERS); + } + + public List getExcludedMeters(){ + return excludedMeters; + } +} From 38cb41a1a10538babe31c9917a98152ac7921ee8 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Tue, 12 Aug 2025 16:49:01 -0700 Subject: [PATCH 26/36] Add tests for Logs API --- .../README.md | 182 +++++++++++++++--- .../AutoConfiguredOpenTelemetrySdk.java | 10 +- .../sdk/logs/NRLogRecordBuilder.java | 10 +- .../sdk/logs/NRLoggerBuilder.java | 3 +- .../sdk/trace/NRSpanBuilder.java | 10 +- .../sdk/trace/NRTracerBuilder.java | 3 +- .../agent/otelhybrid/HybridAgentTest.java | 160 ++++++++++++++- .../sdk/logs/NRLogRecordBuilderTest.java | 42 ++++ .../sdk/logs/NRLogRecordTest.java | 141 ++++++++++++++ .../sdk/logs/NRLoggerBuilderTest.java | 38 ++++ .../sdk/logs/TestLoggerBuilder.java | 56 ++++++ .../sdk/trace/NRSpanBuilderTest.java | 2 +- .../test/resources/distributed_tracing.yml | 11 +- 13 files changed, 630 insertions(+), 38 deletions(-) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/TestLoggerBuilder.java diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md index c061e39ea1..dcbd2ffdee 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md @@ -1,53 +1,86 @@ # OpenTelemetry Instrumentation -This instrumentation module weaves parts of the OpenTelemetry SDK to incorporate bits of OpenTelemetry functionality into the New Relic Java agent. +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. +Specifically, it can: + +* Detect OpenTelemetry Spans and add them to New Relic Java agent traces as New Relic Spans. +* Detect OpenTelemetry LogRecords and report them as New Relic LogEvents. * 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. +## OpenTelemetry Configuration + +To use the OpenTelemetry functionality incorporated into the New Relic Java agent you must enable the following config options: + +```commandline +-Dotel.java.global-autoconfigure.enabled=true +``` + ## 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 + +```yaml + opentelemetry: + sdk: + autoconfigure: + enabled: true + spans: + enabled: true + logs: + enabled: true + instrumentation: + specific-instrumentation-scope-name-1: + enabled: true + specific-instrumentation-scope-name-2: + enabled: true ``` Configuration via system property: + ``` --Dopentelemetry.sdk.autoconfigure.enabled=true --Dopentelemetry.sdk.spans.enabled=true +-Dnewrelic.config.opentelemetry.sdk.autoconfigure.enabled=true +-Dnewrelic.config.opentelemetry.sdk.spans.enabled=true +-Dnewrelic.config.opentelemetry.sdk.logs.enabled=true + +# To disable specific OpenTelemetry instrumentation scopes, use: +-Dnewrelic.config.opentelemetry.instrumentation.[SPECIFIC_INSTRUMENTATION_SCOPE_NAME].enabled=true ``` Configuration via environment variable: + ``` NEW_RELIC_OPENTELEMETRY_SDK_AUTOCONFIGURE_ENABLED=true NEW_RELIC_OPENTELEMETRY_SDK_SPANS_ENABLED=true +NEW_RELIC_OPENTELEMETRY_SDK_LOGS_ENABLED=true + +# To disable specific OpenTelemetry instrumentation scopes, use: +NEW_RELIC_OPENTELEMETRY_INSTRUMENTATION_[SPECIFIC_INSTRUMENTATION_SCOPE_NAME]_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. +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") +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")); +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. @@ -58,36 +91,125 @@ Documented below are several approaches for incorporating OpenTelemetry Spans in ### `@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. +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. +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. +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). +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 + * 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 + * 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 + * 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 + * 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) + * 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) + +## OpenTelemetry Logs + +OpenTelemetry APIs can be used to create log records which will be detected by the New Relic Java agent and reported to the APM entity being monitored by the +New Relic Java agent. The log records will be reported as log events in New Relic, which will be associated with a transaction if the logging occurred within +one. + +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.28.0")) +implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") +implementation("io.opentelemetry:opentelemetry-exporter-logging") +``` + +Example usage of OpenTelemetry Logs APIs: + +```java + // create LogRecordExporter +private static final SystemOutLogRecordExporter systemOutLogRecordExporter = SystemOutLogRecordExporter.create(); + +// create LogRecordProcessor +private static final LogRecordProcessor logRecordProcessor = SimpleLogRecordProcessor.create(systemOutLogRecordExporter); + +// create Attributes +private static final Attributes attributes = Attributes.builder() + .put("service.name", NewRelic.getAgent().getConfig().getValue("app_name", "unknown")) + .put("service.version", "4.5.1") + .put("environment", "production") + .build(); + +// create Resource +private static final Resource customResource = Resource.create(attributes); + +// create SdkLoggerProvider +private static final SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder() + .addLogRecordProcessor(logRecordProcessor) + .setResource(customResource) + .build(); + +// create LoggerBuilder +private static final LoggerBuilder loggerBuilder = sdkLoggerProvider + .loggerBuilder("demo-otel-logger") + .setInstrumentationVersion("1.0.0") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0"); + +// create Logger +private static final Logger logger = loggerBuilder.build(); + +// utility method to build and emit OpenTelemetry log records +public static void emitOTelLogs(Severity severity) { + // create LogRecordBuilder + LogRecordBuilder logRecordBuilder = logger.logRecordBuilder(); + + Instant now = Instant.now(); + logRecordBuilder +// .setContext() + .setBody("Generating OpenTelemetry LogRecord") + .setSeverity(severity) + .setSeverityText("This is the severity text") + .setAttribute(AttributeKey.stringKey("foo"), "bar") + .setObservedTimestamp(now) + .setObservedTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS) + .setTimestamp(now) + .setTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS); + + if (severity == Severity.ERROR) { + try { + throw new RuntimeException("This is a test exception for severity ERROR"); + } catch (RuntimeException e) { + logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.message"), e.getMessage()); + logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.type"), e.getClass().getName()); + logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.stacktrace"), Arrays.toString(e.getStackTrace())); + } + } + + logRecordBuilder.emit(); +} +``` \ No newline at end of file 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 814c054d40..c03a1a6daf 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 @@ -21,9 +21,17 @@ @Weave(type = MatchType.ExactClass) public class AutoConfiguredOpenTelemetrySdk { + /** + * Creates a new {@link AutoConfiguredOpenTelemetrySdkBuilder} with the default configuration. + * If the agent configuration yaml, system property `-Dnewrelic.config.opentelemetry.sdk.autoconfigure.enabled`, + * or environment variable NEW_RELIC_OPENTELEMETRY_SDK_AUTOCONFIGURE_ENABLED is set to true, + * it will append customizers for properties and resources. + * + * @return a new {@link AutoConfiguredOpenTelemetrySdkBuilder} + */ public static AutoConfiguredOpenTelemetrySdkBuilder builder() { final AutoConfiguredOpenTelemetrySdkBuilder builder = Weaver.callOriginal(); - final Boolean autoConfigure = NewRelic.getAgent().getConfig().getValue("opentelemetry.sdk.autoconfigure.enabled"); + final Boolean autoConfigure = NewRelic.getAgent().getConfig().getValue("opentelemetry.sdk.autoconfigure.enabled", false); if (autoConfigure == null || autoConfigure) { NewRelic.getAgent().getLogger().log(Level.INFO, "Appending OpenTelemetry SDK customizers"); builder.addPropertiesCustomizer(OpenTelemetrySDKCustomizer::applyProperties); diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java index ff1bfd228c..96e8895b0d 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java @@ -203,8 +203,16 @@ public void emit() { } } + /** + * Determines if the LogRecordBuilder should be enabled based on the configuration. + * If auto-configuration is enabled and logs are not explicitly disabled, then + * the LogRecordBuilder is enabled. + * + * @param config the agent configuration + * @return true if NRLogRecordBuilder should be used, false otherwise + */ static boolean isLogRecordBuilderEnabled(Config config) { - final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled"); + final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled", false); if (autoConfigure == null || autoConfigure) { // When opentelemetry.sdk.logs.enabled=false, this // setting will prevent NR LogEvents from being created, diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java index e20c8e67b8..7ecf63b23e 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java @@ -44,7 +44,8 @@ public LoggerBuilder setInstrumentationVersion(String instrumentationScopeVersio @Override public Logger build() { - Boolean enabled = config.getValue("opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); + Boolean enabled = config.getValue( + "opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); // FIXME add default so it doesn't return null or cause an exception if (enabled != null && !enabled) { /* * This will return a no-op Logger, which results in 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 e77af8d25c..b0589c066b 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 @@ -77,8 +77,16 @@ public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScop } } + /** + * Determines if the SpanBuilder should be enabled based on the configuration. + * If auto-configuration is enabled and spans are not explicitly disabled, then + * the SpanBuilder is enabled. + * + * @param config The configuration to check. + * @return true if the SpanBuilder should be enabled, false otherwise. + */ static boolean isSpanBuilderEnabled(Config config) { - final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled"); + final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled", false); if (autoConfigure == null || autoConfigure) { final Boolean spansEnabled = config.getValue("opentelemetry.sdk.spans.enabled"); return spansEnabled == null || spansEnabled; 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 f53a4a83fe..31e1de691f 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 @@ -49,7 +49,8 @@ public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersio */ @Override public Tracer build() { - Boolean enabled = config.getValue("opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); + Boolean enabled = config.getValue( + "opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); // FIXME add default so it doesn't return null or cause an exception if (enabled != null && !enabled) { return OpenTelemetry.noop().getTracer(instrumentationScopeName); } else { diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java index 226587a2b6..33a68c3de0 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/agent/otelhybrid/HybridAgentTest.java @@ -10,9 +10,16 @@ import com.newrelic.agent.introspec.InstrumentationTestRunner; import com.newrelic.agent.introspec.Introspector; import com.newrelic.agent.introspec.SpanEvent; +import com.newrelic.agent.model.LogEvent; +import com.newrelic.api.agent.NewRelic; import com.newrelic.api.agent.Trace; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.logs.LogRecordBuilder; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerBuilder; +import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanKind; @@ -25,15 +32,28 @@ import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.ExitTracerSpan; import org.junit.Test; import org.junit.runner.RunWith; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_LIBRARY_NAME; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; @RunWith(InstrumentationTestRunner.class) @InstrumentationTestConfig(includePrefixes = { "io.opentelemetry" }, configName = "distributed_tracing.yml") @@ -42,7 +62,51 @@ public class HybridAgentTest { System.setProperty("otel.java.global-autoconfigure.enabled", "true"); } - static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer("test", "1.0"); + private static final List EMITTED_LOG_RECORDS = new ArrayList<>(); + + private static final LogRecordProcessor LOG_RECORD_PROCESSOR = new LogRecordProcessor() { + @Override + public void onEmit(Context context, ReadWriteLogRecord logRecord) { + EMITTED_LOG_RECORDS.add(logRecord); + } + + @Override + public CompletableResultCode shutdown() { + return LogRecordProcessor.super.shutdown(); + } + + @Override + public CompletableResultCode forceFlush() { + return LogRecordProcessor.super.forceFlush(); + } + + @Override + public void close() { + LogRecordProcessor.super.close(); + } + }; + + private static final Attributes LOG_ATTRIBUTES = Attributes.builder() + .put("service.name", NewRelic.getAgent().getConfig().getValue("app_name", "unknown")) + .put("service.version", "4.5.1") + .put("environment", "production") + .build(); + + private static final Resource CUSTOM_RESOURCE = Resource.create(LOG_ATTRIBUTES); + + private static final SdkLoggerProvider SDK_LOGGER_PROVIDER = SdkLoggerProvider.builder().addLogRecordProcessor(LOG_RECORD_PROCESSOR).setResource( + CUSTOM_RESOURCE).build(); + + private static final String INSTRUMENTATION_SCOPE_NAME = "test"; + + private static final LoggerBuilder LOGGER_BUILDER = SDK_LOGGER_PROVIDER + .loggerBuilder(INSTRUMENTATION_SCOPE_NAME) + .setInstrumentationVersion("1.0.0") + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0"); + + private static final Logger LOGGER = LOGGER_BUILDER.build(); + + static final Tracer OTEL_TRACER = GlobalOpenTelemetry.get().getTracer(INSTRUMENTATION_SCOPE_NAME, "1.0.0"); public static final String SPAN_ID = "spanId"; public static final String TRACE_ID = "traceId"; public static final String PARENT_SPAN_ID = "parentSpanId"; @@ -189,6 +253,57 @@ public void apisInjectOutboundTraceContext() { AssertionEvaluator.assertSpanDetails(introspector, spanDetails); } + // OpenTelemetry Log API will create a LogEvent associated with a transaction + @Test + public void testOtelLogRecordInTxn() { + Map linkingMetadata = emitOtelLogRecordInTxn(null); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Collection logEvents = introspector.getLogEvents(); + assertFalse(logEvents.isEmpty()); + assertFalse(EMITTED_LOG_RECORDS.isEmpty()); + assertEquals(EMITTED_LOG_RECORDS.size(), logEvents.size()); + + LogEvent logEvent = logEvents.iterator().next(); + assertEquals(INSTRUMENTATION_SCOPE_NAME, logEvent.getUserAttributesCopy().get(OTEL_LIBRARY_NAME.getKey())); + + ReadWriteLogRecord readWriteLogRecord = EMITTED_LOG_RECORDS.get(0); + LogRecordData logRecordData = readWriteLogRecord.toLogRecordData(); + SpanContext spanContext = logRecordData.getSpanContext(); + + // The trace.id and span.id from the linking metadata + // should be the same as what is on the SpanContext. + assertEquals(spanContext.getTraceId(), linkingMetadata.get("trace.id")); + assertEquals(spanContext.getSpanId(), linkingMetadata.get("span.id")); + + introspector.clearLogEvents(); + } + + // OpenTelemetry Log API will create a LogEvent outside a transaction + @Test + public void testOtelLogRecordNoTxn() { + Map linkingMetadata = emitOtelLogRecordNoTxn(null); + + Introspector introspector = InstrumentationTestRunner.getIntrospector(); + Collection logEvents = introspector.getLogEvents(); + assertFalse(logEvents.isEmpty()); + assertEquals(1, logEvents.size()); + + LogEvent logEvent = logEvents.iterator().next(); + assertEquals(INSTRUMENTATION_SCOPE_NAME, logEvent.getUserAttributesCopy().get(OTEL_LIBRARY_NAME.getKey())); + + ReadWriteLogRecord readWriteLogRecord = EMITTED_LOG_RECORDS.get(0); + LogRecordData logRecordData = readWriteLogRecord.toLogRecordData(); + SpanContext spanContext = logRecordData.getSpanContext(); + + // The trace.id and span.id from the linking metadata should be empty + // strings, while the SpanContext should be represented by all zeros. + assertNotEquals(spanContext.getTraceId(), linkingMetadata.get("trace.id")); + assertNotEquals(spanContext.getSpanId(), linkingMetadata.get("span.id")); + + introspector.clearLogEvents(); + } + @Trace(dispatcher = true) static Map testApisInTxn() { Span span = OTEL_TRACER.spanBuilder("OTelSpan1").setSpanKind(SpanKind.CLIENT).startSpan(); @@ -453,4 +568,47 @@ public String get(Map carrier, String key) { } }; + @Trace + static Map emitOtelLogRecordNoTxn(Span span) { + emitOtelLogRecord(span); + return NewRelic.getAgent().getLinkingMetadata(); + } + + @Trace(dispatcher = true) + static Map emitOtelLogRecordInTxn(Span span) { + emitOtelLogRecord(span); + return NewRelic.getAgent().getLinkingMetadata(); + } + + static void emitOtelLogRecord(Span span) { + // create LogRecordBuilder + LogRecordBuilder logRecordBuilder = LOGGER.logRecordBuilder(); + + // build a LogRecord + Instant now = Instant.now(); + logRecordBuilder + .setBody("Generating OpenTelemetry LogRecord") + .setSeverity(Severity.ERROR) + .setSeverityText("OMG guise this is so severe!") + .setAttribute(AttributeKey.stringKey("foo"), "bar") + .setObservedTimestamp(now) + .setObservedTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS) + .setTimestamp(now) + .setTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS); + + if (span != null) { + logRecordBuilder.setContext(Context.current().with(span)); + } + + try { + throw new RuntimeException("This is a test exception for severity ERROR"); + } catch (RuntimeException e) { + logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.message"), e.getMessage()); + logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.type"), e.getClass().getName()); + logRecordBuilder.setAttribute(AttributeKey.stringKey("exception.stacktrace"), Arrays.toString(e.getStackTrace())); + } + + // emit the LogRecord + logRecordBuilder.emit(); + } } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java new file mode 100644 index 0000000000..cccf14da69 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import com.newrelic.api.agent.Config; +import junit.framework.TestCase; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NRLogRecordBuilderTest extends TestCase { + + public void testIsLogRecordBuilderEnabled() { + assertTrue(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(null, null))); + assertTrue(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(true, null))); + assertTrue(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(null, true))); + assertTrue(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(true, true))); + } + + public void testIsLogRecordBuilderDisabled() { + assertFalse(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(false, true))); + assertFalse(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(false, null))); + assertFalse(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(null, false))); + assertFalse(NRLogRecordBuilder.isLogRecordBuilderEnabled(createConfig(true, false))); + } + + private Config createConfig(Boolean autoconfigureEnabled, Boolean logsEnabled) { + Config config = mock(Config.class); + if (autoconfigureEnabled != null) { + when(config.getValue("opentelemetry.sdk.autoconfigure.enabled", false)).thenReturn(autoconfigureEnabled); + } + if (logsEnabled != null) { + when(config.getValue("opentelemetry.sdk.logs.enabled")).thenReturn(logsEnabled); + } + return config; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java new file mode 100644 index 0000000000..e633e14827 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java @@ -0,0 +1,141 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import com.nr.agent.instrumentation.utils.AttributesHelper; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.resources.Resource; +import junit.framework.TestCase; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class NRLogRecordTest extends TestCase { + static String exceptionMessage; + static String exceptionType; + static String exceptionStacktrace; + + public void testNRLogRecord() throws Exception { + final List emitted = new ArrayList<>(); + LogRecordProcessor logRecordProcessor = new LogRecordProcessor() { + @Override + public void onEmit(Context context, ReadWriteLogRecord logRecord) { + emitted.add(logRecord); + } + + @Override + public CompletableResultCode shutdown() { + return LogRecordProcessor.super.shutdown(); + } + + @Override + public CompletableResultCode forceFlush() { + return LogRecordProcessor.super.forceFlush(); + } + + @Override + public void close() { + LogRecordProcessor.super.close(); + } + }; + + final String instrumentationScopeName = "test"; + final String body = "This is a test log message"; + final String severityText = "This is severity text"; + + Logger logger = new TestLoggerBuilder(instrumentationScopeName).addLogRecordProcessor(logRecordProcessor) + .setResource(Resource.getDefault()) + .setSchemaUrl("https://opentelemetry.io/schemas/1.0.0") + .setInstrumentationVersion("1.0.0") + .build(); + + Map attributesMap = getAttributesMap(); + Attributes attributes = AttributesHelper.toAttributes(attributesMap); + + Instant now = Instant.now(); + logger.logRecordBuilder(). + setSeverity(Severity.INFO) + .setSeverityText(severityText) + .setBody(body) + .setAllAttributes(attributes) + .setAttribute(AttributeKey.stringKey("foo"), "bar") + .setObservedTimestamp(now) + .setObservedTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS) + .setTimestamp(now) + .setTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS) +// .setContext() + .emit(); + + assertEquals(1, emitted.size()); + + ReadWriteLogRecord readWriteLogRecord = emitted.get(0); + LogRecordData logRecordData = readWriteLogRecord.toLogRecordData(); + + assertEquals(instrumentationScopeName, logRecordData.getInstrumentationScopeInfo().getName()); + assertEquals("1.0.0", logRecordData.getInstrumentationScopeInfo().getVersion()); + assertEquals(instrumentationScopeName, logRecordData.getAttributes().get(AttributeKey.stringKey("otel.library.name"))); + assertEquals("1.0.0", logRecordData.getAttributes().get(AttributeKey.stringKey("otel.library.version"))); + + // The +3 is because our instrumentation adds otel.library.version and + // otel.library.name as attributes and one additional attribute is added via + // the explicit setAttribute(AttributeKey.stringKey("foo"), "bar") call. + assertEquals((attributesMap.size() + 3), logRecordData.getAttributes().size()); + assertEquals(4, logRecordData.getResource().getAttributes().size()); + assertEquals("opentelemetry", logRecordData.getResource().getAttributes().get(AttributeKey.stringKey("telemetry.sdk.name"))); + + assertEquals(exceptionMessage, logRecordData.getAttributes().get(AttributeKey.stringKey("exception.message"))); + assertEquals(exceptionType, logRecordData.getAttributes().get(AttributeKey.stringKey("exception.type"))); + assertEquals(exceptionStacktrace, logRecordData.getAttributes().get(AttributeKey.stringKey("exception.stacktrace"))); + + assertEquals("bar", logRecordData.getAttributes().get(AttributeKey.stringKey("foo"))); + + assertEquals(body, logRecordData.getBody().asString()); + assertEquals(Severity.INFO, logRecordData.getSeverity()); + assertEquals(severityText, logRecordData.getSeverityText()); + + long observedTimestampEpochNanos = TimeUnit.SECONDS.toNanos(now.getEpochSecond()) + now.getNano(); + assertEquals(observedTimestampEpochNanos, logRecordData.getObservedTimestampEpochNanos()); + + final long threadId = Thread.currentThread().getId(); + final String threadName = Thread.currentThread().getName(); + + assertEquals(threadId, ((NRLogRecord.BasicLogRecordData) logRecordData).getThreadId()); + assertEquals(threadName, ((NRLogRecord.BasicLogRecordData) logRecordData).getThreadName()); + } + + private static Map getAttributesMap() { + Map attributesMap = new HashMap() {{ + put("service.name", "test-service"); + put("service.version", "1.0.0"); + put("environment", "production"); + }}; + + try { + throw new RuntimeException("This is a test exception for severity ERROR"); + } catch (RuntimeException e) { + exceptionMessage = e.getMessage(); + exceptionType = e.getClass().toString(); + exceptionStacktrace = Arrays.toString(e.getStackTrace()); + attributesMap.put("exception.message", exceptionMessage); + attributesMap.put("exception.type", exceptionType); + attributesMap.put("exception.stacktrace", exceptionStacktrace); + } + return attributesMap; + } +} \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java new file mode 100644 index 0000000000..2b3cfff719 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java @@ -0,0 +1,38 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import com.newrelic.api.agent.Config; +import com.newrelic.api.agent.NewRelic; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.resources.Resource; +import junit.framework.TestCase; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NRLoggerBuilderTest extends TestCase { + final LoggerSharedState LOGGER_SHARED_STATE = new LoggerSharedState(Resource.empty(), LogLimits::getDefault, NoopLogRecordProcessor.getInstance(), + Clock.getDefault()); + + public void testBuild() { + Logger logger = new NRLoggerBuilder(NewRelic.getAgent().getConfig(), "test-lib", LOGGER_SHARED_STATE).build(); + + assertTrue(logger.getClass().getName(), logger.getClass().getName().startsWith( + "io.opentelemetry.sdk.logs.NRLoggerBuilder")); + } + + public void testBuildDisabled() { + Config config = mock(Config.class); + when(config.getValue("opentelemetry.instrumentation.test-lib.enabled")).thenReturn(false); + Logger logger = new NRLoggerBuilder(config, "test-lib", LOGGER_SHARED_STATE).build(); + assertSame(OpenTelemetry.noop().getLogsBridge().get("test-lib"), logger); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/TestLoggerBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/TestLoggerBuilder.java new file mode 100644 index 0000000000..b8358ef97d --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/TestLoggerBuilder.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package io.opentelemetry.sdk.logs; + +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerBuilder; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.resources.Resource; + +import java.util.function.Supplier; + +public class TestLoggerBuilder implements LoggerBuilder { + private final String instrumentationScopeName; + private String instrumentationScopeVersion; + private LogRecordProcessor logRecordProcessor; + private Resource resource = Resource.empty(); + private String schemaUrl; + + public TestLoggerBuilder(String instrumentationScopeName) { + this.instrumentationScopeName = instrumentationScopeName; + } + + public TestLoggerBuilder addLogRecordProcessor(LogRecordProcessor processor) { + this.logRecordProcessor = processor; + return this; + } + + public TestLoggerBuilder setResource(Resource resource) { + this.resource = resource; + return this; + } + + @Override + public LoggerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public LoggerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + this.instrumentationScopeVersion = instrumentationScopeVersion; + return this; + } + + @Override + public Logger build() { + Supplier logLimitsSupplier = () -> LogLimits.getDefault(); + LoggerSharedState sharedState = new LoggerSharedState(resource, logLimitsSupplier, logRecordProcessor, Clock.getDefault()); + return () -> new NRLogRecordBuilder(instrumentationScopeName, instrumentationScopeVersion, schemaUrl, sharedState); + } +} 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 c8bba060d8..e3007c6d9f 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 @@ -36,7 +36,7 @@ public void testIsSpanBuilderDisabled() { private Config createConfig(Boolean autoconfigureEnabled, Boolean spansEnabled) { Config config = mock(Config.class); if (autoconfigureEnabled != null) { - when(config.getValue("opentelemetry.sdk.autoconfigure.enabled")).thenReturn(autoconfigureEnabled); + when(config.getValue("opentelemetry.sdk.autoconfigure.enabled", false)).thenReturn(autoconfigureEnabled); } if (spansEnabled != null) { when(config.getValue("opentelemetry.sdk.spans.enabled")).thenReturn(spansEnabled); 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 index acdf6d1a3d..54d48b6cf9 100644 --- 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 @@ -1,3 +1,12 @@ common: &default_settings distributed_tracing: - enabled: true \ No newline at end of file + enabled: true + + opentelemetry: + sdk: + autoconfigure: + enabled: true + spans: + enabled: true + logs: + enabled: true From 8f4c7137452d1752c61dfe4ad43463b1214dbdcd Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Tue, 12 Aug 2025 17:25:06 -0700 Subject: [PATCH 27/36] Some cleanup --- .../opentelemetry/sdk/logs/NRLogRecord.java | 26 ++---------- .../sdk/logs/NRLogRecordBuilder.java | 40 +------------------ .../sdk/logs/NRLoggerBuilder.java | 2 +- .../sdk/trace/NRSpanBuilder.java | 2 +- .../sdk/trace/NRTracerBuilder.java | 3 +- .../sdk/logs/NRLogRecordBuilderTest.java | 2 +- .../sdk/logs/NRLoggerBuilderTest.java | 2 +- .../sdk/trace/NRSpanBuilderTest.java | 2 +- .../sdk/trace/NRTracerBuilderTest.java | 2 +- 9 files changed, 13 insertions(+), 68 deletions(-) diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java index 05234f2a55..2d46036d75 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java @@ -29,7 +29,7 @@ public class NRLogRecord implements ReadWriteLogRecord { public static final AttributeKey OTEL_EXCEPTION_TYPE = AttributeKey.stringKey("exception.type"); public static final AttributeKey OTEL_EXCEPTION_STACKTRACE = AttributeKey.stringKey("exception.stacktrace"); - private final LogLimits logLimits; + private final LogLimits logLimits; // LogLimits is not used in this implementation, but kept for compatibility private final Resource resource; private final InstrumentationScopeInfo instrumentationScopeInfo; private final long timestampEpochNanos; @@ -39,12 +39,10 @@ public class NRLogRecord implements ReadWriteLogRecord { private final String severityText; private final Body body; private final Object lock = new Object(); - // TODO use Map or AttributesMap? seems to break the weaver, causes instrumentation to not apply private final Map attributes; -// private AttributesMap attributes; private NRLogRecord( - LogLimits logLimits, // TODO: can we use this without AttributesMap? + LogLimits logLimits, Resource resource, InstrumentationScopeInfo instrumentationScopeInfo, long timestampEpochNanos, @@ -53,7 +51,6 @@ private NRLogRecord( Severity severity, String severityText, Body body, -// AttributesMap attributes Map attributes ) { this.logLimits = logLimits; @@ -81,7 +78,6 @@ static NRLogRecord create( Severity severity, String severityText, Body body, -// AttributesMap attributes Map attributes) { return new NRLogRecord( logLimits, @@ -102,11 +98,6 @@ public ReadWriteLogRecord setAttribute(AttributeKey key, T value) { return this; } synchronized (lock) { -// if (attributes == null) { -// attributes = -// AttributesMap.create( -// logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); -// } attributes.put(key.getKey(), value); } return this; @@ -128,22 +119,11 @@ public LogRecordData toLogRecordData() { severityText, body, AttributesHelper.toAttributes(attributes), -// getImmutableAttributes(), // FIXME - attributes.size() // FIXME any way to get attributes.getTotalAddedValues() without AttributesMap? -// attributes == null ? 0 : attributes.getTotalAddedValues() + attributes.size() ); } } -// private Attributes getImmutableAttributes() { -// synchronized (lock) { -// if (attributes == null || attributes.isEmpty()) { -// return Attributes.empty(); -// } -// return attributes.immutableCopy(); -// } -// } - public static class BasicLogRecordData implements LogRecordData { private final Resource resource; private final InstrumentationScopeInfo instrumentationScopeInfo; diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java index 96e8895b0d..8795637d2b 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java @@ -34,10 +34,8 @@ */ public class NRLogRecordBuilder implements LogRecordBuilder { private final Map attributes = new HashMap<>(); - // private final AttributesMap attributes; private final LoggerSharedState loggerSharedState; private final InstrumentationScopeInfo instrumentationScopeInfo; -// private LogLimits logLimits; // TODO: is this needed? Looks like it could be used in setAttribute to configure an AttributesMap private long timestampEpochNanos; private long observedTimestampEpochNanos; @@ -48,26 +46,12 @@ public class NRLogRecordBuilder implements LogRecordBuilder { public NRLogRecordBuilder(String instrumentationScopeName, String instrumentationScopeVersion, String schemaUrl, LoggerSharedState loggerSharedState) { this.loggerSharedState = loggerSharedState; - -// this.logLimits = loggerSharedState.getLogLimits(); -// if (this.logLimits == null) { -// this.logLimits = LogLimits.getDefault(); -// } - this.instrumentationScopeInfo = InstrumentationScopeInfo .builder(instrumentationScopeName) .setVersion(instrumentationScopeVersion) .setSchemaUrl(schemaUrl) .build(); -// this.attributes = initalizeAttributesMap(this.logLimits); -// -// this.attributes.put(NRLogRecord.OTEL_LIBRARY_NAME, instrumentationScopeName); -// -// if (instrumentationScopeVersion != null) { -// this.attributes.put(NRLogRecord.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); -// } - attributes.put(NRLogRecord.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); if (instrumentationScopeVersion != null) { @@ -75,14 +59,6 @@ public NRLogRecordBuilder(String instrumentationScopeName, String instrumentatio } } - // Create an AttributesMap with the limits defined in logLimits -// public AttributesMap initalizeAttributesMap(LogLimits logLimits) { -// return AttributesMap.create( -// logLimits.getMaxNumberOfAttributes(), -// logLimits.getMaxAttributeValueLength() -// ); -// } - @Override public LogRecordBuilder setTimestamp(long timestamp, TimeUnit unit) { this.timestampEpochNanos = unit.toNanos(timestamp); @@ -133,17 +109,9 @@ public LogRecordBuilder setBody(String body) { @Override public LogRecordBuilder setAttribute(AttributeKey key, T value) { - // TODO implement this properly? if (key == null || key.getKey().isEmpty() || value == null) { return this; } -// if (this.attributes == null) { -// this.attributes = -// AttributesMap.create( -// logLimits.getMaxNumberOfAttributes(), logLimits.getMaxAttributeValueLength()); -// } -// this.attributes.put(key, value); - this.attributes.put(key.getKey(), value); return this; } @@ -175,7 +143,6 @@ public void emit() { severity, severityText, body, -// attributes Collections.unmodifiableMap(new HashMap<>(attributes)) ); @@ -195,10 +162,6 @@ public void emit() { if (AppLoggingUtils.isApplicationLoggingForwardingEnabled()) { // Generate New Relic LogEvents LogEventUtil.recordNewRelicLogEvent(logRecordData); - - System.out.println(); // TODO remove logging - System.out.println(logRecordData); // TODO remove logging - System.out.println(); // TODO remove logging } } } @@ -217,7 +180,8 @@ static boolean isLogRecordBuilderEnabled(Config config) { // When opentelemetry.sdk.logs.enabled=false, this // setting will prevent NR LogEvents from being created, // without preventing OTel LogRecords from being emitted. - final Boolean logsEnabled = config.getValue("opentelemetry.sdk.logs.enabled"); + final Boolean logsEnabled = config.getValue("opentelemetry.sdk.logs.enabled", + true); // TODO: Verify default value. This is added so it doesn't return null or cause a class cast exception from String to Boolean. return logsEnabled == null || logsEnabled; } return false; diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java index 7ecf63b23e..a9930fcd1a 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLoggerBuilder.java @@ -45,7 +45,7 @@ public LoggerBuilder setInstrumentationVersion(String instrumentationScopeVersio @Override public Logger build() { Boolean enabled = config.getValue( - "opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); // FIXME add default so it doesn't return null or cause an exception + "opentelemetry.instrumentation." + instrumentationScopeName + ".enabled", true); // TODO: Verify default value. This is added so it doesn't return null or cause a class cast exception from String to Boolean. if (enabled != null && !enabled) { /* * This will return a no-op Logger, which results in 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 b0589c066b..012b32529f 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 @@ -88,7 +88,7 @@ public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScop static boolean isSpanBuilderEnabled(Config config) { final Boolean autoConfigure = config.getValue("opentelemetry.sdk.autoconfigure.enabled", false); if (autoConfigure == null || autoConfigure) { - final Boolean spansEnabled = config.getValue("opentelemetry.sdk.spans.enabled"); + final Boolean spansEnabled = config.getValue("opentelemetry.sdk.spans.enabled", true); // TODO: Verify default value. This is added so it doesn't return null or cause a class cast exception from String to Boolean. return spansEnabled == null || spansEnabled; } 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 index 31e1de691f..588d46ceb3 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 @@ -50,7 +50,8 @@ public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersio @Override public Tracer build() { Boolean enabled = config.getValue( - "opentelemetry.instrumentation." + instrumentationScopeName + ".enabled"); // FIXME add default so it doesn't return null or cause an exception + "opentelemetry.instrumentation." + instrumentationScopeName + ".enabled", + true); // TODO: Verify default value. This is added so it doesn't return null or cause a class cast exception from String to Boolean. if (enabled != null && !enabled) { return OpenTelemetry.noop().getTracer(instrumentationScopeName); } else { diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java index cccf14da69..b834ad3ef3 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordBuilderTest.java @@ -35,7 +35,7 @@ private Config createConfig(Boolean autoconfigureEnabled, Boolean logsEnabled) { when(config.getValue("opentelemetry.sdk.autoconfigure.enabled", false)).thenReturn(autoconfigureEnabled); } if (logsEnabled != null) { - when(config.getValue("opentelemetry.sdk.logs.enabled")).thenReturn(logsEnabled); + when(config.getValue("opentelemetry.sdk.logs.enabled", true)).thenReturn(logsEnabled); } return config; } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java index 2b3cfff719..650099e2df 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLoggerBuilderTest.java @@ -31,7 +31,7 @@ public void testBuild() { public void testBuildDisabled() { Config config = mock(Config.class); - when(config.getValue("opentelemetry.instrumentation.test-lib.enabled")).thenReturn(false); + when(config.getValue("opentelemetry.instrumentation.test-lib.enabled", true)).thenReturn(false); Logger logger = new NRLoggerBuilder(config, "test-lib", LOGGER_SHARED_STATE).build(); assertSame(OpenTelemetry.noop().getLogsBridge().get("test-lib"), logger); } 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 e3007c6d9f..4f9ce3aca3 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 @@ -39,7 +39,7 @@ private Config createConfig(Boolean autoconfigureEnabled, Boolean spansEnabled) when(config.getValue("opentelemetry.sdk.autoconfigure.enabled", false)).thenReturn(autoconfigureEnabled); } if (spansEnabled != null) { - when(config.getValue("opentelemetry.sdk.spans.enabled")).thenReturn(spansEnabled); + when(config.getValue("opentelemetry.sdk.spans.enabled", true)).thenReturn(spansEnabled); } return config; } 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 a5ff86f305..299fa29b5e 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 @@ -34,7 +34,7 @@ public void testBuild() { public void testBuildDisabled() { Config config = mock(Config.class); - when(config.getValue("opentelemetry.instrumentation.test-lib.enabled")).thenReturn(false); + when(config.getValue("opentelemetry.instrumentation.test-lib.enabled", true)).thenReturn(false); Tracer tracer = new NRTracerBuilder(config, "test-lib", TRACER_SHARED_STATE).build(); assertSame(OpenTelemetry.noop().getTracer("dude"), tracer); From b67d94122b3aec22bc85a50999298faeea7554a2 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Fri, 15 Aug 2025 16:09:09 -0700 Subject: [PATCH 28/36] Fix AutoConfiguredOpenTelemetrySdkBuilder --- .../sdk/autoconfigure/AutoConfiguredOpenTelemetrySdk.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 56bd333621..c03a1a6daf 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 @@ -34,8 +34,8 @@ public static AutoConfiguredOpenTelemetrySdkBuilder builder() { final Boolean autoConfigure = NewRelic.getAgent().getConfig().getValue("opentelemetry.sdk.autoconfigure.enabled", false); 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; } From 6cde0e33a9d263a534cdb5f9a7e4885b26a948ee Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Fri, 15 Aug 2025 16:50:59 -0700 Subject: [PATCH 29/36] Flesh out meter excludes tests --- .../AutoConfiguredOpenTelemetrySdk.java | 2 +- .../MeterProviderCustomizer.java | 28 ----- .../OpenTelemetrySDKCustomizer.java | 27 ++++ .../OpenTelemetrySDKCustomizerTest.java | 119 +++++++++++++++++- 4 files changed, 146 insertions(+), 30 deletions(-) delete mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java 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 5ef1ae9dd8..73e6bcf3f2 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 @@ -28,7 +28,7 @@ public static AutoConfiguredOpenTelemetrySdkBuilder builder() { NewRelic.getAgent().getLogger().log(Level.INFO, "Appending OpenTelemetry SDK customizers"); builder.addPropertiesCustomizer(OpenTelemetrySDKCustomizer::applyProperties); builder.addResourceCustomizer(OpenTelemetrySDKCustomizer::applyResources); - builder.addMeterProviderCustomizer(new MeterProviderCustomizer()); + builder.addMeterProviderCustomizer(OpenTelemetrySDKCustomizer::applyMeterExcludes); } return builder; } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java deleted file mode 100644 index 8ceb3a5b76..0000000000 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/autoconfigure/MeterProviderCustomizer.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.opentelemetry.sdk.autoconfigure; - -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.metrics.Aggregation; -import io.opentelemetry.sdk.metrics.InstrumentSelector; -import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; -import io.opentelemetry.sdk.metrics.View; - -import java.util.List; -import java.util.function.BiFunction; -import com.newrelic.agent.service.ServiceFactory; - -public class MeterProviderCustomizer implements BiFunction { - - private final List excludedMeters = ServiceFactory.getConfigService().getDefaultAgentConfig().getOtelConfig().getExcludedMeters(); - - @Override - public SdkMeterProviderBuilder apply(SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProperties) { - for (String meterName : excludedMeters) { - sdkMeterProviderBuilder.registerView( - InstrumentSelector.builder().setMeterName(meterName).build(), - View.builder().setAggregation(Aggregation.drop()).build() - ); - } - return sdkMeterProviderBuilder; - } - -} 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 ca7d87f792..20f18c9b32 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 @@ -8,16 +8,23 @@ package io.opentelemetry.sdk.autoconfigure; import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.config.OtelConfig; +import com.newrelic.agent.service.ServiceFactory; 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.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.ResourceBuilder; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.logging.Level; @@ -87,4 +94,24 @@ static Resource applyResources(Resource resource, com.newrelic.agent.bridge.Agen } return builder.build(); } + + /** + * Read list of excluded meters, and customize the meter provider to drop any with matching names. + */ + + static SdkMeterProviderBuilder applyMeterExcludes(SdkMeterProviderBuilder sdkMeterProviderBuilder, ConfigProperties configProperties) { + return applyMeterExcludes(sdkMeterProviderBuilder, NewRelic.getAgent(), ServiceFactory.getConfigService().getDefaultAgentConfig().getOtelConfig()); + } + + static SdkMeterProviderBuilder applyMeterExcludes(SdkMeterProviderBuilder sdkMeterProviderBuilder, Agent agent, OtelConfig otelConfig) { + final List excludedMeters = otelConfig.getExcludedMeters(); + agent.getLogger().log(Level.FINE, "Suppressing excluded OpenTelemetry meters: {0}", excludedMeters); + for (String meterName : excludedMeters) { + sdkMeterProviderBuilder.registerView( + InstrumentSelector.builder().setMeterName(meterName).build(), + View.builder().setAggregation(Aggregation.drop()).build() + ); + } + return sdkMeterProviderBuilder; + } } 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 44625ce3b3..9813b3ca55 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 @@ -7,18 +7,34 @@ package io.opentelemetry.sdk.autoconfigure; +import com.newrelic.agent.config.OtelConfig; 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.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import io.opentelemetry.sdk.resources.Resource; import junit.framework.TestCase; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Map; import static io.opentelemetry.sdk.autoconfigure.OpenTelemetrySDKCustomizer.SERVICE_INSTANCE_ID_ATTRIBUTE_KEY; +import static io.opentelemetry.sdk.metrics.data.AggregationTemporality.DELTA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -58,4 +74,105 @@ public void testApplyResources() { assertNotNull(resource.getAttribute(SERVICE_INSTANCE_ID_ATTRIBUTE_KEY)); assertEquals("myguid", resource.getAttribute(AttributeKey.stringKey("entity.guid"))); } -} + + public void testApplyMeterExcludesDropsExcludedMeters() { + DummyExporter metricExporter = new DummyExporter(); + MetricReader reader = PeriodicMetricReader.create(metricExporter); + List expectedExclude = Arrays.asList("drop-me", "drop-me-too", "never-used"); + + SdkMeterProvider provider = setupProviderFromExcludesConfig(reader, expectedExclude); + + //produce to some meters and force them to be exported + provider.get("drop-me").counterBuilder("foo").build().add(1); + provider.get("drop-me-too").counterBuilder("bar").build().add(2); + provider.get("keep-me").counterBuilder("baz").build().add(3); + provider.get("keep-me").counterBuilder("hello").build().add(3); + reader.forceFlush(); + + Collection collectedMetrics = metricExporter.getLatestMetricData(); + List collectedMetricNames = metricNames(collectedMetrics); + + assertEquals(2, collectedMetricNames.size()); + assertFalse(collectedMetricNames.contains("foo")); + assertFalse(collectedMetricNames.contains("bar")); + assertTrue(collectedMetricNames.contains("baz")); + assertTrue(collectedMetricNames.contains("hello")); + } + + public void testApplyMeterExcludesDropsNothingWhenEmpty(){ + DummyExporter metricExporter = new DummyExporter(); + MetricReader reader = PeriodicMetricReader.create(metricExporter); + List noExcludedMeters = new ArrayList<>(); + SdkMeterProvider provider = setupProviderFromExcludesConfig(reader, noExcludedMeters); + + //produce to some meters and force them to be exported + provider.get("keep-me").counterBuilder("foo").build().add(1); + provider.get("keep-me-too").counterBuilder("bar").build().add(3); + reader.forceFlush(); + + Collection collectedMetrics = metricExporter.getLatestMetricData(); + List actualMetricNames = metricNames(collectedMetrics); + + assertEquals(2, collectedMetrics.size()); + assertTrue(actualMetricNames.contains("foo")); + assertTrue(actualMetricNames.contains("bar")); + } + + private List metricNames(Collection collectedMetrics) { + List metricNames = new ArrayList<>(); + for (MetricData metricData : collectedMetrics) { + metricNames.add(metricData.getName()); + } + return metricNames; + } + + private SdkMeterProvider setupProviderFromExcludesConfig(MetricReader reader, List excludedMeters) { + Agent agent = mock(Agent.class); + Logger logger = mock(Logger.class); + when(agent.getLogger()).thenReturn(logger); + OtelConfig otelConfig = mock(OtelConfig.class); + when(otelConfig.getExcludedMeters()).thenReturn(excludedMeters); + SdkMeterProviderBuilder customizedBuilder = OpenTelemetrySDKCustomizer.applyMeterExcludes( + SdkMeterProvider.builder().registerMetricReader(reader), agent, otelConfig + ); + return customizedBuilder.build(); + } + + // A dummy exporter exposes the last round of collected metrics on its result field. + static class DummyExporter implements MetricExporter { + + //expose the most recently exported metrics + private Collection lastestMetricData = null; + + //this is required to register views without throwing an exception + @Override + public Aggregation getDefaultAggregation(InstrumentType instrumentType) { + return Aggregation.sum(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return DELTA; + } + + @Override + public CompletableResultCode export(Collection metrics) { + this.lastestMetricData = metrics; + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return null; + } + + @Override + public CompletableResultCode shutdown() { + return null; + } + + public Collection getLatestMetricData(){ + return lastestMetricData; + } + } +} \ No newline at end of file From b02023b3ffec6b2acea456c8f8633599873c466e Mon Sep 17 00:00:00 2001 From: Kate Anderson Date: Mon, 18 Aug 2025 10:18:35 -0700 Subject: [PATCH 30/36] Add otel config tests and some naming --- .../com/newrelic/agent/config/OtelConfig.java | 2 +- .../newrelic/agent/config/OtelConfigTest.java | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 newrelic-agent/src/test/java/com/newrelic/agent/config/OtelConfigTest.java diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java index d49952b832..f9d26dcf21 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/OtelConfig.java @@ -8,7 +8,7 @@ public class OtelConfig extends BaseConfig{ - public static final String EXCLUDE_METERS = "meters.exclude"; + public static final String EXCLUDE_METERS = "metrics.exclude"; public static final String SYSTEM_PROPERTY_ROOT = "newrelic.config.opentelemetry."; diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/config/OtelConfigTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/config/OtelConfigTest.java new file mode 100644 index 0000000000..e273049bff --- /dev/null +++ b/newrelic-agent/src/test/java/com/newrelic/agent/config/OtelConfigTest.java @@ -0,0 +1,75 @@ +package com.newrelic.agent.config; + +import static org.junit.Assert.*; + +import com.google.common.collect.ImmutableMap; +import com.newrelic.agent.SaveSystemPropertyProviderRule.TestEnvironmentFacade; +import com.newrelic.agent.SaveSystemPropertyProviderRule.TestSystemProps; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class OtelConfigTest { + + private Map configProps; + + private static final String ENV_VAR_METRICS_EXCLUDE = "NEW_RELIC_OPENTELEMETRY_METRICS_EXCLUDE"; + + private static final String SYSTEM_PROP_METRICS_EXCLUDE = "newrelic.config.opentelemetry.metrics.exclude"; + @Before + public void setup(){ + configProps = new HashMap<>(); + SystemPropertyFactory.setSystemPropertyProvider(new SystemPropertyProvider( + new TestSystemProps(), + new TestEnvironmentFacade() + )); + } + + @Test + public void testDefaultConfigValues() { + OtelConfig config = new OtelConfig(configProps); + assertTrue(config.getExcludedMeters().isEmpty()); + } + + @Test + public void testGetExcludedMetersFromProps(){ + configProps.put("metrics.exclude", "foo,bar,baz"); + OtelConfig config = new OtelConfig(configProps); + assertEquals(3, config.getExcludedMeters().size()); + assertTrue(config.getExcludedMeters().contains("foo")); + assertTrue(config.getExcludedMeters().contains("bar")); + assertTrue(config.getExcludedMeters().contains("baz")); + } + + @Test + public void testExcludeMetersFromEnvVars(){ + TestEnvironmentFacade environmentFacade = new TestEnvironmentFacade(ImmutableMap.of( + ENV_VAR_METRICS_EXCLUDE, "hello,foo-bar,goodbye" + )); + SystemPropertyFactory.setSystemPropertyProvider(new SystemPropertyProvider( + new TestSystemProps(), + environmentFacade + )); + OtelConfig config = new OtelConfig(configProps); + assertEquals(3, config.getExcludedMeters().size()); + assertTrue(config.getExcludedMeters().contains("hello")); + assertTrue(config.getExcludedMeters().contains("foo-bar")); + assertTrue(config.getExcludedMeters().contains("goodbye")); + } + + @Test + public void testExcludeMetersFromSystemProps(){ + Properties props = new Properties(); + props.put(SYSTEM_PROP_METRICS_EXCLUDE, "apple,banana"); + SystemPropertyFactory.setSystemPropertyProvider(new SystemPropertyProvider( new TestSystemProps(props), new TestEnvironmentFacade())); + OtelConfig config = new OtelConfig(configProps); + assertEquals(2, config.getExcludedMeters().size()); + assertTrue(config.getExcludedMeters().contains("apple")); + assertTrue(config.getExcludedMeters().contains("banana")); + } + + +} \ No newline at end of file From b3fcb922d85ea51d77b2853c9d41cfc4546a4121 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Mon, 18 Aug 2025 15:20:52 -0700 Subject: [PATCH 31/36] Fix NRLogRecordTest --- .../src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java index e633e14827..33d91ec905 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java @@ -76,7 +76,6 @@ public void close() { .setAllAttributes(attributes) .setAttribute(AttributeKey.stringKey("foo"), "bar") .setObservedTimestamp(now) - .setObservedTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS) .setTimestamp(now) .setTimestamp(now.toEpochMilli(), java.util.concurrent.TimeUnit.MILLISECONDS) // .setContext() From f218a8a2e4c929d2ce7a63a246ac18db7c9ca23a Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Mon, 18 Aug 2025 15:27:58 -0700 Subject: [PATCH 32/36] Fix SpanTest --- .../src/test/java/io/opentelemetry/context/SpanTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 465b49c890..c8daa371e2 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 @@ -60,6 +60,7 @@ public void testInternalSpansNoTransaction() { Introspector introspector = InstrumentationTestRunner.getIntrospector(); // no transactions because there was no dispatcher trace around the spans assertEquals(0, introspector.getFinishedTransactionCount()); + introspector.clear(); } @Test @@ -78,6 +79,7 @@ public void testConsumerSpan() { assertEquals(1, metricsForTransaction.size()); assertTrue(metricsForTransaction.keySet().toString(), metricsForTransaction.containsKey("Span/consume")); + introspector.clear(); } @Test @@ -100,6 +102,7 @@ public void testServerSpan() throws IOException { assertEquals(1, metricsForTransaction.size()); assertTrue(metricsForTransaction.keySet().toString(), metricsForTransaction.containsKey("Span/GET /owners")); + introspector.clear(); } @Test @@ -117,6 +120,7 @@ public void testSimpleSpans() { assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/simpleSpans")); assertTrue(metricsForTransaction.containsKey("Span/MyCustomSpan")); assertTrue(metricsForTransaction.containsKey("Span/kid")); + introspector.clear(); } @Test @@ -137,6 +141,7 @@ public void testAsyncSpans() { assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/asyncSpans")); assertTrue(metricsForTransaction.containsKey("Java/OpenTelemetry/AsyncScope")); assertTrue(metricsForTransaction.containsKey("Span/MyCustomAsyncSpan")); + introspector.clear(); } finally { executor.shutdown(); } @@ -171,6 +176,7 @@ public void testAsyncSpansWithParentNotWorking() { assertEquals(1, metricsForTransaction.size()); assertTrue(metricsForTransaction.containsKey("Java/io.opentelemetry.context.SpanTest/asyncSpans")); + introspector.clear(); } finally { executor.shutdown(); } @@ -200,6 +206,7 @@ public void testDatabaseSpan() { Arrays.asList("db.collection", "db.sql.table", "db.system", "db.operation").forEach(key -> { assertNull(key, dbSpan.getUserAttributes().get(key)); }); + introspector.clear(); } @Trace(dispatcher = true) @@ -241,6 +248,7 @@ public void testExternalSpan() { 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))); + introspector.clear(); } @Trace(dispatcher = true) From 0794620f797c6a07ecaae556bb72c5553d95da7f Mon Sep 17 00:00:00 2001 From: Jerry Duffy Date: Tue, 19 Aug 2025 11:04:07 -0400 Subject: [PATCH 33/36] OTel attribute mapper impl --- .../build.gradle | 44 +++++ .../utils/span/AttributeKey.java | 32 ++++ .../utils/span/AttributeMapper.java | 123 ++++++++++++++ .../utils/span/AttributeType.java | 11 ++ .../main/resources/attribute-mappings.json | 35 ++++ .../utils/span/AttributeMapperTest.java | 61 +++++++ .../test/resources/attribute-mappings.json | 157 ++++++++++++++++++ 7 files changed, 463 insertions(+) create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeKey.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeMapper.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeType.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/com/nr/agent/instrumentation/utils/span/AttributeMapperTest.java create mode 100644 instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/attribute-mappings.json 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 60621408b7..b75a22d96c 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/build.gradle @@ -1,20 +1,64 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { id("java") } +configurations { + shadowIntoJar +} +configurations.implementation.extendsFrom(configurations.shadowIntoJar) + dependencies { implementation(project(":agent-bridge")) implementation(project(":newrelic-api")) implementation(project(":newrelic-weaver-api")) implementation(project(":newrelic-agent")) implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.28.0") + + shadowIntoJar('com.googlecode.json-simple:json-simple:1.1.1') { + transitive = false + } + 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") } +/** + * We have to shadow dependencies into instrumentation modules + * so they're accessible. We try not to rely on the agent dependencies + * otherwise. + */ +tasks.create("shadowJar", ShadowJar) { + archiveClassifier.set("shadowed") + setConfigurations([project.configurations.shadowIntoJar]) + from(sourceSets.main.output.classesDirs) + relocate("org.json.simple", "com.nr.agent.deps.org.json.simple") +} + +artifacts { + instrumentationWithDependencies shadowJar +} + +project.tasks.getByName("writeCachedWeaveAttributes").dependsOn(shadowJar) + +/** + * shadowJar takes care of dependencies, but the jar task is what + * the agent build wants, so we copy the shadowJar contents. + */ jar { + dependsOn("shadowJar") + from(zipTree(project.tasks["shadowJar"].archiveFile.get().asFile.path)) + + // The default jar task re-includes the original classes files, which we don't want. + exclude { + sourceSets.main.output.classesDirs.any {dir -> + it.getFile().getPath().startsWith(dir.getPath()) + } + } + manifest { attributes "Implementation-Title" : "com.newrelic.instrumentation.opentelemetry-sdk-extension-autoconfigure-1.28.0" } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeKey.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeKey.java new file mode 100644 index 0000000000..fceb1c9a2b --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeKey.java @@ -0,0 +1,32 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation.utils.span; + +public class AttributeKey { + private final String key; + private final String semanticConvention; + private final String version; + + AttributeKey(String key, String semanticConventionString) { + this.key = key; + String [] conventionNameAndVersion = semanticConventionString.split(":"); + this.semanticConvention = conventionNameAndVersion[0]; + this.version = conventionNameAndVersion[1]; + } + + public String getKey() { + return key; + } + + public String getVersion() { + return version; + } + + public String getSemanticConvention() { + return semanticConvention; + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeMapper.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeMapper.java new file mode 100644 index 0000000000..39e7e634fe --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeMapper.java @@ -0,0 +1,123 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation.utils.span; + +import com.newrelic.api.agent.NewRelic; +import io.opentelemetry.api.trace.SpanKind; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +public class AttributeMapper { + private static final String MAPPING_RESOURCE = "attribute-mappings.json"; + + private static volatile AttributeMapper instance; + private final Map>> mappings = new HashMap<>(); + + private AttributeMapper() { + for(SpanKind spanKind : SpanKind.values()) { + Map> typeToKeysMap = new HashMap<>(); + for (AttributeType attributeType : AttributeType.values()) { + typeToKeysMap.put(attributeType, new ArrayList<>()); + } + this.mappings.put(spanKind, typeToKeysMap); + } + } + + public static AttributeMapper getInstance() { + // Double-Checked Locking init + if (instance == null) { + synchronized (AttributeMapper.class) { + if (instance == null) { + try (InputStream inputStream = AttributeMapper.class.getClassLoader().getResourceAsStream(MAPPING_RESOURCE); + InputStreamReader reader = new InputStreamReader(inputStream)) { + + instance = new AttributeMapper(); + + JSONParser parser = new JSONParser(); + JSONArray rootArray = (JSONArray) parser.parse(reader); + + for (Object spanKindObj : rootArray) { + // Span kind and a list of attribute types + JSONObject spanKindObject = (JSONObject) spanKindObj; + SpanKind spanKind = SpanKind.valueOf((String) spanKindObject.get("spanKind")); + JSONArray jsonAttributeTypes = (JSONArray) spanKindObject.get("attributeTypes"); + + for (Object typeObj : jsonAttributeTypes) { + JSONObject categoryObject = (JSONObject) typeObj; + + // Grab the attribute type (Port, Host, etc) and then iterate over the actual attribute keys + AttributeType attributeType = AttributeType.valueOf((String) categoryObject.get("attributeType")); + JSONArray jsonAttributes = (JSONArray) categoryObject.get("attributes"); + for (Object jsonAttribute : jsonAttributes) { + JSONObject attribute = (JSONObject) jsonAttribute; + instance.addAttributeMapping(spanKind, attributeType, new AttributeKey((String) attribute.get("name"), (String) attribute.get("version"))); + } + } + } + } catch (Exception e) { + // Should never happen... + NewRelic.getAgent().getLogger().log(Level.SEVERE, "Unable to read OTel attribute mappings", e); + } + } + } + } + + return instance; + } + + /** + * Based on the SpanKind and type of attribute, search through the available OTel keys and find the correct key + * since we don't know ahead of time what semantic convention version is in use and some keys vary based on + * that version. + * + * @param spanKind the span kind (SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL) + * @param type the "type" of key we're looking for: host, port, etc + * @param otelKeys the available OTel keys to search through + * + * @return the available key String + */ + public String findProperOtelKey(SpanKind spanKind, AttributeType type, Set otelKeys) { + List keys = mappings.get(spanKind).get(type); + for (AttributeKey key : keys) { + if (otelKeys.contains(key.getKey())) { + return key.getKey(); + } + } + + return ""; + } + + /** + * Visible for testing + * + * @return the configured mappings: SpanKind --> Map> + */ + Map>> getMappings() { + return mappings; + } + + /** + * Adds a new mapping to this mapper + * + * @param spanKind the SpanKind this mapping belongs to + * @param attributeType the type (Port, Host...) + * @param attributeKey the AttributeKey instance for this mapping + */ + private void addAttributeMapping(SpanKind spanKind, AttributeType attributeType, AttributeKey attributeKey) { + this.mappings.get(spanKind).get(attributeType).add(attributeKey); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeType.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeType.java new file mode 100644 index 0000000000..9260ea9304 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/span/AttributeType.java @@ -0,0 +1,11 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation.utils.span; + +public enum AttributeType { + Port, Host +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json new file mode 100644 index 0000000000..25beeea1c0 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json @@ -0,0 +1,35 @@ +[ + { + "spanKind": "SERVER", + "attributeTypes": [ + { + "Port": [ + { + "server.port": { + "semanticConvention": "HTTP-Server:1.23" + } + }, + { + "net.host.port": { + "semanticConvention": "HTTP-Server:1.20" + } + } + ] + }, + { + "Host": [ + { + "server.address": { + "semanticConvention": "HTTP-Server:1.23" + } + }, + { + "net.host.name": { + "semanticConvention": "HTTP-Server:1.20" + } + } + ] + } + ] + } +] \ No newline at end of file diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/com/nr/agent/instrumentation/utils/span/AttributeMapperTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/com/nr/agent/instrumentation/utils/span/AttributeMapperTest.java new file mode 100644 index 0000000000..a7f9a18225 --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/com/nr/agent/instrumentation/utils/span/AttributeMapperTest.java @@ -0,0 +1,61 @@ +/* + * + * * Copyright 2025 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package com.nr.agent.instrumentation.utils.span; + +import io.opentelemetry.api.trace.SpanKind; +import org.junit.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class AttributeMapperTest { + @Test + public void getInstance_withValidJson_createsValidMapper() { + AttributeMapper attributeMapper = AttributeMapper.getInstance(); + + // 5 SpanKind types + Map>> mappings = attributeMapper.getMappings(); + assertEquals(5, mappings.size()); + + for (SpanKind spanKind : SpanKind.values()) { + Map> attributeTypesBySpan = mappings.get(spanKind); + assertEquals(2, attributeTypesBySpan.size()); + + // The actual attribute key mappings... + for (AttributeType attributeType : AttributeType.values()) { + assertEquals(2, attributeTypesBySpan.get(attributeType).size()); + } + } + } + + @Test + public void findProperOtelKey_returnsProperKey() { + AttributeMapper attributeMapper = AttributeMapper.getInstance(); + Set otelKeys = new HashSet<>(); + otelKeys.add("key1"); + otelKeys.add("key2"); + otelKeys.add("key3"); + otelKeys.add("server.port"); //Should find this in the mapper + + assertEquals("server.port", attributeMapper.findProperOtelKey(SpanKind.SERVER, AttributeType.Port, otelKeys)); + } + + @Test + public void findProperOtelKey_returnsEmptyString_whenRequestedKeyNotFound() { + AttributeMapper attributeMapper = AttributeMapper.getInstance(); + Set otelKeys = new HashSet<>(); + otelKeys.add("key1"); + otelKeys.add("key2"); + otelKeys.add("key3"); + + assertEquals("", attributeMapper.findProperOtelKey(SpanKind.SERVER, AttributeType.Port, otelKeys)); + } +} diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/attribute-mappings.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/attribute-mappings.json new file mode 100644 index 0000000000..8bbb4a233c --- /dev/null +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/resources/attribute-mappings.json @@ -0,0 +1,157 @@ +[ + { + "spanKind": "SERVER", + "attributeTypes": [ + { + "attributeType": "Port", + "attributes": [ + { + "name": "server.port", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.port", + "version": "HTTP-Server:1.20" + } + ] + }, + { + "attributeType": "Host", + "attributes": [ + { + "name": "server.address", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.name", + "version": "HTTP-Server:1.20" + } + ] + } + ] + }, + { + "spanKind": "INTERNAL", + "attributeTypes": [ + { + "attributeType": "Port", + "attributes": [ + { + "name": "server.port", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.port", + "version": "HTTP-Server:1.20" + } + ] + }, + { + "attributeType": "Host", + "attributes": [ + { + "name": "server.address", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.name", + "version": "HTTP-Server:1.20" + } + ] + } + ] + }, + { + "spanKind": "CONSUMER", + "attributeTypes": [ + { + "attributeType": "Port", + "attributes": [ + { + "name": "server.port", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.port", + "version": "HTTP-Server:1.20" + } + ] + }, + { + "attributeType": "Host", + "attributes": [ + { + "name": "server.address", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.name", + "version": "HTTP-Server:1.20" + } + ] + } + ] + }, + { + "spanKind": "PRODUCER", + "attributeTypes": [ + { + "attributeType": "Port", + "attributes": [ + { + "name": "server.port", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.port", + "version": "HTTP-Server:1.20" + } + ] + }, + { + "attributeType": "Host", + "attributes": [ + { + "name": "server.address", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.name", + "version": "HTTP-Server:1.20" + } + ] + } + ] + }, + { + "spanKind": "CLIENT", + "attributeTypes": [ + { + "attributeType": "Port", + "attributes": [ + { + "name": "server.port", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.port", + "version": "HTTP-Server:1.20" + } + ] + }, + { + "attributeType": "Host", + "attributes": [ + { + "name": "server.address", + "version": "HTTP-Server:1.23" + }, + { + "name": "net.host.name", + "version": "HTTP-Server:1.20" + } + ] + } + ] + } +] \ No newline at end of file From 59a7e6e588ee499c3a053b65c5f0b0d9cd12bd0c Mon Sep 17 00:00:00 2001 From: Jerry Duffy Date: Tue, 19 Aug 2025 11:41:14 -0400 Subject: [PATCH 34/36] Update JSON --- .../main/resources/attribute-mappings.json | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json index 25beeea1c0..400d0db59a 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/resources/attribute-mappings.json @@ -3,30 +3,15 @@ "spanKind": "SERVER", "attributeTypes": [ { - "Port": [ + "attributeType": "Port", + "attributes": [ { - "server.port": { - "semanticConvention": "HTTP-Server:1.23" - } + "name": "server.port", + "version": "HTTP-Server:1.23" }, { - "net.host.port": { - "semanticConvention": "HTTP-Server:1.20" - } - } - ] - }, - { - "Host": [ - { - "server.address": { - "semanticConvention": "HTTP-Server:1.23" - } - }, - { - "net.host.name": { - "semanticConvention": "HTTP-Server:1.20" - } + "name": "net.host.port", + "version": "HTTP-Server:1.20" } ] } From 7d77e68c1da2cca5ed14f38bdc1d01e40909cc8b Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Tue, 19 Aug 2025 14:42:57 -0700 Subject: [PATCH 35/36] Update config in README --- .../README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md index dcbd2ffdee..fff5b70b91 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/README.md @@ -25,6 +25,7 @@ Configuration via yaml: ```yaml opentelemetry: + # config to enable different types of telemetry from the OpenTelemetry SDK sdk: autoconfigure: enabled: true @@ -32,11 +33,15 @@ Configuration via yaml: enabled: true logs: enabled: true + # instrumentation scope names which are excluded from reporting traces and logs instrumentation: specific-instrumentation-scope-name-1: enabled: true specific-instrumentation-scope-name-2: enabled: true + metrics: + # comma-separated list of meter names which are excluded from reporting metrics + exclude: foo-module,bar-module ``` Configuration via system property: @@ -46,8 +51,11 @@ Configuration via system property: -Dnewrelic.config.opentelemetry.sdk.spans.enabled=true -Dnewrelic.config.opentelemetry.sdk.logs.enabled=true -# To disable specific OpenTelemetry instrumentation scopes, use: +# instrumentation scope names which are excluded from reporting traces and logs -Dnewrelic.config.opentelemetry.instrumentation.[SPECIFIC_INSTRUMENTATION_SCOPE_NAME].enabled=true + +# comma-separated list of meter names which are excluded from reporting metrics +-Dnewrelic.config.opentelemetry.metrics.exclude=foo-module,bar-module ``` Configuration via environment variable: @@ -57,8 +65,11 @@ NEW_RELIC_OPENTELEMETRY_SDK_AUTOCONFIGURE_ENABLED=true NEW_RELIC_OPENTELEMETRY_SDK_SPANS_ENABLED=true NEW_RELIC_OPENTELEMETRY_SDK_LOGS_ENABLED=true -# To disable specific OpenTelemetry instrumentation scopes, use: +# instrumentation scope names which are excluded from reporting traces and logs NEW_RELIC_OPENTELEMETRY_INSTRUMENTATION_[SPECIFIC_INSTRUMENTATION_SCOPE_NAME]_ENABLED=true + +# comma-separated list of meter names which are excluded from reporting metrics +NEW_RELIC_OPENTELEMETRY_METRICS_EXCLUDE=foo-module,bar-module ``` ## OpenTelemetry Dimensional Metrics @@ -69,7 +80,7 @@ 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(platform("io.opentelemetry:opentelemetry-bom:1.44.1")) implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") implementation("io.opentelemetry:opentelemetry-exporter-otlp") ``` From d28ebb7cae190e3e72517d93fc11a9c62f1c18b6 Mon Sep 17 00:00:00 2001 From: Jason Keller Date: Thu, 28 Aug 2025 14:26:12 -0700 Subject: [PATCH 36/36] Some cleanup around attributes --- .../utils/logs/LogEventUtil.java | 49 +++++++++---------- .../opentelemetry/sdk/logs/NRLogRecord.java | 2 + .../sdk/logs/NRLogRecordBuilder.java | 6 --- .../SdkLoggerProvider_Instrumentation.java | 4 ++ .../sdk/trace/ExitTracerSpan.java | 4 ++ .../sdk/trace/NRSpanBuilder.java | 2 + .../sdk/logs/NRLogRecordTest.java | 7 +-- .../sdk/trace/ExitTracerSpanTest.java | 2 +- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java index 02ab975ece..25b9c02262 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/com/nr/agent/instrumentation/utils/logs/LogEventUtil.java @@ -43,11 +43,11 @@ import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_EXCEPTION_TYPE; import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_LIBRARY_NAME; import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_LIBRARY_VERSION; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_SCOPE_NAME; +import static io.opentelemetry.sdk.logs.NRLogRecord.OTEL_SCOPE_VERSION; public class LogEventUtil { private static final Set OTEL_ATTRIBUTES = new HashSet<>(Arrays.asList( - OTEL_LIBRARY_NAME.getKey(), - OTEL_LIBRARY_VERSION.getKey(), OTEL_EXCEPTION_MESSAGE.getKey(), OTEL_EXCEPTION_TYPE.getKey(), OTEL_EXCEPTION_STACKTRACE.getKey()) @@ -76,6 +76,27 @@ public static void recordNewRelicLogEvent(LogRecordData logRecordData) { } logEventMap.put(TIMESTAMP, logRecordData.getTimestampEpochNanos()); + // otel.scope.version and otel.scope.name should be reported along with the deprecated versions otel.library.version and otel.library.name + String instrumentationScopeName = logRecordData.getInstrumentationScopeInfo().getName(); + + if (instrumentationScopeName != null && !instrumentationScopeName.isEmpty()) { + LogAttributeKey instrumentationScopeNameKey = new LogAttributeKey(OTEL_SCOPE_NAME.getKey(), LogAttributeType.AGENT); + logEventMap.put(instrumentationScopeNameKey, instrumentationScopeName); + + LogAttributeKey instrumentationLibraryNameKey = new LogAttributeKey(OTEL_LIBRARY_NAME.getKey(), LogAttributeType.AGENT); + logEventMap.put(instrumentationLibraryNameKey, instrumentationScopeName); + } + + String instrumentationScopeVersion = logRecordData.getInstrumentationScopeInfo().getVersion(); + + if (instrumentationScopeVersion != null && !instrumentationScopeVersion.isEmpty()) { + LogAttributeKey instrumentationScopeVersionKey = new LogAttributeKey(OTEL_SCOPE_VERSION.getKey(), LogAttributeType.AGENT); + logEventMap.put(instrumentationScopeVersionKey, instrumentationScopeVersion); + + LogAttributeKey instrumentationLibraryVersionKey = new LogAttributeKey(OTEL_LIBRARY_VERSION.getKey(), LogAttributeType.AGENT); + logEventMap.put(instrumentationLibraryVersionKey, instrumentationScopeVersion); + } + if (AppLoggingUtils.isAppLoggingContextDataEnabled()) { for (Map.Entry, Object> entry : contextAttributes.asMap().entrySet()) { String key = entry.getKey().getKey(); @@ -90,18 +111,6 @@ public static void recordNewRelicLogEvent(LogRecordData logRecordData) { // These attributes come from the attribute map, but they should not be prefixed with context since they are defined in the OTel semantic conventions. if (!contextAttributes.isEmpty()) { - String otelLibraryName = contextAttributes.get(OTEL_LIBRARY_NAME); - if (otelLibraryName != null) { - LogAttributeKey logAttrKey = new LogAttributeKey(OTEL_LIBRARY_NAME.getKey(), LogAttributeType.AGENT); - logEventMap.put(logAttrKey, otelLibraryName); - } - - String otelLibraryVersion = contextAttributes.get(OTEL_LIBRARY_VERSION); - if (otelLibraryVersion != null) { - LogAttributeKey logAttrKey = new LogAttributeKey(OTEL_LIBRARY_VERSION.getKey(), LogAttributeType.AGENT); - logEventMap.put(logAttrKey, otelLibraryVersion); - } - // Exceptions are captured in the attributes map for OTel logs. // https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-logs/ if (ExceptionUtil.getErrorMessage(errorMessage) != null) { @@ -136,18 +145,6 @@ public static void recordNewRelicLogEvent(LogRecordData logRecordData) { long threadId = ((BasicLogRecordData) logRecordData).getThreadId(); logEventMap.put(THREAD_ID, threadId); - String loggerName = logRecordData.getInstrumentationScopeInfo() - .getName(); // FIXME doesn't seem to map to otel semantic conventions, should we add this? - if (loggerName != null) { - logEventMap.put(LOGGER_NAME, loggerName); - } - - String loggerFqcn = logRecordData.getInstrumentationScopeInfo() - .getName(); // FIXME doesn't seem to map to otel semantic conventions, should we add this? - if (loggerFqcn != null) { - logEventMap.put(LOGGER_FQCN, loggerFqcn); - } - AgentBridge.getAgent().getLogSender().recordLogEvent(logEventMap); } } diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java index 2d46036d75..c9e11400e2 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecord.java @@ -23,6 +23,8 @@ * New Relic Java representation of an OpenTelemetry LogRecord. */ public class NRLogRecord implements ReadWriteLogRecord { + public static final AttributeKey OTEL_SCOPE_VERSION = AttributeKey.stringKey("otel.scope.version"); + public static final AttributeKey OTEL_SCOPE_NAME = AttributeKey.stringKey("otel.scope.name"); public static final AttributeKey OTEL_LIBRARY_VERSION = AttributeKey.stringKey("otel.library.version"); public static final AttributeKey OTEL_LIBRARY_NAME = AttributeKey.stringKey("otel.library.name"); public static final AttributeKey OTEL_EXCEPTION_MESSAGE = AttributeKey.stringKey("exception.message"); diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java index 8795637d2b..bac84c7b44 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/NRLogRecordBuilder.java @@ -51,12 +51,6 @@ public NRLogRecordBuilder(String instrumentationScopeName, String instrumentatio .setVersion(instrumentationScopeVersion) .setSchemaUrl(schemaUrl) .build(); - - attributes.put(NRLogRecord.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); - - if (instrumentationScopeVersion != null) { - attributes.put(NRLogRecord.OTEL_LIBRARY_VERSION.getKey(), instrumentationScopeVersion); - } } @Override diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java index bdd1635e49..8bf853337c 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider_Instrumentation.java @@ -24,6 +24,10 @@ public final class SdkLoggerProvider_Instrumentation { public LoggerBuilder loggerBuilder(String instrumentationScopeName) { final LoggerBuilder loggerBuilder = Weaver.callOriginal(); Config config = NewRelic.getAgent().getConfig(); + + // Generate the instrumentation module supportability metric + NewRelic.incrementCounter("Supportability/Logging/Java/OpenTelemetryBridge/enabled"); + if (NRLogRecordBuilder.isLogRecordBuilderEnabled(config)) { // return our logger builder instead of the OTel instance return new NRLoggerBuilder(config, instrumentationNameOrDefault(instrumentationScopeName), sharedState); 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 49b80ab746..a72a95dfa2 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 @@ -51,8 +51,12 @@ * Representation of a Span */ public class ExitTracerSpan implements ReadWriteSpan { + // otel.scope.version and otel.scope.name should be reported along with the deprecated versions otel.library.version and otel.library.name + static final String OTEL_SCOPE_VERSION = "otel.scope.version"; + static final AttributeKey OTEL_SCOPE_NAME = AttributeKey.stringKey("otel.scope.name"); 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"); 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 012b32529f..ab428c6857 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 @@ -65,8 +65,10 @@ public NRSpanBuilder(Instrumentation instrumentation, String instrumentationScop this.spanName = spanName; this.sharedState = sharedState; instrumentationLibraryInfo = InstrumentationLibraryInfo.create(instrumentationScopeName, instrumentationScopeVersion); + attributes.put(ExitTracerSpan.OTEL_SCOPE_NAME.getKey(), instrumentationScopeName); attributes.put(ExitTracerSpan.OTEL_LIBRARY_NAME.getKey(), instrumentationScopeName); if (instrumentationScopeVersion != null) { + attributes.put(ExitTracerSpan.OTEL_SCOPE_VERSION, instrumentationScopeVersion); attributes.put(ExitTracerSpan.OTEL_LIBRARY_VERSION, instrumentationScopeVersion); } if (sharedState.getActiveSpanProcessor().isEndRequired()) { diff --git a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java index 33d91ec905..2e11a3df8d 100644 --- a/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java +++ b/instrumentation/opentelemetry-sdk-extension-autoconfigure-1.28.0/src/test/java/io/opentelemetry/sdk/logs/NRLogRecordTest.java @@ -88,13 +88,10 @@ public void close() { assertEquals(instrumentationScopeName, logRecordData.getInstrumentationScopeInfo().getName()); assertEquals("1.0.0", logRecordData.getInstrumentationScopeInfo().getVersion()); - assertEquals(instrumentationScopeName, logRecordData.getAttributes().get(AttributeKey.stringKey("otel.library.name"))); - assertEquals("1.0.0", logRecordData.getAttributes().get(AttributeKey.stringKey("otel.library.version"))); - // The +3 is because our instrumentation adds otel.library.version and - // otel.library.name as attributes and one additional attribute is added via + // The +1 is because one additional attribute is added via // the explicit setAttribute(AttributeKey.stringKey("foo"), "bar") call. - assertEquals((attributesMap.size() + 3), logRecordData.getAttributes().size()); + assertEquals((attributesMap.size() + 1), logRecordData.getAttributes().size()); assertEquals(4, logRecordData.getResource().getAttributes().size()); assertEquals("opentelemetry", logRecordData.getResource().getAttributes().get(AttributeKey.stringKey("telemetry.sdk.name"))); 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 534dba4b80..bb3cbcd255 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 @@ -79,7 +79,7 @@ public boolean isEndRequired() { ReadWriteSpan startedSpan = started.get(0); assertEquals("SELECT petclinic", startedSpan.getName()); SpanData spanData = startedSpan.toSpanData(); - assertEquals(48, spanData.getAttributes().size()); + assertEquals(49, spanData.getAttributes().size()); assertEquals(4, spanData.getResource().getAttributes().size()); assertEquals("opentelemetry", spanData.getResource().getAttributes().get(AttributeKey.stringKey("telemetry.sdk.name")));