【问题标题】:What would a Log4Net Wrapper class look like?Log4Net Wrapper 类会是什么样子?
【发布时间】:2010-09-15 01:14:02
【问题描述】:

我一直在寻找 .net (c#) 的日志记录框架,并在阅读了 stackoverflow 上的几个问题/答案线程后决定试一试 log4net。我看到人们一遍又一遍地提到他们使用 log4net 的包装类,我想知道那会是什么样子。

我将我的代码分成不同的项目(数据访问/业务/webservice/..)。 log4net 包装器类会是什么样子?包装类是否需要包含在所有项目中?我应该将它作为一个单独的项目一起构建吗?

包装器应该是单例类吗?

【问题讨论】:

  • Log4Net 本身确实有严格的基于接口的 API。真的没必要包起来。
  • Wrapper 也有一个小缺点 - 当你使用 %M 、 %stacktrace 或 %stacktracedetails 时,这将给出包装类的方法/类型名称,因为 log4net 是从包装方法调用的。跨度>

标签: design-patterns log4net


【解决方案1】:

本质上,您创建一个接口,然后对该接口的具体实现直接包装 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));

前一个例子被认为更易于维护。

您不想创建一个单例来处理所有日志记录,因为 Log4Net 记录调用类型;让每种类型都使用自己的记录器,而不是只在日志文件中查看报告所有消息的单一类型,这样会更加简洁和有用。

因为您的实现应该是相当可重用的(您组织中的其他项目),您可以将其作为自己的程序集,或者最好将其包含在您自己的个人/组织的框架/实用程序程序集中。不要在每个业务/数据/UI 程序集中单独重新声明类,这是不可维护的。

【讨论】:

  • 我想说你不想没有一个记录器,但这更像是一个你想要的记录工厂。每个类都应该有自己的记录器,因为它有助于记录反射。
  • 为什么没有一个单例在Dictionary<type,ILogger> 中查找现有的(延迟实例化的)记录器?我可能误解了一些东西,但看起来你的实现会为使用它的类的每个实例创建一个新的记录器。假设类的构造函数调用LogManager.GetLogger()?还是您假设所有消费类都必须为各自的ILoggers 管理自己的单例?
  • ILogger 实例是静态的,因此每个类只能获得一个实例,而不是每个类实例都有一个新的记录器。
  • 在这种情况下,您是否还必须在使用此包装器的任何地方添加 log4net.dll?这个包装器不能以某种方式嵌入吗?
【解决方案2】:

假设您使用 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 的替代方案),只是简单一点。

【讨论】:

  • 不错的答案!我认为您的 GetLogger 覆盖应该返回 ILogger 而不是 ILog。知道使用 StackTrace 而不是传递类型是否有任何性能开销会很有趣
  • @RaceFace - 修复了 ILogger,谢谢。至于性能,我不确定,但可能会受到一点影响......但是,只要您的 ILogger 实例是静态的(它们通常是静态的),它最多只能执行一次每种类型在您的应用程序中,我认为这对整体性能来说是微不足道的。
  • 我也使用这种方法。然而,在尝试更好地记录泛型类型时,Eric Lippert 警告我堆栈帧并不总是可靠的:stackoverflow.com/questions/7586140/…
  • 我喜欢这种方法,但在检查堆栈跟踪是否有任何内容时我总是过于谨慎。为了安全起见,我在 VS 中使用代码完成宏来生成 GetLogger(typeof(TypeNameHere))。
  • 您可以使用安全方法public static ILogger GetLogger(MethodBase method) { return GetLogger(method.DeclaringType); }来简化调用LogManager.GetLogger(MethodBase.GetCurrentMethod());
【解决方案3】:

您打算从为 log4net 编写包装器中获得什么好处。我建议先熟悉 log4net 类,然后再围绕它们编写包装器。 cfeduke 关于如何编写所述包装器的回答是正确的,但除非您需要向他的示例添加实际功能,否则包装器只会成功减慢日志记录过程并为未来的维护者增加复杂性。当 .Net 中可用的重构工具使此类更改变得超级容易时,尤其如此。

【讨论】:

  • 人们“付出这个代价”的原因之一是为了在未来将 Log4Net 换成其他东西......以后成本会更低。一旦你被一个停业并且不会放弃任何源代码的实现搞砸了,你就更倾向于预先“付出这个代价”。 Log4Net 可能不在此类别中......但其他可能是。
【解决方案4】:

我已经成功地将 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

【讨论】:

  • 你是什么意思“在接口项目的AssemblyInfo.cs 添加这个”你的意思是包含你的包装器的项目(在你的代码中这个答案)?
  • @StevieV ,是的,你是对的。通常它位于您的项目中\Properties\AssemblyInfo.cs
【解决方案5】:

有像 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 警告的那样,生成调用者类信息的速度很慢,因此必须明智地使用它。

【讨论】:

    【解决方案6】:

    我的理解是 log4net 的包装类将是一个静态类,负责从 app.config/web.config 或通过代码(例如与 NUnit 集成)初始化日志记录对象。

    【讨论】:

      【解决方案7】:

      log4net 包装器的一个可能用途是通过反射获取调用类和方法的类,以了解您的日志记录条目发生的位置。至少我经常用这个。

      【讨论】:

        【解决方案8】:

        Alconja,我喜欢你使用堆栈跟踪跳转回调用方法的想法。我正在考虑进一步封装调用,不仅要检索记录器对象,还要执行实际执行记录。我想要的是一个处理日志的静态类,通过从使用的特定实现中抽象出来。 IE。

        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 方法中,因此您将失去此好处。更多详情见log4net.sourceforge.net/release/1.2.0.30316/doc/manual/…
        【解决方案9】:

        我知道这个答案已经晚了,但它可能对将来的某人有所帮助。

        听起来您想要 XQuiSoft Logging 为您提供的编程 API。您不必使用 XQuiSoft 指定您想要的记录器。就这么简单:

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

        然后通过配置,您可以按来源、类别、级别或任何其他自定义过滤器将消息定向到不同的位置(文件、电子邮件等...)。

        有关介绍,请参阅this article

        【讨论】:

        • 这看起来像是对其中一个答案的评论,而不是答案本身
        猜你喜欢
        • 2011-09-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-04
        • 2011-02-04
        • 2011-04-01
        相关资源
        最近更新 更多