Integrating log4net and Using AdoNetAppender in ASP.NET Core

While there is a default logging feature provided by the ASP.NET Core, you would have to register the providers via code, as in:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
    .ConfigureLogging(logging =>
    {
        // clear default logging providers
        logging.ClearProviders();

        logging.AddConsole();  
        logging.AddDebug();
        logging.AddEventLog();
        // add more providers here
    })
    .UseStartup<Startup>();

Now, if you’ve used some logging frameworks such as log4net or Enterprise Library, you would find that using a file to configure how your application logs messages provides more flexibility as to where to store your logs and what information you want to include. As of this writing, log4net has some support to ASP.NET Core with a few appenders that are not yet available as of the moment (e.g. AdoNetAppender).

Normally, configuration in ASP.NET Core is inside your appsettings.json. Configuration for log4net is still using xml as in any other platforms. Thus, you have two options – either to integrate it on your web.config file or create a new config file. In this sample, we are going to create a new config file called log4net.config:

<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="true">
  <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="Logs/WebApp.log" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="10MB" />
    <staticLogFileName value="true" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <layout type="log4net.Layout.PatternLayout">
      <!-- Format is [date/time] [log level] [thread] message-->
      <conversionPattern value="[%date] [%level] [%thread] %m%n" />
    </layout>
  </appender>

  <root>
    <level value="ALL" />
    <appender-ref ref="RollingLogFileAppender" />
  </root>
</log4net>

This is a simple configuration with no customization on the log message. We will explore custom patterns in a later post. Here, we are using a RollingLogFileAppender with a minimal lock, i.e. for each log entry, it will open, write, and close the file immediately.

Next, we need to add the log4net package to our project. Select your project on the Solution Explorer > right click > Manage NuGet Packages. Go to the Browse tab, search for log4net, and click Install.

Let’s now load the configuration in our code. Open Program.cs and add the following at the first part of your Main() method:

public class Program
{
    public static void Main(string[] args)
    {
        XmlDocument log4netConfig = new XmlDocument();
        log4netConfig.Load(File.OpenRead("log4net.config"));
        log4net.Config.XmlConfigurator.Configure(log4net.LogManager.GetRepository(Assembly.GetEntryAssembly()), log4netConfig["log4net"]);

        CreateWebHostBuilder(args).Build().Run();
    }

    ...
    ...
    ...
}

You can use the usual logging methods to log a message to your configured appenders:

public class ValuesController : Controller
{
    private static readonly ILog log = LogManager.GetLogger(typeof(ValuesController));
    
    [HttpPost]
    public async Task<IActionResult> Login(string userName, string password)
    {
        log.Info("Action start");
        
        // More code here ...

        log.Info("Action end");
    }
    
    // More code here...
}

Currently, AdoNetAppender is not yet officially supported. Fortunately, some guy by the name Frank Løvendahl Nielsen made it easy for us by implementing the usual AdoNetAppender and made it available via NuGet. So, add a reference to the package named MicroKnights.Log4NetAdoNetAppender:

Let us first create our table to place all our logs. We name it – ApplicationLog. We have a minimalistic approach of the data that we are storing here in our example but there can be more data you usually store in a real-world application (e.g. user name, module, and action). Also, ensure that the ApplicationLogKey is an auto-incrementing numeric identity.

After our table is created, we add the AdoNetAppender from our previous log4net.config:

<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="true">
  <!-- definition of the RollingLogFileAppender goes here -->
  <appender name="AdoNetAppender" type="MicroKnights.Logging.AdoNetAppender, MicroKnights.Log4NetAdoNetAppender">
    <bufferSize value="1" />
    <connectionType value="System.Data.SqlClient.SqlConnection, System.Data" />
    <connectionStringName value="log4net" />
    <connectionStringFile value="appsettings.json" />
    <commandText value="INSERT INTO ApplicationLog (ActionTime, Details) VALUES (@actionTime, @details)" />
    <parameter>
      <parameterName value="@actionTime" />
      <dbType value="DateTime" />
      <layout type="log4net.Layout.RawTimeStampLayout" />
    </parameter>
    
    <parameter>
      <parameterName value="@details" />
      <dbType value="String" />
      <size value="4000" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message" />
      </layout>
    </parameter>
  </appender>

  <root>
    <level value="ALL" />
    <appender-ref ref="RollingLogFileAppender" />
    <appender-ref ref="AdoNetAppender" />
  </root>
</log4net>

Most of the configuration here is similar to the usual AdoNetAppender except for the following:

  • type attribute (of the appender element) – MicroKnights.Logging.AdoNetAppender, MicroKnights.Log4NetAdoNetAppender
  • connectionType element has a value attribute = System.Data.SqlClient.SqlConnection, System.Data
  • connectionStringName element has a value attribute pointing to the name of the connection string property in the connectionStrings section of your connection strings JSON file
  • connectionStringFile element has a value attribute pointing to your connection strings JSON file. Here, we use the appsettings.json file.

Let’s configure the connection string where our appender would log the messages in our appsettings.json file:

{
  "connectionStrings": {
    "log4net": "Server=MICKO-PC;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

You may now run the application and see your log messages created in both the file and table.