Integrar Log4Net con Application Insights en ASP.NET Core

Muchos de nuestros clientes utilizan diferentes librerías para generar las trazas de sus aplicativos. En el mundo .NET por ahora me suelo encontrar Log4Net y Serilog mayormente. En este artículo te cuento cómo integrar Log4Net con Application Insights para aplicaciones en ASP.NET Core, para que puedas reaprovechar las trazas que ya tenías configuradas y centralizarlas en este servicio.

Aplicación de ejemplo

La aplicación que he utilizado para hacer esta demo la puedes descargar de este repositorio de GitHub. Básicamente tiene las siguientes trazas de Log4Net con diferentes niveles, a modo de ejemplo (lo ideal es que tuvieras una clase centralizada que gestionara el logging, por si el día de mañana lo quieres cambiar):

using log4net;
using Log4NetLovesAppInsights.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

namespace Log4NetLovesAppInsights.Controllers
{
    public class HomeController : Controller
    {
        ILog logger = LogManager.GetLogger("debug");

        public HomeController()
        {
            logger.Debug($"{nameof(HomeController)} instanciated.");
        }

        public IActionResult Index()
        {
            logger.Info("/Index");

            return View();
        }

        public IActionResult Privacy()
        {
            logger.Warn("Warn! Privacy zone!");

            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            logger.Error("Error action");

            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Para ello, he tenido que instalar la librería de Log4Net para ASP.NET Core:

Instalar el paquete nuget Microsoft.Extensions.Logging.Log4Net.AspNetCore

Y habilitar en el archivo Startup.cs, en el método Configure, la opción logger.AddLog4Net():

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Log4NetLovesAppInsights
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory logger)
        {
            logger.AddLog4Net(); //Enable Log4Net

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Por último, tengo un archivo de configuración llamado log4net.config con un appender por el momento, que vuelca las trazas en un archivo:

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingLogFileAppender"
      type="log4net.Appender.RollingFileAppender">
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
    <file value="C:\Logs\Log4NetLovesAppInsights.log" />
    <datePattern value="yyyy-MM-dd.'txt'"/>
    <staticLogFileName value="false"/>
    <appendToFile value="true"/>
    <rollingStyle value="Date"/>
    <maxSizeRollBackups value="100"/>
    <maximumFileSize value="15MB"/>
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern
        value="%date [%thread] %-5level App  %newline %message %newline %newline"/>
    </layout>
  </appender>
  <root>
    <level value="INFO"/>   
    <appender-ref ref="RollingLogFileAppender"/>
  </root>
</log4net>

Crear un recurso de Application Insights

Ahora que ya tenemos un ejemplo base, lo siguiente que necesito es habilitar Application Insights. La forma más sencilla de hacerlo desde Visual Studio es haciendo clic sobre el apartado Connected Services del proyecto y hacer clic sobre Add Connected Service.

Add Connected Service para configurar Application Insights

En la parte superior de la nueva ventana verás el apartado de configuración de Application Insights, que es bastante sencillo de seguir.

La forma de integrar Application Insights con Log4Net se hace de la misma forma que cuando utilizas otros destinos: a través de un appender. Para ello es necesario instalar Microsoft.ApplicationInsights.Log4NetAppender:

Nuget Microsoft.ApplicationInsights.Log4NetAppender

Modifica el archivo log4net.config con un nuevo appender llamado aiAppender:

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingLogFileAppender"
      type="log4net.Appender.RollingFileAppender">
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
    <file value="C:\Logs\Log4NetLovesAppInsights.log" />
    <datePattern value="yyyy-MM-dd.'txt'"/>
    <staticLogFileName value="false"/>
    <appendToFile value="true"/>
    <rollingStyle value="Date"/>
    <maxSizeRollBackups value="100"/>
    <maximumFileSize value="15MB"/>
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern
        value="%date [%thread] %-5level App  %newline %message %newline %newline"/>
    </layout>
  </appender>
  <appender name="aiAppender" type="Microsoft.ApplicationInsights.Log4NetAppender.ApplicationInsightsAppender, Microsoft.ApplicationInsights.Log4NetAppender">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%message%newline" />
    </layout>
    <threshold value="DEBUG" />
    <InstrumentationKey value="YOUR_INSTRUMENTATION_KEY" />
  </appender>
  <root>
    <level value="INFO"/>
    <appender-ref ref="aiAppender" />
    <appender-ref ref="RollingLogFileAppender"/>
  </root>
</log4net>

Como ves, es necesario añadir el instrumentation key generado para tu recurso, el cual puedes localizar en appsettings.json. Si vuelves a ejecutar la aplicación, verás que además de la telemetría propia generada por Application Insights, también aparecerán las trazas que tenías definidas con Log4Net:

Trazas de Log4Net en Application Insights

El código de ejemplo lo tienes en mi GitHub.

¡Saludos!