-
Notifications
You must be signed in to change notification settings - Fork 52
Add OpenTelemetry sample and update deps #637
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
Changes from all commits
02f9638
ff03198
d61afbf
55f978f
0644db7
1c0fb34
268f4f6
70c80e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
torosent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.Hosting" /> | ||
| <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" /> | ||
| <PackageReference Include="OpenTelemetry.Extensions.Hosting" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <!-- Using p2p references so we can show latest changes in samples. --> | ||
| <ProjectReference Include="$(SrcRoot)Client/AzureManaged/Client.AzureManaged.csproj" /> | ||
| <ProjectReference Include="$(SrcRoot)Worker/AzureManaged/Worker.AzureManaged.csproj" /> | ||
| <ProjectReference Include="$(SrcRoot)Analyzers/Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| // This sample demonstrates how to configure OpenTelemetry distributed tracing with the Durable Task SDK. | ||
| // Traces are exported to Jaeger via OTLP, allowing you to visualize orchestration execution in the Jaeger UI. | ||
|
|
||
| using Microsoft.DurableTask; | ||
| using Microsoft.DurableTask.Client; | ||
| using Microsoft.DurableTask.Client.AzureManaged; | ||
| using Microsoft.DurableTask.Worker; | ||
| using Microsoft.DurableTask.Worker.AzureManaged; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Hosting; | ||
| using OpenTelemetry.Resources; | ||
| using OpenTelemetry.Trace; | ||
|
|
||
| HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); | ||
|
|
||
| // Read the DTS emulator connection string from configuration. | ||
| string connectionString = builder.Configuration.GetValue<string>("DURABLE_TASK_SCHEDULER_CONNECTION_STRING") | ||
| ?? throw new InvalidOperationException("DURABLE_TASK_SCHEDULER_CONNECTION_STRING is not set."); | ||
| // Configure OpenTelemetry tracing. | ||
| // The Durable Task SDK automatically emits traces using the "Microsoft.DurableTask" ActivitySource. | ||
| // We subscribe to that source and export traces to Jaeger via OTLP. | ||
| builder.Services.AddOpenTelemetry() | ||
| .ConfigureResource(resource => resource.AddService("DistributedTracingSample")) | ||
| .WithTracing(tracing => | ||
| { | ||
| tracing.AddSource("Microsoft.DurableTask"); | ||
| tracing.AddOtlpExporter(); | ||
| }); | ||
|
|
||
| // Configure the Durable Task worker with a fan-out/fan-in orchestration and activities. | ||
| builder.Services.AddDurableTaskWorker(workerBuilder => | ||
| { | ||
| workerBuilder.AddTasks(tasks => | ||
| { | ||
| tasks.AddOrchestratorFunc("FanOutFanIn", async context => | ||
| { | ||
| // Fan-out: schedule multiple activity calls in parallel. | ||
| string[] cities = ["Tokyo", "London", "Seattle", "Paris", "Sydney"]; | ||
| List<Task<string>> parallelTasks = new(); | ||
| foreach (string city in cities) | ||
| { | ||
| parallelTasks.Add(context.CallActivityAsync<string>("GetWeather", city)); | ||
| } | ||
|
|
||
| // Fan-in: wait for all activities to complete. | ||
| string[] results = await Task.WhenAll(parallelTasks); | ||
|
|
||
| // Aggregate results in a final activity. | ||
| string summary = await context.CallActivityAsync<string>("CreateSummary", results); | ||
| return summary; | ||
| }); | ||
|
|
||
| tasks.AddActivityFunc<string, string>("GetWeather", (context, city) => | ||
| { | ||
| // Simulate fetching weather data for a city. | ||
| // Use uint cast to avoid negative modulo results (Math.Abs(int.MinValue) overflows). | ||
| string[] conditions = ["Sunny", "Cloudy", "Rainy", "Snowy", "Windy"]; | ||
| uint hash = (uint)city.GetHashCode(); | ||
| string condition = conditions[hash % conditions.Length]; | ||
| int temperature = 15 + (int)(hash % 20); | ||
| return $"{city}: {condition}, {temperature}°C"; | ||
| }); | ||
|
|
||
| tasks.AddActivityFunc<string[], string>("CreateSummary", (context, forecasts) => | ||
| { | ||
| return $"Weather report for {forecasts.Length} cities:\n" + string.Join("\n", forecasts); | ||
| }); | ||
| }); | ||
|
|
||
| workerBuilder.UseDurableTaskScheduler(connectionString); | ||
| }); | ||
|
|
||
| // Configure the Durable Task client to connect to the DTS emulator. | ||
| builder.Services.AddDurableTaskClient(clientBuilder => | ||
| { | ||
| clientBuilder.UseDurableTaskScheduler(connectionString); | ||
| }); | ||
|
|
||
| IHost host = builder.Build(); | ||
| await host.StartAsync(); | ||
|
|
||
| // Schedule the orchestration and wait for it to complete. | ||
| await using DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>(); | ||
| string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("FanOutFanIn"); | ||
| Console.WriteLine($"Started orchestration instance: '{instanceId}'"); | ||
|
|
||
| using CancellationTokenSource timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); | ||
| OrchestrationMetadata result = await client.WaitForInstanceCompletionAsync( | ||
| instanceId, | ||
| getInputsAndOutputs: true, | ||
torosent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| timeoutCts.Token); | ||
|
|
||
| Console.WriteLine($"Orchestration completed with status: {result.RuntimeStatus}"); | ||
| Console.WriteLine($"Output:\n{result.ReadOutputAs<string>()}"); | ||
| Console.WriteLine(); | ||
| Console.WriteLine("View traces in Jaeger UI at http://localhost:16686"); | ||
| Console.WriteLine("View orchestrations in DTS Emulator dashboard at http://localhost:8082"); | ||
| Console.WriteLine("Look for service 'DistributedTracingSample' to see the orchestration trace."); | ||
|
|
||
| await host.StopAsync(); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,86 @@ | ||||||||||||||||
| # Distributed Tracing with OpenTelemetry Sample | ||||||||||||||||
|
|
||||||||||||||||
| This sample demonstrates how to configure OpenTelemetry distributed tracing with the Durable Task SDK. Traces are exported to Jaeger via OTLP, allowing you to visualize the full execution flow of orchestrations and activities. | ||||||||||||||||
|
|
||||||||||||||||
| ## Overview | ||||||||||||||||
|
|
||||||||||||||||
| The Durable Task SDK automatically emits traces using the `Microsoft.DurableTask` [ActivitySource](https://learn.microsoft.com/dotnet/api/system.diagnostics.activitysource). This sample subscribes to that source using the OpenTelemetry SDK and exports traces to Jaeger using the OTLP exporter. | ||||||||||||||||
|
|
||||||||||||||||
| The sample runs a **fan-out/fan-in** orchestration that: | ||||||||||||||||
| 1. Fans out to 5 parallel `GetWeather` activity calls (one per city) | ||||||||||||||||
| 2. Fans in by waiting for all activities to complete | ||||||||||||||||
| 3. Calls a `CreateSummary` activity to aggregate the results | ||||||||||||||||
|
|
||||||||||||||||
| This pattern produces a rich trace with multiple parallel spans, making it easy to visualize in Jaeger. | ||||||||||||||||
|
|
||||||||||||||||
| ## Prerequisites | ||||||||||||||||
|
|
||||||||||||||||
| - .NET 10.0 SDK or later | ||||||||||||||||
| - [Docker](https://www.docker.com/get-started) and [Docker Compose](https://docs.docker.com/compose/) | ||||||||||||||||
|
|
||||||||||||||||
| ## Running the Sample | ||||||||||||||||
|
|
||||||||||||||||
| ### 1. Start Jaeger and the DTS Emulator | ||||||||||||||||
|
|
||||||||||||||||
| From this directory, start the infrastructure containers: | ||||||||||||||||
|
|
||||||||||||||||
| ```bash | ||||||||||||||||
| docker compose up -d | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| This starts: | ||||||||||||||||
| - **Jaeger** — UI at [http://localhost:16686](http://localhost:16686), OTLP receiver on ports 4317 (gRPC) and 4318 (HTTP) | ||||||||||||||||
| - **DTS Emulator** — gRPC endpoint on port 8080, dashboard UI at [http://localhost:8082](http://localhost:8082) | ||||||||||||||||
|
|
||||||||||||||||
| ### 2. Run the Sample | ||||||||||||||||
|
|
||||||||||||||||
| ```bash | ||||||||||||||||
| dotnet run | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| The sample connects to the DTS emulator by default using the connection string: | ||||||||||||||||
| ``` | ||||||||||||||||
| Endpoint=http://localhost:8080;Authentication=None;TaskHub=default | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| To override, set the `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` environment variable: | ||||||||||||||||
|
Comment on lines
+41
to
+46
|
||||||||||||||||
| The sample connects to the DTS emulator by default using the connection string: | |
| ``` | |
| Endpoint=http://localhost:8080;Authentication=None;TaskHub=default | |
| ``` | |
| To override, set the `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` environment variable: | |
| Before running, configure the DTS emulator connection string using the `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` environment variable. For the local emulator started via Docker Compose, use: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| services: | ||
| jaeger: | ||
| image: jaegertracing/jaeger:latest | ||
torosent marked this conversation as resolved.
Show resolved
Hide resolved
torosent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ports: | ||
| - "16686:16686" # Jaeger UI | ||
| - "4317:4317" # OTLP gRPC receiver | ||
| - "4318:4318" # OTLP HTTP receiver | ||
| environment: | ||
| - COLLECTOR_OTLP_ENABLED=true | ||
|
|
||
| dts-emulator: | ||
| image: mcr.microsoft.com/dts/dts-emulator:latest | ||
torosent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ports: | ||
| - "8080:8080" # DTS emulator gRPC endpoint | ||
| - "8082:8082" # DTS emulator dashboard UI | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |||||
| </ItemGroup> | ||||||
|
|
||||||
| <ItemGroup> | ||||||
| <PackageReference Include="Google.Protobuf" VersionOverride="3.33.2" /> | ||||||
| <PackageReference Include="Google.Protobuf" VersionOverride="3.33.5" /> | ||||||
|
||||||
| <PackageReference Include="Google.Protobuf" VersionOverride="3.33.5" /> | |
| <PackageReference Include="Google.Protobuf" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Google.Protobuf is centrally versioned (and already updated to 3.33.5). This VersionOverride matches the central version, so it's redundant—consider removing it and letting central package management control the version.