{"id":1336,"date":"2018-03-01T10:00:34","date_gmt":"2018-03-01T08:00:34","guid":{"rendered":"http:\/\/tpodolak.com\/blog\/?p=1336"},"modified":"2018-02-28T22:38:44","modified_gmt":"2018-02-28T20:38:44","slug":"asp-net-core-custom-iloggert-nlog-callsite-layout-renderer-issue","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2018\/03\/01\/asp-net-core-custom-iloggert-nlog-callsite-layout-renderer-issue\/","title":{"rendered":"ASP.NET Core \u2013  custom ILogger&lt;T&gt; and Nlog callsite layout renderer issue"},"content":{"rendered":"<h3>1. Introduction<\/h3>\n<p>In my current project, we use <i>Microsoft.Extensions.Logging.ILogger&lt;T&gt;<\/i> default implementation as our logging mechanism. This basically comes with the <i>ASP.NET Core 2.0<\/i> package and it was working fine so far. However recently we decided to switch the default implementation with a custom one, due to the fact that we wanted to have some additional logic applied for all of the logging providers. <\/p>\n<h3>2. Implementation<\/h3>\n<p>The custom implementation itself is very similar to the original one, we came up with something like that<\/p>\n<pre lang=\"csharp\">\r\npublic class Logger<T> : ILogger<T>\r\n{\r\n    private readonly ILogger _logger;\r\n\r\n    public Logger(ILoggerFactory loggerFactory)\r\n    {\r\n        _logger = loggerFactory.CreateLogger<T>();\r\n    }\r\n\r\n    public void Log<TState>(LogLevel, EventId, TState state, Exception, Func<TState, Exception, string> formatter)\r\n    {\r\n        string Formatter(TState innserState, Exception innerException)\r\n        {\r\n            \/\/ additional logic goes here, in my case that was extracting additional information from custom exceptions\r\n            var message = formatter(innserState, innerException) ?? string.Empty;\r\n            return message + \" additional stuff in here\";\r\n        }\r\n\r\n        _logger.Log(logLevel, eventId, state, exception, Formatter);\r\n    }\r\n\r\n    public bool IsEnabled(LogLevel logLevel)\r\n    {\r\n        return _logger.IsEnabled(logLevel);\r\n    }\r\n\r\n    public IDisposable BeginScope<TState>(TState state)\r\n    {\r\n        return _logger.BeginScope(state);\r\n    }\r\n}\r\n<\/pre>\n<p>and we replaced the default logger with this code<\/p>\n<pre lang=\"csharp\">\r\nservices.Replace(new ServiceDescriptor(typeof(ILogger<>), typeof(Logger.Logger<>), ServiceLifetime.Singleton));\r\n<\/pre>\n<p>As you can see we basically wanted to add information from custom exceptions to the message. Due to the fact that this is added in here, we don\u2019t have to modify all of our existing logging providers. <\/p>\n<h3>3. <i>NLog<\/i> call site renderer issue<\/h3>\n<p>Once we run the application all of the providers were working fine and were producing expected output apart from <i>NLog<\/i> provider. Originally <i>NLog<\/i> provider for the following code<\/p>\n<pre lang=\"csharp\">\r\n[Route(\"api\/[controller]\")]\r\npublic class ValuesController : Controller\r\n{\r\n    private readonly ILogger<ValuesController> _logger;\r\n\r\n    public ValuesController(ILogger<ValuesController> logger)\r\n    {\r\n        _logger = logger;\r\n    }\r\n\r\n    [HttpGet]\r\n    public IEnumerable<string> Get()\r\n    {\r\n        _logger.LogTrace(\"Get method called trace\");\r\n        _logger.LogWarning(\"Get method called warning\");\r\n        _logger.LogError(\"Get method called error\");\r\n        _logger.LogInformation(\"Get method called information\");\r\n\r\n        return new string[] {\"value1\", \"value2\"};\r\n    }\r\n}\r\n<\/pre>\n<p>was producing this output<\/p>\n<pre lang=\"bash\">\r\nTimeStamp=2018-02-28 20:14:37.2711 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Controllers.ValuesController.Get Level=TRACE Message=Get method called trace \r\nTimeStamp=2018-02-28 20:14:37.3261 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Controllers.ValuesController.Get Level=WARN Message=Get method called warning \r\nTimeStamp=2018-02-28 20:14:37.3261 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Controllers.ValuesController.Get Level=ERROR Message=Get method called error \r\nTimeStamp=2018-02-28 20:14:37.3261 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Controllers.ValuesController.Get Level=INFO Message=Get method called information \r\n<\/pre>\n<p>However, after introducing new implementation of <i>ILogger&lt;T&gt;<\/i>, it started appending wrong caller member name<\/p>\n<pre lang=\"bash\">\r\nTimeStamp=2018-02-28 20:17:27.4044 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Logger`1.Log Level=TRACE Message=Get method called trace additional stuff in here \r\nTimeStamp=2018-02-28 20:17:27.5117 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Logger`1.Log Level=WARN Message=Get method called warning additional stuff in here \r\nTimeStamp=2018-02-28 20:17:27.5117 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Logger`1.Log Level=ERROR Message=Get method called error additional stuff in here \r\nTimeStamp=2018-02-28 20:17:27.5202 Callsite=AspNetCoreCustomLoggerAndNLogCallsiteIssue.Logger`1.Log Level=INFO Message=Get method called information additional stuff in here \r\n<\/pre>\n<p>After some digging, it turned out that the issue lies in the (indirect)configuration of <i>NLog&#8217;s<\/i> <a href=\"https:\/\/github.com\/NLog\/NLog\/wiki\/Callsite-layout-renderer\">callsite layout renderer<\/a>. Long story short, when you add <i>NLog<\/i> provider to your logger providers, under the hood it adds <i>Microsoft.Extensions.Logging.Abstractions<\/i> assembly(among others) to something called hidden assembly collection. Once the <i>NLog<\/i> tries to figure out what is the caller member name, it crawls the stack trace to find a method name. If the method in current frame comes from a hidden assembly, logger skips it and continues iteration. As we changed the <i>Logger&lt;T&gt;<\/i> to our custom one, it is no longer part of <i>Microsoft.Extensions.Logging.Abstractions<\/i> assembly, so the <i>NLog<\/i> reports <i>Log<\/i> method from our custom logger as a caller member name. <\/p>\n<h3>4. The fix <\/h3>\n<p>In order to restore previous behavior you have to add custom logger assembly to the hidden assembly collection. You can do it via<\/p>\n<pre lang=\"csharp\">\r\nLogManager.AddHiddenAssembly(typeof(Logger.Logger<>).Assembly);\r\n<\/pre>\n<p>Of course, if the logger is located in the same assembly as rest of your code, this might not be the perfect solution &#8211; as this will impact the way the callsite is rendered for other parts of your code in this assembly. In that case, you might consider moving the logging piece to separate assembly or resign from using callsite layout renderer at all. We decided to replace it with <a href=\"https:\/\/github.com\/NLog\/NLog\/wiki\/Logger-Layout-Renderer\">logger layout renderer<\/a> as callsite layout renderer, either way, wouldn\u2019t be used in production environments. <\/p>\n<p>Source code for this post can be found <a href=\"https:\/\/github.com\/tpodolak\/Blog\/tree\/master\/AspNetCoreCustomLoggerAndNLogCallsiteIssue\">here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Introduction In my current project, we use Microsoft.Extensions.Logging.ILogger&lt;T&gt; default implementation as our logging mechanism. This basically comes with the ASP.NET Core 2.0 package and it was working fine so far. However recently we decided to switch the default implementation with a custom one, due to the fact that we wanted to have some additional [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[317],"tags":[318,299],"class_list":["post-1336","post","type-post","status-publish","format-standard","hentry","category-asp-net-core","tag-asp-net-core","tag-logging"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1336","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=1336"}],"version-history":[{"count":11,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1336\/revisions"}],"predecessor-version":[{"id":1347,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1336\/revisions\/1347"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=1336"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=1336"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=1336"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}