I have been looking for a way to log class names and method names as part of my logging infrastructure. Obviously I'd like to make it simple to use and fast at runtime. I've done a lot of reading about logging class names and method names, but I've run across 2 themes.
If you humor me for a second, I'd like to reset.
I created a class like this in my project
public static class Log {
private static Dictionary<Type, ILog> _loggers = new Dictionary<Type, ILog>();
private static bool _logInitialized = false;
private static object _lock = new object();
public static string SerializeException(Exception e) {
return SerializeException(e, string.Empty);
}
private static string SerializeException(Exception e, string exceptionMessage) {
if (e == null) return string.Empty;
exceptionMessage = string.Format(
"{0}{1}{2}\n{3}",
exceptionMessage,
(exceptionMessage == string.Empty) ? string.Empty : "\n\n",
e.Message,
e.StackTrace);
if (e.InnerException != null)
exceptionMessage = SerializeException(e.InnerException, exceptionMessage);
return exceptionMessage;
}
private static ILog getLogger(Type source) {
lock (_lock) {
if (_loggers.ContainsKey(source)) {
return _loggers[source];
}
ILog logger = log4net.LogManager.GetLogger(source);
_loggers.Add(source, logger);
return logger;
}
}
public static void Debug(object source, object message) {
Debug(source.GetType(), message);
}
public static void Debug(Type source, object message) {
getLogger(source).Debug(message);
}
public static void Info(object source, object message) {
Info(source.GetType(), message);
}
public static void Info(Type source, object message) {
getLogger(source).Info(message);
}
...
private static void initialize() {
XmlConfigurator.Configure();
}
public static void EnsureInitialized() {
if (!_logInitialized) {
initialize();
_logInitialized = true;
}
}
}
(If this code looks familiar it's because it is borrowed from the examples!)
In any case, throughout my project I use lines like this to log:
Log.Info(typeof(Program).Name, "System Start");
Well, this kind of works. Most importantly, I get the class name but no method name. Less, importantly, I am polluting my code with this "typeof" junk. If I copy and paste a snippet of code between files, etc. the logging framework will be lying!
I tried playing with the PatternLayout (%C{1}.{M}) but that didn't work (all it did was write "Log.Info" to the log--because everything is routing through the Log.X static methods!). Besides, that's supposed to be slow.
So, what is the best way, given my setup and my desire to be simple and fast?
Appreciate any help in advance.
log4net categorizes logging into levels: DEBUG, INFO, WARN, ERROR and FATAL. The format of the log output can be easily changed by implementing a new layout class. The target of the log output as well as the writing strategy can be altered by writing a new appender class.
Just create a log4net. config file with a log file as an appender, then add two using statements and a single line of code to the new . NET 6 hosting model: //Program.
Log4Net is a framework for implementing logging mechanisms. It is an open source framework. Log4net provides a simple mechanism for logging information to a variety of sources. Information is logged via one or more loggers.
log4net (and NLog) both expose a logging method that makes it possible to "wrap" their loggers and still get correct the call site information. Essentially, the log4net (or NLog) logger needs to be told the Type that forms the "boundary" between logging code and application code. I think they call this the "logger type" or something similar. When the libraries get the call site information, they navigate up the call stack until the MethodBase.DeclaringType is equal (or maybe AssignableFrom) the "logger type". The next stack frame will be the application calling code.
Here is an example of how to log via NLog from within a wrapper (log4net would be similar - look in log4net docs for ILogger (not ILog) interface:
LogEventInfo logEvent = new LogEventInfo(level, _logger.Name, null, "{0}", new object[] { message }, exception);
_logger.Log(declaringType, logEvent);
Where declaringType is a member variable that was set something like this:
private readonly static Type declaringType = typeof(AbstractLogger);
And "AbstractLogger" is the type of your logger wrapper. In your case, it would probably look like this:
private readonly static Type declaringType = typeof(Log);
If NLog needs to get the call site info (because of call site operators in the layout), it will navigate up the stack until the MethodBase.DeclaringType for the current frame is equal (or AssignableFrom) declaringType. The next frame in the stack will be the actual call site.
Here is some code that will work for logging with a "wrapped" log4net logger. It uses the log4net ILogger interface and passes the type of the "wrapping" logger to preserve the call site information. You don't have to fill in an event class/structure with this method:
_logger.Log(declaringType, level, message, exception);
Again, "declaringType" is the type of your wrapper. _logger is the log4net logger, Level is the log4net.LogLevel value, message is the message, exception is the exception (if any, null otherwise).
As far as polluting your call sites with Typeof(whatever), I think you are stuck with that if you want to use a single static "Log" object. Alternatively, inside of the "Log" object's logging methods, you could get the calling method like the accepted answer in this post
How can I find the method that called the current method?
That link shows how to get the immediately preceding caller. If you needed to get the method that called the logging function, but your work is being done a couple of layers deeper, you will need to go up the stack some number of frames rather than just one frame.
Taking all of this together, you would write your Debug method something like this (again, this is in terms of NLog because that is what I have in front of me):
public static void Debug(object message)
{
MethodBase mb = GetCallingMethod();
Type t = mb.DeclaringType;
LogEventInfo logEvent = new LogEventInfo(LogLevel.Debug, t.Name, null, "{0}", new object [] message, null);
ILogger logger = getLogger(t) As ILogger;
logger.Log(declaringType, logEvent)
}
Note that you probably won't find many people here on StackOverflow that would recommend writing a logging wrapper function like this (that explicitly gets the calling method for ever log call). I can't say that I would recommend it either, but it does more or less answer the question that you asked. If you want to use a static "Log" object, then you will either have to explicitly pass the Type at each logging call site (to get the correct class logger) or you will have to add code inside of the logging call to navigate the stack and figure out that information for yourself. I don't think that either of those options are particularly attractive.
Now, having said all of that, you might consider using either log4net or NLog directly rather than adding this complicated (and not necessarily reliable) code for getting the call site information. As Matthew points out, NLog provides an easy way to get the logger for the current class. To get the logger for the current class using log4net, you would do this in every class:
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
vs this way with NLog:
private static readonly NLog.logger log = NLog.LogManager.GetCurrentClassLogger();
That is a pretty common usage.
If you don't want to be dependent on a particular logging implementation, you could use one of the logging abstractions available such as Common.Logging (NET) or Simple Logging Facade (SLF).
Even if you don't use one of these abstractions, download the source for Common.Logging and look at the abstraction for log4net. It will show exactly how to wrap a log4net logger such that the call site information is preserved (and available to the layout operators).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With