【问题标题】:How should I architect logging within my application?我应该如何在我的应用程序中构建日志记录?
【发布时间】:2011-10-05 19:01:02
【问题描述】:

因此,我对此进行了大量研究,但在我说“是的,那个”的地方没有找到任何答案。我希望博学多才的 StackOverflow 人群可以帮助我。

我在几个不同的场景中遇到过这个问题。假设我有一个 C# 应用程序,并且有一些重要的事情要记录。

public class MyClass
{
    ... 

    public void ImportantMethod()
    {
        DoInterestingThing();

        var result = SomethingElseImportant();
        if (result == null)
        {
            logger.Log("I wasn't expecting that. No biggie.");
            return;
        }

        MoreInterestingStuff(); 
}

我感兴趣的是我从哪里得到logger

在我看来,我有几个选择。

  1. 在构造函数中将其注入到 MyClass 中。
  2. 使用全球可用的服务定位器检索它。
  3. 使用方法装饰器和 AOP 为我完成日志记录。

这些似乎都不是很好的选择。 #3 看起来是不可能的,因为我在我的业务逻辑中间记录,而不仅仅是对我的方法调用、输入参数和/或抛出的异常进行简单的跟踪。 #2,虽然看起来很简单,但单元测试真的很难。当然,我想对所有内容进行单元测试。 #1,虽然它可以正常工作,但我所有的业务逻辑都被记录对象弄乱了与业务对象本身无关

关于上述选项之一的任何替代想法或想法? 非常感谢!

编辑:为了清楚起见,我已经知道如何进行 DI(我使用 Unity),并且我已经知道一个好的日志框架(我使用 log4net)。只是想知道如何以最智能的方式在整个应用程序中使用架构意义上的日志记录。


* 编辑 *

我将 Mark Seeman 的答案标记为解决方案。我浏览了我的应用程序,发现我的大部分日志调用都在做装饰器可以做的事情。即,记录方法的条目、抛出的任何异常以及退出返回值。

在某些情况下,我仍然需要直接在方法中记录。一个例子是我想在一个不返回任何东西但不返回 throw an Exception 的方法中快速失败。在这些情况下,我有一个单例,它持有一个引用 LogProvider,它反过来会检索一个命名的日志实例。代码如下所示:

private ILog logger = LogProviderFactory.Instance.GetLogger(typeof(Foo));

LogProviderFactory 有一个方法SetProvider,它允许你换出单例。所以在单元测试中我可以做到:

// LogProviderFactory.Instance now is our mock
LogProviderFactory.SetProvider(MockLogProvider);

日志装饰器使用与单例相同的 LogProvider(通过注入获得),因此日志在整个系统中是统一的。

所以实际上最终的解决方案主要是选项 #3 和混合 #2(它是服务定位器模式,但服务被“注入”到定位器中)。

AOP

就“面向方面的编程”而言,我对该语言的局限性感到有点失望。希望 AOP 在未来的版本中被视为一等公民。

  • 我尝试了 PostSharp,但无法让它在我的机器上正确运行。此外,您必须在系统上安装 PostSharp 才能使用它(而不是仅调用解决方案随附的 dll 或类似的东西),这是一个很大的限制。
  • 我使用了 LinFu 并且能够使其部分工作。然而,它在少数情况下爆炸了。新的 2.0 版本几乎没有文档记录,所以这是一个障碍。
  • 然而,Unity 的接口拦截似乎开箱即用。我很幸运,我想记录的大部分内容都在实现接口的类中。

【问题讨论】:

  • 始终密切关注您是否需要记录某些内容,或者只是通过抛出异常快速失败。快速摆脱困境往往比您预期的要好。
  • 好点史蒂文!在我最近的应用程序中,我需要放入一些不一定与异常/错误相关的日志记录。诸如“收到新消息”或“添加用户”之类的内容。不是调试语句本身,而是有关应用程序执行以保存记录的信息。

标签: architecture logging dependency-injection service-locator cross-cutting-concerns


【解决方案1】:

Ninject Contetual Binding docs在工厂或工厂方法中使用请求上下文进行上下文绑定部分中,我有一个利用容器为您的类注入适当记录器的示例: (忍者语):

Bind<ILog>().ToMethod( context => LogFactory.CreateLog( context.Request.Target.Type ) );

对于追踪类型的东西,Mark 的拦截文章描述了最好的方法。

我能否再问一次,您是否深入阅读了@Mark Seemann 引用的文章,然后在没有投票的情况下将其丢弃。

【讨论】:

  • 抱歉,没有看到@Mark Seemann 文章的第一个链接。不过,它看起来并没有解决我的例子。 AOP/Interception 非常适合跟踪,但示例方法的作用远不止于此。我在我的业务逻辑中间调用日志记录函数——我感兴趣的不仅仅是方法调用之前和之后发生的事情。 Ninject 文章反映了 log4net 如何做到这一点,并且与选项 #2 相同。我喜欢它...但是很难进行单元测试。使用服务定位器实现如何在单元测试中指定模拟记录器?
  • @Onisemus:不知道你在说什么 SL。如果你真的读过它,你会看到 ILogger 接口的构造函数注入是可测试/可模拟的。关键是您的 DI 工具可以处理松散耦合,您只需要一个 Binding 即可处理 20 个带有日志记录的类。
  • 哦,我读到了。有两个例子,一个是构造函数注入,另一个是工厂方法。我指的是工厂方法。构造函数注入很有意义,我明白为什么它很受欢迎。只是诸如日志记录之类的横切关注点使域变得混乱。我对使用它犹豫不决 - 我可以将 ILog 注入到域中的一半类中。
  • @Onisemus:属性注入与单个 Bind 相关,如我在此处的回答中可以解决的问题。但主要的一点是,如果你的东西被正确拆分,你的代码就不需要做太多的日志记录——你可以通过 AOP 和/或 mini- 注入跟踪在你的域类之外调用的东西DI 容器的 AOP 设施。然后对于剩余的东西,这应该是非常罕见的,让 CR/R# 生成 ctors,这些 ctors 表明这个域类确实认为它有重要的东西要记录(而不是你到处乱扔的东西)
  • @Onisemus:我会将层次结构作为 ctor 注入、prop inj、服务定位器、单例、其他静态方法/全局变量、全局变量。但是 ctor 注射效果更好。一旦你正确使用了 DI 容器(不是 SL!),Singletons/statics 的感知好处很快就会消失。但是说了这么多,省去所有这些输入,并默认使用 ctor 注入,并寻找成群结队的依赖组,这些依赖组代表缺失的抽象。
【解决方案2】:

两位:

(1) - 一个预构建的日志框架。

有些人喜欢 Log4Net,但我是 EntLibs 的粉丝。这在实际日志记录方面起到了很大的作用。 EntLibs 等工具可让您登录到不同类型的日志存储库(数据库、消息队列、滚动文本文件等)。它们还允许您根据类别等登录到不同的实例。它们通常是高度可配置的。

(2) - 包装日志框架的自定义类。

所以“logger”是你写的东西,它调用日志框架来进行实际的日志记录。

我喜欢这种方法有几个原因:

  • 当自定义包装器进入单独的程序集时,您可以将日志记录框架 (#1) 与应用程序的其余部分分离。
  • 通过编写您自己的 Logging API,您可以定义适合您需要的方法签名,并且可以对其进行扩展。
  • 如果您在一个团队中工作,您可以使方法签名非常易于使用,这样没有人有理由说使用日志记录太难了。
  • 它使记录保持一致。它还可以轻松地对写入文件、控制台或事件日志的“非法”代码进行代码搜索,因为您的日志记录中不会有任何代码(这一切都在框架中)。
  • 通过为每一层编写特定的自定义类,您可以在幕后预填充大量数据,从而使编写实际应用程序代码的人的工作更加轻松。您可以设置严重性、优先级、默认事件 ID、类别等。
  • 就应用程序的复杂性和增长而言,它可以很好地扩展;对于较小的应用程序来说,它可能看起来很笨重,但如果它随着时间的推移开始增长,你会有足够的空间。

这是我参与的项目中的信息日志记录类的示例。它有一堆易于调用的公共方法,以及一个调用框架的私有方法(ConcreteLogInformation)。

public static void LogInformation(string title, string message)

public static void LogInformation(string title, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId, string category)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

private static void ConcreteLogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

【讨论】:

  • 我同意你所说的一切,除了你使用静态方法的最后一部分。使用静态方法使测试变得更加困难。最好在需要记录器的类型的构造函数中注入ILogger 接口。一种对我来说效果很好的方法是在该接口上使用单个 Log(LogEntry) 方法,并使用一组调用 Log(LogEntry) 接口方法的扩展方法(例如 Log(string)Log(Exception))。 (仍然为您的回答 +1)。
  • @Steven - 是的,这听起来很酷。我必须承认我从来没有足够的动力使用 DI 进行日志记录(如果你的 DI 失败 - 你如何记录?)但仍然是一个值得考虑的好主意。
  • “如果你的 DI 失败”到底是什么意思?
  • 使用 DI - 依赖倒置(框架/子系统)。假设您进行了新的部署,它基于配置,配置错误 - 您的日志记录不起作用,可能会使诊断问题变得更加困难。另一件事是,您可以将使用 DI 视为使整个日志记录系统更加复杂 - 就日志记录子系统而言,我认为 KISS(保持简单,愚蠢)原则是好的 - 假设保持简单 = 健壮性。
  • 在您的 DI 容器中应该不可能有这样的错误配置。单元测试应确保配置正确(这些测试通常非常简单)。错误配置的日志系统当然仍然是可能的,但这不是 DI 特定的。使用 DI 当然是额外的抽象层,但我的经验是它使开发变得更加简单。事实上,让您的应用程序依赖于ILogger 接口而不是具体的日志记录子系统,很难被认为是过度设计,我当然会考虑这个 KISS。
【解决方案3】:

使用loggingDecorator

【讨论】:

  • 原则上,这似乎是个好主意。但是在实践中,我可能有 20 个需要记录的不同对象或服务。在那种情况下,我会有效地将这个数字翻倍。一个 LoggingArticleManager,一个 LoggingOrderService?似乎效率不高。
  • @Onisemus:这就是拦截的用武之地。
  • +1 @Onisemus:我不相信你已经正确阅读了链接的文章,建议重新阅读以获得拦截点
  • 如果中间需要登录一个方法,该方法做的太多了:)
  • 如果有,注入的日志依赖是唯一的方法,但我不认为有太多的情况应该从方法内部记录。不过,永远不要说永远:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-29
  • 2012-08-18
  • 2016-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多