在旧 ASP.NET 4.8 中读取和传播 TraceId

Kli*_*nki 2 c# asp.net

我正在维护几个用 ASP.NET 4.8(完整框架)和 ASP.NET Core 编写的应用程序。

.NET Core 实现了https://www.w3.org/TR/trace-context/,因此我可以看到我的日志(我使用 Serilog 进行结构化日志记录),其中包含 ParentId、SpanId 以及最重要的 TraceId。

有没有办法在旧的 ASP.NET 4.8 应用程序中读取和传播 TraceId?

我的应用程序之间正在执行大量请求,这将极大地改善调试体验。不幸的是,大多数请求源自旧的 ASP.NET 4.8 应用程序,并转到较新的 .NET Core 应用程序。

理想情况下,我希望达到与 ASP.NET Core 应用程序相同的状态 - 如果 Request-Id 来自 HTTP 标头,则使用它并将其填充到 ParentId 和 TraceId 中,并基于此生成 SpanId。此外,它还会进一步传播到源自 .NET Core 应用程序的其他 HTTP 请求。

谢谢!

Kli*_*nki 5

好吧,我自己设法解决了这个问题。

首先,需要添加System.Diagnostics.DiagnosticSourcenugget包。

然后,创建ActivityManager类:

public static class ActivityManager
{
    public static void StartActivity()
    {
        if (Activity.Current == null)
        {
            var activity = new Activity("Default Activity");

            string parentIdFromHeaders = HttpContext.Current?.Request.Headers[GetRequestIdHeaderName()];
            if (!string.IsNullOrEmpty(parentIdFromHeaders))
            {
                activity.SetParentId(parentIdFromHeaders);
            }

            activity.Start();
            Activity.Current = activity;

            // Sometimes I had issues with Activity.Current being empty even though I set it
            // So just to be sure, I add it also to HttpContext Items.
            HttpContext.Current?.Items.Add("Activity", activity);
        }
    }

    public static void StopActivity()
    {
        GetActivity()?.Stop();
    }

    public static Activity GetActivity()
    {
        Activity activity = Activity.Current ?? (Activity)HttpContext.Current.Items["Activity"];
        return activity;
    }

    public static string GetRequestIdHeaderName()
    {
        return "Request-Id";
    }

    public static string GetRequestId()
    {
        Activity activity = GetActivity();

        if (activity != null)
        {
            string activityId = activity.Id;
            return activityId;
        }

        // For the rare cases when something happens and activity is not set
        // Try to read Request-Id first, if none, then create new GUID
        return HttpContext.Current?.Request.Headers.Get(GetRequestIdHeaderName())
                ?? Guid.NewGuid().ToString().Replace("-", "");
    }
}
Run Code Online (Sandbox Code Playgroud)

并配置Global.asax.cs处理程序

protected void Application_Start()
{
    Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical;
}

protected void Application_BeginRequest()
{
    ActivityManager.StartActivity();
}

protected void Application_EndRequest()
{
    ActivityManager.StopActivity();
}
Run Code Online (Sandbox Code Playgroud)

现在,所有传入请求都会创建新的Activity并正确设置其ParentId.

为了添加SpanId,TraceIdParentId到日志记录上下文,我创建了自定义日志丰富器:

public class TraceLogEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        Activity activity = ActivityManager.GetActivity();

        if (activity != null)
        {
            string parentId = activity.ParentId;
            string rootId = activity.RootId;
            string activityId = activity.Id;

            logEvent.AddPropertyIfAbsent(new LogEventProperty("SpanId", new ScalarValue(activityId)));
            logEvent.AddPropertyIfAbsent(new LogEventProperty("ParentId", new ScalarValue(parentId)));
            logEvent.AddPropertyIfAbsent(new LogEventProperty("TraceId", new ScalarValue(rootId)));           }
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到 Serilog 配置中.Enrich.With<TraceLogEnricher>()

最后一步是配置传出请求。正在HttpClient使用DefaultRequestHeaders。(为了更方便,也可以在IHttpClientFactory配置中配置)。

var requestId = ActivityManager.GetRequestId();
client.DefaultRequestHeaders.Add("Request-Id", requestId);
Run Code Online (Sandbox Code Playgroud)

如果使用 Web 服务,最好Request-Id也向 Web 服务请求添加标头。为此,请覆盖GetWebRequestWeb 服务客户端的方法。(这些通常生成为分部类,因此请在您自己的分部类文件中覆盖它)。

protected override WebRequest GetWebRequest(Uri uri)
{
    var request = base.GetWebRequest(uri);

    request.Headers.Add("Request-Id", ActivityManager.GetRequestId());
    
    return request;
}
Run Code Online (Sandbox Code Playgroud)