如何在依赖注入中使用log4net

79

我正在尝试弄清楚在依赖注入框架中使用log4net的正确模式和用法。

Log4Net使用ILog接口,但需要我调用

LogManager.GetLogger(Reflection.MethodBase.GetCurrentMethod().DeclaringType)

在每个需要记录信息的类或方法中,我都需要使用Log4Net进行日志记录。这似乎违反了IoC原则并将我与Log4Net耦合。

我是否应该在某处加入另一层抽象?

另外,我需要记录自定义属性,例如当前用户名:

log4net.ThreadContext.Properties["userName"] = ApplicationCache.CurrentUserName;

我该如何封装它,以便我不必每次都记得这样做,同时仍然保持当前记录的方法。我应该像这样做还是完全错了?

public static class Logger
{
    public static void LogException(Type declaringType, string message, Exception ex)
    {
        log4net.ThreadContext.Properties["userName"] = ApplicationCache.CurrentUserName;
        ILog log = LogManager.GetLogger(declaringType);
        log.Error(message, ex);
    }
}

1
更有趣的问题是你使用了哪个依赖注入容器? - CodeMonkeyKing
4个回答

61
我认为你在这里把小事看得太重了。ILog和LogManager是一个轻量级的门面,几乎与Apache commons-logging一一对应,并且不会将您的代码实际耦合到log4net的其余部分。
另外,我发现当有人在log4net周围创建MyCompanyLogger包装器时,他们几乎总是错失重点,会失去框架的重要和有用的功能,抛弃有用的信息,失去使用甚至是简化的ILog接口所可能获得的性能收益,或者以上全部。换句话说,将log4net封装起来以避免耦合是反模式。
如果您需要注入它,请通过属性使日志记录器实例可访问以启用注入,但以传统方式创建默认实例。
至于在每个日志消息中包含上下文状态,您需要添加一个全局属性,其ToString()解析为您要查找的内容。例如,对于当前堆大小:
public class TotalMemoryProperty
{
    public override string ToString()
    {
        return GC.GetTotalMemory(false).ToString();
    }
}

然后在启动时插入它:

GlobalContext.Properties["TotalMemory"] = new TotalMemoryProperty();

10
你的抱怨与此无关。这个人在问如何将 ILog 实例注入到他容器内部的对象中。有许多原因需要这样做。 - CodeMonkeyKing
1
@CodeMonkeyKing:这篇发言是针对他提出的在log4net和他的代码之间创建抽象层的方法,尤其是在问题末尾的示例代码块。 - Jeffrey Hantin
4
在我看来,这可能是一种反模式,但我发现,就像编写代码的工程师一样,存在着各种不同的观点。当然,这只是我的观点 :) - CodeMonkeyKing
1
封装log4net是很好的实践。问题不在于封装库,而在于封装得不好。 - Dan
1
但是使用 ILogLogManager 确实将您与 log4net 的其余部分耦合在一起,因为它要求您在代码中具有 using log4net 并且引用 log4net.dll。 log4net.ILogSomeStd.ILog 可能具有相同的基本名称和成员,但它们仍然是两个完全不同的类。 - James Curran
显示剩余6条评论

3

另一种方法是通过以下方式连接log4net ILog 接口的容器注册序列(下面以在 ASP.NET 上下文中使用 Castle 为例):

container.Register(Component.For<ILog>().LifeStyle
    .PerWebRequest.UsingFactoryMethod(() => LogManager.GetLogger(
    MethodBase.GetCurrentMethod().DeclaringType)));

只需要在需要时用构造函数注入 ILog 即可。


我认为这样做更好,因为您可以为正在构建的实现类获取正确的记录器:Component.For<ILog>().UsingFactoryMethod((kernel, model, cc) => LogManager.GetLogger(cc.Handler.ComponentModel.Implementation)) - Mark

2
我是如何做到的是创建了自己的接口,并有一个类实现这个接口,使用Log4Net,并注入该类。这样你就不会被绑定在Log4Net上...你可以为新的日志记录器创建另一个类,并注入该类。

2
如果你非常关心有多个记录器,你可以声明一个全局记录器,并为所有的日志记录需求调用它。这样做的缺点是你失去了内置的能力来识别发出日志的类,但你也可以注入自己的自定义消息到日志中以识别该类。
这取决于你希望日志记录变得多么强大。你也可以简单地将记录器放在主类中,并让所有未处理的异常上升到它进行日志记录。

但是通常你肯定想记录不止异常吧? - UpTheCreek
你是在暗示这里使用单例模式吗?我希望不是。 - Nate Zaugg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接