【问题标题】:Why do loggers recommend using a logger per class?为什么记录器建议每个班级使用记录器?
【发布时间】:2011-03-09 19:20:46
【问题描述】:

根据 NLog 的文档:

大多数应用程序会为每个类使用一个记录器,其中记录器的名称与类的名称相同。

这与 log4net 的操作方式相同。为什么这是一个好的做法?

【问题讨论】:

  • 嗯。似乎这里有两个问题 - 一个是每个类都有一个实际的日志对象,一个是日志的名称与类相同。

标签: c# log4net logging nlog


【解决方案1】:

使用 log4net,每个类使用一个记录器可以轻松捕获日志消息的来源(即写入日志的类)。如果每个类没有一个记录器,而是为整个应用程序设置一个记录器,则需要使用更多反射技巧来了解日志消息的来源。

比较以下:

按类记录

using System.Reflection;
private static readonly ILog _logger = 
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);    

public void SomeMethod()
{
    _logger.DebugFormat("File not found: {0}", _filename);
}

每个应用(或类似应用)一个记录器

Logger.DebugFormat("File not found: {0}", _filename); // Logger determines caller

-- or --

Logger.DebugFormat(this, "File not found: {0}", _filename); // Pass in the caller

使用第二个示例,Logger 需要构建堆栈跟踪以查看谁在调用它,否则您的代码将始终必须传入调用者。使用 logger-per-class 样式,您仍然可以这样做,但您可以为每个类而不是每次调用一次,并消除严重的性能问题。

【讨论】:

  • 谢谢,这有助于澄清事情。我们只是手动将类名和方法放入消息中(即“ImageCreator.CreateThumbnail() 调用”),但如果记录器能够处理它会更好。
  • 仅供参考,每个实例而不是每个类(即静态)都有一个 Logger 成为“更好”的做法,因为这样更容易捕获线程信息等信息。显然这是一个品味问题,没有“硬性规定”,但我想把它扔掉。
  • @will,你能再解释一下吗?当使用每个类的记录器进行记录时,我总是记录线程 ID,以便记录器可以获取当前线程信息。记录器也可以使用任何其他线程信息。
  • @Jeremy Wiebe:这是唯一的原因吗?如果我为整个应用程序使用单个全局变量类型的记录器,在功能上没有问题?
  • @Giorgi 不,我不这么认为。如今,您可以通过 CallerInformation 属性获得大量此类信息,这使得每个类的一个记录器的相关性降低 - msdn.microsoft.com/en-us/library/hh534540.aspx
【解决方案2】:

在 NLog 中使用“每个文件的记录器”的优势:您可以通过命名空间和类名来管理/过滤日志。示例:

<logger name="A.NameSpace.MyClass"      minlevel="Debug" writeTo="ImportantLogs" /> 
<logger name="A.NameSpace.MyOtherClass" minlevel="Trace" writeTo="ImportantLogs" /> 
<logger name="StupidLibrary.*"          minlevel="Error" writeTo="StupidLibraryLogs" />

<!-- Hide other messages from StupidLibrary -->
<logger name="StupidLibrary.*" final="true" /> 

<!-- Log all but hidden messages -->
<logger name="*" writeTo="AllLogs" /> 

NLogger 有一个非常有用的代码 sn-p 可以做到这一点。 nloggersn-p 将创建以下代码:

private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

所以只有几次击键,每个班级都有记录器。它将使用命名空间和类名作为记录器的名称。要为您的类记录器设置不同的名称,您可以使用:

private static NLog.Logger logger = NLog.LogManager.GetLogger("MyLib.MyName");

而且,正如@JeremyWiebe 所说,您不必使用技巧来获取尝试记录消息的类的名称:记录器的名称(通常是类的名称)可以很容易在布局中使用${logger} 记录到文件(或其他目标)。

【讨论】:

    【解决方案3】:

    我可以看出做出这种选择的几个原因。

    • 如果您在日志输出格式中包含记录器的名称,您将始终知道特定日志语句的来源。
    • 您可以通过打开或关闭某些记录器或设置它们的级别来控制您在细粒度级别看到的日志语句。

    【讨论】:

      【解决方案4】:

      在 NLog 的情况下还有性能优势。大多数用户会使用

      Logger logger = LogManager.GetCurrentClassLogger()
      

      从堆栈跟踪中查找当前类需要一些(但不多)性能。

      【讨论】:

        【解决方案5】:

        在大多数情况下,类的名称为记录器提供了一个好的名称。扫描日志文件时,可以看到日志信息,直接关联一行代码。

        这不是最好的方法的一个很好的例子是 Hibernate 的 SQL 日志。有一个名为“Hibernate.SQL”或类似名称的共享记录器,其中许多不同的类将原始 SQL 写入单个记录器类别。

        【讨论】:

          【解决方案6】:

          两个原因立即浮现在脑海:

          1. 为每个类设置单独的日志可以轻松地将与给定类相关的所有日志消息/错误分组在一起。
          2. 在类中使用日志可让您记录在类外部可能无法访问的内部详细信息(例如,私有状态、处理类实现的信息等)。

          【讨论】:

          • 无论是在类级别还是全局定义的,类中都有一个记录器。从可见性的角度来看,全局记录器不在类“外部”。您仍然在相关类中引用全局记录器,因此您具有完全的可见性。
          【解决方案7】:

          从开发的角度来看,如果您不必每次都创建一个记录器对象,这是最简单的。另一方面,如果你不这样做,而是使用反射动态创建它,它会降低性能。为了解决这个问题,您可以使用以下代码以异步方式动态创建记录器:

          using NLog;
          using System;
          using System.Collections.Generic;
          using System.Diagnostics;
          using System.Linq;
          using System.Text;
          using System.Threading.Tasks;
          
          namespace WinForms
          {
              class log
              {
          
                  public static async void Log(int severity, string message)
                  {
                      await Task.Run(() => LogIt(severity, message));
                  }
          
                  private static void LogIt(int severity, string message)
                  {
                      StackTrace st = new StackTrace();
                      StackFrame x = st.GetFrame(2);     //the third one goes back to the original caller
                      Type t = x.GetMethod().DeclaringType;
                      Logger theLogger = LogManager.GetLogger(t.FullName);
          
                      //https://github.com/NLog/NLog/wiki/Log-levels
                      string[] levels = { "Off", "Trace", "Debug", "Info", "Warn", "Error", "Fatal" };
                      int level = Math.Min(levels.Length, severity);
                      theLogger.Log(LogLevel.FromOrdinal(level), message);
          
                  }
              }
          }
          

          【讨论】:

            【解决方案8】:

            可能是因为您希望能够在不破坏封装的情况下记录仅对类可见的方法,这也使得在不破坏记录功能的情况下在另一个应用程序中使用该类变得容易。

            【讨论】:

            • 这使得在另一个应用程序中使用该类变得很困难。不管你喜欢与否,你都必须引用日志库。
            【解决方案9】:

            使按命名空间或类配置附加程序变得容易。

            【讨论】:

              【解决方案10】:

              如果您使用的是 NLOG,您可以在配置中指定调用站点,这将记录日志语句所在的类名和方法。

              <property name="CallSite" value="${callsite}" />
              

              然后您可以使用常量作为记录器名称或程序集名称。

              免责声明:我不知道 NLOG 是如何收集这些信息的,我的猜测是反射,所以您可能需要考虑性能。如果您不使用 NLOG v4.4 或更高版本,则异步方法存在一些问题。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-01-22
                • 1970-01-01
                • 2013-12-09
                • 2020-07-21
                • 2022-11-27
                • 1970-01-01
                • 2021-09-12
                相关资源
                最近更新 更多