Apache log4net™ 手册 - 内部机制
性能
人们经常反对日志记录的一个理由是它的计算成本。这是一个合理的担忧,因为即使中等规模的应用程序也可能生成数千个日志请求。我们花费了大量精力来衡量和调整日志记录性能。log4net 声称速度快且灵活:速度第一,灵活性第二。
用户应该注意以下性能问题。
-
关闭日志记录时的日志记录性能。
当完全关闭日志记录或仅关闭一组级别时,日志请求的成本包括方法调用加上一个整数比较。
但是,方法调用涉及参数构造的“隐藏”成本。
例如,对于某个日志记录器 log,写入:
log.Debug("Entry number: " + i + " is " + entry[i].ToString());
会产生构造消息参数的成本,即无论消息是否会被记录,都要将整数 i 和 entry[i] 转换为字符串,并将中间字符串连接起来。这种参数构造的成本可能相当高,并且取决于所涉及的参数数量和类型。
为了避免参数构造成本,请写入
if(log.IsDebugEnabled) { log.Debug("Entry number: " + i + " is " + entry[i].ToString()); }
如果禁用了调试,这将不会产生参数构造的成本。另一方面,如果日志记录器启用了调试,它将产生评估日志记录器是否启用的两倍成本:一次在 IsDebugEnabled 中,一次在 Debug 中。这是一个微不足道的开销,因为评估日志记录器大约需要实际记录时间的 1%。
某些用户采用预处理或编译时技术来编译所有日志语句。这在日志记录方面带来了完美的性能效率。但是,由于生成的应用程序二进制文件不包含任何日志语句,因此无法为该二进制文件启用日志记录。许多人认为,为了换取微小的性能提升,付出如此大的代价是不值得的。
-
当启用日志记录时,决定是否记录的性能。
这本质上是遍历日志记录器层次结构的性能。当启用日志记录时,log4net 仍然需要将日志请求的级别与请求日志记录器的级别进行比较。但是,日志记录器可能没有分配的级别;它们可以从日志记录器层次结构继承级别。因此,在继承级别之前,日志记录器可能需要搜索其祖先。
我们已经做出了认真的努力,使这种层次结构遍历尽可能快。例如,子日志记录器仅链接到其现有的祖先。在前面显示的 BasicConfigurator 示例中,名为 Com.Foo.Bar 的日志记录器直接链接到 root 日志记录器,从而绕过了不存在的 Com 或 Com.Foo 日志记录器。这显着提高了遍历速度,尤其是在“稀疏”层次结构中。
遍历层次结构的典型成本通常比完全关闭日志记录时慢 3 倍。
-
实际输出日志消息
这是格式化日志输出并将其发送到目标位置的成本。在这里,我们也做出了认真的努力,使布局(格式化程序)尽可能快地执行。附加程序也是如此。
尽管 log4net 具有许多功能,但其首要设计目标是速度。一些 log4net 组件已经多次重写以提高性能。尽管如此,贡献者经常提出新的优化方法。您应该很高兴知道,当与 SimpleLayout 配置时,性能测试表明 log4net 的日志记录速度与 System.Console.WriteLine 的速度在一个数量级内。
日志事件流
以下是消息在记录时经过的一系列步骤和检查。为了说明这个例子,我们将记录一个在日志记录器 ConsoleApp.LoggingExample 上记录的 INFO 级别的消息。此日志记录器配置为使用 log4net.Appender.ConsoleAppender。此示例中使用的存储库是 log4net.Repository.Hierarchy 对象。
-
用户使用 ILog.Info 方法在日志记录器上记录消息,该日志记录器是通过调用 log4net.LogManager.GetLogger("ConsoleApp.LoggingExample") 获取的。例如:log4net.LogManager.GetLogger("ConsoleApp.LoggingExample").Info("Application Start"); ILog 接口实际上是 log4net 的扩展,它提供了特定于级别的日志记录方法(即 Debug、Info、Warn、Error 和 Fatal)。
-
然后,消息通过适当的 log4net.Repository.Hierarchy.Logger 对象上的 ILogger.Log 方法进行记录。ILogger.Log 方法将要记录的 Level 作为参数,因此适用于所有级别。
-
将存储库阈值级别与消息级别进行比较,以确定是否可以记录消息。如果消息级别低于阈值级别,则不会记录消息。在本例中,存储库是 log4net.Repository.Hierarchy 对象。
-
将 Logger 级别与消息级别进行比较,以确定是否可以记录消息。请注意,如果未为此 Logger 明确指定 Logger 级别,则它将从父 Logger 继承。如果消息级别低于 Logger 级别,则不会记录消息。
-
创建一个 LoggingEvent 实例来封装要记录的消息。
-
构建 Logger 的附加程序列表。这包括附加到父 Logger 的附加程序,除非被 Logger.Additivity 属性排除。
-
将 LoggingEvent 对象传递给每个附加程序的 IAppender.DoAppend 方法。
对于传递 LoggingEvent 的每个附加程序,以下操作将执行
-
将附加程序阈值级别与消息级别进行比较,以确定是否可以记录消息。如果消息级别低于阈值级别,则不会记录消息。
-
如果附加程序具有过滤器链,则将 LoggingEvent 传递到过滤器链,过滤器链可以决定是否可以记录消息。
-
接下来,执行特定于附加程序的检查。通常,此检查将验证附加程序的所有必需属性是否已设置(例如,如果需要,则设置 Layout)。
-
将 LoggingEvent 传递到特定于附加程序的 Append 方法。现在发生的事情是特定于附加程序的。
以下操作在 ConsoleAppender.Append 方法中执行
-
ConsoleAppender 使用 Layout 将消息格式化为要显示的字符串。
-
Layout 使用 LoggingEvent.RenderedMessage 属性获取消息对象的字符串。这将使用为消息对象类型注册的 IObjectRenderer。
-
使用 Console.WriteLine 方法在控制台上显示消息文本。