You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在OpenTelemetry中能否通过独立请求设置Span的开始与结束时间

针对Azure Logic Apps引入链路追踪的可行解决方案

你当前实现生成的链路效果如下:
链路效果示意图

现有实现的核心问题

  • 你当前代码中每次调用SendTrace都会新建TracerProvider和ActivitySource,这两个对象设计上是应用生命周期内的单例资源,重复创建会产生不必要的性能开销,也会导致链路上下文无法复用。
  • 你在创建Span后立刻执行Stop()和ForceFlush(),所以Span直接被上报到链路服务,自然无法后续更新结束时间和附加属性,这不是OpenTelemetry规范的限制,是你的实现逻辑导致的。

可行解决方案

方案1:拆分Span创建与上报逻辑(适合工作流耗时<Function App超时阈值场景)

不需要用Durable Function,只要拆分两个接口即可:

  1. 根Span创建接口:Logic Apps首次调用时,创建并启动根Span,将TraceId、SpanId、以及Span的缓存Key返回给Logic Apps,此时不停止、不上报Span,将Span实例存在内存缓存或者分布式缓存中。
  2. Span结束更新接口:Logic Apps所有嵌套步骤执行完成后,调用该接口传入缓存Key、自定义属性、执行结果等信息,取出缓存中的Span,补充属性、设置结束时间后停止Span,再执行Flush上报即可。

方案2:旁路构造Span上报(适合长耗时工作流场景)

完全不依赖.NET Activity的生命周期管理,更符合Logic Apps无状态多步骤的特性:

  1. 首次调用Function App时,直接生成符合W3C trace context规范的TraceId和根SpanId,返回给Logic Apps透传到后续所有步骤。
  2. 每个Logic Apps步骤执行完成后,将当前步骤的父SpanId、自身耗时、执行状态、标签等信息上报给Function App。
  3. Function App侧直接按照OpenTelemetry的Span数据格式构造所有Span数据,批量上报给Zipkin/Jaeger后端即可,不需要维护Span的内存状态。

优化后基础代码示例

using System.Diagnostics;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using OpenTelemetry;
using OpenTelemetry.Trace;
using System.Collections.Concurrent;

public static class JaegerHelper
{
    // 单例声明TracerProvider和ActivitySource
    private static readonly TracerProvider _tracerProvider = Sdk.CreateTracerProviderBuilder()
        .SetSampler(new AlwaysOnSampler())
        .AddSource("JaegerHelper")
        .AddZipkinExporter(config =>
        {
            config.Endpoint = new Uri("http://127.0.0.1:10003/api/v2/spans");
        })
        .Build();
    private static readonly ActivitySource _activitySource = new ActivitySource("JaegerHelper");
    // 内存缓存存储未上报的Span,长耗时场景可以替换为Azure Redis Cache
    private static readonly ConcurrentDictionary<string, Activity> _pendingSpans = new();

    // 根Span创建接口
    [Function("CreateRootSpan")]
    public static HttpResponseData CreateRootSpan([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
    {
        var activity = _activitySource.StartActivity("LogicAppRoot", ActivityKind.Server);
        var spanKey = Guid.NewGuid().ToString("N");
        _pendingSpans.TryAdd(spanKey, activity);
        
        var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
        response.WriteAsJsonAsync(new
        {
            TraceId = activity.TraceId.ToString(),
            RootSpanId = activity.SpanId.ToString(),
            SpanCacheKey = spanKey
        });
        return response;
    }

    // 结束并上报根Span接口
    [Function("FinishRootSpan")]
    public static HttpResponseData FinishRootSpan([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
    {
        var requestData = req.ReadFromJsonAsync<FinishSpanRequest>().Result;
        if (_pendingSpans.TryRemove(requestData.SpanCacheKey, out var activity))
        {
            // 附加自定义属性
            foreach (var tag in requestData.Tags)
            {
                activity.SetTag(tag.Key, tag.Value);
            }
            // 设置状态
            activity.SetStatus(requestData.IsSuccess ? Status.Ok : Status.Error);
            // 停止Span,自动设置结束时间
            activity.Stop();
            // 上报
            _tracerProvider.ForceFlush();
        }
        return req.CreateResponse(System.Net.HttpStatusCode.OK);
    }

    public class FinishSpanRequest
    {
        public string SpanCacheKey { get; set; }
        public bool IsSuccess { get; set; }
        public Dictionary<string, object> Tags { get; set; }
    }
}

内容的提问来源于stack exchange,提问作者Spaceman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 03:36:04