为什么记录器建议每个类使用一个记录器?

91

根据 NLog 文档:

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

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


1
嗯,这里似乎有两个问题 - 一个是每个类都有一个实际的日志对象,另一个是日志的名称与类名相同。 - Peter Recore
10个回答

62

使用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的风格,您仍然可以这样做,但是您可以每个类只做一次而不是每次调用都要做,并消除严重的性能问题。


谢谢,这有助于澄清事情。我们之前只是手动将类名和方法放入消息中(例如“ImageCreator.CreateThumbnail() 被调用”),但如果日志记录器可以处理它,那就更好了。 - Daniel T.
1
只是提供信息,现在更好的做法是每个实例都有一个Logger,而不是每个类(即静态)有一个,因为这样更容易捕获线程信息等信息。显然这是一种品味问题,没有“硬性规定”,但我想提出这个建议。 - Will Hartung
7
@will,你能再解释一下吗?当我使用每个类一个记录器时,我总是记录线程ID,以便记录器可以获取当前线程的信息。任何其他线程信息也将对记录器可用。 - Jeremy Wiebe
@Jeremy Wiebe:这是唯一的原因吗?如果我为整个应用程序使用单个类型为logger的全局变量,功能上是否没有问题? - Giorgi Moniava
1
@Giorgi 不,我不这么认为。现在你可以使用CallerInformation属性获取很多信息,这使得每个类一个记录器的重要性稍微降低了一些-https://msdn.microsoft.com/en-us/library/hh534540.aspx - Jeremy Wiebe

17
在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有一个非常有用的代码片段可以完成此操作。 nlogger代码片段将创建以下代码:

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

只需少量按键,您就可以为每个类创建一个日志记录器。它将使用命名空间和类名作为日志记录器的名称。要为您的类记录器设置不同的名称,您可以使用以下代码:

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

正如@JeremyWiebe所说,你不必使用技巧来获取试图记录消息的类的名称:可以通过在布局中使用${logger}轻松地将记录器的名称(通常是类的名称)记录到文件(或其他目标)中。


5
我能看到选择这种方式的几个原因:
  • 如果在日志输出格式中包含记录器名称,您将始终知道特定日志语句来自哪里。
  • 通过打开或关闭某些记录器或设置它们的级别,您可以对细粒度级别控制要查看的日志语句。

4

NLog 在性能方面也有优势。大多数用户都会使用

Logger logger = LogManager.GetCurrentClassLogger()

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

3

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

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


1

从开发角度来看,如果您不必每次创建日志记录器对象,则最容易。另一方面,如果您使用反射动态创建它,将会降低性能。为了解决这个问题,您可以使用以下代码异步动态创建日志记录器:

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

        }
    }
}

1

我立刻想到了两个原因:

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

2
无论在类级别还是全局定义,您都有一个记录器。从可见性的角度来看,全局记录器并不是“在”类外部。您仍然从所涉及的类内部引用全局记录器,因此具有完全的可见性。 - Robert

0

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


1
这使得在另一个应用程序中使用该类变得困难。无论你喜不喜欢,你都必须引用日志记录库。 - Piotr Perak

0

通过命名空间或类轻松配置附加器。


0
如果您正在使用NLOG,您可以在配置中指定调用站点,这将记录日志语句所在的类名和方法。
<property name="CallSite" value="${callsite}" />

你可以使用一个常量作为你的日志记录器名称或程序集名称。
免责声明:我不知道NLOG是如何收集这些信息的,我的猜测是使用反射,所以您可能需要考虑性能。如果你没有使用NLOG v4.4或更高版本,异步方法会有一些问题。

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