How to use NLog in ASP.NET Core 3.1 Azure WebApp – Application Insights – Part 2


In my earlier post, we have discussed about using NLog in ASP.NET Core 3.1 Azure Web Application to write log files to Web App file system.

Azure Web App file system has limited storage, which is dependent upon the the app service plan and the number of instances you are running. Which means the logs files can easily grow and eat the space in the file system. To avoid this we can utilize the the following properties of file target which is available in NLog.

FileTarget.MaxArchiveDays – available from NLog 4.7 – Gets or sets the maximum days of archive files that should be kept. Following configuration will keep only last 30 days old log files and remove rest.

<target xsi:type="File" name="allfile" fileName="${aspnet-appbasepath}\logs\nlog-AspNetCore3-all-${shortdate}.log"
            maxArchiveDays="30"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

FileTarget.MaxArchiveFiles – Gets or sets the maximum number of archive files that should be kept. Following configuration will keep only last 100 old log files and remove rest.

<target xsi:type="File" name="allfile" fileName="${aspnet-appbasepath}\logs\nlog-AspNetCore3-all-${shortdate}.log"
            maxArchiveFiles="100"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

Now, we can try to add Microsoft.ApplicationInsights.NLogTarget, which will send the log files in an application insights resource for additional capabilities.

In order add this target we will first install the NuGet package in our project. This will install Microsoft.ApplicationInsights package as a dependency.

Fig – Add Microsoft.ApplicationInsights.NLogTarget package
Fig – Application Insights package references are added in csproj file

Next thing we need to do, is add a new target in our nlog.config file of type ApplicationInsightsTarget.

<target name="aiTarget" xsi:type="ApplicationInsightsTarget"
     layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}">
      <instrumentationKey>MY-APP-INSIGHTS-KEY</instrumentationKey>
      <contextproperty name="threadid" layout="${threadid}" />
    </target>

And add a rule to use this target.

<logger name="*" minlevel="Trace" writeTo="aiTarget" />

And don’t forget to add the extension to let nlog know where to find this new target

 <extensions>
    <add assembly="Microsoft.ApplicationInsights.NLogTarget"/>
  </extensions>

As you can see from target, there is a node named instrumentationKey, which will hold the instrumentation key of your application insights resource. To get the key, open Azure Portal and go to your application insights resource and copy the instrumentation key and paste there.

Fig – Copy application insights instrumentation key from Azure Portal

Following is my full – updated nlog.config file

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwConfigExceptions="true"
      internalLogLevel="info"
      internalLogFile="${aspnet-appbasepath}\logs\internal-nlog-AspNetCore3.txt">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
    <add assembly="Microsoft.ApplicationInsights.NLogTarget"/>
  </extensions>

  <!-- the targets to write to -->
  <targets>
    <!-- File Target for all log messages with basic details -->
    <target xsi:type="File" name="allfile" fileName="${aspnet-appbasepath}\logs\nlog-AspNetCore3-all-${shortdate}.log"
            maxArchiveFiles="100"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />
    
    <!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
    <target xsi:type="File" name="ownFile-web" fileName="${aspnet-appbasepath}\logs\nlog-AspNetCore3-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|" />

    <!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
    <target xsi:type="Console" name="lifetimeConsole" layout="${level:truncate=4:lowercase=true}: ${logger}[0]${newline}      ${message}${exception:format=tostring}" />

    <target name="aiTarget" xsi:type="ApplicationInsightsTarget"
     layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}">
      <instrumentationKey>e******************c</instrumentationKey>
      <contextproperty name="threadid" layout="${threadid}" />
    </target>
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <!--All logs, including from Microsoft-->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <!--Output hosting lifetime messages to console target for faster startup detection -->
    <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />

    <!--Skip non-critical Microsoft logs and so log only own logs-->
    <logger name="Microsoft.*" maxlevel="Info" final="true" />
    <!-- BlackHole -->

    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
    
    <!-- Write to application insights -->
    <logger name="*" minlevel="Trace" writeTo="aiTarget" />
  </rules>
</nlog>

Okay, we are all set now. Lets run the project.

It might take few minutes for your logs to show in Application Insights resource in Azure, but take a look at the output window of you project. For the actions you want to log to application insights, there will be calls like the one highlighted in the following image.

Fig – Application insights call in output window of Visual Studio

And after few minutes of wait, data showed up in Azure Portal as well.

Fig – Application insights log in Azure Portal

At this point we are able to add application insights target in our ASP.NET Core 3.1 App service web application and log data in Application Insights.

How to use NLog in ASP.NET Core 3.1 Azure WebApp – Part 1


How to configure NLog with ASP.NET Core 3.1 Web App running in Azure App Service web apps, use Application Insights target in nlog.config to push logs into Applications Insights, and how to read the application insights key from Web App configuration instead of hard coding it in nlog.config file.

Following tutorial shows how to configure NLog in ASP.NET Core 3.1 Web Application which is deployed in Azure App Service. I will create another part of the tutorial to show the changes required to include Application Insights.

First create a sample ASP.NET Core 3.1 Web application in Visual Studio 2019.

Fig – Create new ASP.NET Core Web App in Visual Studio 2019

I’ve named my project as ASPNETCore31WebAppNLogAppInsights

Fig – Configure new project
Fig – Chose target framework as .NET Core 3.1
Fig – Project created
Fig – Right click on the project and open ‘Manage NuGet Packages’ page
Fig – Install NLog and NLog.Web.AspNetCore latest packages
Fig – Check .csproj file to ensure the packages are installed then build the project

Create a new file called nlog.config (lowercase all) file in the root of your project and use the sample provided in NLog readme .

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwConfigExceptions="true"
      internalLogLevel="info"
      internalLogFile="c:\temp\internal-nlog-AspNetCore3.txt">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!-- the targets to write to -->
  <targets>
    <!-- File Target for all log messages with basic details -->
    <target xsi:type="File" name="allfile" fileName="c:\temp\nlog-AspNetCore3-all-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

    <!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
    <target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-AspNetCore3-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|" />

    <!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
    <target xsi:type="Console" name="lifetimeConsole" layout="${level:truncate=4:lowercase=true}: ${logger}[0]${newline}      ${message}${exception:format=tostring}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <!--All logs, including from Microsoft-->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <!--Output hosting lifetime messages to console target for faster startup detection -->
    <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />

    <!--Skip non-critical Microsoft logs and so log only own logs-->
    <logger name="Microsoft.*" maxlevel="Info" final="true" />  <!-- BlackHole -->

    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
  </rules>
</nlog>

Generally I right click on the project in Visual Studio solution explorer, and select ‘Open folder in file explorer’ option. When the file explorer is opened, I right click and create a new text document, rename it to nlog.config.

Then open the file in Visual Studio and past the content from NLog readme metioned above.

Fig – Open folder in file explorer
Fig – Create a new text document and name it nlog.config
Fig – nlog.config file in Visual Studio
Fig – ensure ‘Copy to Output Directory’ option as ‘Copy if newer’ chosen for the nlog.config file

Now, open Program.cs file and update the it to use Nlog. Check the NLog readme.

using System;
using NLog.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;

namespace ASPNETCore31WebAppNLogAppInsights
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                logger.Debug("init main");
                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception exception)
            {
                //NLog: catch setup errors
                logger.Error(exception, "Stopped program because of exception");
                throw;
            }
            finally
            {
                // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
                NLog.LogManager.Shutdown();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
              .ConfigureWebHostDefaults(webBuilder =>
              {
                  webBuilder.UseStartup<Startup>();
              })
              .ConfigureLogging(logging =>
              {
                  logging.ClearProviders();
                  logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
              })
              .UseNLog();  // NLog: Setup NLog for Dependency injection
    }
}

Now, build the program and run it. Then open ‘c:\temp\’ path to see the NLog log files. This step ensures that NLog setup is successful.

Fig – NLog log files are present in C:\temp folder
Fig – NLog log content

Now, try to add some log from your controller and check that the logs have been generated for them as well

Fig – Add information log in IndexModel controller
Fig – Logs are showing up

Now publish the project in Azure Web App by right clicking on the project name in solution explorer and selecting ‘Publish’

Fig – Select ‘Azure’ as publish target
Fig – Chose Azure App Service (Windows)
Fig – Create an new Azure App Service
Fig – Add appropriate settings and hit create
Fig – Select the newly created App Service Instance and hit ‘Finish’
Fig – Once te publish settings are configured hit ‘Publish’
Fig – Application is now published
Fig – Open the App service URL in browser

Now, you won’t be able to see your log files since the location were selected in ‘C:\temp’. In order to make it work update the nlog.config file and change the log file starting location as ${aspnet-appbasepath}.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwConfigExceptions="true"
      internalLogLevel="info"
      internalLogFile="c:\temp\internal-nlog-AspNetCore3.txt">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!-- the targets to write to -->
  <targets>
    <!-- File Target for all log messages with basic details -->
    <target xsi:type="File" name="allfile" fileName="${aspnet-appbasepath}\logs\nlog-AspNetCore3-all-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

    <!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
    <target xsi:type="File" name="ownFile-web" fileName="${aspnet-appbasepath}\logs\nlog-AspNetCore3-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|" />

    <!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
    <target xsi:type="Console" name="lifetimeConsole" layout="${level:truncate=4:lowercase=true}: ${logger}[0]${newline}      ${message}${exception:format=tostring}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <!--All logs, including from Microsoft-->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <!--Output hosting lifetime messages to console target for faster startup detection -->
    <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />

    <!--Skip non-critical Microsoft logs and so log only own logs-->
    <logger name="Microsoft.*" maxlevel="Info" final="true" />
    <!-- BlackHole -->

    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
  </rules>
</nlog>

Above is the updated nlog.config file. Now, publish the changes in Azure and browse the site.

Fig – Go to Console in Azure Portal App Services settings and you will see a new folder named ‘logs’ created.
Fig – Log shows the expected content

Now time for Application Insights, which I will cover in the next part.