From 75b994937a6b051ef804efa783f18b44a915aced Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Tue, 18 Feb 2025 20:48:21 -0500 Subject: [PATCH 1/6] add azure-functions instrumentation --- .../azure-functions/build.gradle | 13 +++ .../functions/AzureFunctionsDecorator.java | 84 ++++++++++++++++++ .../AzureFunctionsInstrumentation.java | 87 +++++++++++++++++++ .../HttpRequestMessageExtractAdapter.java | 16 ++++ .../java/datadog/trace/api/DDSpanTypes.java | 2 + .../main/java/datadog/trace/api/Config.java | 13 ++- .../trace/api/naming/v1/CloudNamingV1.java | 12 ++- .../api/InternalSpanTypes.java | 2 + settings.gradle | 1 + 9 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 dd-java-agent/instrumentation/azure-functions/build.gradle create mode 100644 dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsDecorator.java create mode 100644 dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java create mode 100644 dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/HttpRequestMessageExtractAdapter.java diff --git a/dd-java-agent/instrumentation/azure-functions/build.gradle b/dd-java-agent/instrumentation/azure-functions/build.gradle new file mode 100644 index 00000000000..2b5f97f9b11 --- /dev/null +++ b/dd-java-agent/instrumentation/azure-functions/build.gradle @@ -0,0 +1,13 @@ +muzzle { + pass { + group = 'com.microsoft.azure.functions' + module = 'azure-functions-java-library' + versions = '[1.2.2,)' + } +} + +apply from: "$rootDir/gradle/java.gradle" + +dependencies { + compileOnly group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '1.2.2' +} diff --git a/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsDecorator.java b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsDecorator.java new file mode 100644 index 00000000000..a7654462b91 --- /dev/null +++ b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsDecorator.java @@ -0,0 +1,84 @@ +package datadog.trace.instrumentation.azurefunctions; + +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes; +import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; +import datadog.trace.bootstrap.instrumentation.api.URIDefaultDataAdapter; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator; + +public class AzureFunctionsDecorator + extends HttpServerDecorator< + HttpRequestMessage, HttpRequestMessage, HttpResponseMessage, HttpRequestMessage> { + public static final CharSequence AZURE_FUNCTIONS = UTF8BytesString.create("azure-functions"); + + public static final AzureFunctionsDecorator DECORATE = new AzureFunctionsDecorator(); + public static final CharSequence AZURE_FUNCTIONS_REQUEST = + UTF8BytesString.create( + SpanNaming.instance().namingSchema().cloud().operationForFaas("azure")); + + @Override + protected String[] instrumentationNames() { + return new String[] {"azure-functions"}; + } + + @Override + protected CharSequence component() { + return AZURE_FUNCTIONS; + } + + @Override + protected AgentPropagation.ContextVisitor getter() { + return HttpRequestMessageExtractAdapter.GETTER; + } + + @Override + protected AgentPropagation.ContextVisitor responseGetter() { + return null; + } + + @Override + public CharSequence spanName() { + return AZURE_FUNCTIONS_REQUEST; + } + + @Override + protected String method(final HttpRequestMessage request) { + return request.getHttpMethod().name(); + } + + @Override + protected URIDataAdapter url(final HttpRequestMessage request) { + return new URIDefaultDataAdapter(request.getUri()); + } + + @Override + protected String peerHostIP(final HttpRequestMessage request) { + return null; + } + + @Override + protected int peerPort(final HttpRequestMessage request) { + return 0; + } + + @Override + protected CharSequence spanType() { + return InternalSpanTypes.SERVERLESS; + } + + @Override + protected int status(final HttpResponseMessage response) { + return response.getStatusCode(); + } + + public AgentSpan afterStart(final AgentSpan span, final String functionName) { + span.setTag("aas.function.name", functionName); + span.setTag("aas.function.trigger", "Http"); + return super.afterStart(span); + } +} diff --git a/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java new file mode 100644 index 00000000000..a335d769e9a --- /dev/null +++ b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java @@ -0,0 +1,87 @@ +package datadog.trace.instrumentation.azurefunctions; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresMethod; +import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.isAnnotatedWith; +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan; +import static datadog.trace.bootstrap.instrumentation.decorator.http.HttpResourceDecorator.HTTP_RESOURCE_DECORATOR; +import static datadog.trace.instrumentation.azurefunctions.AzureFunctionsDecorator.DECORATE; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumenterModule.class) +public class AzureFunctionsInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { + public AzureFunctionsInstrumentation() { + super("azure-functions"); + } + + @Override + public String hierarchyMarkerType() { + return null; + } + + @Override + public ElementMatcher hierarchyMatcher() { + return declaresMethod( + isAnnotatedWith(named("com.microsoft.azure.functions.annotation.FunctionName"))); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".AzureFunctionsDecorator", packageName + ".HttpRequestMessageExtractAdapter" + }; + } + + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + isMethod() + .and(isPublic()) + .and(takesArgument(0, named("com.microsoft.azure.functions.HttpRequestMessage"))) + .and(takesArgument(1, named("com.microsoft.azure.functions.ExecutionContext"))), + AzureFunctionsInstrumentation.class.getName() + "$AzureFunctionsAdvice"); + } + + public static class AzureFunctionsAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AgentScope methodEnter( + @Advice.Argument(0) final HttpRequestMessage request, + @Advice.Argument(1) final ExecutionContext context) { + final AgentSpanContext.Extracted extractedContext = DECORATE.extract(request); + final AgentSpan span = DECORATE.startSpan(request, extractedContext); + DECORATE.afterStart(span, context.getFunctionName()); + DECORATE.onRequest(span, request, request, extractedContext); + HTTP_RESOURCE_DECORATOR.withRoute( + span, request.getHttpMethod().name(), request.getUri().getPath()); + return activateSpan(span); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.Enter final AgentScope scope, + @Advice.Return final HttpResponseMessage response, + @Advice.Thrown final Throwable throwable) { + final AgentSpan span = scope.span(); + DECORATE.onError(span, throwable); + DECORATE.onResponse(span, response); + DECORATE.beforeFinish(span); + scope.close(); + span.finish(); + } + } +} diff --git a/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/HttpRequestMessageExtractAdapter.java b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/HttpRequestMessageExtractAdapter.java new file mode 100644 index 00000000000..60b3ad86b8f --- /dev/null +++ b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/HttpRequestMessageExtractAdapter.java @@ -0,0 +1,16 @@ +package datadog.trace.instrumentation.azurefunctions; + +import com.microsoft.azure.functions.HttpRequestMessage; +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; +import datadog.trace.bootstrap.instrumentation.api.ContextVisitors; + +public class HttpRequestMessageExtractAdapter + implements AgentPropagation.ContextVisitor { + public static final HttpRequestMessageExtractAdapter GETTER = + new HttpRequestMessageExtractAdapter(); + + @Override + public void forEachKey(HttpRequestMessage carrier, AgentPropagation.KeyClassifier classifier) { + ContextVisitors.stringValuesEntrySet().forEachKey(carrier.getHeaders().entrySet(), classifier); + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/DDSpanTypes.java b/dd-trace-api/src/main/java/datadog/trace/api/DDSpanTypes.java index a0118b62899..c380b6078c9 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/DDSpanTypes.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/DDSpanTypes.java @@ -37,4 +37,6 @@ public class DDSpanTypes { public static final String MULE = "mule"; public static final String VALKEY = "valkey"; + + public static final String SERVERLESS = "serverless"; } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index d758f675c32..6bc4afd2cc7 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -511,6 +511,7 @@ public static String getHostName() { private final int telemetryDependencyResolutionQueueSize; private final boolean azureAppServices; + private final boolean azureFunctions; private final String traceAgentPath; private final List traceAgentArgs; private final String dogStatsDPath; @@ -795,6 +796,9 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins baggageMapping = configProvider.getMergedMapWithOptionalMappings(null, true, BAGGAGE_MAPPING); + azureFunctions = + getEnv("FUNCTIONS_WORKER_RUNTIME") != null && getEnv("FUNCTIONS_EXTENSION_VERSION") != null; + spanAttributeSchemaVersion = schemaVersionFromConfig(); // following two only used in v0. @@ -3741,8 +3745,13 @@ private Map getAzureAppServicesTags() { } private int schemaVersionFromConfig() { - String versionStr = - configProvider.getString(TRACE_SPAN_ATTRIBUTE_SCHEMA, "v" + SpanNaming.SCHEMA_MIN_VERSION); + String defaultVersion; + if (azureFunctions) { + defaultVersion = "v1"; + } else { + defaultVersion = "v" + SpanNaming.SCHEMA_MIN_VERSION; + } + String versionStr = configProvider.getString(TRACE_SPAN_ATTRIBUTE_SCHEMA, defaultVersion); Matcher matcher = Pattern.compile("^v?(0|[1-9]\\d*)$").matcher(versionStr); int parsedVersion = -1; if (matcher.matches()) { diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v1/CloudNamingV1.java b/internal-api/src/main/java/datadog/trace/api/naming/v1/CloudNamingV1.java index 2798506b2bb..5f463c851e9 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v1/CloudNamingV1.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v1/CloudNamingV1.java @@ -46,9 +46,13 @@ public String serviceForRequest( @Nonnull @Override public String operationForFaas(@Nonnull final String provider) { - // for now only aws is implemented. For the future provider might be used to return specific - // function as a service name - // (e.g. azure automation) - return "aws.lambda.invoke"; + switch (provider) { + case "aws": + return "aws.lambda.invoke"; + case "azure": + return "azure.functions.invoke"; + default: + return "aws.lambda.invoke"; + } } } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InternalSpanTypes.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InternalSpanTypes.java index 6fc420b9e9a..72e47c2a131 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InternalSpanTypes.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/InternalSpanTypes.java @@ -49,4 +49,6 @@ public class InternalSpanTypes { public static final UTF8BytesString TIBCO_BW = UTF8BytesString.create("tibco_bw"); public static final UTF8BytesString MULE = UTF8BytesString.create(DDSpanTypes.MULE); public static final CharSequence VALKEY = UTF8BytesString.create(DDSpanTypes.VALKEY); + + public static final CharSequence SERVERLESS = UTF8BytesString.create(DDSpanTypes.SERVERLESS); } diff --git a/settings.gradle b/settings.gradle index 54bdd9e80dc..de41750b2bf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -204,6 +204,7 @@ include ':dd-java-agent:instrumentation:aws-java-s3-2.0' include ':dd-java-agent:instrumentation:aws-lambda-handler' include ':dd-java-agent:instrumentation:axis-2' include ':dd-java-agent:instrumentation:axway-api' +include ':dd-java-agent:instrumentation:azure-functions' include ':dd-java-agent:instrumentation:caffeine' include ':dd-java-agent:instrumentation:cdi-1.2' include ':dd-java-agent:instrumentation:classloading' From 9b4e61db91a7031a12f0d27caf5a754952b3f4ee Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Thu, 20 Feb 2025 17:32:36 -0500 Subject: [PATCH 2/6] add unit tests for azure-functions instrumentation --- .../azure-functions/build.gradle | 7 ++ .../src/test/groovy/AzureFunctionsTest.groovy | 79 +++++++++++++++++++ .../src/test/groovy/Function.java | 22 ++++++ 3 files changed, 108 insertions(+) create mode 100644 dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy create mode 100644 dd-java-agent/instrumentation/azure-functions/src/test/groovy/Function.java diff --git a/dd-java-agent/instrumentation/azure-functions/build.gradle b/dd-java-agent/instrumentation/azure-functions/build.gradle index 2b5f97f9b11..c8d026ddc22 100644 --- a/dd-java-agent/instrumentation/azure-functions/build.gradle +++ b/dd-java-agent/instrumentation/azure-functions/build.gradle @@ -8,6 +8,13 @@ muzzle { apply from: "$rootDir/gradle/java.gradle" +addTestSuiteForDir('latestDepTest', 'test') + dependencies { compileOnly group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '1.2.2' + + testImplementation group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '1.2.2' + testImplementation libs.bundles.mockito + + latestDepTestImplementation group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '+' } diff --git a/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy b/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy new file mode 100644 index 00000000000..d5c2447d793 --- /dev/null +++ b/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy @@ -0,0 +1,79 @@ +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.when + +import com.microsoft.azure.functions.ExecutionContext +import com.microsoft.azure.functions.HttpMethod +import com.microsoft.azure.functions.HttpRequestMessage +import com.microsoft.azure.functions.HttpResponseMessage +import com.microsoft.azure.functions.HttpStatus +import datadog.trace.agent.test.naming.VersionedNamingTestBase +import datadog.trace.api.DDSpanTypes + +abstract class AzureFunctionsTest extends VersionedNamingTestBase { + + @Override + String service() { + null + } + + def "test azure functions http trigger"() { + given: + HttpRequestMessage> request = mock(HttpRequestMessage) + HttpResponseMessage.Builder responseBuilder = mock(HttpResponseMessage.Builder) + HttpResponseMessage response = mock(HttpResponseMessage) + ExecutionContext context = mock(ExecutionContext) + + and: + when(request.getHeaders()).thenReturn(Collections.emptyMap()) + when(request.getHttpMethod()).thenReturn(HttpMethod.GET) + when(request.getUri()).thenReturn(new URI("https://localhost:7071/api/HttpTest")) + when(request.createResponseBuilder(HttpStatus.OK)).thenReturn(responseBuilder) + + when(responseBuilder.body("Hello Datadog test!")).thenReturn(responseBuilder) + when(responseBuilder.build()).thenReturn(response) + + when(response.getStatusCode()).thenReturn(200) + when(response.getBody()).thenReturn("Hello Datadog test!") + + when(context.getFunctionName()).thenReturn("HttpTest") + + when: + new Function().run(request, context) + + then: + assertTraces(1) { + trace(1) { + span { + operationName operation() + spanType DDSpanTypes.SERVERLESS + errored false + } + } + } + } +} + + +class AzureFunctionsV0ForkedTest extends AzureFunctionsTest { + @Override + int version() { + 0 + } + + @Override + String operation() { + "dd-tracer-serverless-span" + } +} + +class AzureFunctionsV1Test extends AzureFunctionsTest { + @Override + int version() { + 1 + } + + @Override + String operation() { + "azure.functions.invoke" + } +} diff --git a/dd-java-agent/instrumentation/azure-functions/src/test/groovy/Function.java b/dd-java-agent/instrumentation/azure-functions/src/test/groovy/Function.java new file mode 100644 index 00000000000..94df257d079 --- /dev/null +++ b/dd-java-agent/instrumentation/azure-functions/src/test/groovy/Function.java @@ -0,0 +1,22 @@ +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import java.util.Optional; + +public class Function { + @FunctionName("HttpTest") + public HttpResponseMessage run( + @HttpTrigger( + name = "req", + methods = {HttpMethod.GET}, + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) { + return request.createResponseBuilder(HttpStatus.OK).body("Hello Datadog test!").build(); + } +} From d628cb62978d5206c63be44ebfdb1465d803ff0d Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Fri, 21 Feb 2025 10:31:02 -0500 Subject: [PATCH 3/6] add assertions for azure-functions instrumentation --- .../src/test/groovy/AzureFunctionsTest.groovy | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy b/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy index d5c2447d793..80d9af8dabb 100644 --- a/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy +++ b/dd-java-agent/instrumentation/azure-functions/src/test/groovy/AzureFunctionsTest.groovy @@ -8,6 +8,8 @@ import com.microsoft.azure.functions.HttpResponseMessage import com.microsoft.azure.functions.HttpStatus import datadog.trace.agent.test.naming.VersionedNamingTestBase import datadog.trace.api.DDSpanTypes +import datadog.trace.bootstrap.instrumentation.api.Tags +import okhttp3.internal.Version abstract class AzureFunctionsTest extends VersionedNamingTestBase { @@ -23,19 +25,27 @@ abstract class AzureFunctionsTest extends VersionedNamingTestBase { HttpResponseMessage response = mock(HttpResponseMessage) ExecutionContext context = mock(ExecutionContext) + String functionName = "HttpTest" + Map headers = ["user-agent": Version.userAgent()] + HttpMethod method = HttpMethod.GET + String responseBody = "Hello Datadog test!" + HttpStatus status = HttpStatus.OK + int statusCode = 200 + URI uri = new URI("https://localhost:7071/api/HttpTest") + and: - when(request.getHeaders()).thenReturn(Collections.emptyMap()) - when(request.getHttpMethod()).thenReturn(HttpMethod.GET) - when(request.getUri()).thenReturn(new URI("https://localhost:7071/api/HttpTest")) - when(request.createResponseBuilder(HttpStatus.OK)).thenReturn(responseBuilder) + when(request.getHeaders()).thenReturn(headers) + when(request.getHttpMethod()).thenReturn(method) + when(request.getUri()).thenReturn(uri) + when(request.createResponseBuilder(status)).thenReturn(responseBuilder) - when(responseBuilder.body("Hello Datadog test!")).thenReturn(responseBuilder) + when(responseBuilder.body(responseBody)).thenReturn(responseBuilder) when(responseBuilder.build()).thenReturn(response) - when(response.getStatusCode()).thenReturn(200) - when(response.getBody()).thenReturn("Hello Datadog test!") + when(response.getStatusCode()).thenReturn(statusCode) + when(response.getBody()).thenReturn(responseBody) - when(context.getFunctionName()).thenReturn("HttpTest") + when(context.getFunctionName()).thenReturn(functionName) when: new Function().run(request, context) @@ -44,9 +54,23 @@ abstract class AzureFunctionsTest extends VersionedNamingTestBase { assertTraces(1) { trace(1) { span { + parent() operationName operation() spanType DDSpanTypes.SERVERLESS errored false + tags { + defaultTags() + "$Tags.COMPONENT" "azure-functions" + "$Tags.SPAN_KIND" "$Tags.SPAN_KIND_SERVER" + "$Tags.HTTP_HOSTNAME" "localhost" + "$Tags.HTTP_METHOD" "GET" + "$Tags.HTTP_ROUTE" "/api/HttpTest" + "$Tags.HTTP_STATUS" 200 + "$Tags.HTTP_URL" "https://localhost:7071/api/HttpTest" + "$Tags.HTTP_USER_AGENT" "${Version.userAgent()}" + "aas.function.name" "HttpTest" + "aas.function.trigger" "Http" + } } } } From 5e596f9e4b57435df4273a517217e8762d8d8ce9 Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Wed, 26 Feb 2025 13:47:33 -0500 Subject: [PATCH 4/6] add comment for minimum azure-functions-java-library version --- dd-java-agent/instrumentation/azure-functions/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dd-java-agent/instrumentation/azure-functions/build.gradle b/dd-java-agent/instrumentation/azure-functions/build.gradle index c8d026ddc22..a7ad15700cc 100644 --- a/dd-java-agent/instrumentation/azure-functions/build.gradle +++ b/dd-java-agent/instrumentation/azure-functions/build.gradle @@ -2,6 +2,8 @@ muzzle { pass { group = 'com.microsoft.azure.functions' module = 'azure-functions-java-library' + + // 1.2.2 is the first stable release. The earlier versions are either betas or are unstable versions = '[1.2.2,)' } } From 85648d14ccf082d056ff5d1033901af08d5bebcf Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Wed, 26 Feb 2025 13:57:07 -0500 Subject: [PATCH 5/6] add comment for azure-functions using v1 as default schema version --- internal-api/src/main/java/datadog/trace/api/Config.java | 1 + 1 file changed, 1 insertion(+) diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 6bc4afd2cc7..eb8d2be3985 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3746,6 +3746,7 @@ private Map getAzureAppServicesTags() { private int schemaVersionFromConfig() { String defaultVersion; + // use v1 so Azure Functions operation name is consistent with that of other tracers if (azureFunctions) { defaultVersion = "v1"; } else { From a40817f40919263d4746c38ac5a785dcf03c8ff7 Mon Sep 17 00:00:00 2001 From: Duncan Harvey Date: Wed, 26 Feb 2025 14:57:31 -0500 Subject: [PATCH 6/6] add comment for null hierarchyMarkerType in azure-functions instrumentation --- .../azure/functions/AzureFunctionsInstrumentation.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java index a335d769e9a..604b5d77c42 100644 --- a/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java +++ b/dd-java-agent/instrumentation/azure-functions/src/main/java/datadog/trace/instrumentation/azure/functions/AzureFunctionsInstrumentation.java @@ -32,6 +32,11 @@ public AzureFunctionsInstrumentation() { @Override public String hierarchyMarkerType() { + /* + Due to the class-loading in the Azure Function environment we cannot assume that + "com.microsoft.azure.functions.annotation.FunctionName" will be visible (as in defined as a + resource) for any types using that annotation + */ return null; }