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

ASP.NET Core:从OpenTelemetry生成Server Timing响应头

问题(翻译后):

我希望借助Server Timing API(一组HTTP头)告知客户端(浏览器)后端执行的操作及其耗时。我的ASP.NET Core 6应用已通过OpenTelemetry完成埋点,可收集数据库请求及自定义应用操作的指标。我想在请求管道末尾创建一个中间件,用于查看已收集的指标/追踪数据并生成对应的响应头,但不知如何访问这些已收集的指标,是否需要创建Exporter?


解决方案:无需额外Exporter,直接通过Activity上下文获取追踪数据

OpenTelemetry在ASP.NET Core中基于System.Diagnostics.Activity实现追踪逻辑,你完全不需要额外创建Exporter就能拿到当前请求内的所有埋点数据。下面是具体实现步骤:

1. 确认OpenTelemetry追踪配置正确

先确保你的OpenTelemetry配置已经启用了ASP.NET Core基础追踪、数据库追踪(比如SQL Client)以及自定义操作的追踪源:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddSqlClientInstrumentation()
        .AddSource("你的自定义操作Source名称"));

2. 编写自定义中间件提取数据并生成Server Timing头

在请求管道末尾(必须放在UseRouting和UseEndpoints之后,才能捕获所有后续操作的追踪数据)添加中间件,通过Activity.Current获取当前请求的根Activity,遍历其所有子Activity,提取操作名称和耗时,构造Server-Timing响应头。

示例中间件代码:

public class ServerTimingMiddleware
{
    private readonly RequestDelegate _next;

    public ServerTimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        await _next(context);

        // 仅在请求成功时添加头,可根据需求调整
        if (context.Response.StatusCode is >= 200 and < 400)
        {
            var rootActivity = Activity.Current?.Root;
            if (rootActivity == null) return;

            var timingEntries = new List<string>();
            // 添加整个请求的总耗时
            timingEntries.Add($"total;dur={rootActivity.Duration.TotalMilliseconds:F2};desc=\"请求总耗时\"");

            // 遍历子Activity,提取数据库操作、自定义操作等
            foreach (var activity in rootActivity.EnumerateChildren())
            {
                if (IsActivityRelevant(activity))
                {
                    var escapedDesc = Uri.EscapeDataString(activity.DisplayName);
                    timingEntries.Add($"{activity.OperationName};dur={activity.Duration.TotalMilliseconds:F2};desc=\"{escapedDesc}\"");
                }
            }

            if (timingEntries.Any())
            {
                context.Response.Headers.Append("Server-Timing", string.Join(", ", timingEntries));
            }
        }
    }

    // 自定义过滤逻辑:只保留需要展示的操作
    private bool IsActivityRelevant(Activity activity)
    {
        return activity.Source.Name is "Microsoft.Data.SqlClient" or "你的自定义操作Source名称"
               || activity.Tags.Any(tag => tag.Key == "db.system");
    }
}

// 扩展方法方便注册中间件
public static class ServerTimingMiddlewareExtensions
{
    public static IApplicationBuilder UseServerTiming(this IApplicationBuilder app)
    {
        return app.UseMiddleware<ServerTimingMiddleware>();
    }
}

3. 在Program.cs中注册中间件

将中间件放在UseEndpoints之后,确保能捕获所有接口、数据库操作的追踪数据:

var app = builder.Build();

// 其他中间件配置(比如静态文件、认证授权等)...

app.UseRouting();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

// 注册Server Timing中间件
app.UseServerTiming();

app.Run();

关键细节说明

  • 无需Exporter的原因:Exporter的作用是将追踪数据发送到外部监控系统(如Jaeger、Prometheus),而我们需要的是当前请求内存中的实时追踪数据,直接通过Activity上下文即可获取,完全不需要导出操作。
  • 自定义操作的捕获:如果你的自定义操作是通过ActivitySource.StartActivity创建的,只要在IsActivityRelevant方法中包含对应的Source名称,就能捕获到这些操作的耗时数据。
  • 性能优化:遍历Activity子节点的开销极低,不会影响请求性能。如果请求存在大量无关的小操作,可以在过滤方法中添加耗时阈值(比如只保留耗时超过10ms的操作),减少响应头的体积。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:25:13