8

When I wanted to use the different logging levels available with log4net, I found out that I needed to use the method ILogger.Log, which looks like this:

void Log(Type callerStackBoundaryDeclaringType, Level level, object message, Exception exception);

We can get an ILogger reference from our usual ILog reference to call this method and it is usually suggested to wrap this call in an extension method. What we get is the following:

//typical log4net ILog reference
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

//suggested extension method
public static void Trace(this ILog logger, object message, Exception e)
{
    logger.Logger.Log(MethodBase.GetCurrentMethod().DeclaringType, log4net.Core.Level.Trace, message, e);
}

I have noticed how the reflection call to get the declaring type looks redundant. We have already passed the declaring type to GetLogger when we retrieved our ILog reference. Why did we need to pass another type reference to the method Log, which we are invoking on an ILogger reference which we have retrieved from our ILog reference. More importantly, the declaring type in the extension method would be the class that contains the extension method. Logging all your Trace logs with a class name like Log4NetExtensions would make no sense. Finally, reflection is supposed to be expensive, even though reflecting on the current method might be cheaper, calling reflection code every time we log does not sound right. So I decided to make a test:

//I just created a logger
var logger = logManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

//and then I called Log with different type parameters
logger.Logger.Log(MethodBase.GetCurrentMethod().DeclaringType, log4net.Core.Level.Info, "hello!", null);
logger.Logger.Log(logger.GetType(), log4net.Core.Level.Info, "hello!", null);
logger.Logger.Log(null, log4net.Core.Level.Info, "hello!", null);
logger.Logger.Log(typeof(System.Console), log4net.Core.Level.Info, "hello!", null);

And the resulting logs looked like the following:

INFO  2015-05-29 11:06:57,200 [9] Foo.Program - hello!
INFO  2015-05-29 11:06:57,207 [9] Foo.Program - hello!
INFO  2015-05-29 11:06:57,207 [9] Foo.Program - hello!
INFO  2015-05-29 11:06:57,207 [9] Foo.Program - hello!

So, it seems that the type parameter to this method is ignored. This is very puzzling. Can anyone confirm or deny these results? Does anyone know why this works the way it does? I intend to create my extension method with null for this parameter. Would you suggest the same? Am I missing something?

The following are questions whose answers suggest using the declaring type in the extension method:

Log4net creating custom levels

Why isn't there a trace level in log4Net?

Log4net, how to log a verbose message?

Community
  • 1
  • 1
Can Bud
  • 142
  • 1
  • 11

2 Answers2

5

The type you pass to LogManager.GetLogger is used to name the returned logger, which is why all your logging calls are labelled 'Foo.Program'.

The type passed to Logger.Log though is "The declaring type of the method that is the stack boundary into the logging system for this call" and is used to determine where to stop recording the stack trace - which is required internally, or else the stack trace would include log4net internal methods.

You can certainly create an extension method that passes null as the callerStackBoundaryDeclaringType, as it will internally default to typeof(Logger).

stuartd
  • 70,509
  • 14
  • 132
  • 163
  • this: "as it will internally default to the type of the logger." That's what i was looking for. Any references? Or any suggestion on how to test? When does log4net produce a stack trace? When I pass in an exception? But don't exceptions have their own stack traces? – Can Bud May 29 '15 at 15:59
  • You're right about exceptions having their own stack trace: log4net also [allows the stack trace to be included in the pattern](http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html) but in retrospect that's not a common use case - regarding the reference, I looked in the source, [lines 428 and 725](https://apache.googlesource.com/log4net/+/refs/heads/1.2.12/src/Repository/Hierarchy/Logger.cs) – stuartd May 29 '15 at 16:44
0

You should not create an extension method like you suggested.

Because ILog Logger is static it does not make much sense to use it as a paramter value. Just use Log4Net the following way:

private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); // that part was ok

public static void Trace(object message, Exception e)
{
   Logger.DebugFormat(message, e); // Use InfoFormat or something else if needed
}

if you use just standard functionality, just call DebugFormat() (or something else) directly:

try {
  int i=7/0;
} catch (Exception e){
  Logger.ErrorFormat("Looks like you are not Jon Skeet",e); 
}

It does not make sense to create your own "Trace"-Method if you add nothing to it.

In addition it (usually) does not make sense to set the type when You log something, but ONLY on top of the class when initializig Logger

Ole Albers
  • 8,715
  • 10
  • 73
  • 166
  • The extension method will be used by multiple classes, each of which will have their own ILog references declared in the usual way that you have also suggested. Also, I wish to log messages with Level.Trace, which does not have a standard method provided by ILog. – Can Bud May 29 '15 at 10:46