Most real-world systems have to deal with multiple clients simultaneously. In a typical multithreaded implementation of such a system, different threads will handle different clients. Logging is especially well suited to trace and debug complex distributed applications. An approach to differentiate the logging output of one client from another is to instantiate a new separate logger for each client. However this promotes the proliferation of loggers and increases the management overhead of logging.
A lighter technique is to uniquely stamp each log request initiated from the same client interaction.
Log4net supports different types of contextual logging and contexts with different scopes.
Contextual data can be set in different scopes. These contexts have progressively narrower visibility. In the logging event itself the values from all of the contexts are combined together such that values specified in a lower scoped context hide values from a higher context.
Scope | Type | Description |
---|---|---|
Global | log4net.GlobalContext | The global context is shared by all threads in the current AppDomain. This context is thread safe for use by multiple threads concurrently. |
Thread | log4net.ThreadContext | The thread context is visible only to the current managed thread. |
Logical Thread | log4net.ThreadLogicalContext | The logical thread context is visible to a logical thread. Logical threads can jump from one managed thread to another. For more details see the .NET API System.Runtime.Remoting.Messaging.CallContext. |
Event | log4net.Core.LoggingEvent | Each event captures the current contextual state at the time the event is generated. Contextual data can be set on the event itself. This context is only visible to the code generating the event itself. |
The log4net contexts store properties, i.e. name value pairs. The name is a string the value is any object. A property can be set as follows:
log4net.GlobalContext.Properties["name"] = value;
If properties with the same name are set in more than one context scope then the value in the narrowest scope (lower down in the list above) will hide the other values.
The property values are stored as objects within the LoggingEvent. The PatternLayout supports rendering the value of a named property using the %property{name} syntax. The value is converted to a string by passing it to the log4net.ObjectRenderer.RendererMap which will locate any custom renderer for the value type. The default behavior for custom types is to call the object's ToString() method.
An active property value is one who's value changes over time.
For example, imagine a custom type that implemented the ToString() method to return the number of bytes allocated by the runtime garbage collector.
public class GCAllocatedBytesHelper { public override string ToString() { return GC.GetTotalMemory(true).ToString(); } }
An instance of this type can be added to the log4net.GlobalContext during application startup:
log4net.GlobalContext.Properties["GCAllocatedBytes"] = new GCAllocatedBytesHelper();
Once this property is set in the context all subsequent logging events will have a property called GCAllocatedBytes. The value of the property will be an instance of the GCAllocatedBytesHelper type. When this value is rendered to a string by calling the ToString method the current number of bytes allocated by the garbage collector will be returned and included in the output.
Sometimes simple key value pairs are not the most convenient way of capturing contextual information. A stack of information is a very convenient way of storing data especially as our applications tend to be stack based.
The ThreadContext and LogicalThreadContext also support storing contextual data in a stack. The stack is stored in context property, therefore stacks have names and more than one stack can exist in the same context. A property value set in a narrower context would override a stack with the same property name set in a wider scoped context.
The stack supports Push and Pop methods. As more contextual data is pushed onto the stack the stack grows. When the stack is rendered all the data pushed onto the stack is output with the most recent data to the right hand end of the string.
As the stack is just an object stored in the context properties it is also rendered using the same PatternLayout syntax: %property{name}. Where name is the name of the stack.
Calls the the stack's Push and Pop methods must be matched up so that each push has a corresponding pop. The Push method also returns an IDisposable object that will perform the required pop operation when it is disposed. This allows the C# using syntax to be used to automate the stack management.
using(log4net.ThreadContext.Stacks["NDC"].Push("context")) { log.Info("Message"); }
The INFO level log has a stack stored in its NDC property. The top item in the stack is the string context. The using syntax ensures that the value context is popped off the stack at the end of the block.
The using syntax is recommended because it removes some work load from the developer and reduces errors in matching up the Push and Pop calls, especially when exceptions can occur.
The NDC (Nested Diagnostic Context) exists for compatibility with older versions of log4net. This helper class implements a stack which is stored in the thread context property named NDC.
The MDC (MappedDiagnostic Context) exists for compatibility with older versions of log4net. This helper class implements a properties map which is mapped directly through to the thread context properties.
To illustrate this point, let us take the example of a web service delivering content to numerous clients. The web service can build the NDC at the very beginning of the request before executing other code. The contextual information can be the client's host name and other information inherent to the request, typically information contained in cookies. Hence, even if the web service is serving multiple clients simultaneously, the logs initiated by the same code, i.e. belonging to the same logger, can still be distinguished because each client request will have a different NDC stack. Contrast this with the complexity of passing a freshly instantiated logger to all code exercised during the client's request.
Nevertheless, some sophisticated applications, such as virtual hosting web servers, must log differently depending on the virtual host context and also depending on the software component issuing the request. Log4net supports multiple logger repositories. This would allow each virtual host to possess its own copy of the logger hierarchy. Configuring multiple logger hierarchies is beyond the scope of this document.