Here is one approach that uses Enterprise Library that is fairly easy to setup. You can use Activity Tracing for storing global context and Extended Properties for storing local context.
For the sake of an example I'll use a service locator without any wrapper class to demonstrate the approach.
var traceManager = EnterpriseLibraryContainer.Current.GetInstance<TraceManager>();
using (var tracer1 = traceManager.StartTrace("MyRequestId=" + GetRequestId().ToString()))
using (var tracer2 = traceManager.StartTrace("ClientID=" + clientId))
{
DoSomething();
}
static void DoSomething()
{
var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
logWriter.Write("doing something", "General");
DoSomethingElse("ABC.txt");
}
static void DoSomethingElse(string fileName)
{
var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
// Oops need to log
LogEntry logEntry = new LogEntry()
{
Categories = new string[] { "General" },
Message = "requested file not found",
ExtendedProperties = new Dictionary<string, object>() { { "filename", fileName } }
};
logWriter.Write(logEntry);
}
The configuration would look like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
</configSections>
<loggingConfiguration name="" tracingEnabled="true" defaultCategory="General"
logWarningsWhenNoCategoriesMatch="false">
<listeners>
<add name="Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
fileName="trace.log" formatter="Text Formatter" traceOutputOptions="LogicalOperationStack" />
</listeners>
<formatters>
<add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
template="Timestamp: {timestamp}
Message: {message}
ActivityID: {activity}
Context: {category}
Priority: {priority}
EventId: {eventid}
Severity: {severity}
Title:{title}
Machine: {localMachine}
App Domain: {localAppDomain}
ProcessId: {localProcessId}
Process Name: {localProcessName}
Thread Name: {threadName}
Win32 ThreadId:{win32ThreadId}
Local Context: {dictionary({key} - {value}{newline})}"
name="Text Formatter" />
</formatters>
<categorySources>
<add switchValue="All" name="General">
<listeners>
<add name="Flat File Trace Listener" />
</listeners>
</add>
</categorySources>
<specialSources>
<allEvents switchValue="All" name="All Events" />
<notProcessed switchValue="All" name="Unprocessed Category" />
<errors switchValue="All" name="Logging Errors & Warnings" />
</specialSources>
</loggingConfiguration>
</configuration>
This will result in an output like this:
----------------------------------------
Timestamp: 1/16/2013 3:50:11 PM
Message: doing something
ActivityID: 5b765d8c-935a-445c-b9fb-bde4db73124f
Context: General, ClientID=123456, MyRequestId=8f2828be-44bf-436c-9e24-9641963db09a
Priority: -1
EventId: 1
Severity: Information
Title:
Machine: MACHINE
App Domain: LoggingTracerNDC.exe
ProcessId: 5320
Process Name: LoggingTracerNDC.exe
Thread Name:
Win32 ThreadId:8472
Local Context:
----------------------------------------
----------------------------------------
Timestamp: 1/16/2013 3:50:11 PM
Message: requested file not found
ActivityID: 5b765d8c-935a-445c-b9fb-bde4db73124f
Context: General, ClientID=123456, MyRequestId=8f2828be-44bf-436c-9e24-9641963db09a
Priority: -1
EventId: 0
Severity: Information
Title:
Machine: MACHINE
App Domain: LoggingTracerNDC.exe
ProcessId: 5320
Process Name: LoggingTracerNDC.exe
Thread Name:
Win32 ThreadId:8472
Local Context: filename - ABC.txt
----------------------------------------
Things to note:
- Since we are using tracing we get the .NET activity ID for free which can be used to correlate activities. Of course we can use our own context information as well (custom request ID, client ID, etc.).
- Enterprise Library uses the tracing "operation name" as a category so we need to set logWarningsWhenNoCategoriesMatch="false" otherwise we'll get a flurry of warning messages.
- A downside of this approach may be performance (but I haven't measured it).
If you want to disable global context (which is, in this implementation, tracing) then all you need to do is edit the configuration file and set tracingEnabled="false".
This seems to be a fairly straight forward way to achieve your objectives using built-in Enterprise Library functionality.
Other approaches to consider would be to potentially use some sort of interception (custom LogCallHandler) which can be quite elegant (but that might depend on the existing design).
If you are going to go with a custom implementation to collect and manage context then you could consider looking at using Trace.CorrelationManager for per thread context. You could also look at creating an IExtraInformationProvider
to populate the extended properties dictionary (see Enterprise Library 3.1 Logging Formatter Template - Include URL Request for an example).