{"id":714,"date":"2016-07-05T20:38:53","date_gmt":"2016-07-05T20:38:53","guid":{"rendered":"http:\/\/tpodolak.com\/blog\/?p=714"},"modified":"2016-07-05T20:38:53","modified_gmt":"2016-07-05T20:38:53","slug":"nlog-tracking-flow-of-requests-using-mappeddiagnosticslogicalcontext","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2016\/07\/05\/nlog-tracking-flow-of-requests-using-mappeddiagnosticslogicalcontext\/","title":{"rendered":"NLog &#8211; tracking flow of requests using MappedDiagnosticsLogicalContext"},"content":{"rendered":"<h3>1. Problem<\/h3>\n<p>Everyone who has ever had to analyze logs from production environment knows, that quite often it is not an easy task. Usually application flow is deduced by log entries matched by thread id, which is problematic when you deal with multithreading scenarios. Say for example we have following piece of code<\/p>\n<pre lang=\"csharp\">\r\npublic class HomeController : Controller\r\n{\r\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\r\n    \r\n    public async Task<ActionResult> Index()\r\n    {\r\n        await PreprocessRequest(nameof(Index));\r\n        return View();\r\n    }\r\n    \r\n    private static async Task PreprocessRequest(string actionName)\r\n    {\r\n        Logger.Info($\"Log from {actionName} action, from thread={Thread.CurrentThread.ManagedThreadId}\");\r\n        \r\n        await Task.Run(() => Logger.Info($\"Log from {actionName}, from thread {Thread.CurrentThread.ManagedThreadId} assigned by scheduler\"))\r\n                  .ConfigureAwait(continueOnCapturedContext: false);\r\n        Logger.Info($\"Log from {actionName} action, from async continuation, from thread {Thread.CurrentThread.ManagedThreadId}\");\r\n               \r\n        var thread =\r\n            new Thread(() => Logger.Info($\"Log from {actionName} action, from explicitly created thread {Thread.CurrentThread.ManagedThreadId}\"));\r\n        thread.Start();\r\n        thread.Join();\r\n    }\r\n}\r\n<\/pre>\n<p>Default log file usually looks like that<\/p>\n<pre lang=\"bash\">\r\nTimeStamp=2016-07-03 11:56:02.2327 ThreadId=5 Level=INFO Message=About to start GET \/ request\r\nTimeStamp=2016-07-03 11:56:02.3797 ThreadId=5 Level=INFO Message=Log from Index action, from thread=5\r\nTimeStamp=2016-07-03 11:56:02.3797 ThreadId=6 Level=INFO Message=Log from Index, from thread 6 assigned by scheduler\r\nTimeStamp=2016-07-03 11:56:02.3797 ThreadId=6 Level=INFO Message=Log from Index action, from async continuation, from thread 6\r\nTimeStamp=2016-07-03 11:56:02.3917 ThreadId=14 Level=INFO Message=Log from Index action, from explicitly created thread 14\r\nTimeStamp=2016-07-03 11:56:04.8250 ThreadId=8 Level=INFO Message=Request completed with status code: 200 \r\n<\/pre>\n<p>Having multiple simultaneous <i>Index<\/i> requests, makes it almost impossible to figure out which log entry is part of which call. Fortunately with <i>NLog<\/i> it is possible to solve this problem without too much of a hassle. <\/p>\n<h3>2. Proposed solution <\/h3>\n<p>The whole idea is to leverage <i>MappedDiagnosticsLogicalContext<\/i> &#8220;storage&#8221; to keep some unique identifier of request for entire call. Note, that this identifier will be accessible also from other threads created from within given call. In order to set unique request id (also known as correlation id) I will use custom <i>HttpModule<\/i>. The very first implementation looks like that<\/p>\n<pre lang=\"csharp\">\r\npublic class LoggingHttpModule : IHttpModule\r\n{\r\n    private const string CorrelationIdKey = \"correlationid\";\r\n\r\n    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();\r\n\r\n    public void Init(HttpApplication context)\r\n    {\r\n        context.BeginRequest += HandleBeginRequest;\r\n        context.EndRequest += HandleEndRequest;\r\n    }\r\n\r\n    public void Dispose()\r\n    {\r\n    }\r\n\r\n    private void HandleBeginRequest(object sender, EventArgs e)\r\n    {\r\n        var value = Guid.NewGuid().ToString();\r\n        var httpApplication = (HttpApplication) sender;\r\n        var httpContext = httpApplication.Context;\r\n        MappedDiagnosticsLogicalContext.Set(CorrelationIdKey, value);\r\n        Logger.Info($\"About to start {httpContext.Request.HttpMethod} {httpContext.Request.RawUrl} request\");\r\n    }\r\n\r\n    private void HandleEndRequest(object sender, EventArgs e)\r\n    {\r\n        var httpApplication = (HttpApplication) sender;\r\n        var httpContext = httpApplication.Context;\r\n        Logger.Info($\"Request completed with status code: {httpContext.Response.StatusCode} \");\r\n    }\r\n}\r\n<\/pre>\n<p>Once the <i>HttpModule<\/i> is ready it is necessary to register it in <i>web.config<\/i>.<\/p>\n<pre lang=\"xml\">\r\n<!--rest of the code omitted -->\r\n<system.webServer>\r\n  <modules>\r\n  <!--rest of the code omitted -->\r\n    <add name=\"LoggingHttpModule\" type=\"AspNetMvcLoggingWithCorrelationId.HttpModules.LoggingHttpModule\"\/>\r\n  <\/modules>\r\n<\/system.webServer>\r\n<!--rest of the code omitted -->\r\n<system.web>\r\n<!--rest of the code omitted -->\r\n  <httpModules>\r\n  <!--rest of the code omitted -->\r\n    <add name=\"ApplicationInsightsWebTracking\" type=\"Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule,Microsoft.AI.Web\"\/>\r\n  <\/httpModules>\r\n<\/system.web>\r\n<\/pre>\n<p>From now on, every request goes through <i>LogginHttpModule<\/i> and proper <i>correlationId<\/i> is set in <i>MappedDiagnosticsLogicalContext<\/i>.<br \/>\nAs we want to have <i>correlationId<\/i> included in log files it is necessary to modify <i>Nlog.config<\/i>. In my case it looks like that<\/p>\n<pre lang=\"xml\">\r\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<nlog xmlns=\"http:\/\/www.nlog-project.org\/schemas\/NLog.xsd\"\r\n      xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\"\r\n      xsi:schemaLocation=\"http:\/\/www.nlog-project.org\/schemas\/NLog.xsd NLog.xsd\"\r\n      autoReload=\"true\"\r\n      throwExceptions=\"false\"\r\n      internalLogLevel=\"Off\" internalLogFile=\"c:\\temp\\nlog-internal.log\" >\r\n\r\n  <variable name=\"myvar\" value=\"myvalue\"\/>\r\n  <targets>\r\n    <target xsi:type=\"File\" name=\"f\" fileName=\"${basedir}\/logs\/${shortdate}.log\"\r\n            layout=\"TimeStamp=${longdate} CorrelationId=${mdlc:item=correlationid} Level=${uppercase:${level}} Message=${message}\" \/>\r\n  <\/targets>\r\n\r\n  <rules>\r\n    <logger name=\"*\" minlevel=\"Debug\" writeTo=\"f\" \/>\r\n  <\/rules>\r\n<\/nlog>\r\n<\/pre>\n<p>Note that we can access our <i>correlationId<\/i> variable via <i>${mdlc:item=correlationid}<\/i><br \/>\nLog file after the changes looks like that<\/p>\n<pre lang=\"bash\">\r\nTimeStamp=2016-07-03 12:41:35.7292 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=About to start GET \/ request\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index action, from thread=6\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index, from thread 7 assigned by scheduler\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index action, from async continuation, from thread 7\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index action, from explicitly created thread 9\r\nTimeStamp=2016-07-03 12:41:38.4454 CorrelationId= Level=INFO Message=Request completed with status code: 200\r\n<\/pre>\n<p>This is almost perfect, however for some reasons <i>correlationId<\/i> is lost in <i>EndRequest<\/i> event handler. I am not sure if this is bug in <i>NLog<\/i> (I&#8217;ve posted question in <i>Nlog&#8217;s<\/i> github) or it is by design, however there is easy workaround for that.  We can store our <i>correlationId<\/i> in <i>HttpContext.Items<\/i> collection and reassign it to <i>MappedDiagnosticsLogicalContext<\/i> in <i>EndRequest<\/i> event handler.<\/p>\n<pre lang=\"csharp\">\r\nprivate void HandleBeginRequest(object sender, EventArgs e)\r\n{\r\n    var value = Guid.NewGuid().ToString();\r\n    var httpApplication = (HttpApplication) sender;\r\n    var httpContext = httpApplication.Context;\r\n    MappedDiagnosticsLogicalContext.Set(CorrelationIdKey, value);\r\n\r\n    httpContext.Items[CorrelationIdKey] = value;\r\n    Logger.Info($\"About to start {httpContext.Request.HttpMethod} {httpContext.Request.RawUrl} request\");\r\n}\r\n\r\nprivate void HandleEndRequest(object sender, EventArgs e)\r\n{\r\n    var httpApplication = (HttpApplication) sender;\r\n    var httpContext = httpApplication.Context;\r\n\r\n    var correlationId = httpContext.Items[CorrelationIdKey].ToString();\r\n    MappedDiagnosticsLogicalContext.Set(CorrelationIdKey, correlationId);\r\n\r\n    Logger.Info($\"Request completed with status code: {httpContext.Response.StatusCode} \");\r\n}\r\n<\/pre>\n<p>After these changes our log file finally looks OK<\/p>\n<pre lang=\"bash\">\r\nTimeStamp=2016-07-03 12:41:35.7292 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=About to start GET \/ request\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index action, from thread=6\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index, from thread 7 assigned by scheduler\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index action, from async continuation, from thread 7\r\nTimeStamp=2016-07-03 12:41:35.8752 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Log from Index action, from explicitly created thread 9\r\nTimeStamp=2016-07-03 12:41:38.4454 CorrelationId=b322d1fa-47b2-4582-b7a9-c9463b7c0a47 Level=INFO Message=Request completed with status code: 200 \r\n<\/pre>\n<p>Source code for this post can be found <a href=\"https:\/\/github.com\/tpodolak\/Blog\/tree\/master\/AspNetMvcLoggingWithCorrelationId\">here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Problem Everyone who has ever had to analyze logs from production environment knows, that quite often it is not an easy task. Usually application flow is deduced by log entries matched by thread id, which is problematic when you deal with multithreading scenarios. Say for example we have following piece of code public class [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,300],"tags":[299,298,297],"class_list":["post-714","post","type-post","status-publish","format-standard","hentry","category-net-4-5","category-nlog","tag-logging","tag-mappeddiagnosticslogicalcontext","tag-nlog"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/714","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=714"}],"version-history":[{"count":17,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/714\/revisions"}],"predecessor-version":[{"id":731,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/714\/revisions\/731"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=714"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=714"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=714"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}