What would a Log4Net Wrapper class look like?

Design PatternsLog4net

Design Patterns Problem Overview


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?

Design Patterns Solutions


Solution 1 - Design Patterns

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.

Solution 2 - Design Patterns

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.

Solution 3 - Design Patterns

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.

Solution 4 - Design Patterns

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

Solution 5 - Design Patterns

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.

Solution 6 - Design Patterns

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

Solution 7 - Design Patterns

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.

Solution 8 - Design Patterns

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?

Solution 9 - Design Patterns

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionXerxView Question on Stackoverflow
Solution 1 - Design PatternscfedukeView Answer on Stackoverflow
Solution 2 - Design PatternsAlconjaView Answer on Stackoverflow
Solution 3 - Design PatternsWhite Vans IncView Answer on Stackoverflow
Solution 4 - Design PatternsJeson MartajayaView Answer on Stackoverflow
Solution 5 - Design PatternsLeonardoView Answer on Stackoverflow
Solution 6 - Design PatternsdevioView Answer on Stackoverflow
Solution 7 - Design PatternsJoachim KerschbaumerView Answer on Stackoverflow
Solution 8 - Design PatternsJeppe AndreasenView Answer on Stackoverflow
Solution 9 - Design PatternsMichaelView Answer on Stackoverflow