Apache log4net™ 手册——概述【翻译】

时间:2023-03-08 16:32:10

原文地址

本文内容

  • 概述
  • 框架
  • 日志(Loggers)和追加器(Appenders)
  • 日志层次(Logger hierarchy)
  • 追加器(Appenders)
  • 筛选(Filters)
  • 布局(Layouts)
  • 对象渲染(Object Renderers)

最近公司有个项目,需要解析三种二进制流,我写其中的两种,最后用一个 Windos 服务来实时处理。之前的项目都只是简单记下,主要是数据库层日志,但针对这个项目,貌似需要点复杂的日志系统,在软件各个层次上都需要日志,否则,出毛病的话,还真看不出来问题在哪,而且调试 Windows 服务也很麻烦~于是,用了 log4net。

本文只是概述,接下来较具体说明 log4net。

Apache log4net™ 手册——介绍【翻译】

概述


log4net 框架是基于 Apache log4j™,关于 log4j 的更多信息查看 http://logging.apache.org/log4j/

本文介绍 log4net API,其独特的功能和设计原理。log4net 是一个基于很多作者的工作的开源项目。log4net 允许开发人员用任意粒度控制日志语句的输出。log4net 使用外部配置文件在运行时完全可配置。

几乎每个大型应用程序都包含它自己的日志,或追踪 API。向代码中插入日志语句对于调试程序是一个很低级的方法。这也是唯一一个方法,因为调试器不会总是可靠或可用的。通常是在大规模多线程应用程序和分布式应用程序的情况。

应用程序一旦被部署,使用开发或调试工具将不是不可能的。一个管理员可以使用有效的日志系统来诊断和修复很多配置问题。

经验说明,日志是一个开发周期中很重要的组件。它提供了很多优势。如提供关于应用程序执行的准确的上下文环境(context )。日志一旦插入到代码,日志输出的产生不需要人工干预。此外,日志输出可以保存在永久介质,以在稍后进行研究。除了用在开发周期中,丰富的日志记录包也可以被看作一个审计工具。

日志确实也有它的缺点。它可以减慢应用程序。如果太详细,它可能会导致滚动失败。为缓解这些问题,log4net 被设计成可靠的,快速的和可扩展的。由于日志很少是一个应用程序的主要焦点,因此,log4net API 致力于易于理解和使用。

框架(Frameworks)


Log4net 对很多框架可用。如下所示:

  • Microsoft® .NET Framework 1.0
  • Microsoft .NET Framework 1.1
  • Microsoft .NET Framework 2.0
  • Microsoft .NET Framework 3.5
  • Microsoft .NET Framework 4.0
  • Microsoft .NET Framework 3.5 Client Profile
  • Microsoft .NET Framework 4.0 Client Profile
  • Microsoft .NET Compact Framework 1.0
  • Microsoft .NET Compact Framework 2.0
  • Mono 1.0
  • Mono 2.0
  • Microsoft Shared Source CLI 1.0
  • CLI 1.0 Compatible

不是所有的框架都是一样的,而且有些功能已经被排除。更多信息可查看 Framework Support

日志(Loggers)和追加器(Appenders)


Log4net 有三个主要组件:loggers,appenders 和 layouts这三个组件一起工作使得开发者能够根据信息类型和等级(Level)记录信息,以及在运行时控制信息的格式化和信息的写入位置(如控制台,文件,内存,数据库等)。过滤器(filter)帮助这些组件,控制追加器(appender)的行为和把对象转换成字符串的对象渲染。

日志层次(Logger hierarchy)


普通的 System.Console.WriteLine 任何日志记录 API 的第一个也是最重要的优点在于,它可以禁用某些日志语句,而让其他人轻松打印。这个功能假定,日志空间,也就是,所有可能的日志语句的空间,可以根据开发者选择的标准被分类。

日志器(logger)被命名为实体。日志器名称是大小写敏感的,它们遵循以下层次的命名规则:

命名层次

如果后面跟一个点的名字是它后代(descendant )日志器名称的一个前缀,那么,一个日志器被当做是另一个日志器的祖先(ancestor)。如果自身和后代日志器之间没有祖先,那么,一个日志器被当做是一个子记录器的父母。

该层次的工作机制非常类似 .NET 中的命名空间(namespace)和类层次结构(class hierarchy)。我们将会看到,这很方便。

例如,名为“Foo.Bar”的日志器是一个名为“Foo.Bar.Baz”日志器的父。类似的,“System”是“System.Text”的父,以及“System.Text.StringBuilder”的祖先。这种命名方式对很多开发者来说很熟悉。

也就是说,应用程序中每个类,都可能存在一个日志/记录器~那么,接下来,在类的继承层次结构中,如果一个类继承另一个类,那么它们自己的日志器,也会存在一个继承关系。

root 日志器位于日志器层次的最顶部。有三个有点:

  1. 它总是存在
  2. 它不能通过名称检索
  3. 它总是分配一个等级

使用 log4net.LogManager 类的静态方法来检索日志器。GetLogger 方法采取需要记录日志的类作为参数。如下所示:

namespace log4net

{

    public class LogManager

    {

        public static ILog GetLogger(string name);

        public static ILog GetLogger(Type type);

    }

}

GetLogger 方法具有一个参数,或是字符串,或是 Type 对象。log4net 用利用反射获取需要写日志的对象。

GetLogger 方法都返回一个 ILog 接口。代表一个传递给开发者的 Logger。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);

    }

}

Loggers 会分配一个等级。等级是 log4net.Core.Level 类的一个实例。下面是按优先级递增的定义的等级:

  • ALL
  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL
  • OFF

如果一个给定的记录器没有被分配一个级别,那么,它将继承它最近的、并已分配值的祖先。更正式地说:

等级层次(Level Inheritance)

The inherited level for a given logger X, is equal to the first non-null level in the logger hierarchy, starting at X and proceeding upwards in the hierarchy towards the root logger.

为了确保所有日志器最终都继承一个等级,root 日志器总是具有一个已分配的等级。root 日志器的默认值是 DEBUG

根据上面规则,下面四个表是各种分配的值和继承的值。

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

none

Proot

X.Y

none

Proot

X.Y.Z

none

Proot

上表的例子,只有 root logger 被分配了一个等级,其他三个 X、X.Y 和 X.Y.Z 的继承值都是 Proot。

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

Px

Px

X.Y

Pxy

Pxy

X.Y.Z

Pxyz

Pxyz

上表的例子,所有的日志器都分配了一个等级。这样,不需要继承值。

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

Px

Px

X.Y

none

Px

X.Y.Z

Pxyz

Pxyz

上表的例子,日志器 root、X 和 X.Y.Z 被分别分配等级值 Proot、Px 和 Pxyz。日志器 X.Y 会从它的父 X 继承等级值。

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

Px

Px

X.Y

none

Px

X.Y.Z

none

Px

上表的例子,日志器 root 和 X 等级值分别分配为 Proot 和 Px。日志器 X.Y 和 X.Y.Z 会从离它最近的,并已分配值的父来继承。

日志请求是通过调用一个日志实例的打印方法(log4net.ILog)完成。这些打印方法是 Debug、Info、Warn、Error Fatal

根据定义,打印方法决定日志请求的等级。例如,如果 log 是一个日志器的实例,那么,语句 log.Info("..") 是等级为 INFO 的日志请求。

如果它的等级大于等于它日志器的等级,那么日志请求就被认为已启用。否则,请求被认为禁用。没有分配等级的日志器将从层次上继承。规则如下:

Basic Selection Rule

A log request of level L in a logger with (either assigned or inherited, whichever is appropriate) level K, is enabled if L >= K.

该规则是 log4net 的核心。它假设等级是有序的。对于标准等级,具有 DEBUG < INFO < WARN < ERROR < FATAL。

用相同的参数调用 log4net.LogManager.GetLogger 方法总是返回引用一个完全相同 logger 对象。如下所示:

ILog x = LogManager.GetLogger("wombat");

ILog y = LogManager.GetLogger("wombat");

x 和 y 完全引用一个相同的 logger 对象。

因此,有可能配置一个日志器,然后在代码中的任何地方都可以检索到相同的实例。在生物学上,父母总是先于它们的孩子,而 log4net 日志器可以以任何顺序创建和配置。具体地说,一个“父”日志器将发现和链接到它的后代,即使它在它的后代之后才实例化。

log4net 环境的配置通常是在应用程序初始化。优先的方法是读取一个配置文件。

追加器(Appenders)


基于日志器选择启用或禁用日志请求的能力仅仅是其一部分。log4net 允许日志请求打印到多个目的地。按 log4net 的定义,一个输出目的地被称为附加器(appender)。追加器必须实现 log4net.Appenders.IAppender 接口。

下面是 log4net 定义的追加器:

类型 描述
log4net.Appender.AdoNetAppender 把日志事件写到数据库,通过语句或存储过程。
log4net.Appender.AnsiColorTerminalAppender 把按颜色高亮标记的日志事件写到 ANSI 终端窗口。
log4net.Appender.AspNetTraceAppender 把日志事件写到 ASP 跟踪环境。之后,就可以在 ASP 页面底部或跟踪页面呈现。
log4net.Appender.BufferingForwardingAppender 在把日志转发给子追加器之前,缓冲区记录事件。
log4net.Appender.ColoredConsoleAppender 把日志事件写到应用程序控制台。可以写到标准输出流或 error 流。可以为每个等级定义文本和颜色。
log4net.Appender.ConsoleAppender 把日志事件写到应用程序控制台。可以写到标准输出流或 error 流。
log4net.Appender.DebugAppender 把日志事件写到 .NET 系统。
log4net.Appender.EventLogAppender 把日志事件写到 Windows Event Log。
log4net.Appender.FileAppender 把日志事件写到文件系统的一个文件。
log4net.Appender.ForwardingAppender 把日志事件转发给子追加器。
log4net.Appender.LocalSyslogAppender 把日志事件写到 local syslog service(仅针对 Unix)。
log4net.Appender.MemoryAppender 把日志事件存储在内存缓存。
log4net.Appender.NetSendAppender 把日志事件写到 Windows Messenger service。这些信息显示在一个用户终端的对话框。
log4net.Appender.OutputDebugStringAppender 把日志事件写到调试器。如果应用程序没有调试器,那么系统调试器会显示字符串。如果应用程序没有调试器,并且系统调试器不活跃,那么消息会被忽略。
log4net.Appender.RemoteSyslogAppender 把日志事件通过 UDP 网络写到一个 remoting syslog service。
log4net.Appender.RemotingAppender 把日志事件通过 .NET remoting 写到一个 remoting sink。
log4net.Appender.RollingFileAppender 把日志事件写到文件系统中的一个文件。RollingFileAppender 可以被配置,基于日期或大小约束写入到多个文件。
log4net.Appender.SmtpAppender 把日志事件发送到一个 email 地址。
log4net.Appender.SmtpPickupDirAppender 把日志事件写成 SMTP 消息到一个文件拾取目录。这些文件可以被 SMTP 代理读取或发送,如 IIS SMTP 代理。
log4net.Appender.TelnetAppender 客户端通过 Telnet 连接,来接受日志事件。
log4net.Appender.TraceAppender 把日志事件写到 .NET trace system。
log4net.Appender.UdpAppender 使用 UdpClient 把日志事件作为无连接 UDP 数据发送到一个远程主机或多播组。

日志器可以采用多个追加器。

对于给定的日志器,每个启用日志请求都将被转发到所有追加器,以及层次结构中更高的追加器。也就是说,追加器是从日志器层次结构中相加继承的(inherited additively)。例如,如果一个 console appender 被添加到 root 日志器,那么,所有启用日志请求都将至少打印到控制台上。如果再添加一个文件追加器,那么,对于 X,对 X 和 X 的子,启用日志请求将打印到一个文件和控制台上。覆盖此默认行为也是可以的,通过在日志器中设置相加标识为 false,追加器积累将不再相加。

管理附加器相加(additivity )的规则如下。

Appender Additivity

The output of a log statement of logger X will go to all the appenders in X and its ancestors. This is the meaning of the term "appender additivity".

However, if an ancestor of logger X, say Y, has the additivity flag set to false, then X's output will be directed to all the appenders in X and it's ancestors up to and including Y but not the appenders in any of the ancestors of Y.

Loggers have their additivity flag set to true by default.

下表列出一个例子:

Logger Name
Added Appenders
Additivity Flag Output Targets
Comment
root

A1 not applicable A1 There is no default appender attached to root.
x
A-x1, A-x2 true A1, A-x1, A-x2 Appenders of "x" and root.
x.y none true
A1, A-x1, A-x2
Appenders of "x" and root.
x.y.z A-xyz1
true
A1, A-x1, A-x2, A-xyz1 Appenders in "x.y.z", "x" and root.
security A-sec false A-sec
No appender accumulation since the additivity flag is set to false.
security.access none true A-sec
Only appenders of "security" because the additivity flag in "security" is set to false.

筛选(Filters)


追加器可以筛选被传递给它们的事件。在配置中指定过滤器,允许更好地控制通过不同的追加器记录的事件。

控制的最简单形式是在追加器中指定阈值。

更复杂的和自定义事件过滤可以使用每个追加器中定义的过滤器链来完成。过滤器必须实现 log4net.Filter.IFilter 接口。

下表列出 log4net 中定义的过滤器:

类型 描述
log4net.Filter.DenyAllFilter 丢弃所有日志事件。
log4net.Filter.LevelMatchFilter 准确匹配事件等级。
log4net.Filter.LevelRangeFilter 匹配一个范围的等级。
log4net.Filter.LoggerMatchFilter 匹配一个日志器名字的开始。
log4net.Filter.PropertyFilter 匹配指定属性名称的子字符串。
log4net.Filter.StringMatchFilter 匹配事件消息的子字符串。

过滤器可以被配置为根据匹配接受或拒绝事件。

布局(Layouts)


很多时候,用户不仅希望自定义输出目的地,还要定义输出格式。这是通过与追加器相关的布局(layout)来实现的。布局负责根据用户意愿格式化日志请求,而一个追加器负责发送已格式化的输出到目的地。 PatternLayout,是标准的 log4net 的一部分,让用户根据转换模式指定数据格式,类似于 C 语言 printf。

例如,带转换模式的 PatternLayout "%timestamp [%thread] %-5level %logger - %message%newline" 将会输出:

176 [main] INFO  Com.Foo.Bar - Located nearest gas station.

第一个域是流逝毫秒时间;第二个域是日志请求的线程;第三个域日志语句等级,如 INFO、WARN、ERROR 等;第四个域是与日志请求相关的日志器的名称。“-”后面的文本是具体消息。

下表是 log4net 中定义的布局:

类型

描述

log4net.Layout.ExceptionLayout

Renders the exception text from the logging event.

log4net.Layout.PatternLayout

Formats the logging event according to a flexible set of formatting flags.

log4net.Layout.RawTimeStampLayout

Extracts the timestamp from the logging event.

log4net.Layout.RawUtcTimeStampLayout

Extracts the timestamp from the logging event in Universal Time.

log4net.Layout.SimpleLayout

Formats the logging event very simply: [level] - [message]

log4net.Layout.XmlLayout

Formats the logging event as an XML element.

log4net.Layout.XmlLayoutSchemaLog4j

Formats the logging event as an XML element that complies with the log4j event dtd.

对象渲染(Object Renderers)


同样重要的是,log4net 会根据用户指定的标准呈现日志消息的内容。例如,如果你经常需要记录 Oranges,它是你项目中使用的一个对象类型,那么,你可以注册一个 OrangeRenderer,这样无论何时需要记录 Orange 就可以调用。

对象渲染按照类的层次结构。例如,假设橙子是水果,如果你注册一个 FruitRenderer,所有的水果,包括橙子,将按 FruitRenderer 渲染,除非你已经注册一个橙子的 OrangeRenderer。

对象渲染器必须实现 log4net.ObjectRenderer.IObjectRenderer 接口。

注意,DebugFormat、InfoFormat、WarnFormat、ErrorFormatFatalFormat 方法不使用 ObjectRenderers

 

Apache log4net™ 手册——介绍【翻译】