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.
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>
*/