Apache log4net

Most historic .NET logging framework by Apache Foundation (ported from Java log4j). Known for rich configuration options and flexibility, capable of handling complex requirements. Mature solution providing diverse output destinations and customization.

logging.NETC#configuration-basedlegacyApache Foundationappenders

Library

Apache log4net

Overview

Apache log4net is "the most historic .NET logging framework by Apache Foundation," developed as a ported version of Java log4j logging library. Known for rich configuration options and flexibility, it's a mature solution capable of handling complex requirements. It provides diverse output destinations and customization, with a long track record in Enterprise environments. However, other choices tend to be prioritized in modern .NET development.

Details

Apache log4net 2025 edition shows declining recommendation for new projects, with limitations in structured logging support, maintainability issues, and performance disadvantages leading to involvement only in maintenance of existing systems. However, its important role in legacy systems continues, with deep integration with .NET Framework, detailed control through XML configuration, and rich appenders (file, database, event log, etc.), it's still used in traditional enterprise applications.

Key Features

  • Java log4j Compatible Design: Configuration methods and architecture familiar to Java developers
  • Rich Configuration Options: Detailed log control and customization through XML configuration
  • Diverse Appenders: Output to files, databases, event logs, SMTP, etc.
  • Level-based Control: Hierarchical log levels - DEBUG, INFO, WARN, ERROR, FATAL
  • Custom Filters: Conditional log output and message filtering
  • .NET Integration: Deep integration with .NET Framework and Configuration API support

Pros and Cons

Pros

  • Stability through long-term support by Apache Foundation and rich track record
  • External configuration management and operational changes through XML configuration files
  • Flexible output destination settings and customization through diverse appenders
  • High compatibility with legacy systems and low migration costs
  • Accumulated rich documentation and community support
  • Track record of handling complex log requirements in Enterprise environments

Cons

  • Limited structured logging support making integration with modern monitoring tools difficult
  • Performance inferior to Serilog, NLog, and Microsoft.Extensions.Logging
  • Insufficient support for new .NET technologies (.NET Core/.NET 5+)
  • Configuration complexity and maintainability issues leading to reduced development efficiency
  • Limited asynchronous processing support with constraints in high-load environments
  • Difficult integration with modern DevOps practices

Reference Pages

Usage Examples

Installation and Basic Setup

<!-- NuGet package reference -->
<PackageReference Include="log4net" Version="2.0.17" />
# NuGet Package Manager
Install-Package log4net

# .NET CLI
dotnet add package log4net
// Configuration in AssemblyInfo.cs or Global.asax.cs
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

// Or configuration attribute in App.config / Web.config
[assembly: log4net.Config.XmlConfigurator(Watch = true)]

Basic Log Output

using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Reflection;

public class BasicLoggingExample
{
    // Get logger (auto-named by class name)
    private static readonly ILog logger = LogManager.GetLogger(typeof(BasicLoggingExample));

    static void Main(string[] args)
    {
        // Load configuration file
        var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
        XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));

        // Basic log level output
        logger.Debug("Debug info: application initialization start");
        logger.Info("General info: application start");
        logger.Warn("Warning: using default values in configuration file");
        logger.Error("Error: database connection failed", new Exception("Connection timeout"));
        logger.Fatal("Fatal error: system shutdown");

        // Formatted messages
        string userId = "user123";
        int transactionId = 98765;
        logger.InfoFormat("Transaction processing complete: User={0}, TransactionID={1}, Amount={2:C}", 
                         userId, transactionId, 15000);

        // Conditional log output (performance consideration)
        if (logger.IsDebugEnabled)
        {
            string expensiveData = GetExpensiveDebugInfo();
            logger.DebugFormat("Heavy processing result: {0}", expensiveData);
        }

        // Detailed exception logging
        try
        {
            ProcessData();
        }
        catch (Exception ex)
        {
            logger.Error("Data processing error", ex);
            throw;
        }

        logger.Info("Application end");
    }

    private static string GetExpensiveDebugInfo()
    {
        // Heavy processing simulation
        System.Threading.Thread.Sleep(100);
        return $"Debug info: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
    }

    private static void ProcessData()
    {
        // Business process simulation
        logger.Debug("Data processing start");
        
        // Intentionally generate error
        throw new InvalidOperationException("Sample error");
    }
}

Detailed Configuration with XML Configuration File (log4net.config)

<?xml version="1.0" encoding="utf-8"?>
<log4net>
  <!-- Appender definitions -->
  
  <!-- Console appender -->
  <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %message%newline" />
    </layout>
    <threshold value="INFO" />
  </appender>

  <!-- File appender (rolling) -->
  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="logs/application.log" />
    <appendToFile value="true" />
    <rollingStyle value="Composite" />
    <datePattern value="yyyyMMdd" />
    <maxSizeRollBackups value="30" />
    <maximumFileSize value="10MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger{2} - %message%newline" />
    </layout>
  </appender>

  <!-- Error-specific file appender -->
  <appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="logs/error.log" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="5MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger - %message%newline%exception" />
    </layout>
    <threshold value="ERROR" />
  </appender>

  <!-- Database appender -->
  <appender name="DatabaseAppender" type="log4net.Appender.AdoNetAppender">
    <bufferSize value="100" />
    <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <connectionString value="Data Source=localhost;Initial Catalog=LogDB;Integrated Security=True" />
    <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
    
    <parameter>
      <parameterName value="@log_date" />
      <dbType value="DateTime" />
      <layout type="log4net.Layout.RawTimeStampLayout" />
    </parameter>
    <parameter>
      <parameterName value="@thread" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%thread" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@log_level" />
      <dbType value="String" />
      <size value="50" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%level" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@logger" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%logger" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@message" />
      <dbType value="String" />
      <size value="4000" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@exception" />
      <dbType value="String" />
      <size value="2000" />
      <layout type="log4net.Layout.ExceptionLayout" />
    </parameter>
    
    <threshold value="WARN" />
  </appender>

  <!-- SMTP appender (critical error notifications) -->
  <appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
    <to value="[email protected]" />
    <from value="[email protected]" />
    <subject value="[ERROR] Application Error" />
    <smtpHost value="smtp.example.com" />
    <port value="587" />
    <username value="[email protected]" />
    <password value="password123" />
    <enableSsl value="true" />
    <bufferSize value="10" />
    <threshold value="ERROR" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %message%newline%exception" />
    </layout>
  </appender>

  <!-- Event log appender -->
  <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
    <applicationName value="MyApplication" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %message" />
    </layout>
    <threshold value="WARN" />
  </appender>

  <!-- Logger configuration -->
  
  <!-- Detailed logs for specific namespaces -->
  <logger name="MyApplication.Services">
    <level value="DEBUG" />
    <appender-ref ref="RollingFileAppender" />
  </logger>

  <!-- Data access layer logs -->
  <logger name="MyApplication.DataAccess">
    <level value="TRACE" />
    <appender-ref ref="RollingFileAppender" />
  </logger>

  <!-- Framework log restrictions -->
  <logger name="System" additivity="false">
    <level value="WARN" />
    <appender-ref ref="ConsoleAppender" />
  </logger>

  <!-- Root logger -->
  <root>
    <level value="INFO" />
    <appender-ref ref="ConsoleAppender" />
    <appender-ref ref="RollingFileAppender" />
    <appender-ref ref="ErrorFileAppender" />
    <appender-ref ref="DatabaseAppender" />
    <appender-ref ref="SmtpAppender" />
    <appender-ref ref="EventLogAppender" />
  </root>
</log4net>

Custom Appenders and Advanced Configuration

// Custom appender implementation example
using log4net.Appender;
using log4net.Core;
using System;
using System.IO;
using Newtonsoft.Json;

public class JsonFileAppender : AppenderSkeleton
{
    public string FileName { get; set; }
    private FileStream fileStream;
    private StreamWriter writer;

    public override void ActivateOptions()
    {
        base.ActivateOptions();
        
        // Initialize file stream
        var directory = Path.GetDirectoryName(FileName);
        if (!Directory.Exists(directory))
        {
            Directory.CreateDirectory(directory);
        }
        
        fileStream = new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.Read);
        writer = new StreamWriter(fileStream);
    }

    protected override void Append(LoggingEvent loggingEvent)
    {
        // Output logs in JSON format
        var logEntry = new
        {
            Timestamp = loggingEvent.TimeStamp,
            Level = loggingEvent.Level.ToString(),
            Logger = loggingEvent.LoggerName,
            Message = loggingEvent.RenderedMessage,
            Exception = loggingEvent.ExceptionObject?.ToString(),
            Thread = loggingEvent.ThreadName,
            Username = loggingEvent.UserName,
            Properties = loggingEvent.Properties
        };

        string jsonLog = JsonConvert.SerializeObject(logEntry);
        writer.WriteLine(jsonLog);
        writer.Flush();
    }

    protected override void OnClose()
    {
        writer?.Close();
        fileStream?.Close();
        base.OnClose();
    }
}

// Custom appender usage example
public class CustomAppenderExample
{
    private static readonly ILog logger = LogManager.GetLogger(typeof(CustomAppenderExample));

    static void Main(string[] args)
    {
        // Programmatic configuration
        ConfigureLog4Net();

        // Set context information
        log4net.GlobalContext.Properties["Environment"] = "Production";
        log4net.GlobalContext.Properties["Version"] = "1.0.0";

        // Thread context information
        log4net.ThreadContext.Properties["UserId"] = "user123";
        log4net.ThreadContext.Properties["SessionId"] = "session456";

        // Log output
        logger.Info("Custom appender test start");
        logger.Warn("Warning test: High memory usage");
        logger.Error("Error test", new Exception("Test exception"));

        // MDC (Mapped Diagnostic Context) usage
        log4net.ThreadContext.Properties["TransactionId"] = "tx789";
        logger.Info("Transaction processing execution");
        log4net.ThreadContext.Properties.Remove("TransactionId");

        logger.Info("Custom appender test complete");
    }

    private static void ConfigureLog4Net()
    {
        // Programmatic configuration example
        var hierarchy = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
        
        // Pattern layout
        var patternLayout = new log4net.Layout.PatternLayout
        {
            ConversionPattern = "%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger - %message%newline"
        };
        patternLayout.ActivateOptions();

        // Custom JSON appender
        var jsonAppender = new JsonFileAppender
        {
            Name = "JsonFileAppender",
            FileName = "logs/application.json"
        };
        jsonAppender.ActivateOptions();

        // Logger configuration
        hierarchy.Root.AddAppender(jsonAppender);
        hierarchy.Root.Level = log4net.Core.Level.Info;
        hierarchy.Configured = true;
    }
}

ASP.NET Integration and Web Application Support

// Global.asax.cs
using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Web;

public class Global : HttpApplication
{
    private static readonly ILog logger = LogManager.GetLogger(typeof(Global));

    protected void Application_Start(object sender, EventArgs e)
    {
        // Initialize log4net configuration
        XmlConfigurator.Configure(new FileInfo(Server.MapPath("~/log4net.config")));
        
        logger.Info("Web application start");
    }

    protected void Application_Error(object sender, EventArgs e)
    {
        Exception exception = Server.GetLastError();
        string url = HttpContext.Current.Request.Url.ToString();
        string userAgent = HttpContext.Current.Request.UserAgent;
        string userIP = HttpContext.Current.Request.UserHostAddress;

        logger.Error($"Unhandled exception occurred: URL={url}, UserAgent={userAgent}, IP={userIP}", exception);
    }

    protected void Session_Start(object sender, EventArgs e)
    {
        string sessionId = Session.SessionID;
        string userIP = Request.UserHostAddress;
        
        logger.Info($"New session start: SessionID={sessionId}, IP={userIP}");
    }

    protected void Application_End(object sender, EventArgs e)
    {
        logger.Info("Web application end");
        LogManager.Shutdown();
    }
}

// Usage example in controller or page
public partial class UserManager : System.Web.UI.Page
{
    private static readonly ILog logger = LogManager.GetLogger(typeof(UserManager));

    protected void Page_Load(object sender, EventArgs e)
    {
        // Request information logging
        string sessionId = Session.SessionID;
        string userIP = Request.UserHostAddress;
        string userAgent = Request.UserAgent;

        // MDC configuration
        log4net.ThreadContext.Properties["SessionId"] = sessionId;
        log4net.ThreadContext.Properties["UserIP"] = userIP;

        logger.Info($"Page load: {Request.Url}");

        try
        {
            LoadUserData();
        }
        catch (Exception ex)
        {
            logger.Error("User data loading error", ex);
            // Redirect to error page, etc.
        }
        finally
        {
            // Clear MDC
            log4net.ThreadContext.Properties.Clear();
        }
    }

    private void LoadUserData()
    {
        logger.Debug("User data retrieval start");
        
        // Data retrieval processing
        System.Threading.Thread.Sleep(100); // Processing simulation
        
        logger.Info("User data retrieval complete: count=150");
    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
        string userId = Request["userId"];
        
        // Operation log
        log4net.ThreadContext.Properties["UserId"] = userId;
        log4net.ThreadContext.Properties["Action"] = "Save";

        try
        {
            logger.Info("User save processing start");
            
            // Save processing simulation
            SaveUserData(userId);
            
            logger.Info("User save processing complete");
        }
        catch (Exception ex)
        {
            logger.Error("User save processing error", ex);
            throw;
        }
        finally
        {
            log4net.ThreadContext.Properties.Clear();
        }
    }

    private void SaveUserData(string userId)
    {
        logger.Debug($"User data save: UserID={userId}");
        
        // Actual save processing
        if (string.IsNullOrEmpty(userId))
        {
            throw new ArgumentException("UserID not specified");
        }
        
        // Database save processing simulation
        System.Threading.Thread.Sleep(200);
        
        logger.Info($"User data save success: UserID={userId}");
    }
}

Performance Considerations and Best Practices

// Performance optimization example
public class PerformanceOptimizedLogging
{
    private static readonly ILog logger = LogManager.GetLogger(typeof(PerformanceOptimizedLogging));

    public static void Main(string[] args)
    {
        // Performance test
        PerformanceTest();
        
        // Memory usage test
        MemoryUsageTest();
        
        // Asynchronous processing simulation
        AsyncProcessingSimulation();
    }

    private static void PerformanceTest()
    {
        logger.Info("Performance test start");
        
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        int logCount = 10000;

        for (int i = 0; i < logCount; i++)
        {
            // Optimization through condition checking
            if (logger.IsDebugEnabled)
            {
                logger.Debug($"Debug log: iteration={i}");
            }

            // Execute string formatting only for INFO and above
            if (logger.IsInfoEnabled)
            {
                logger.InfoFormat("Info log: iteration={0}, timestamp={1}", i, DateTime.Now);
            }

            // Heavy logs conditionally
            if (i % 1000 == 0 && logger.IsWarnEnabled)
            {
                logger.WarnFormat("Progress log: {0}/{1} complete", i, logCount);
            }
        }

        stopwatch.Stop();
        double throughput = logCount / stopwatch.Elapsed.TotalSeconds;
        
        logger.InfoFormat("Performance test complete: time={0}ms, throughput={1:F0} logs/sec", 
                         stopwatch.ElapsedMilliseconds, throughput);
    }

    private static void MemoryUsageTest()
    {
        logger.Info("Memory usage test start");
        
        long startMemory = GC.GetTotalMemory(true);
        
        // Massive log output
        for (int i = 0; i < 5000; i++)
        {
            logger.InfoFormat("Memory test: object={0}", new { Id = i, Name = $"Object{i}" });
        }
        
        long endMemory = GC.GetTotalMemory(true);
        long memoryDiff = endMemory - startMemory;
        
        logger.InfoFormat("Memory usage: start={0:N0} bytes, end={1:N0} bytes, difference={2:N0} bytes", 
                         startMemory, endMemory, memoryDiff);
    }

    private static void AsyncProcessingSimulation()
    {
        logger.Info("Asynchronous processing simulation start");
        
        // Log output with multiple threads
        var tasks = new System.Threading.Tasks.Task[5];
        
        for (int taskId = 0; taskId < tasks.Length; taskId++)
        {
            int capturedTaskId = taskId;
            tasks[taskId] = System.Threading.Tasks.Task.Run(() =>
            {
                // Set thread-local information
                log4net.ThreadContext.Properties["TaskId"] = capturedTaskId;
                
                for (int i = 0; i < 1000; i++)
                {
                    logger.InfoFormat("Task{0}: processing{1}", capturedTaskId, i);
                    
                    if (i % 100 == 0)
                    {
                        logger.DebugFormat("Task{0}: progress {1}%", capturedTaskId, i / 10);
                    }
                }
                
                logger.InfoFormat("Task{0}: complete", capturedTaskId);
                log4net.ThreadContext.Properties.Clear();
            });
        }
        
        System.Threading.Tasks.Task.WaitAll(tasks);
        logger.Info("Asynchronous processing simulation complete");
    }
}

// Performance optimization through configuration
/*
Performance optimization configuration example in Web.config:

<appSettings>
  <!-- Disable log4net internal debugging -->
  <add key="log4net.Internal.Debug" value="false" />
</appSettings>

<log4net>
  <!-- Use asynchronous appenders -->
  <appender name="AsyncAppender" type="log4net.Appender.AsyncAppender">
    <bufferSize value="1000" />
    <appender-ref ref="RollingFileAppender" />
  </appender>
  
  <!-- Buffering configuration -->
  <appender name="BufferedFileAppender" type="log4net.Appender.BufferingForwardingAppender">
    <bufferSize value="100" />
    <appender-ref ref="RollingFileAppender" />
  </appender>
</log4net>
*/