ASP.NET MVC 4/5/6中的W3C TraceId传播

ny6fqffe  于 2023-04-08  发布在  .NET
关注(0)|答案(1)|浏览(227)

我在ASP.NETMVC5和6上有一些旧的应用程序,我想完成一些类似ASP.NETCore所做的自动W3C跟踪ID传播的功能。(例如,阅读传入的traceparent请求标头,相应地设置Activity.Current属性。将值存储在请求上下文中并不理想,因为它不会自动对其他任务可用,但这将是一个很好的开始)。
我想有一些中间件,在每个控制器之前运行并设置Activity。要创建这个中间件,我需要使用OWIN吗?或者在ASP.NET MVC中有一些内置的东西,我可以使用它来运行代码,以便在将请求提交给控制器方法之前读取请求?
我尝试设置一个OWIN中间件来尝试设置Activity.Current属性和OwinContext。我不知道我做得对不对,但是Activity.Current总是null,控制器中的Request.GetOwinContext()调用也总是null,即使我可以在它从OWIN中间件传递到控制器之前看到值,并且线程ID在Startup.cs中的lambda函数和ValuesController.Get方法中是相同的。
看起来OWIN中间件甚至在控制器被调用之前就已经退出了,也许这就是原因吧?

// Startup.cs
using Microsoft.Owin;
using Owin;
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using System.Web.Http;

[assembly: OwinStartup(typeof(ActivityDemo.Startup))]

namespace ActivityDemo
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            Activity.DefaultIdFormat = ActivityIdFormat.W3C;
            app.Use(async (ctx, next) =>
            {
                ActivityContext activityCtx = default;
                string traceparent = ctx.Request.Headers.Get("traceparent");
                if (traceparent != null)
                {
                    try
                    {
                        string tracestate = ctx.Request.Headers.Get("tracestate");
                        ActivityContext.TryParse(traceparent, tracestate, out activityCtx);
                    }
                    catch { }
                }
                // We depend on the activity being set (for logging with Serilog),
                // so create one manually even if no one is listening to the
                // ActivitySource.
                Activity activity = new Activity("ReceiveHttpRequest").SetParentId(traceparent).Start();

                // ctx.Request.Set("traceparent", traceparent);
                ctx.Environment["traceparent"] = traceparent;
                var current = Activity.Current; // inserted to inspect with debugger
                await next();
            });
            var config = new HttpConfiguration();
            config.MapHttpAttributeRoutes();
            app.UseWebApi(config);
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
            // app.Run();
        }
    }
}
// ValuesController.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace ActivityDemo.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            var activity = Activity.Current;
            var owinContext = Request.GetOwinContext(); // inserted to inspect with debugger, always `null`
            var owinEnv = Request.GetOwinEnvironment(); // inserted to inspect with debugger, always `null`
            return new string[] { "value1", "value2", Activity.Current?.TraceId.ToString() };
        }
    }
}

这只是我尝试的第一件事我对非OWIN解决方案也持开放态度。

bjp0bcyl

bjp0bcyl1#

我想我找到了!
1.将System.Diagnostics.DiagnosticSourceSystem.Reactive.Core包添加到ASP.NET项目中。
1.创建一个新文件App_Start\DiagnosticsConfig.cs,包含以下内容:

// DiagnosticsConfig.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web;

namespace DiagnosticsTest
{
    public class DiagnosticsConfig
    {
        // Set up an ActivitySource
        private static readonly AssemblyName AssemblyName =
            typeof(DiagnosticsConfig).Assembly.GetName();
        internal static readonly ActivitySource ActivitySource =
            new ActivitySource(AssemblyName.Name, AssemblyName.Version.ToString());

        public static void StartDiagnosticsListeners()
        {
            // Set the format to W3C
            Activity.DefaultIdFormat = ActivityIdFormat.W3C;
            Activity.ForceDefaultIdFormat = true;
            
            // Start the ActivityListener
            ActivitySource.AddActivityListener(new ActivityListener()
            {
                Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
                ShouldListenTo = _ => true,
            }) ;

            // Using the Subscribe(IObservable<T>, Action<T>) extension method from
            // the System.Reactive.Core package.
            var subscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener)
            {
                if (listener.Name == "System.Net.Http.Desktop")
                {
                    listener.Subscribe(delegate (KeyValuePair<string, object> diagnostic)
                    {
                        if (
                            diagnostic.Key == "System.Net.Http.Desktop.HttpRequestOut.Start"
                            && Activity.Current != null
                        )
                        {
                            // Set the parent to workaround HttpDiagnosticListener adding
                            // an extra intermediate span.
                            HttpWebRequest request = diagnostic.Value.GetType()
                                .GetRuntimeProperty("Request")
                                .GetValue(diagnostic.Value) as HttpWebRequest;
                            request.Headers.Add("traceparent", Activity.Current.ParentId);
                            var tracestate = Activity.Current.TraceStateString;
                            if (!string.IsNullOrWhiteSpace(tracestate))
                            {
                                request.Headers.Add("tracestate", tracestate);
                            }
                        }
                    });
                }
                
                // You can listen to other DiagnosticSources by adding more
                // handlers here.
            });
        }
    }
}

1.解析Global.Asax.cstraceparentApplication_BeginRequest()Application_EndRequest()方法。另外,注册侦听器。

// Global.asax.cs

using System.Diagnostics;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace DiagnosticsTest
{
    public class WebApiApplication : HttpApplication
    {
        protected void Application_Start()
        {
            // <Other startup boilerplate>
            // ...
            
            // Register your DiagnosticSource
            DiagnosticsConfig.StartDiagnosticsListeners();
        }

        protected void Application_BeginRequest()
        {
            // Parse traceparent on incoming requests.
            var ctx = HttpContext.Current;
            const string operationName = "MyCompany.AspNet.HandleRequest";

            var traceParent = ctx.Request.Headers["traceparent"];
            var traceState = ctx.Request.Headers["tracestate"];
            Activity activity = (traceParent != null && ActivityContext.TryParse(traceParent, traceState, out ActivityContext activityCtx))
                ? DiagnosticsConfig.ActivitySource.StartActivity(operationName, ActivityKind.Server, activityCtx)
                : DiagnosticsConfig.ActivitySource.StartActivity(operationName, ActivityKind.Server);
            ctx.Items["activity"] = activity;
        }

        protected void Application_EndRequest()
        {
            // Clean up the Activity at the end of the request.
            var ctx = HttpContext.Current;
            var activity = (Activity)ctx.Items["activity"];
            activity.Stop();
        }
    }
}

之后,您应该将Activity.Current设置为Controller代码/日志的传入traceparent头,并且来自System.Net.Http的传出请求将自动开始一个新的span。

相关问题