Apache log4net™ 手册 - 简介
概述
本文档基于 Ceki Gülcü 的 log4j 简要介绍。
log4net 框架基于 Apache log4j™,有关 log4j 的更多信息,请参见 https://logging.apache.org/log4j/。log4net 框架、源代码、二进制文件、文档、示例和相关材料是在 Apache 许可证 2.0 版 的条款下发布的,该许可证的副本已包含在本发行版中的 LICENSE.txt 文件中。
本文档介绍了 log4net API、其独特功能和设计原理。Log4net 是一个开源项目,基于许多作者的工作。它允许开发人员控制哪些日志语句输出,并具有任意粒度。它可以使用外部配置文件在运行时完全配置。
几乎每个大型应用程序都包含自己的日志记录或跟踪 API。在代码中插入日志语句是调试代码的一种低技术方法。它也可能是唯一的方法,因为调试器并不总是可用或适用。对于多线程应用程序和大型分布式应用程序来说,通常就是这种情况。
应用程序部署后,可能无法使用开发和调试工具。管理员可以使用有效的日志记录系统来诊断和修复许多配置问题。
经验表明,日志记录是开发周期中的一个重要组成部分。它提供了几个优势。它提供了有关应用程序执行的精确 上下文。一旦插入代码,生成日志输出就不需要人工干预。此外,日志输出可以保存在持久性介质中,以便稍后研究。除了在开发周期中的使用之外,一个足够丰富的日志记录包也可以被视为一个审计工具。
日志记录确实有其缺点。它会减慢应用程序的速度。如果过于冗长,它会导致滚动盲。为了缓解这些问题,log4net 被设计为可靠、快速且可扩展的。由于日志记录很少是应用程序的主要关注点,因此 log4net API 努力做到简单易懂和使用。
框架
log4net 可用于多个框架。对于每个支持的框架,都会构建一个针对该框架的程序集
- .NET Standard 2.0 通过 .NET 8.0
- Microsoft .NET Framework 4.6.2
并非所有框架都是平等的,某些功能已从某些构建中排除。有关更多信息,请参见 框架支持 文档。
记录器和追加器
log4net 有三个主要组件:记录器、追加器 和 布局。这三种类型的组件协同工作,使开发人员能够根据消息类型和级别记录消息,并在运行时控制这些消息的格式和报告位置。这些组件由 过滤器 和 对象渲染器 帮助,它们控制追加器的操作并将对象转换为字符串。
记录器层次结构
任何日志记录 API 相对于普通 System.Console.WriteLine 的首要优势在于它能够禁用某些日志语句,同时允许其他语句不受阻碍地打印。此功能假设日志空间,即所有可能的日志语句的空间,根据某些开发人员选择的标准进行分类。
记录器是命名的实体。记录器名称区分大小写,并遵循以下分层命名规则
- 命名层次结构
-
如果一个记录器的名称后跟一个点是 后代 记录器名称的前缀,则称该记录器是另一个记录器的 祖先。如果一个记录器与其后代记录器之间没有祖先,则称该记录器是 子 记录器的 父 记录器。
层次结构的工作方式与 .NET 中的命名空间和类层次结构非常相似。正如我们很快将看到的那样,这非常方便。
例如,名为 "Foo.Bar" 的记录器是名为 "Foo.Bar.Baz" 的记录器的父记录器。类似地,"System" 是 "System.Text" 的父记录器,是 "System.Text.StringBuilder" 的祖先。这种命名方案应该为大多数开发人员所熟悉。
根 记录器位于记录器层次结构的顶部。它在三个方面是特殊的
- 它始终存在
- 它不能通过名称检索
- 它始终具有分配的级别
记录器使用 log4net.LogManager 类的静态方法检索。GetLogger 方法将所需记录器的名称作为参数。它们列在下面
namespace log4net { public class LogManager { public static ILog GetLogger(string name); public static ILog GetLogger(Type type); } }
使用 Type 参数的 GetLogger 方法使用完全限定的类型名称作为要检索的记录器的名称。
这些 GetLogger 方法返回一个 ILog 接口。这是传递回开发人员的 记录器 的表示形式。ILog 接口定义如下
namespace log4net { public interface ILog { /* Test if a level is enabled for logging */ bool IsDebugEnabled { get; } bool IsInfoEnabled { get; } bool IsWarnEnabled { get; } bool IsErrorEnabled { get; } bool IsFatalEnabled { get; } /* Log a message object */ void Debug(object message); void Info(object message); void Warn(object message); void Error(object message); void Fatal(object message); /* Log a message object and exception */ void Debug(object message, Exception t); void Info(object message, Exception t); void Warn(object message, Exception t); void Error(object message, Exception t); void Fatal(object message, Exception t); /* Log a message string using the System.String.Format syntax */ void DebugFormat(string format, params object[] args); void InfoFormat(string format, params object[] args); void WarnFormat(string format, params object[] args); void ErrorFormat(string format, params object[] args); void FatalFormat(string format, params object[] args); /* Log a message string using the System.String.Format syntax */ void DebugFormat(IFormatProvider provider, string format, params object[] args); void InfoFormat(IFormatProvider provider, string format, params object[] args); void WarnFormat(IFormatProvider provider, string format, params object[] args); void ErrorFormat(IFormatProvider provider, string format, params object[] args); void FatalFormat(IFormatProvider provider, string format, params object[] args); } }
记录器 可以 分配级别。级别是 log4net.Core.Level 类的实例。以下级别按优先级递增的顺序定义
- ALL
- DEBUG
- INFO
- WARN
- ERROR
- FATAL
- OFF
如果给定记录器未分配级别,则它将从其具有分配级别的最近祖先继承一个级别。更正式地说
- 级别继承
-
给定记录器 X 的 继承级别 等于记录器层次结构中第一个非空级别,从 X 开始,向上沿着层次结构一直到 根 记录器。
为了确保所有记录器最终都能继承一个级别,根 记录器始终具有分配的级别。根 记录器的默认值为 DEBUG。
以下是四个表格,其中包含各种分配的级别值以及根据上述规则得出的继承级别。
记录器名称 | 分配的级别 | 继承的级别 |
---|---|---|
root | Proot | Proot |
X | 无 | Proot |
X.Y | 无 | Proot |
X.Y.Z | 无 | Proot |
在上面的 示例 1 中,只有 根 记录器分配了级别。此级别值 Proot 由其他记录器 X、X.Y 和 X.Y.Z 继承。
记录器名称 | 分配的级别 | 继承的级别 |
---|---|---|
root | Proot | Proot |
X | Px | Px |
X.Y | Pxy | Pxy |
X.Y.Z | Pxyz | Pxyz |
在上面的 示例 2 中,所有记录器都具有分配的级别值。不需要级别继承。
记录器名称 | 分配的级别 | 继承的级别 |
---|---|---|
root | Proot | Proot |
X | Px | Px |
X.Y | 无 | Px |
X.Y.Z | Pxyz | Pxyz |
在上面的 示例 3 中,记录器 root、X 和 X.Y.Z 分别分配了级别 Proot、Px 和 Pxyz。记录器 X.Y 从其父记录器 X 继承其级别值。
记录器名称 | 分配的级别 | 继承的级别 |
---|---|---|
root | Proot | Proot |
X | Px | Px |
X.Y | 无 | Px |
X.Y.Z | 无 | Px |
在上面的 示例 4 中,记录器 root 和 X 分别分配了级别 Proot 和 Px。记录器 X.Y 和 X.Y.Z 从其具有分配级别的最近父记录器 X 继承其级别值。
日志记录请求是通过调用记录器实例的打印方法之一(通过 log4net.ILog)来完成的。这些打印方法是 Debug、Info、Warn、Error 和 Fatal。
根据定义,打印方法确定日志记录请求的级别。例如,如果 log 是一个记录器实例,则语句 log.Info("..") 是一个级别为 INFO 的日志记录请求。
如果日志记录请求的级别高于或等于其记录器的级别,则称该请求为 已启用。否则,该请求被称为 已禁用。没有分配级别的记录器将从层次结构继承一个级别。此规则总结如下。
- 基本选择规则
-
如果 L >= K,则级别为 L 的记录器请求在级别为(分配或继承的,以适用者为准)K 的记录器中被启用。
此规则是 log4net 的核心。它假设级别是有序的。对于标准级别,我们有 DEBUG < INFO < WARN < ERROR < FATAL。
使用相同名称调用 log4net.LogManager.GetLogger 方法将始终返回对完全相同的记录器对象的引用。
例如,在
ILog x = LogManager.GetLogger("wombat"); ILog y = LogManager.GetLogger("wombat");
x 和 y 引用 完全 相同的记录器对象。
因此,可以配置一个记录器,然后在代码中的其他地方检索相同的实例,而无需传递引用。与生物学上的父母关系形成根本矛盾的是,父母总是先于子女,而 log4net 记录器可以按任何顺序创建和配置。特别是,"父" 记录器将找到并链接到其后代,即使它是在它们之后实例化的。
log4net 环境的配置通常在应用程序初始化时完成。首选方法是读取配置文件。这种方法将在稍后讨论。
log4net 使得通过 软件组件 命名记录器变得容易。这可以通过在每个类中静态实例化一个记录器来实现,记录器名称等于类的完全限定名称。这是一种定义记录器的有用且直接的方法。由于日志输出带有生成记录器的名称,因此这种命名策略使识别日志消息的来源变得容易。但是,这仅仅是命名记录器的一种可能的,尽管是常见的,策略。log4net 并不限制可能的记录器集。开发人员可以自由地根据需要命名记录器。
尽管如此,将记录器命名为它们所在的类似乎是目前已知的最佳策略。它简单明了,开发人员可以清楚地知道每个日志消息来自哪里。最重要的是,它利用应用程序的设计来生成记录器层次结构的设计。希望应用程序的设计已经经过深思熟虑。
附加器
根据记录器选择性地启用或禁用日志请求只是其中的一部分。Log4net 允许日志请求打印到多个目标。在 log4net 中,输出目标称为附加器。附加器必须实现 log4net.Appenders.IAppender 接口。
log4net 包中定义了以下附加器
类型 | 描述 |
---|---|
log4net.Appender.AdoNetAppender | 使用准备好的语句或存储过程将日志事件写入数据库。 |
log4net.Appender.AnsiColorTerminalAppender | 将彩色突出显示的日志事件写入 ANSI 终端窗口。 |
log4net.Appender.AspNetTraceAppender | 将日志事件写入 ASP 跟踪上下文。然后可以在 ASP 页面末尾或 ASP 跟踪页面上呈现这些事件。 |
log4net.Appender.BufferingForwardingAppender | 在将日志事件转发到子附加器之前缓冲它们。 |
log4net.Appender.ColoredConsoleAppender | 将日志事件写入应用程序的控制台。这些事件可以发送到标准输出流或标准错误流。事件可以具有为每个级别定义的可配置文本和背景颜色。 |
log4net.Appender.ConsoleAppender | 将日志事件写入应用程序的控制台。这些事件可以发送到标准输出流或标准错误流。 |
log4net.Appender.DebugAppender | 将日志事件写入 .NET 调试系统。 |
log4net.Appender.EventLogAppender | 将日志事件写入 Windows 事件日志。 |
log4net.Appender.FileAppender | 将日志事件写入文件系统中的文件。 |
log4net.Appender.ForwardingAppender | 将日志事件转发到子附加器。 |
log4net.Appender.LocalSyslogAppender | 将日志事件写入本地 syslog 服务(仅限 UNIX)。 |
log4net.Appender.MemoryAppender | 将日志事件存储在内存缓冲区中。 |
log4net.Appender.NetSendAppender | 将日志事件写入 Windows Messenger 服务。这些消息将在用户终端上的对话框中显示。 |
log4net.Appender.OutputDebugStringAppender | 将日志事件写入调试器。如果应用程序没有调试器,系统调试器将显示该字符串。如果应用程序没有调试器并且系统调试器未处于活动状态,则该消息将被忽略。 |
log4net.Appender.RemoteSyslogAppender | 使用 UDP 网络将日志事件写入远程 syslog 服务。 |
log4net.Appender.RemotingAppender | 使用 .NET 远程处理将日志事件写入远程处理接收器。 |
log4net.Appender.RollingFileAppender | 将日志事件写入文件系统中的文件。RollingFileAppender 可以配置为根据日期或文件大小限制记录到多个文件。 |
log4net.Appender.SmtpAppender | 将日志事件发送到电子邮件地址。 |
log4net.Appender.SmtpPickupDirAppender | 将 SMTP 消息作为文件写入提取目录。然后,这些文件可以被 SMTP 代理(例如 IIS SMTP 代理)读取和发送。 |
log4net.Appender.TelnetAppender | 客户端通过 Telnet 连接以接收日志事件。 |
log4net.Appender.TraceAppender | 将日志事件写入 .NET 跟踪系统。 |
log4net.Appender.UdpAppender | 使用 UdpClient 将日志事件作为无连接 UDP 数据报发送到远程主机或多播组。 |
可以将多个附加器附加到记录器。
对于给定记录器的每个已启用日志请求,都将转发到该记录器中的所有附加器以及层次结构中更高层的附加器。 换句话说,附加器是从记录器层次结构中累加继承的。例如,如果将控制台附加器添加到根记录器,则所有已启用的日志请求至少会打印到控制台。如果另外将文件附加器添加到记录器(例如X),则X及其子级的已启用日志请求将打印到文件和控制台。可以通过将记录器上的附加性标志设置为 false 来覆盖此默认行为,以便附加器累加不再是累加的。
下面总结了控制附加器附加性的规则。
- 附加器附加性
-
记录器X的日志语句的输出将发送到X及其祖先中的所有附加器。这就是“附加器附加性”一词的含义。
但是,如果记录器X的祖先(例如Y)的附加性标志设置为 false,则X的输出将被定向到X及其祖先(包括Y)中的所有附加器,但不包括Y的任何祖先中的附加器。
默认情况下,记录器的附加性标志设置为 true。
下表显示了一个示例
记录器名称 | 添加的附加器 | 附加性标志 | 输出目标 | 注释 |
---|---|---|---|---|
root | A1 | 不适用 | A1 | 没有默认附加器附加到根。 |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | "x"和根的附加器。 |
x.y | 无 | true | A1, A-x1, A-x2 | "x"和根的附加器。 |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | "x.y.z"、"x"和根中的附加器。 |
security | A-sec | false | A-sec | 由于附加性标志设置为 false,因此没有附加器累加。 |
security.access | 无 | true | A-sec | 只有"security"的附加器,因为"security"中的附加性标志设置为 false。 |
过滤器
附加器可以过滤传递给它们的事件。可以在配置中指定过滤器,以允许对通过不同附加器记录的事件进行精细控制。
最简单的控制形式是在附加器上指定一个 Threshold。这通过仅记录级别大于或等于阈值的事件来实现。
可以使用每个附加器上定义的过滤器链进行更复杂和自定义的事件过滤。过滤器必须实现 log4net.Filter.IFilter 接口。
log4net 包中定义了以下过滤器
类型 | 描述 |
---|---|
log4net.Filter.DenyAllFilter | 丢弃所有日志事件。 |
log4net.Filter.LevelMatchFilter | 与事件的级别完全匹配。 |
log4net.Filter.LevelRangeFilter | 与级别范围匹配。 |
log4net.Filter.LoggerMatchFilter | 与记录器名称的开头匹配。 |
log4net.Filter.PropertyFilter | 与特定属性值的子字符串匹配。 |
log4net.Filter.StringMatchFilter | 与事件消息的子字符串匹配。 |
可以将过滤器配置为根据匹配结果接受或拒绝事件。
布局
通常,用户不仅希望自定义输出目标,还希望自定义输出格式。这是通过将布局与附加器关联来实现的。布局负责根据用户的意愿格式化日志请求,而附加器负责将格式化的输出发送到其目标。 PatternLayout(标准 log4net 发行版的一部分)允许用户根据类似于 C 语言 printf 函数的转换模式指定输出格式。
例如,具有转换模式 "%timestamp [%thread] %-5level %logger - %message%newline" 的 PatternLayout 将输出类似于以下内容
176 [main] INFO Com.Foo.Bar - Located nearest gas station.
第一个字段是程序启动后经过的毫秒数。第二个字段是发出日志请求的线程。第三个字段是日志语句的级别。第四个字段是与日志请求关联的记录器的名称。'-' 后的文本是语句的消息。
log4net 包中包含以下布局
类型 | 描述 |
---|---|
log4net.Layout.ExceptionLayout | 呈现日志事件中的异常文本。 |
log4net.Layout.PatternLayout | 根据一组灵活的格式化标志格式化日志事件。 |
log4net.Layout.RawTimeStampLayout | 从日志事件中提取时间戳。 |
log4net.Layout.RawUtcTimeStampLayout | 以通用时间从日志事件中提取时间戳。 |
log4net.Layout.SimpleLayout | 以非常简单的格式格式化日志事件:[level] - [message] |
log4net.Layout.XmlLayout | 将日志事件格式化为 XML 元素。 |
log4net.Layout.XmlLayoutSchemaLog4j | 将日志事件格式化为符合 log4j 事件 dtd 的 XML 元素。 |
对象渲染器
同样重要的是,log4net 将根据用户指定的标准呈现日志消息的内容。例如,如果您经常需要记录 Oranges(当前项目中使用的对象类型),那么您可以注册一个 OrangeRenderer,它将在需要记录橙子时被调用。
对象渲染遵循类层次结构。例如,假设橙子是水果,如果您注册了一个 FruitRenderer,那么所有水果(包括橙子)都将由 FruitRenderer 渲染,除非您注册了一个特定于橙子的 OrangeRenderer。
对象渲染器必须实现 log4net.ObjectRenderer.IObjectRenderer 接口。
请注意,DebugFormat、InfoFormat、WarnFormat、ErrorFormat 和 FatalFormat 方法不使用对象渲染器。