Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Logging ClassName and MethodName using log4net for a .NET project

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.

  1. That log4net uses an internally throws exception to generate a stack frame and that gets expensive if you use that generally for all logging.
  2. Confusion. There is a lot of literature out there. I've tried a bunch of it and not gotten to something useful.

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.

like image 607
Steinbekker Avatar asked Sep 22 '10 15:09

Steinbekker


People also ask

What is the use of log4net in C#?

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.

How do I use log4net net 6?

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.

What is log4net in asp net?

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.


1 Answers

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).

like image 117
wageoghe Avatar answered Sep 22 '22 19:09

wageoghe