Skip to content

Add tracing support to send information to the WebJobs extension #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

bachuv
Copy link
Contributor

@bachuv bachuv commented Apr 16, 2025

Issue describing the changes in this PR

This PR adds distributed tracing support to send traceparent and tracestate information to the WebJobs extension. It adds traceParent and traceState properties to NewOrchestrationInstanceOptions which users can pass when they are scheduling a new orchestration instance. With that information, we add it to the CreateInstanceRequest protobuf property.

It's a draft PR for now to see if we can remove the additional properties to NewOrchestrationInstanceOptions and instead read the current span from scheduleNewOrchestrationInstance().

I followed the instructions at https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto?tabs=app-insights&pivots=programming-language-java to test.

Sample Durable Functions code:

package com.function;

import java.util.Optional;
import java.util.logging.Level;

import com.azure.monitor.opentelemetry.exporter.AzureMonitorExporterBuilder;
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.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import com.microsoft.durabletask.DurableTaskClient;
import com.microsoft.durabletask.NewOrchestrationInstanceOptions;
import com.microsoft.durabletask.TaskOrchestrationContext;
import com.microsoft.durabletask.azurefunctions.DurableActivityTrigger;
import com.microsoft.durabletask.azurefunctions.DurableClientContext;
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;

@FunctionName("StartOrchestration")
    public HttpResponseMessage startOrchestration(
            @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
            @DurableClientInput(name = "durableContext") DurableClientContext durableContext,
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");

        Tracer tracer = initTracer();
        Span span = tracer.spanBuilder("StartOrchestration").setSpanKind(SpanKind.CLIENT).startSpan();
    
        try (Scope scope = span.makeCurrent()) {
            context.getLogger().info("Java HTTP trigger processed a request.");

            // Extract traceparent from the span
            String traceparent = String.format("00-%s-%s-01",
                span.getSpanContext().getTraceId(),
                span.getSpanContext().getSpanId()
            );

            // Extract tracestate if available
            String tracestate = span.getSpanContext().getTraceState().isEmpty() ? null : span.getSpanContext().getTraceState().toString();

            // Create NewOrchestrationInstanceOptions and set traceparent and tracestate
            NewOrchestrationInstanceOptions options = new NewOrchestrationInstanceOptions();
            options.setTraceParent(traceparent);
            options.setTraceState(tracestate);
           
            DurableTaskClient client = durableContext.getClient();
            String instanceId = client.scheduleNewOrchestrationInstance("Cities", options);
            context.getLogger().log(Level.INFO, "Created new Java orchestration with instance ID = {0}", instanceId);
            return durableContext.createCheckStatusResponse(request, instanceId);
        } finally {
            span.end();
        }
    }

  /**
     * This is the orchestrator function, which can schedule activity functions, create durable timers,
     * or wait for external events in a way that's completely fault-tolerant.
     */
    @FunctionName("Cities")
    public String citiesOrchestrator(
            @DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
        String result = "";
        result += ctx.callActivity("Capitalize", "Tokyo", String.class).await() + ", ";
        result += ctx.callActivity("Capitalize", "London", String.class).await() + ", ";
        result += ctx.callActivity("Capitalize", "Seattle", String.class).await() + ", ";
        result += ctx.callActivity("Capitalize", "Austin", String.class).await();
        return result;
    }

    /**
     * This is the activity function that gets invoked by the orchestration.
     */
    @FunctionName("Capitalize")
    public String capitalize(
            @DurableActivityTrigger(name = "name") String name,
            final ExecutionContext context) {
        context.getLogger().info("Capitalizing: " + name);
        return name.toUpperCase();
    }

   private static Tracer initTracer() {
        String connectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING");
    
        SpanExporter exporter = new AzureMonitorExporterBuilder()
            .connectionString(connectionString)
            .buildTraceExporter();
    
        SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
        .addSpanProcessor(BatchSpanProcessor.builder(exporter).build())
        .setResource(Resource.create(
            Attributes.of(AttributeKey.stringKey("service.name"), "java-functions")
        ))
        .build();
    
        return OpenTelemetrySdk.builder()
            .setTracerProvider(tracerProvider)
            .build()
            .getTracer("java-functions");
    }

pom.xml dependencies

<dependencies>
        <dependency>
            <groupId>com.microsoft.azure.functions</groupId>
            <artifactId>azure-functions-java-library</artifactId>
            <version>${azure.functions.java.library.version}</version>
        </dependency>
        <!-- Test -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.4.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.23.4</version>
            <scope>test</scope>
        </dependency>

        <!-- Durable -->
        <dependency>
            <groupId>com.microsoft</groupId>
            <artifactId>durabletask-azure-functions</artifactId>
            <version>1.8.0</version>
        </dependency>

        <dependency>
            <groupId>com.microsoft</groupId>
            <artifactId>durabletask-client</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!-- Jackson Core -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.15.3</version>
        </dependency>

        <!-- Jackson Databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.3</version>
        </dependency>

        <!-- Jackson Annotations -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.15.3</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.59.0</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.59.0</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.59.0</version>
        </dependency>

        <!-- Tracing -->

        <!-- OpenTelemetry API (core tracing API) -->
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <version>1.34.0</version>
        </dependency>

        <!-- OpenTelemetry SDK (required for manual span creation and exporting) -->
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk</artifactId>
            <version>1.34.0</version>
        </dependency>

        <!-- OpenTelemetry Context Propagation -->
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-context</artifactId>
            <version>1.34.0</version>
        </dependency>

        <!-- Azure Monitor Exporter for OpenTelemetry -->
        <dependency>
            <groupId>com.azure</groupId>
            <artifactId>azure-monitor-opentelemetry-exporter</artifactId>
            <version>1.0.0-beta.16</version>
        </dependency>

    </dependencies>
image

Pull request checklist

  • My changes do not require documentation changes
    • Otherwise: Documentation issue linked to PR
  • My changes are added to the CHANGELOG.md
  • I have added all required tests (Unit tests, E2E tests)

@@ -12,6 +12,8 @@ public final class NewOrchestrationInstanceOptions {
private String instanceId;
private Object input;
private Instant startTime;
private String traceParent;
private String traceState;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the draft PR description you mentioned that you were going to investigate removing these properties from the start options. What was the result of that investigation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants