57

I have been looking for a logging framework for .net (c#) and decided to give log4net a go after reading up on a few question/answer threads here on stackoverflow. I see people mentioning over and over that they use a wrapper class for log4net and I am wonder what that would look like.

I have my code split up into different projects (data access/business/webservice/..). How would a log4net wrapper class look like? Would the wrapper class need to be included in all of the projects? Should I build it as a separate project all together?

Should the wrapper be a singleton class?

Zaid Masud
  • 13,225
  • 9
  • 67
  • 88
Xerx
  • 3,755
  • 8
  • 32
  • 28
  • 7
    Log4Net does have strictly interface based API's by itself. There is really no need to wrap it. – Martin Buberl Mar 13 '10 at 12:10
  • 2
    Wrapper has a little drawback also - when you use %M , %stacktrace OR %stacktracedetails this will give the method / type name for the wrapper classes , because log4net is called from the wrapper methods. – Baljeetsingh Sucharia Jul 25 '12 at 18:04

9 Answers9

55

Essentially you create an interface and then a concrete implementation of that interface that wraps the classes and methods of Log4net directly. Additional logging systems can be wrapped by creating more concrete classes which wrap other classes and methods of those systems. Finally use a factory to create instances of your wrappers based on a configuration setting or line of code change. (Note: you can get more flexible - and complex - using an Inversion of Control container such as StructureMap.)

public interface ILogger
{
    void Debug(object message);
    bool IsDebugEnabled { get; }

    // continue for all methods like Error, Fatal ...
}

public class Log4NetWrapper : ILogger
{
    private readonly log4net.ILog _logger;

    public Log4NetWrapper(Type type)
    {
        _logger = log4net.LogManager.GetLogger(type);
    }

    public void Debug(object message)
    {
        _logger.Debug(message);
    }

    public bool IsDebugEnabled
    {
        get { return _logger.IsDebugEnabled; }
    }

    // complete ILogger interface implementation
}

public static class LogManager
{
    public static ILogger GetLogger(Type type)
    {
        // if configuration file says log4net...
        return new Log4NetWrapper(type);
        // if it says Joe's Logger...
        // return new JoesLoggerWrapper(type);
    }
}

And an example of using this code in your classes (declared as a static readonly field):

private static readonly ILogger _logger =
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

You can get the same slightly more performance friendly effect using:

private static readonly ILogger _logger = 
    LogManager.GetLogger(typeof(YourTypeName));

The former example is considered more maintainable.

You would not want to create a Singleton to handle all logging because Log4Net logs for the invoking type; its much cleaner and useful to have each type use its own logger rather than just seeing a single type in the log file reporting all messages.

Because your implementation should be fairly reusable (other projects in your organization) you could make it its own assembly or ideally include it with your own personal/organization's framework/utility assembly. Do not re-declare the classes separately in each of your business/data/UI assemblies, that's not maintainable.

cfeduke
  • 23,100
  • 10
  • 61
  • 65
  • I was going to say that you don't wan't to have a single logger, but this is more of a logging factory which is what you want. Each class should have it's own logger, because it helps with logging reflection. – Omar Kooheji Oct 03 '08 at 12:11
  • Why wouldn't you have a singleton that looks for an existing (lazily instantiated) logger in a `Dictionary`? I may be misunderstanding something, but it looks like your implementation would create a new logger for each instantiation of a class that uses it. Assuming the class's constructor is what calls `LogManager.GetLogger()`? Or are you assuming all the consuming classes have to manage their own singletons for their respective `ILogger`s? – mo. Dec 10 '12 at 18:17
  • The ILogger instances are static so you only get one instance per class, not a new logger per instance of a class. – cfeduke Dec 12 '12 at 12:00
  • In this case, do you have to still add the log4net.dll wherever this wrapper is used? can't this wrapper have it somehow embedded? – juagicre May 16 '16 at 10:56
27

Assuming you were going with something like cfeduke's answer above, you could also add an overload to your LogManager like this:

public static ILogger GetLogger()
{
    var stack = new StackTrace();
    var frame = stack.GetFrame(1);
    return new Log4NetWrapper(frame.GetMethod().DeclaringType);
}

That way in your code you can now just use:

private static readonly ILogger _logger = LogManager.GetLogger();

instead of either of these:

private static readonly ILogger _logger =
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILogger _logger = 
    LogManager.GetLogger(typeof(YourTypeName));

Which is effectively equivalent of the first alternative (i.e. the one that uses MethodBase.GetCurrentMethod().DeclaringType), only a little simpler.

Community
  • 1
  • 1
Alconja
  • 14,834
  • 3
  • 60
  • 61
  • Nice answer! I think your GetLogger override should be returning ILogger not ILog. Would be interesting to know if there is any performance overhead using StackTrace instead of passing type – Dunc Oct 13 '11 at 11:46
  • @RaceFace - Fixed the `ILogger`, thanks. As for performance, I'm not sure, but there's probably a small hit... however, as long as your `ILogger` instances are static (which they generally would be), it'd only be executed at most once per type in your application, which I would imagine would be insignificant in overall performance. – Alconja Oct 18 '11 at 12:16
  • 3
    I also use this approach. However in trying to better log generic types I was warned by Eric Lippert that stack frames are not always reliable: http://stackoverflow.com/questions/7586140/getting-type-t-from-a-stackframe – ErnieL Nov 03 '11 at 16:42
  • 1
    I like this approach but I'm always overly cautious when it comes to examining the stack trace for anything. To be on the safe side I use a code completion macro in VS to generate the GetLogger(typeof(TypeNameHere)). – cfeduke Aug 15 '12 at 11:54
  • You can use safe method `public static ILogger GetLogger(MethodBase method) { return GetLogger(method.DeclaringType); }` to simplify call `LogManager.GetLogger(MethodBase.GetCurrentMethod());` – xmedeko May 17 '15 at 20:11
5

What benefits are you planning on getting out of writing a wrapper for log4net. I'd recommend getting comfortable with the log4net classes first before writing a wrapper around them. cfeduke is right in his answer on how to write said wrapper, but unless you need to add actual functionality to his example a wrapper would only succeed in slowing the logging process down and adding complexity for future maintainers. This especially true when refactoring tools available in .Net make such changes super easy.

  • 3
    One of the reasons that people "pay this price" is so that swapping out Log4Net for something else in the future........will be less costly down the road. Once you've been screwed by an implementation that goes out of business and won't give up any source code, you are more prone to "pay this price" upfront. Log4Net may not be in this category.....but others may be. – granadaCoder Nov 05 '13 at 22:19
1

My understanding is that a wrapper class for log4net would be a static class which takes care of initializing the logging object from app.config/web.config or by code (e.g. integration with NUnit).

devio
  • 36,858
  • 7
  • 80
  • 143
1

I have successfully isolated log4net dependency into a single project. If you intend to do the same, here is what my wrapper class look like:

using System;

namespace Framework.Logging
{
    public class Logger
    {
        private readonly log4net.ILog _log;

        public Logger()
        {
            _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        }

        public Logger(string name)
        {
            _log = log4net.LogManager.GetLogger(name);
        }

        public Logger(Type type)
        {
            _log = log4net.LogManager.GetLogger(type);
        }

        public void Debug(object message, Exception ex = null)
        {
            if (_log.IsDebugEnabled)
            {
                if (ex == null)
                {
                    _log.Debug(message);
                }
                else
                {
                    _log.Debug(message, ex);
                }
            }
        }

        public void Info(object message, Exception ex = null)
        {
            if (_log.IsInfoEnabled)
            {
                if (ex == null)
                {
                    _log.Info(message);
                }
                else
                {
                    _log.Info(message, ex);
                }
            }
        }

        public void Warn(object message, Exception ex = null)
        {
            if (_log.IsWarnEnabled)
            {
                if (ex == null)
                {
                    _log.Warn(message);
                }
                else
                {
                    _log.Warn(message, ex);
                }
            }
        }

        public void Error(object message, Exception ex = null)
        {
            if (_log.IsErrorEnabled)
            {
                if (ex == null)
                {
                    _log.Error(message);
                }
                else
                {
                    _log.Error(message, ex);
                }
            }
        }

        public void Fatal(object message, Exception ex = null)
        {
            if (_log.IsFatalEnabled)
            {
                if (ex == null)
                {
                    _log.Fatal(message);
                }
                else
                {
                    _log.Fatal(message, ex);
                }
            }
        }
    }
}

And dont forget to add this in the AssemblyInfo.cs of the interfacing project (took me a good few hours to find this)

[assembly: log4net.Config.XmlConfigurator(Watch = true, ConfigFile = "log4net.config")]

And put your log4net configuration xml in log4net.config file, set it as Content, Copy Always

Jeson Martajaya
  • 6,996
  • 7
  • 54
  • 56
  • What do you mean "add this in the `AssemblyInfo.cs` of the interfacing project" you mean the project that contains your wrapper (in your code on this answer)? – Snoop Jul 20 '16 at 13:23
  • 1
    @StevieV , yes, you are right. Usually it is located inside your project\Properties\AssemblyInfo.cs – Jeson Martajaya Aug 01 '16 at 22:51
1

There are frameworks like the Prism Library for WPF that promote the usage of a facade for the logging framework of your choice.

This is an example that uses log4net:

using System;
using log4net;
using log4net.Core;
using Prism.Logging;

public class Log4NetLoggerFacade : ILoggerFacade
{
    private static readonly ILog Log4NetLog = LogManager.GetLogger(typeof (Log4NetLoggerFacade));

    public void Log(string message, Category category, Priority priority)
    {
        switch (category)
        {
            case Category.Debug:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Debug, message, null);
                break;
            case Category.Exception:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Error, message, null);
                break;
            case Category.Info:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Info, message, null);
                break;
            case Category.Warn:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Warn, message, null);
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(category), category, null);
        }
    }
}

Note that by specifying the callerStackBoundaryDeclaringType you can still get the class name of the caller issuing the logging request. All you need to do is to include %C %M in your conversion pattern:

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %C.%M - %message%newline" />
</layout>

However, as the documentation warns, generating the caller class information is slow, therefore it must be used wisely.

Leonardo
  • 2,065
  • 2
  • 26
  • 27
0

Alconja, I like your idea of using the stacktrace to jump back to the calling method. I was thinking of further encapsulating the calls, to not just retrieve the logger object, but to perform actually perform the logging. What I want is a static class that handles the logging, by abstracting from the specific implementation used. I.e.

LoggingService.LogError("my error message");

That way I only need to change the internals of the static class, if I later decide to user another logging system.

So I used your idea to get the calling object using the stack trace :

public static class LoggingService
{
    private static ILog GetLogger()
    {    
        var stack = new StackTrace();    
        var frame = stack.GetFrame(2);    
        return log4net.LogManager.GetLogger(frame.GetMethod().DeclaringType);
    }

    public static void LogError(string message)
    {
        ILog logger = GetLogger();
        if (logger.IsErrorEnabled)
            logger.Error(message);
    }
    ...
}

Does anybody see a problem with this approach?

Tim S. Van Haren
  • 8,861
  • 2
  • 30
  • 34
  • 4
    I think your code sample misses the point of `IsErrorEnabled` (and related properties). These exist to allow you to avoid the run-time cost of building the string that gets passed to the corresponding log method. Since you have wrapped all of this code in your `LogError` method you lose this benefit. For more details see http://log4net.sourceforge.net/release/1.2.0.30316/doc/manual/faq.html#fastLogging – Richard Ev Jan 15 '10 at 14:51
0

a possible use for a log4net wrapper could be a class that gets the calling class and method via reflection to get an idea of where your logging entry happened. at least i use this frequently.

Joachim Kerschbaumer
  • 9,695
  • 7
  • 49
  • 84
-3

I know this answer is late, but it may help someone in the future.

It sounds like you want a programmatic API that XQuiSoft Logging gives you. You don't have to specify which logger you want with XQuiSoft. it is as simple as this:

Log.Write(Level.Verbose, "source", "category", "your message here");

Then via configuration you direct messages by source, category, level, or any other custom filter to different locations (files, emails, etc...).

See this article for an introduction.

Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
Michael
  • 1
  • 2