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