我一直在寻找适用于.NET (C#)的日志记录框架,阅读了StackOverflow上的一些问题/回答帖子后决定尝试使用log4net。我看到人们一遍又一遍地提到他们使用log4net的包装类,我想知道那是什么样子。
我的代码分为不同的项目(数据访问/业务/网络服务/…)。log4net的包装类会是什么样子?这个包装类需要包含在所有项目中吗?我应该将其作为一个独立的项目来构建吗?
这个包装类应该是一个单例类吗?
我一直在寻找适用于.NET (C#)的日志记录框架,阅读了StackOverflow上的一些问题/回答帖子后决定尝试使用log4net。我看到人们一遍又一遍地提到他们使用log4net的包装类,我想知道那是什么样子。
我的代码分为不同的项目(数据访问/业务/网络服务/…)。log4net的包装类会是什么样子?这个包装类需要包含在所有项目中吗?我应该将其作为一个独立的项目来构建吗?
这个包装类应该是一个单例类吗?
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);
}
}
以下是在类中使用此代码的示例(声明为静态只读字段):
private static readonly ILogger _logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILogger _logger =
LogManager.GetLogger(typeof(YourTypeName));
前者的示例被认为更易于维护。
您不希望创建一个Singleton来处理所有日志记录,因为Log4Net记录调用类型的日志;每个类型使用自己的记录器要比只在日志文件中看到单个类型报告所有消息更加清晰和有用。
由于您的实现应该是相当可重复使用的(在您组织中的其他项目),所以您可以将其作为自己的程序集或理想情况下包含在您自己的个人/组织的框架/实用程序程序集中。不要在您的业务/数据/UI程序集中分别重新声明这些类,这是不可维护的。
Dictionary<type,ILogger>
中已存在的(懒加载实例化的)记录器?我可能误解了一些东西,但是看起来你的实现会为每个使用它的类的实例化创建一个新的记录器。假设这个类的构造函数调用了LogManager.GetLogger()
?或者你是假设所有消费类都必须管理自己的单例以获取它们各自的ILogger
? - mo.假设你选择的是类似于cfeduke上面的回答,你还可以像这样在LogManager
中添加一个重载:
public static ILogger GetLogger()
{
var stack = new StackTrace();
var frame = stack.GetFrame(1);
return new Log4NetWrapper(frame.GetMethod().DeclaringType);
}
这样,在你的代码中现在可以直接使用:
private static readonly ILogger _logger = LogManager.GetLogger();
不要使用这两个方法中的任何一个:
private static readonly ILogger _logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILogger _logger =
LogManager.GetLogger(typeof(YourTypeName));
这实际上等同于第一种选择(即使用MethodBase.GetCurrentMethod().DeclaringType
的选择),只是更简单一些。
ILogger
,谢谢。至于性能,我不确定,但可能会有一点影响...然而,只要你的 ILogger
实例是静态的(通常都是这样),它只会在应用程序中每种类型执行一次,我想这对整体性能来说微不足道。 - Alconjapublic static ILogger GetLogger(MethodBase method) { return GetLogger(method.DeclaringType); }
来简化调用 LogManager.GetLogger(MethodBase.GetCurrentMethod());
。 - xmedeko我已成功将log4net依赖项隔离到单个项目中。如果您打算执行相同操作,请参考我的包装类示例:
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);
}
}
}
}
}
不要忘记在接口项目的AssemblyInfo.cs
中添加此内容(我花了好几个小时才找到这个)。
[assembly: log4net.Config.XmlConfigurator(Watch = true, ConfigFile = "log4net.config")]
log4net.config
的文件中,将其设置为Content
和Copy Always
。AssemblyInfo.cs
中,是指包含你的包装器的项目(在你的代码中的这个答案中)吗? - Snoop有一些框架,比如WPF Prism库,它们推广使用一个门面模式来管理所选的日志框架。
下面是一个使用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);
}
}
}
callerStackBoundaryDeclaringType
,您仍然可以获得发出日志请求的调用者的类名。您需要做的就是在转换模式中包含%C %M
:<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %C.%M - %message%newline" />
</layout>
然而,正如文档所警告的那样,生成调用者类信息是很慢的,因此必须明智地使用它。
我的理解是,log4net的包装类应该是一个静态类,它负责从app.config/web.config或代码中初始化日志对象(例如与NUnit集成)。
LoggingService.LogError("my error message");
这样,如果我以后决定使用另一个日志系统,我只需要更改静态类的内部即可。
因此,我使用了您的想法来使用堆栈跟踪获取调用对象:
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);
}
...
}
有人看到这种方法有问题吗?
IsErrorEnabled
(以及相关属性)的意义。它们的存在是为了避免运行时构建传递给相应日志方法的字符串的成本。由于你将所有代码都包装在LogError
方法中,你失去了这个好处。欲知详情,请参阅http://log4net.sourceforge.net/release/1.2.0.30316/doc/manual/faq.html#fastLogging。 - Richard Ev我知道这个答案有点晚,但它可能会帮助到未来的某个人。
听起来你想要一个编程API,XQuiSoft Logging可以提供给你。你不需要指定想要哪个记录器。只需要这样简单:
Log.Write(Level.Verbose, "source", "category", "your message here");
然后通过配置,你可以将消息按照来源、类别、级别或任何其他自定义过滤器定向到不同的位置(文件、电子邮件等)。
请参见this article进行介绍。