我一直在寻找.net(c#)的日志框架,并决定在阅读stackoverflow上的几个问题/答案线程后给log4net一个机会。我看到人们一遍又一遍地提到他们使用log4net的包装类,我很想知道它会是什么样子。
我将我的代码拆分为不同的项目(数据访问/业务/ webservice / ..)。 log4net包装器类怎么样?包装类是否需要包含在所有项目中?我应该将它作为一个单独的项目一起构建吗?
包装器应该是单件类吗?
答案 0 :(得分:50)
基本上,您创建一个接口,然后创建该接口的具体实现,该接口直接包装Log4net的类和方法。可以通过创建更具体的类来包装其他日志记录系统,这些类包装了这些系统的其他类和方法。最后使用工厂根据配置设置或代码更改行创建包装器的实例。 (注意:使用Inversion of Control等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);
}
}
在您的类中使用此代码的示例(声明为静态只读字段):
private static readonly ILogger _logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
使用以下方法可以获得相同的性能友好效果:
private static readonly ILogger _logger =
LogManager.GetLogger(typeof(YourTypeName));
前一个例子被认为更易于维护。
您不希望创建Singleton来处理所有日志记录,因为Log4Net会记录调用类型;让每种类型使用自己的记录器,而不是仅仅在日志文件中看到报告所有消息的单一类型,它更清晰,更有用。
因为您的实现应该是可以重复使用的(您组织中的其他项目),您可以将其设置为自己的程序集,或者理想情况下将其包含在您自己的个人/组织的框架/实用程序集中。不要在每个业务/数据/ UI程序集中单独重新声明类,这是不可维护的。
答案 1 :(得分:24)
假设您使用cfeduke's answer above这样的内容,您还可以像这样添加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
的方案),只是稍微简单一些。
答案 2 :(得分:5)
您计划在编写log4net包装器方面有什么好处。我建议在编写包装器之前先熟悉log4net类。 cfeduke在他关于如何编写所述包装器的答案中是正确的,但除非你需要在他的示例中添加实际功能,否则包装器只能成功地减慢日志记录过程并增加未来维护者的复杂性。当.Net中的重构工具使这些变化变得非常容易时,尤其如此。
答案 3 :(得分:1)
我已成功将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配置xml放在log4net.config
文件中,将其设置为Content
,Copy Always
答案 4 :(得分:1)
像Prism Library for WPF这样的框架可以促进facade对您选择的日志框架的使用。
这是一个使用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>
但是,当documentation发出警告时,生成调用者类信息的速度很慢,因此必须明智地使用它。
答案 5 :(得分:0)
我的理解是log4net的包装类是一个静态类,负责从app.config / web.config或代码初始化日志对象(例如与NUnit集成)。
答案 6 :(得分:0)
log4net包装器的一个可能用途可能是通过反射获取调用类和方法的类,以了解日志条目的发生位置。至少我经常使用它。
答案 7 :(得分:0)
Alconja,我喜欢你使用stacktrace跳回到调用方法的想法。我想进一步封装调用,不只是检索记录器对象,而是执行实际执行日志记录。我想要的是一个静态类,通过从使用的特定实现中抽象出来来处理日志记录。即
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);
}
...
}
有人看到这种方法有问题吗?
答案 8 :(得分:-3)
我知道这个答案很晚,但未来可能对某人有帮助。
听起来您需要XQuiSoft Logging为您提供的编程API。您不必使用XQuiSoft指定所需的记录器。它很简单:
Log.Write(Level.Verbose,“source”,“category”,“您的消息在这里”);
然后通过配置,您可以按源,类别,级别或任何其他自定义过滤器将邮件定向到不同的位置(文件,电子邮件等)。
请参阅this article了解相关信息。