【问题标题】:What are the best practices for including logging using log4net?使用 log4net 进行日志记录的最佳实践是什么?
【发布时间】:2009-12-14 17:00:33
【问题描述】:

有人告诉我使用 log4net 在我的代码中添加“日志记录”,问题是没有人可以及时旅行,看看需要使用日志记录来解决哪些现实问题。

因此,是否有一套关于记录什么以实现合理的成本/收益权衡的指导方针?

因此:

应该添加什么样的日志记录 到一个有用的应用程序 稍后?

(代码使用了很多WCF,一边是Winforms,另一边是正常运行在同一台机器上的“服务器”)

--

我已经排除了 AJM 的答案,以使用它指向的许多 cmets 来撰写有用的博客文章,但如果有人想出了一组不错的“rules of thumb”,我可能会更改预期的答案。

【问题讨论】:

  • 以下答案很少能解决 Ian 的主要问题(我认为这是一个非常好的问题):应该向应用程序添加哪些类型的日志记录以便以后有用?假设调试日志记录。 (希望)有一个甜蜜点,在记录每个语句、所有变量的值和记录应用程序的主要功能区域之间的中间地带。它是什么?哪些类型/示例的日志记录有用,而不是仅仅将日志文件作为垃圾?
  • 我想不言而喻,如果 WCF 服务转移到其他服务器,您将拥有两组日志而不是一组。

标签: .net wcf log4net


【解决方案1】:

要记住的一点是,虽然您的配置将处理不同级别的日志记录,但您可能会在日志调用中造成大量开销。例如:

// some kind of loop
// do some operations
Logger.LogDebug(myObject.GetXmlRepresentation());
// end loop

这显然只会在您有一个记录器侦听调试日志的情况下记录该对象,但是无论您的记录级别如何,构建 XML 对象的调用都会运行,并且可能会导致相当大的速度下降。


正确的解决方案是:

// some kind of loop
// do some operations
if (Logger.IsDebug)
{
    Logger.LogDebug(myObject.GetXmlRepresentation());
}
// end loop

【讨论】:

    【解决方案2】:

    我发现这篇文章很有帮助:http://blog.codinghorror.com/the-problem-with-logging/

    特别是我认为极简主义方法确实是要走的路。过去我尝试记录太多,但这会使代码膨胀

    还认为日志条目越多越好的想法是错误的,因为它会使日志本身膨胀。我现在将日志记录的主要好处视为提供“立足点”或对正在发生的事情的概述。如果特定区域需要更多细节,那就这样吧,但默认位置应该越少越好

    【讨论】:

      【解决方案3】:

      对于这类问题,我最喜欢的信息来源是Release It——一本来自实用主义者的书。强烈推荐。

      他们关于您的问题的基本观点是,日志记录应该针对操作级别的需求。运营人员最关心站点可能出现故障的异常情况(即连接池已满,与服务器的连接已关闭等)。确保消息是不言自明的,并且非常清楚问题是什么,如果适用,修复是什么。编写供人类消费的消息。

      我在函数进入/退出样式日志中看到了一点点。顶级捕获异常的堆栈跟踪很有用,记录可能发生系统崩溃的区域(即完整的连接池)很有用,记录以前系统崩溃的区域也很有用。

      【讨论】:

      • @Brian:好点:OP 没有提到日志记录的目的是为了谁。操作日志!= 开发人员日志。我完全同意您对操作日志记录的建议,但正如@GrayWizard 在下面提到的那样,如果开发人员无法直接访问他们正在使用的系统,他们有时需要强大的日志记录。 (我曾经调试过一个系统,操作人员甚至不让我们查看日志......!)
      【解决方案4】:

      一般来说,我按以下顺序添加日志记录:

      1. 函数进入/退出
      2. 函数内部的主要逻辑步骤
      3. 所有中间计算的详细日志记录

      很明显,我很少接触到最后一个,如果您为 log4net 滚动自己的包装器并使用处理模式,并且可能还有一点反射魔法,那么第一个是微不足道的。

      第二个通常在验收/集成和回归测试期间完成,因为主要的逻辑流程和问题区域被识别出来。在此阶段添加日志记录也相当少,因为您通常知道在调试和测试时需要在哪里添加它。

      第三个通常(无论如何对我来说)只在经历回归或特别重要的代码部分完成。

      我已经为 log4net 实现了一个基本的包装器对象,它为我提供了直接的日志记录功能以及一个上下文对象,它可以与 IDisposable 一起使用,将“进入/退出”逻辑包装在一个很好的方便包中。

      【讨论】:

      • 第 1 步之后的日志记录是不是太冗长了?这似乎是您将日志记录与调试混合在一起 - 如果您在日志中需要这么多信息,那么无论如何您都应该在调试器中单步执行应用程序。如果您的方法太大以至于您需要记录每个“主要步骤”(=如果您的函数有多个主要设置),那么您将遇到不同的问题...
      • 我猜有可能。进入/退出由单个 using 块处理,可以按方面完成,所以我一般都不担心。主要逻辑一般在 1:10 左右,所以会有一些 logger.Debug、logger.Verbose 行。正如我所说,我很少到达#3,但有时它会派上用场。我不允许在生产环境中调试已部署的代码,并且很少访问数据(出于安全原因),因此日志往往可以方便地找到开始回归的位置。
      【解决方案5】:

      log4net 的一大优点是您可以将事件记录到不同的类别。默认值为调试、信息、警告和错误。我喜欢这些意思

      调试 - 非常冗长,包含大量调试信息。例如,SQL 查询。
      信息 - 值得了解的有用信息。
      警告 - 没有什么致命的,但操作员应该意识到这个问题。
      错误 - 应用程序现在不稳定,日志包含异常消息和堆栈跟踪等诊断信息。

      在代码中使用这些,例如

      _log.Info("Updating object.");

      会向任何感兴趣的听众写一条 INFO 级别的消息。

      然后你可以在配置中连接监听器来处理日志消息。这是我正在使用的一个:

      <log4net>
      <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
        </layout>
      </appender>
      <appender name="FileAppender" type="log4net.Appender.FileAppender">
        <file value="c:\temp\servicelog.txt" />
        <appendToFile value="true" />
        <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline%exception" />
        </layout>
      </appender>
      <root>
        <level value="ERROR" />
        <appender-ref ref="ConsoleAppender" />
      </root>
      <logger name="Warehouse.Core">
        <level value="INFO" />
        <appender-ref ref="FileAppender" />
      </logger>
      </log4net>
      

      这表示:所有 ERROR 消息到控制台,所有 INFO 消息从记录器 Warehouse.Core 到给定文件。

      由于将类别连接到侦听器是在配置中完成的,因此您可以在部署后更改日志记录。如果没有任何内容在监听,日志记录几乎没有性能损失。


      关于日志记录的成本与收益,肯定存在过多的日志记录(没有人会使用的巨大日志)和不足的日志记录(单行表示“失败”)之间的最佳平衡点。

      我的策略是在 INFO 中记录可能失败的情况:外部依赖项(应用程序启动、服务调用、SQL 连接),以及在 DEBUG 中更复杂的大量代码(业务逻辑中的诊断消息、单个 SQL 调用、某些方法调用)。

      在不寻常的情况下(例如)采用通常不会出现的默认值会转至 WARN,而异常会转至 ERROR 或 FATAL。

      另外:请记住,WCF 有一个非常出色的服务跟踪查看器,可让您“深入了解”各个数据包以及堆栈两端如何处理它们。这也可以通过配置获得,无需更改代码。因此,我通常只会对 WCF 服务调用和响应进行非常简短的日志记录。

      【讨论】:

        【解决方案6】:

        记录日志不是一件容易的事,但我的经验是所有日志都应该可供负责方搜索。一个有用的错误目标是直接发送电子邮件(在某些情况下是短信)。但最终,所有日志记录数据都应该可以在具有可靠用户界面的数据库中进行搜索。

        当特定帐户收到电子邮件时,可以对其进行处理并直接放入数据库。下面有一些分类和处理规则:

        • 严重错误:应立即通过电子邮件/短信提供
        • 未来问题:每日/每周电子邮件
        • 调试信息 ==> 每天/每周发送电子邮件,通知自上次以来产生了多少调试信息。

        调试的内容可以以不同的方式写入数据库,但我们需要考虑性能。大量数据不应该在“生产模式”下写入数据库。这应该以每日/每周为基础进行。最好的方法是生成一个本地文件(例如 XML 或纯文本),并在维护时间(晚上)将该文件放入数据库。应该可以启动/停止调试会话,并且仅在调试会话完成时将调试信息写入数据库。

        调试组件可以实现为 WCF 和 log2net,直接访问数据库和/或定期放入数据库的本地文件存储。

        有一点很清楚...所有错误/异常都应该记录在某处。没有什么比丢失错误消息更令人恼火的了:)

        调试愉快!

        【讨论】:

          【解决方案7】:

          应该将哪些类型的日志记录添加到以后有用的应用程序中?

          如果您从自己的异常类中抛出异常,或者更好的是,您的所有异常类都派生自基类,请在(基)异常构造函数中添加 ERROR 级别的日志记录;让您不必记住每次接球/投球。如果您有大型代码库,则很有用。

          对于 CLR 或第 3 方异常,记录 Exception.ToString() 而不仅仅是消息,否则您会错过完整的堆栈跟踪(假设程序员没有吞下异常或重新抛出内部异常)

          在您知道或怀疑您会遇到问题的领域中关注 DEBUG 详细信息(只需询问 QA 或技术支持在哪里查看;-)

          如果您关注robustness principle,那么当您忽略或更改输入或预期行为时,您可能需要 INFO 或 WARN 日志记录。如果您的 WCF 服务开始接收意外(但可解析)的输入,这可能会很有用。

          为确保您的应用程序运行良好,请不要在默认启用调试级别日志记录的情况下发布/安装它,错误或警告可能是要走的路。

          我不同意接受答案的最后一部分,因为 log4net 具有出色的过滤功能;条件是您的程序员了解日志记录是有代价的(根据 CK 的回答)并且他们(以及 QA 和技术支持)知道过滤器,并且在 DEBUG 配置所有内容是一个坏主意。如果您在 DEBUG 级别记录大型对象图、xml 文档、数据库结果等,请将其包装在一些代码中以减少开销:

          if (log.IsDebugEnabled)
          {
              log.DebugFormat("Loaded in {0} ms {1}", timer.ElapsedMilliseconds, dataSet.GetXml());
          }
          

          我建议您遵循recommended static logger-per-class 方法,只是因为当您必须实际使用它并使用过滤器缩小问题范围时,它应该使日志记录更有用,例如记录器匹配过滤器。

          如果您采用这种方法并且愿意接受(相当小的)性能损失,这里有一种方法可以使用堆栈跟踪为任何类创建 ILog 对象并确保连接配置文件以监控更改:

          public static class LogFactory
          {
              /// <summary>
              /// Create log whose name is the calling methods class name.
              /// </summary>
              /// <remarks>
              /// <para>
              /// Configures the log repository if it hasn't been configured before.
              /// </para>
              /// <para>
              /// Creates a debug log message right after getting the logger, this follows
              /// the log4net recommendation to log first message as early as possible.
              /// </para>
              /// </remarks>
              /// <returns>Log ready for work.</returns>
              public static log4net.ILog Create()
              {
                  var method = new StackTrace().GetFrame(1).GetMethod();
                  var log = log4net.LogManager.GetLogger(method.DeclaringType);
          
                  if (log4net.LogManager.GetRepository().Configured == false)
                  {
                      try
                      {
                          new FileIOPermission(FileIOPermissionAccess.Read,
                              AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)
                              .Demand();
          
                          var configFile = new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
                          log4net.Config.XmlConfigurator.ConfigureAndWatch(configFile);
                          log.DebugFormat("Log4net configured and watching {0}", configFile.FullName);
                      }
                      catch (System.Security.SecurityException e)
                      {
                          log.DebugFormat("Unable to watch config file due to security permissions. {0}", e.ToString());
                      }
                  }
          
                  log.DebugFormat("Logging {0}", log.Logger.Name);
          
                  return log;
              }
          }
          

          【讨论】:

            【解决方案8】:

            将事件记录到事件查看器。

            明智地使用 INFO DEBUG WARNING 和 ERROR。

            在生产中开发显示所有内容(使用服务器端的控制台 - 见下文)时,仅记录错误(可配置)

            我在每个班级的乞讨中创建了一个记录器,给它 typeof(myClass) 但这是可选的...

            我在 DEV 中将 WCF 作为控制台应用程序托管 - 因此在控制台中查看服务器日志也很容易,但是一旦它成为服务,您就必须使用事件查看器....

            啊 - 如果你将它与 WCF“调用时间”(从客户端到服务器的调用)进行比较,它会非常快,所以它不会真正影响你的时间,除非你有一些疯狂的日志(例如 nHibernate) )

            【讨论】:

              【解决方案9】:

              对于 WCF,可能有更好的方法来执行此操作;对于 WinForms,您可以考虑查看 PostSharp。这将允许您记录方法调用而不会弄乱您的代码。在幕后,您仍然会使用出色的 log4net。

              警告:我自己没有使用过;我在代码营中看到了一个非常令人印象深刻的演示。

              【讨论】:

              • 面向方面的日志记录往往有点“全有或全无”:另外,以这种方式记录参数值可能很困难,所以通常你最终还是会进行自定义日志调用。
              • @JeremyMcGee:PostSharp 允许记录参数值 - 请参阅 MethodExecutionEventArgs.GetReadOnlyArgumentArray 方法。您还可以使用带通配符的全局(程序集)属性有选择地应用它。
              • @TrueWill:(点头)抱歉,这表明我对在构建后干预我的代码的非自然工具的偏见!
              • @JeremyMcGee:明白。这以及它与持续集成的交互是我自己还没有使用 PostSharp 的主要原因。 (不过,出于类似的日志记录原因,我真的很想上一个项目。)
              猜你喜欢
              • 2013-11-19
              • 1970-01-01
              • 2017-11-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-11-02
              • 1970-01-01
              • 2018-03-27
              相关资源
              最近更新 更多