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