使用 Durable Task Scheduler 启用 OpenTelemetry 分布式跟踪

分布式跟踪提供业务流程执行的端到端可见性。 使用 Durable Task Scheduler 启用 OpenTelemetry 时,每个编排、活动和子编排都会生成关联的跨度,显示整个工作流中的计时、排序和错误。 可以将这些跟踪导出到任何与 OpenTelemetry 兼容的后端,例如 Azure Monitor Application Insights、Jaeger 或 Zipkin。

Durable Functions 和独立的 Durable Task SDK 在使用 Durable Task Scheduler 作为后端时,均支持 OpenTelemetry 分布式跟踪。

工作原理

Durable Task SDKs 使用 OpenTelemetry span 自动为业务流程和活动提供检测。 SDK 为每个业务流程创建一个父范围,为每个活动调用、子业务流程和计时器创建子范围。 跟踪上下文会在所有这些操作中自动传播,因此可以获得整个工作流程的单一关联的跟踪。

生成的跟踪树如下所示:

create_orchestration (client)
  └─ orchestration (server)
       ├─ activity:Step1
       ├─ activity:Step2
       └─ activity:Step3

无需将自定义仪表添加到协调器或活动代码中。 将 Microsoft.DurableTask 活动源注册到 OpenTelemetry 配置,SDK 将处理其余内容。

先决条件

  • 具有 Durable Functions 扩展 2.13.0 或更高版本的 Azure Functions 项目。
  • 配置为函数应用的存储后端的持久任务计划程序。
  • 用于查看跟踪的 OpenTelemetry 兼容后端(Application Insights、Jaeger 或其他 OTLP 收集器)。
  • .NET 8 SDK 或更高版本。
  • Microsoft.DurableTask.Worker.AzureManaged和 Microsoft.DurableTask.Client.AzureManaged NuGet 包。
  • OpenTelemetry、OpenTelemetry.Extensions.Hosting 和 OpenTelemetry.Exporter.OpenTelemetryProtocol NuGet 包。
  • 与 OpenTelemetry 兼容的后端,用于查看跟踪,例如用于生产的 Application Insights 或用于本地开发的 Jaeger。

启用分布式跟踪

若要在 Durable Functions 中启用分布式跟踪,请更新 host.json 并配置与 OpenTelemetry 兼容的遥测后端。

更新 host.json

在 tracing 下的 durableTask 文件中添加 节:

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "tracing": {
        "DistributedTracingEnabled": true,
        "Version": "V2"
      }
    }
  }
}

配置 Application Insights

在 APPLICATIONINSIGHTS_CONNECTION_STRING 函数应用中设置环境变量。

对于本地开发,请将其添加到 local.settings.json:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "APPLICATIONINSIGHTS_CONNECTION_STRING": "<your-connection-string>"
  }
}

对于Azure托管的应用,请在 Azure 门户中的 Configuration 下将其添加为应用程序设置。

注意

如果您之前使用过 APPINSIGHTS_INSTRUMENTATIONKEY,请切换到 APPLICATIONINSIGHTS_CONNECTION_STRING 以获得最新功能。

减少遥测干扰

若要防止 Application Insights 采样跟踪数据,请从 Request中的采样规则中排除:

{
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  }
}

将 Microsoft.DurableTask 活动源注册到 OpenTelemetry 配置。 当您注册此源时,Durable Task SDK 会自动为协调和活动创建时间跨度。

在工作进程的 Program.cs 中,使用持久任务活动源添加 OpenTelemetry 跟踪:

using Microsoft.DurableTask;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = Host.CreateApplicationBuilder(args);

// Configure OpenTelemetry tracing
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService("durable-worker"))
    .WithTracing(tracing =>
    {
        tracing
            .AddSource("Microsoft.DurableTask")
            .AddOtlpExporter(opts =>
            {
                opts.Endpoint = new Uri(
                    Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
                    ?? "http://localhost:4317");
            });
    });

// Build connection string from environment variables
string endpoint = Environment.GetEnvironmentVariable("ENDPOINT") ?? "http://localhost:8080";
string taskHub = Environment.GetEnvironmentVariable("TASKHUB") ?? "default";
string connectionString = endpoint.Contains("localhost")
    ? $"Endpoint={endpoint};TaskHub={taskHub};Authentication=None"
    : $"Endpoint={endpoint};TaskHub={taskHub};Authentication=DefaultAzure";

// Configure Durable Task worker
builder.Services.AddDurableTaskWorker()
    .AddTasks(tasks =>
    {
        tasks.AddOrchestratorFunc<string, string>(
            "OrderProcessingOrchestration", async (ctx, input) =>
        {
            var validated = await ctx.CallActivityAsync<string>("ValidateOrder", input);
            var payment = await ctx.CallActivityAsync<string>("ProcessPayment", validated);
            var shipment = await ctx.CallActivityAsync<string>("ShipOrder", payment);
            var result = await ctx.CallActivityAsync<string>("SendNotification", shipment);
            return result;
        });

        tasks.AddActivityFunc<string, string>("ValidateOrder", (ctx, input) =>
            Task.FromResult($"Validated({input})"));
        tasks.AddActivityFunc<string, string>("ProcessPayment", (ctx, input) =>
            Task.FromResult($"Paid({input})"));
        tasks.AddActivityFunc<string, string>("ShipOrder", (ctx, input) =>
            Task.FromResult($"Shipped({input})"));
        tasks.AddActivityFunc<string, string>("SendNotification", (ctx, input) =>
            Task.FromResult($"Notified({input})"));
    })
    .UseDurableTaskScheduler(connectionString);

var host = builder.Build();
await host.RunAsync();

关键行是 .AddSource("Microsoft.DurableTask"),这告诉 OpenTelemetry 捕获 Durable Task SDK 发出的 span。

配置 OTLP 终结点

上面的代码片段引用 OTEL_EXPORTER_OTLP_ENDPOINT 环境变量来设置跟踪数据的目标。 根据后端设置此变量:

Backend 终结点值 协议
杰格(本地) http://localhost:4317 gRPC
Jaeger (本地,HTTP) http://localhost:4318 HTTP/protobuf
OpenTelemetry 收集器 http://<collector-host>:4317 gRPC
Azure Monitor(通过 OTLP) 请改用Azure Monitor导出程序 不适用

对于使用 Jaeger 进行本地开发时,如果 Jaeger 启用 OTLP gRPC 运行(端口 4317),则默认配置 http://localhost:4317 可以正常工作。 JavaScript SDK 默认使用 HTTP/protobuf,因此它将连接到端口4318的/v1/traces路径。

使用 Jaeger UI 在本地查看追踪

若要进行本地开发,请使用持久任务计划程序模拟器及 Jaeger 来查看跟踪。 使用一个 docker-compose.yml 来启动两个服务:

services:
  dts-emulator:
    image: mcr.microsoft.com/dts/dts-emulator:latest
    ports:
      - "8080:8080"  # gRPC
      - "8082:8082"  # Dashboard
  jaeger:
    image: jaegertracing/jaeger:latest
    ports:
      - "16686:16686"  # Jaeger UI
      - "4317:4317"    # OTLP gRPC
      - "4318:4318"    # OTLP HTTP

启动基础结构:

docker compose up -d

运行应用程序后,在 http://localhost:16686 打开 Jaeger UI,并搜索您的服务名称(例如,durable-worker)以查看跟踪。

对于使用 Durable Functions 进行本地开发,分布式跟踪数据默认发送到 Application Insights。 若要在不进行部署的情况下查看本地跟踪,可以在函数应用的 Program.cs ,在 Application Insights 旁边添加一个 OTLP 导出程序。

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing
            .AddSource("Microsoft.DurableTask")
            .AddOtlpExporter(opts =>
            {
                opts.Endpoint = new Uri("http://localhost:4317");
            });
    });

然后在本地运行 Jaeger,并在 docker run -d -p 16686:16686 -p 4317:4317 jaegertracing/jaeger:latest 其中 http://localhost:16686打开 Jaeger UI。

在 Application Insights 中查看跟踪

对于生产工作负荷, 建议使用 Application Insights 遥测后端。

当在DistributedTracingEnabled中将true设置为Version并将V2设置为host.json之后,Durable Functions 应用程序会向 Application Insights 发出相关的跨度。 若要查看 Azure 门户中的完整编排跟踪,请进行以下操作:

  1. 请在 Azure 门户中访问您的 Application Insights 资源。
  2. 打开 事务搜索 ,按名字或实例标识搜索编排。
  3. 选择一条追踪以查看端到端事务及其所有关联跨度。

跟踪显示编排为父跨度,子跨度对应于每个活动调用、子编排和计时器等待。 以下模式生成不同的跟踪形状:

Pattern 跟踪形状
函数链式调用 顺序活动在协调器范围下嵌套。
扇出/扇入 并行活动在时间上重叠。
人际互动 一个执行协调的跨度,需要长时间等待外部事件。
Monitor 活动重复周期,计时器在各次迭代之间进行等待。

将 OTLP 导出程序配置为使用 Azure Monitor OpenTelemetry 导出程序将追踪数据发送到 Application Insights,或者通过 OTLP 将其导出到一个 OpenTelemetry 收集器,然后再转发到 Application Insights。

安装 Azure.Monitor.OpenTelemetry.Exporter NuGet 包,并将 OTLP 导出程序替换为:

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService("durable-worker"))
    .WithTracing(tracing =>
    {
        tracing
            .AddSource("Microsoft.DurableTask")
            .AddAzureMonitorTraceExporter(opts =>
            {
                opts.ConnectionString = Environment.GetEnvironmentVariable(
                    "APPLICATIONINSIGHTS_CONNECTION_STRING");
            });
    });

跟踪数据的显示内容

持久任务 SDK 生成的跟踪数据包括:

范围类型 Description
create_orchestration 当安排新编排时发出的客户端追踪跨度
orchestration 涵盖完整执行生命周期的服务器端编排跨度
activity:<name> 每个活动调用的时间间隔,显示计时和结果
sub_orchestration:<name> 每个子编排调用的跨度
timer 持久计时器等待的时间段

每个范围包括属性,例如durabletask.type,durabletask.task.name和durabletask.task.instance_iddurabletask.task.task_id。 失败的活动和编排流程包含在跨度状态和事件中的错误详细信息。

故障排除

問题 解决方案
没有任何痕迹出现 检查是否已注册 Microsoft.DurableTask 活动源并可访问导出器终结点。
跟踪日志不完整 请确保在初始化 Durable Task SDK(尤其是在 JavaScript/TypeScript 中)之前初始化 OpenTelemetry SDK。
Application Insights 中缺少时间段 禁用或调整 采样设置 ,以防止删除跟踪数据。
跟踪信息不相关 检查是否使用持久任务计划程序作为后端。 跟踪上下文传播需要调度器。

代码示例