56

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

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

包装器应该是单例类吗?

4

9 回答 9

54

本质上,您创建了一个接口,然后是该接口的具体实现,它直接包装了 Log4net 的类和方法。可以通过创建包装这些系统的其他类和方法的更具体的类来包装其他日志记录系统。最后,使用工厂根据配置设置或代码行更改创建包装器的实例。(注意:您可以使用诸如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 程序集中单独重新声明类,这是不可维护的。

于 2008-10-03T11:49:54.603 回答
26

假设您要使用类似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),只是简单一点。

于 2009-02-26T02:52:23.027 回答
5

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

于 2008-10-03T12:30:30.387 回答
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文件中,将其设置为ContentCopy Always

于 2016-02-02T22:23:00.907 回答
1

有一些框架,如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>

但是,正如文档警告的那样,生成调用者类信息的速度很慢,因此必须明智地使用它。

于 2016-07-15T11:17:50.433 回答
0

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

于 2008-10-03T11:45:47.873 回答
0

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

于 2008-10-03T11:45:49.743 回答
0

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

有人看到这种方法有问题吗?

于 2009-08-17T09:02:38.963 回答
-3

我知道这个答案已经晚了,但它可能会在未来帮助某人。

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

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

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

有关介绍,请参阅本文

于 2010-12-06T19:39:20.817 回答