diff --git a/CHANGELOG.md b/CHANGELOG.md index 87977e6f..8d2483ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## Unreleased +* Add client operation correlation logging: `FunctionInvocationId` is now propagated via gRPC metadata to the host for client operations, enabling correlation with host logs. ## v1.6.2 * Fixing gRPC channel shutdown ([#249](https://github.com/microsoft/durabletask-java/pull/249)) diff --git a/azurefunctions/build.gradle b/azurefunctions/build.gradle index ca7c1a00..e1523372 100644 --- a/azurefunctions/build.gradle +++ b/azurefunctions/build.gradle @@ -10,6 +10,7 @@ version = '1.6.2' archivesBaseName = 'durabletask-azure-functions' def protocVersion = '3.12.0' +def grpcVersion = '1.59.0' repositories { maven { @@ -21,12 +22,21 @@ dependencies { api project(':client') implementation group: 'com.microsoft.azure.functions', name: 'azure-functions-java-library', version: '3.0.0' implementation "com.google.protobuf:protobuf-java:${protocVersion}" + implementation "io.grpc:grpc-api:${grpcVersion}" compileOnly "com.microsoft.azure.functions:azure-functions-java-spi:1.0.0" + + testImplementation(platform('org.junit:junit-bom:5.7.2')) + testImplementation('org.junit.jupiter:junit-jupiter') + testImplementation('org.mockito:mockito-core:4.11.0') } sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 +test { + useJUnitPlatform() +} + publishing { repositories { maven { diff --git a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java index a952db68..a1206572 100644 --- a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java +++ b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java @@ -11,6 +11,7 @@ import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; import com.microsoft.durabletask.OrchestrationMetadata; import com.microsoft.durabletask.OrchestrationRuntimeStatus; +import com.microsoft.durabletask.azurefunctions.internal.FunctionInvocationIdInterceptor; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; @@ -29,6 +30,7 @@ public class DurableClientContext { private String taskHubName; private String requiredQueryStringParameters; private DurableTaskClient client; + private String functionInvocationId; /** * Gets the name of the client binding's task hub. @@ -39,6 +41,18 @@ public String getTaskHubName() { return this.taskHubName; } + /** + * Sets the function invocation ID for correlation with host-side logs. + *
+ * Call this method before calling {@link #getClient()} to enable correlation
+ * between client operations and host-side logs.
+ *
+ * @param invocationId the Azure Functions invocation ID
+ */
+ public void setFunctionInvocationId(String invocationId) {
+ this.functionInvocationId = invocationId;
+ }
+
/**
* Gets the durable task client associated with the current function invocation.
*
@@ -56,7 +70,14 @@ public DurableTaskClient getClient() {
throw new IllegalStateException("The client context RPC base URL was invalid!", ex);
}
- this.client = new DurableTaskGrpcClientBuilder().port(rpcURL.getPort()).build();
+ DurableTaskGrpcClientBuilder builder = new DurableTaskGrpcClientBuilder().port(rpcURL.getPort());
+
+ // Add interceptor for function invocation ID correlation if set
+ if (this.functionInvocationId != null && !this.functionInvocationId.isEmpty()) {
+ builder.addInterceptor(new FunctionInvocationIdInterceptor(this.functionInvocationId));
+ }
+
+ this.client = builder.build();
return this.client;
}
diff --git a/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/internal/FunctionInvocationIdInterceptor.java b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/internal/FunctionInvocationIdInterceptor.java
new file mode 100644
index 00000000..539f0ee3
--- /dev/null
+++ b/azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/internal/FunctionInvocationIdInterceptor.java
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.microsoft.durabletask.azurefunctions.internal;
+
+import io.grpc.*;
+
+/**
+ * A gRPC client interceptor that adds the Azure Functions invocation ID to outgoing calls
+ * for correlation with host-side logs.
+ */
+public final class FunctionInvocationIdInterceptor implements ClientInterceptor {
+ private static final String INVOCATION_ID_METADATA_KEY_NAME = "x-azure-functions-invocationid";
+ private static final Metadata.Key
+ * Interceptors can be used to add custom headers, logging, or other cross-cutting concerns
+ * to gRPC calls. Multiple interceptors can be added and will be applied in the order they
+ * were added.
+ *
+ * @param interceptor the gRPC client interceptor to add
+ * @return this builder object
+ */
+ public DurableTaskGrpcClientBuilder addInterceptor(ClientInterceptor interceptor) {
+ if (interceptor != null) {
+ this.interceptors.add(interceptor);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the list of interceptors that have been added to this builder.
+ *
+ * @return an unmodifiable list of interceptors
+ */
+ List