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

Merged
merged 9 commits into from
May 8, 2025

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.Customers can create a new span in their client function and set that as the current span. In scheduleNewOrchestrationInstance() we read the current span and add the traceparent and tracestate to the CreateInstanceRequest protobuf property.

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.");
           
            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.5.0</version>
        </dependency>

        <dependency>
            <groupId>com.microsoft</groupId>
            <artifactId>durabletask-client</artifactId>
            <version>1.5.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)

@bachuv bachuv marked this pull request as ready for review April 30, 2025 20:33
@bachuv bachuv requested a review from a team as a code owner April 30, 2025 20:33
Copy link
Member

@cgillum cgillum left a comment

Choose a reason for hiding this comment

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

Looks good except for the handling of trace state, which I had some questions on above.

cgillum
cgillum previously approved these changes May 6, 2025
Co-authored-by: Chris Gillum <[email protected]>
Copy link
Member

@cgillum cgillum left a comment

Choose a reason for hiding this comment

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

:shipit:

@bachuv bachuv merged commit abe1449 into main May 8, 2025
4 checks passed
@bachuv bachuv deleted the vabachu/java-tracing-support branch May 8, 2025 16:39
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