Introduction
Overview
This document introduces the log4net API, its unique features, and the rationale behind its design. log4net enables developers to control logging output with fine-grained precision. It is fully configurable at runtime through external configuration files.
Almost every large application includes some form of logging or tracing system. Adding log statements is a straightforward and effective method for debugging. Sometimes it’s the only available method, especially in multithreaded or distributed applications. Traditional debuggers may be limited or unavailable in such environments.
After deployment, logging often becomes the only way to understand application behavior. Effective logging systems allow administrators to diagnose and resolve issues.
Experience shows that logging is a critical part of the development process. It provides detailed context about the application’s execution. It operates automatically once integrated into the codebase. Log output can also be stored persistently for later analysis. Beyond development, a powerful logging system can also serve as an auditing mechanism.
Logging has trade-offs. It can reduce performance. It can produce overwhelming amounts of output. log4net is designed to minimize these downsides. It is fast, reliable, and extensible. The log4net API emphasizes simplicity and ease of use.
Frameworks
log4net is available for several frameworks. For each supported framework, an assembly targeting that framework is built:
-
.NET Standard 2.0 via .NET 8.0
-
Microsoft .NET Framework 4.6.2
Not all frameworks are created equal, and some features have been excluded from certain builds. For more information, see the Supported Frameworks document.
Main Components
log4net has three main components:
-
loggers - capture and categorize log messages.
-
appenders - define where and how log messages are output
-
layouts - control the format of log messages.
These components work together to enable developers to log messages according to message type and level. They also allow controlling at runtime how these messages are formatted and where they are reported.
These components are assisted by filters that control the actions of the appender. Object renderers are used to turn objects into strings.
Logger hierarchy
Loggers are named entities, with case-sensitive names that follow a hierarchical structure.
The main advantage of logging APIs over System.Console.WriteLine
is the ability to disable certain log statements while allowing others to print.
Loggers can have parent-child relationships based on their names.
For example, a logger named Animals.Carnivora
is the parent of Animals.Carnivora.Dog
, and System
is the parent of System.Text
and ancestor of System.Text.StringBuilder
.
This hierarchy is similar to .NET’s namespace and class structure.
The root logger sits at the top of the hierarchy:
-
It always exists
-
It cannot be retrieved by name
-
It always has an assigned level
Loggers are retrieved using the static GetLogger
method from the log4net.LogManager
class.
It can take either a string for the logger name or a Type
for the desired class.
namespace log4net
{
public class LogManager
{
public static ILog GetLogger(string name);
public static ILog GetLogger(Type type);
}
}
The GetLogger
method that takes a Type
parameter uses the fully qualified type name as the name of the logger to retrieve.
These GetLogger
methods return an ILog
interface, which is the representation of the logger passed back to the developer.
The ILog
interface is defined as follows:
namespace log4net
{
public interface ILog
{
/* Test if a level is enabled for logging */
bool IsDebugEnabled { get; }
bool IsInfoEnabled { get; }
bool IsWarnEnabled { get; }
bool IsErrorEnabled { get; }
bool IsFatalEnabled { get; }
/* Log a message object */
void Debug(object message);
void Info(object message);
void Warn(object message);
void Error(object message);
void Fatal(object message);
/* Log a message object and exception */
void Debug(object message, Exception t);
void Info(object message, Exception t);
void Warn(object message, Exception t);
void Error(object message, Exception t);
void Fatal(object message, Exception t);
/* Log a message string using the System.String.Format syntax */
void DebugFormat(string format, params object[] args);
void InfoFormat(string format, params object[] args);
void WarnFormat(string format, params object[] args);
void ErrorFormat(string format, params object[] args);
void FatalFormat(string format, params object[] args);
/* Log a message string using the System.String.Format syntax and provide a culture / format provider */
void DebugFormat(IFormatProvider provider, string format, params object[] args);
void InfoFormat(IFormatProvider provider, string format, params object[] args);
void WarnFormat(IFormatProvider provider, string format, params object[] args);
void ErrorFormat(IFormatProvider provider, string format, params object[] args);
void FatalFormat(IFormatProvider provider, string format, params object[] args);
}
}
Logger Levels
Loggers may be assigned levels.
Levels are instances of the log4net.Core.Level
class.
The following levels are defined in order of increasing priority:
-
ALL
-
DEBUG
-
INFO
-
WARN
-
ERROR
-
FATAL
-
OFF
If a given logger is not assigned a level, it inherits one from its closest ancestor with an assigned level.
More formally:
Level Inheritance
The inherited level for a given logger X is equal to the first non-null level in the logger hierarchy, starting at X and proceeding upwards in the hierarchy towards the root logger.
To ensure that all loggers can eventually inherit a level, the root logger always has an assigned level.
The default value for the root logger is DEBUG
.
Below are four examples showing various assigned level values and the resulting inherited levels according to the inheritance rule.
Example 1: Root Logger Assigned Level
Logger name |
Assigned level |
Inherited level |
root |
DEBUG |
DEBUG |
X |
none |
DEBUG |
X.Y |
none |
DEBUG |
X.Y.Z |
none |
DEBUG |
In this example, the root logger is assigned the level DEBUG
, so all child loggers inherit this level.
Example 2: All Loggers Have Assigned Levels
Logger name |
Assigned level |
Inherited level |
root |
DEBUG |
DEBUG |
X |
INFO |
INFO |
X.Y |
WARN |
WARN |
X.Y.Z |
ERROR |
ERROR |
Here, each logger has its own assigned level, so no inheritance is needed.
Example 3: Some Loggers Inherit Level
Logger name |
Assigned level |
Inherited level |
root |
DEBUG |
DEBUG |
X |
INFO |
INFO |
X.Y |
none |
INFO |
X.Y.Z |
ERROR |
ERROR |
In this example, the X.Y
logger inherits the INFO
level from its parent X
logger. The X.Y.Z
logger has its own assigned level, ERROR
.
Logging Requests
Logging requests are made by invoking one of the printing methods of a logger instance (through the log4net.ILog).
The available printing methods are:
-
Debug
-
Info
-
Warn
-
Error
-
Fatal
By definition, the printing method determines the level of a logging request. For example:
log.Info("...");
This statement represents a logging request with level INFO
.
A logging request is considered "enabled" if its level is higher than or equal to the level of the logger. Otherwise, the request is "disabled."
If a logger does not have an assigned level, it will inherit one from its closest ancestor in the logger hierarchy.
Basic Selection Rule
A log request of level L
in a logger with (either assigned or inherited, whichever is appropriate) level K
is enabled if L >= K
.
This rule is at the heart of log4net. It assumes that levels are ordered. For the standard levels, we have:
-
DEBUG < INFO < WARN < ERROR < FATAL
Calling the log4net.LogManager.GetLogger
method with the same name will always return a reference to the exact same logger object.
For example:
ILog x = LogManager.GetLogger("wombat");
ILog y = LogManager.GetLogger("wombat");
In this case, x
and y
refer to exactly the same logger object.
Thus, it is possible to configure a logger and then to retrieve the same instance somewhere else in the code without passing around references.
In fundamental contradiction to biological parenthood, where parents always precede their children, log4net loggers can be created and configured in any order.
In particular, a "parent" logger will find and link to its descendants even if it is instantiated after them.
Logger configuration is usually done during application startup, typically via a configuration file.
Loggers are often named after the class they’re used in, using the fully qualified class name. This makes log output easy to trace and aligns naturally with the application’s design.
While other naming strategies are possible, this approach is simple, effective, and widely adopted.
Appenders
See Appenders
Filters
See Filters
Layouts
See Layouts
Object Renderers
log4net can render the content of log messages using custom logic defined by the user.
For example, if you often log Orange
objects, you can register an OrangeRenderer
to format them consistently in logs.
Renderers follow the class hierarchy.
If you register a FruitRenderer
, it will be used for all Fruit
types—including Orange
—unless a more specific OrangeRenderer
is also registered.
Renderers must implement the log4net.ObjectRenderer.IObjectRenderer
interface.
Format methods like |