@corriganjc has a lot of good suggestions.
I would add some specifics:
Consider logging messages using UTC, if possible. On the one hand it can be annoying to look at the messagses generated in "your" timezone and then have to remember the right amount to offset them to interpret them "correctly". On the other hand, all of your log messages will be sortable by time/date without further interpretation. (If you have messages logged from two time zones and you have logged them using "local" time, you cannot sort them until you bring them to a common time zone).
Use the the GlobalContext, ThreadContext, and LogicalThreadContext objects to inject additional context into your messages. The suggestion to log something like "session id" or "transaction id" is a good one and can probably be accomplished most effectively using the "context" objects, rather than by explicitly adding these values to the actual logging call sites. You can set the context once and, with the addition of a formatting option, log that context with ever message.
If you use the context object, consider making "standard" value names or even defining string constants that can be referenced by developers so that they don't have make typos when trying to add their context:
//Context value names
public static class DiagnosticContextValueNames
{
public static string TransactionId = "transactionid";
public static string SessionId = "sessionid";
}
//In your code
log4net.ThreadContext.Properties[DiagnosticContextValueNames.TransactionId] = GetTransactionId();
log4net.ThreadContext.Properties[DiagnosticContextValueNames.SessionId] = GetSessionId();
//Somewhere later on...
logger.Info("hello"); // this message can be tagged with the transaction id and session id if you use the appropriate formatting options
You might even consider extension methods on GlobalContext.Properties, ThreadContext.Properties, etc, to help guide developers to setting the context values correctly:
public static class LoggingExtensions
{
public static void SetTransactionId(this ThreadContextProperties props, string trans)
{
props["TransactionId"] = trans;
// Or, using constants as defined above...
props[ThreadContextValueNames.TransactionId] = trans;
}
}
// In your code...
log4net.ThreadContext.Properties.SetTransactionId(GetTransactionId());
// As compared to this:
log4net.ThreadContext.Properties["transactionid"] = GetTransactionId();
If you wrap or inherit from the log4net logger, you can add some context information automatically, thus relieving your developers of that burden. There is a right way to wrap or inherit the log4net logger, but it is not rocket science. The key is to pass log4net the type of your wrapped logger. cfeduke's answer in this post gives one way to wrap a log4net logger.
You could follow his example, but implement all logging calls in terms of the "ILogger.Log" method. Inside of "Log" you can add the properties that you want for all of your log messages:
// This approach requires more effort than simply populating the context properties "normally", and
// is probably overkill for most situations. However, it can prove useful if you are able
// to have access to the context information that you want to log from within the Log method
// of the logger.
public void Log(type loggerBoundaryDeclaringType, LogLevel level, object message, object exception)
{
log4net.ThreadContext.Properties["transactionid"] = GetTransactionId();
log4net.ThreadContext.Properties["sessionid"] = GetSessionId();
_logger.Log(loggerBoundaryDeclaringType, level, message, exception);
}
As far as other logging platforms, you might take a look at NLog. A new version has been released as a Beta release recently. It has many capabilities that are similar to log4net.
You might also consider good old System.Diagnostics.TraceSource. If you go this route, look into Ukadc.Diagnostics over at codeplex. This is an addon library for System.Diagnostics that provides a rich message formatting capability (similar to what you can do with log4net and NLog). One good thing about Ukadc.Diagnostics is that it is a configuration-only dependency. You do not have to take a source or reference dependency to use it.