3

According to this log4net article you should check if debug is enabled prior to any Log.Debug statements to eliminiate the statement construction cost. Is there a better alternative to always having to check if(Log.IsDebugEnabled) prior to any log statements?

Log4Net example:

if (log.IsDebugEnabled)
{ 
    log.Debug("This is entry number: " + i );
}

I don't want to pay the overhead cost of statement construction, but also don't want to check prior to every log statement.

scarpacci
  • 8,957
  • 16
  • 79
  • 144

6 Answers6

7

@Grhm and @David have good ideas, but I don't think that David's wrapper is as good as it could be. Wrapping log4net that way. Simply implementing Debug, Info, etc on the wrapper and delegating those down to log4net's Debug, Info, etc methods break log4net's ability to log the call site information. If you wrap this way and tell log4net to log the call site info, log4net will write out the call site in the wrapper, not the call site in your actual code, which is what you want.

I personally don't like using a singleton logger as you lose the ability to tweak logging levels in different parts of your program. If you are working on several components, you might want Info level logging turned on for one component, but only Warn logging (or none at all) for other components. With a singleton logger, all logging in all of your application will be at the same level.

You are denying yourself a lot of log4net's built in (and powerful) capabilities when you wrap log4net incorrectly and when you use a single logger to cover your entire application.

I answered a similar question (about maintaining call site information) here:

how to log method name when using wrapper class with Log4net

To save time, I have included a code example here (uncompiled and untested, but should be close)...

public class MyLog4NetWrapper
{
  ILog log;

  public MyLog4NetWrapper(string loggerName)
  {
    log = LogManager.GetLogger(loggerName)
  }

  public MyLog4NetWrapper(type loggerType)
  {
    log = LogManager.GetLogger(loggerType)
  }

  public void Info(string message) 
  { 
    if (log.IsInfoEnabled) log.Logger.Log(typeof(MyLog4NetWrapper), LogLevel.Info, message, null);
  }

  //Defer expensive calculations unless logging is enabled - thanks Grhm for the example
  public void Info(Func<string> formattingCallback )
  {
    if(log.IsInfoEnabled)
    {
      log.Logger.Log(typeof(MyLog4NetWrapper), LogLevel.Info, formattingCallback(), null);
    }
  }

  //Debug, Warn, Trace, etc are left as an exercise.
}

You can create these loggers in your code like this:

public class MyClass
{
  private static readonly ILog log = new MyLoggerWrapper(typeof(MyClass));

  public void DoSomething()
  {
    log.Info("Hello world!");
  }
}

The trick to writing a log4net wrapper that preserves the call site information is to use the Log method and to pass the type of your wrapper as the first parameter.

If you are going to write a wrapper in order to implement the functionality that you asked about (deferring execution of any expensive code in the logging call without explicitly checking to see if the desired logging level is enabled), then you might as well put that code in the wrapper rather than implement it as an extension method (which will also suffer from the same loss of call site problem I described above).

Good luck!

Community
  • 1
  • 1
wageoghe
  • 27,390
  • 13
  • 88
  • 116
4

The easiest and cleanest way might be the use of the DebugFormat method which actually does the check if the debug level is enabled (see Github-Code of log4net).

RedX
  • 14,749
  • 1
  • 53
  • 76
0xDECAFBAD
  • 627
  • 1
  • 8
  • 21
3

You could use a lambda expression. Like:

log.Debug(() => "This is entry number:" + i);

That way the lambda is only evaluated after the .IsDebugEnabled call.

We have an extension class defined (taken from http://www.beefycode.com/post/Extension-Methods-for-Deferred-Message-Formatting-in-Log4Net.aspx) that has extension methods like:

public static class Log4NetExtensionMethods
{
    public static void Debug( this ILog log, Func<string> formattingCallback )
    {
        if( log.IsDebugEnabled )
        {
            log.Debug( formattingCallback() );
        }
    }
    // .. other methods as required...
}

I'm not sure if log4net have added lamda type support in more recent releases - but this has been working for me.

Grhm
  • 6,726
  • 4
  • 40
  • 64
  • How does this work if you are trying to trying to enable debug logging on just one particular class? – sgmoore Oct 25 '13 at 14:21
3

but also don't want to check prior to every log statement

When you find yourself repeating the same code over and over, it sounds like a common abstraction may be in order. In this case you can, for example, create a custom wrapper for Log4Net. Something as simple as:

public static class Logger
{
    private static ILog _log;

    static Logger()
    {
        log4net.Config.XmlConfigurator.Configure();
        _log = log4net.LogManager.GetLogger("Log4Net");
    }

    public static void Debug(string message)
    {
        if (_log.IsDebugEnabled)
            _log.Debug(message);
    }

    public static void Info(string message)
    {
        _log.Info(message);
    }

    public static void Warn(string message)
    {
        _log.Warn(message);
    }

    public static void Error(string message)
    {
        _log.Error(message);
    }

    public static void Error(string message, Exception ex)
    {
        _log.Error(message, ex);
    }

    public static void Fatal(string message)
    {
        _log.Fatal(message);
    }

    public static void Fatal(string message, Exception ex)
    {
        _log.Fatal(message, ex);
    }
}

In this case I made the logger instance static. I'm not 100% sure that will always work as expected. Normally I use this behind a dependency injection framework and configure the logger dependency to be a singleton, handled by the framework. You might instead make this an instance class with instance methods and put it behind a static factory class instead. Test and tweak as necessary.

There are a couple of added benefits here:

  1. Your dependency in Log4Net is isolated to a single class. So if you ever want to use a different logger, you only have to change one class instead of everything in the entire project.
  2. You can easily abstract this behind a dependency injector.
  3. Any other common functionality you want to include in all logging statements can be easily and globally included here.

An example I commonly use for the third benefit might be something like this:

private static string GetLocation()
{
    var frame = new StackTrace(1).GetFrame(1);
    var method = frame.GetMethod();
    return string.Format("{0}:{1}.{2}({3})", Environment.MachineName, method.ReflectedType.FullName, method.Name, frame.GetFileLineNumber().ToString());
}

This gets more meaningful debugging information from the runtime system (though there may be a performance hit, for high-volume systems it's worth testing). So my pass-through error logging function might look like this:

public void Error(string message, Exception ex)
{
    _log.Error(string.Format("{0}:{1}", GetLocation(), message), ex);
}
David
  • 208,112
  • 36
  • 198
  • 279
  • 1
    I would not recommend writing a wrapper this way (delegating Info, Debug, etc calls to log4net's Info, Debug, etc). Log4net can figure out the appropriate call site information all by itself without that code in GetLocation. The key to wrapping log4net correctly is to use the Log method and pass the type of your wrapper class as the first parameter. I will paste a small example in an answer. – wageoghe Oct 25 '13 at 21:25
  • this still have the overheard of constructing the string message – Dinesh Rajan Feb 07 '19 at 00:45
2

If you include the namespace log4net.Util, you are able to call the following extension methods on log4net ILog:

public static void ErrorExt(this ILog logger, Func<object> callback)

This will only call the lambda function when logging error level is enabled. No need to write your own extension methods. It also protects from creating an error while constructing the actual log message by wrapping the creation in a try catch method.

Peter
  • 27,590
  • 8
  • 64
  • 84
-2

I would look at preprocessor (precompile?) directives.

#if DEBUG
{your logging code here}
#endif

Something like that should do it for you, and then the code only gets compiled in Debug Mode.

You can also use the the Conditional attribute on a method like this: [System.Diagnostics.Conditional("DEBUG")] private void YourMethodNameHere(YourMethodSignatureHere)

Take a look at this old question for more information on when/why/how you might use them.

http://stackoverflow.com/questions/3788605/if-debug-vs-conditionaldebug

AllenG
  • 8,112
  • 29
  • 40
  • 2
    This has nothing to do with checking which logging levels are enabled (Debug logging enabled != #if DEBUG) – Grhm Oct 25 '13 at 14:13
  • I would strongly advise against this. The whole point of using Log4Net is that you can enable, disable, or reconfigure logging WITHOUT recompiling the application - which won't work if you compile out all the debug statements. Please consider David's answer, possibly complemented with Grhm's tip, instead. – CompuChip Oct 25 '13 at 14:16