microsoft / ApplicationInsights-dotnet

ApplicationInsights-dotnet
MIT License
565 stars 287 forks source link

Use OWIN middleware to populate custom properties on all telemetry #1630

Closed lmolkova closed 2 years ago

lmolkova commented 6 years ago

Application Insights SDK automatically tracks requests for OWIN apps when they are hosted in IIS

Note: request auto-tracking is not supported for self-hosted OWIN apps, please refer here for manual instrumentation steps.

Some scenarios require telemetry to be enriched with additional properties and a good place to set them for all telemetry is the middleware.

So, properties are added to CallContext/AsyncLocal and then telemetry initializer could picks them up and set on all telemetry.

The problem is that IIS hosted ASP.NET applications cannot rely on CallContext/Async local because of native/managed thread hops. Note: Owin-self hosted apps do not have such issue, this is specific to IIS apps only.

One approach could be to set such properties to CallContext in ASP.NET global Mvc or WebApi ActionFilter instead of middleware.

If you want to do some logging in the middleware and want to have these properties populated on such logs, you may still set CallContext and in the middleware, also store it in HttpContext.Current and restore it in the filter,

Middleware:

        public void Configuration(IAppBuilder app)
        {
            TelemetryClient telemeteryClient = new TelemetryClient();
            app.Use(new Func<AppFunc, AppFunc>(next => (async env =>
            {
                var id = Guid.NewGuid().ToString();
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.Items["myId"] = id;
                }

                CallContext.LogicalSetData("myId", id);

                await next.Invoke(env);
                telemeteryClient.TrackTrace("done!");
            })));
            ConfigureAuth(app);
        }

Filter:

    public class RestoreContextAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (HttpContext.Current != null && CallContext.LogicalGetData("myId") == null)
            {
                CallContext.LogicalSetData("myId", HttpContext.Current.Items["myId"]);
            }
        }
    }

...

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new RestoreContextAttribute ());
            filters.Add(new HandleErrorAttribute());
        }
    }

TelemetryIntitializer

    public class MyContextTelemetryIntitializer : ITelemetryInitializer
    {
        public void Initialize(ITelemetry telemetry)
        {
            string id = null;
            if (HttpContext.Current != null)
            {
                id = HttpContext.Current.Items["myId"]?.ToString();
            }

            if (id == null)
            {
                id = CallContext.LogicalGetData("myId")?.ToString();
            }

            if (id != null)
                telemetry.Context.Properties["myId"] = id;
        }
    }

In future this will be simplified by using Activity.Tags and it will eliminate the need for ActionFilter. For now, it is not reliable in certain cases and would not be possible on machines without .NET 4.7.1 installed.

Version Info

SDK Version : any .NET Version : any .NET Desktop
How Application was onboarded with SDK(VisualStudio/StatusMonitor/Azure Extension) : any OS : windows Hosting Info (IIS/Azure WebApps/ etc) : any IIS hosting

github-actions[bot] commented 2 years ago

This issue is stale because it has been open 300 days with no activity. Remove stale label or comment or this will be closed in 7 days.