Implementing Dual Logging in C# with log4net: File and SQLite Database

Project Setup

Begin by creating a new Windows Forms application. Install the log4net library through NuGet Package Manager:

Install-Package log4net

Configuration File

Create a configuration file named log4net.config in your project root. This file defines two distinct logging appenders: one for file-based error logging and another for database storage of informasional messages.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
    </configSections>
    <log4net>
        <root>
        </root>
        <appender name="FileAppender" type="log4net.Appender.RollingFileAppender,log4net" >
            <param name="File" type="" value="Logs/Errors/" />
            <param name="AppendToFile" value="true" />
            <param name="RollingStyle" value="Date" />
            <param name="DatePattern" value="yyyyMMdd".txt"" />
            <param name="StaticLogFileName" value="false" />
            <layout type="log4net.Layout.PatternLayout,log4net">
                <param name="ConversionPattern" value="[%d]%n%m%n" />
            </layout>
        </appender>    
        <appender name="SqliteAppender" type="log4net.Appender.AdoNetAppender">
            <bufferSize value="0" />
            <connectionType value="System.Data.SQLite.SQLiteConnection,System.Data.SQLite" />
            <connectionString value="Data Source=DataBase\\AppLogs.db;Version=3;" />
            <commandText value="INSERT INTO LogEntries (LogTime, Severity, Source, Content) VALUES (@LogTime, @Severity, @Source, @Content)" />
            <parameter>
                <parameterName value="@LogTime" />
                <dbType value="DateTime" />
                <layout type="log4net.Layout.RawTimeStampLayout" />
            </parameter>
            <parameter>
                <parameterName value="@Severity" />
                <dbType value="String" />
                <layout type="log4net.Layout.PatternLayout">
                    <conversionPattern value="%level" />
                </layout>
            </parameter>
            <parameter>
                <parameterName value="@Source" />
                <dbType value="String" />
                <layout type="log4net.Layout.PatternLayout">
                    <conversionPattern value="%logger" />
                </layout>
            </parameter>
            <parameter>
                <parameterName value="@Content" />
                <dbType value="String" />
                <layout type="log4net.Layout.PatternLayout">
                    <conversionPattern value="%message" />
                </layout>
            </parameter>
        </appender>        
        <logger name="Error">
            <level value="ERROR" />
            <appender-ref ref="FileAppender" />
        </logger>
        <logger name="Info">
            <level value="INFO" />
            <appender-ref ref="SqliteAppender" />
        </logger>
    </log4net>
</configuration>

Pattern Layout Tokens:

  • %m - Message text
  • %n - Newline character
  • %d - Timestamp of log entry
  • %r - Runtime in milliseconds
  • %t - Thread identifier
  • %p - Priority/severity level
  • %c - Logger name
  • %L - Source line number
  • %F - Source filename
  • %-10 - Left-align with 10-character minimum width

Logging Utility Class

Implemant a static helper class to centralize logging operations:

public static class LoggerService
{
    /// <summary>
    /// Records informational events
    /// </summary>
    public static void LogInfo(string message)
    {
        ILog logger = LogManager.GetLogger("Info");
        if (logger.IsInfoEnabled)
        {
            logger.Info(message);
        }
    }

    /// <summary>
    /// Logs error with exception details
    /// </summary>
    public static void LogError(string message, Exception exception)
    {
        ILog logger = LogManager.GetLogger("Error");
        if (logger.IsErrorEnabled)
        {
            logger.Error(message, exception);
        }
    }

    /// <summary>
    /// Logs error without exception
    /// </summary>
    public static void LogError(string message)
    {
        ILog logger = LogManager.GetLogger("Error");
        if (logger.IsErrorEnabled)
        {
            logger.Error(message);
        }
    }
}

Set the log4net.config file's Build Action to "Content" and Copy to Output Directory to "Always Copy".

Assembly Configuration

Register the configuration in your AssemblyInfo.cs file:

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", ConfigFileExtension = "config", Watch = true)]

Ensure the SQLite provider is installed:

Install-Package System.Data.SQLite

Usage Example

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private void WriteErrorLog_Click(object sender, EventArgs e)
    {
        statusLabel.Text = string.Empty;
        
        // Without exception
        LoggerService.LogError(inputTextbox.Text.Trim());
        
        // With exception details
        LoggerService.LogError(inputTextbox.Text.Trim(), new Exception("Simulated failure condition"));
        
        statusLabel.Text = "Error log written";
    }

    private void WriteDatabaseLog_Click(object sender, EventArgs e)
    {
        statusLabel.Text = string.Empty;
        
        // Writes to SQLite database
        LoggerService.LogInfo(inputTextbox.Text.Trim());
        LoggerService.LogInfo(new Exception("Test exception").ToString());
        
        statusLabel.Text = "Database log written";
    }
}

The error appender ganerates daily log files in the Logs/Errors/ directory, while the database appender persists informational logs to a SQLite database defined in the connection string. This dual-output approach provides both quick file-based access for debugging and queryable storage for historical analysis.

Tags: Log4Net C# sqlite logging WinForms

Posted on Tue, 11 Aug 2026 16:09:10 +0000 by andynightingale