Log4Net指南

英文好的直接看这里:http://www.codeproject.com/Articles/140911/log4net-Tutorial

介绍

log4net.是.NET下面最伟大的日志工具之一。简单、强大、可扩展,简直是日志工具的黄金标准. 在我看来唯一欠缺是一个比较直接的使用指南。 这个文档,在深度主要讲如何使用,但它还是有点模糊。基本上,如果你已经知道log4net能做什么,如果你只是想知道语法,那么这个文档就适合你了.外面的文档通常是针对一类系统. 我希望我的这份指南能有所突破,我会提供一份完整的指南,包含了一些为曾经遇到的问题。下面的例子和信息是基于log4net组提供的文档的基础上编写的.

基础

log4net有三个部分组成:配置、安装、和调用. 配置通常在app.config 或 web.config 文件中. 为,我们下面会深入的讲解这一块. 如果你想通过独立的配置文件来提升可扩展性,请看 "Getting Away from app.config"这一节. 无论你选择哪一种配置方式,setup相关的代码是必须的,通过这些代码建立与日志模块的通道.最后,最简单部分就是调用相关写日志的方法。

日志等级

一共有7个日志等级,其中有5种等级你可以通过代码调用。他们是下面几种  (等级从高到低):

  1. OFF - 不会产生日志 (不能被调用)
  2. FATAL
  3. ERROR
  4. WARN
  5. INFO
  6. DEBUG
  7. ALL - 所有的操作都会产生日志 (不能被调用)

配置

通常建立一个log4net 日志器的标准方法,在桌面程序中在app.config文件中配置,web程序则在web.config文件中配置. 为了能让log4net正常工作,需要在配置文件增加几项配置,下面章节将详细说明相关配置,修改配置文件后,无需重新编译.

<Root>

你需要在你的logger部分的最顶层放置一个root部分,所有的logger都会从root logger继承相关属性。因此如果一个日志对象没有在配置文件里显式定义,则框架使用根日志中定义的属性。【在<root>标签里,可以定义level级别值和Appender的列表。如果没有定义LEVEL的值,则缺省为DEBUG。可以通过<appender-ref>标签定义日志对象使用的Appender对象。<appender-ref>声明了在其他地方定义的Appender对象的一个引用。在一个logger对象中的设置会覆盖根日志的设置。而对Appender属性来说,子日志对象则会继承父日志对象的Appender列表。这种缺省的行为方式也可以通过显式地设定<logger>标签的additivity属性为false而改变】。下面的例子默认等级为INFO(意味着 DEBUG级别的消息将被忽略)root下面的两个 appenders 将生效:
 
<root>
<level value="INFO"/>
<appender-ref ref ="FileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
<!--Additivity的值缺省是true-->
<logger name="testApp.Logging" additivity="false">
</logger>

<Loggers>

在<root>标签或单个的<logger>标签里的Appender对象可以用<appender>标签定义。
除了root logger外,log4net允许你定义一些额外的logger来进行日志操作。例如,下面有个logger用来将来自OtherClass类对象的日志写到控制台中:
<logger name="Log4NetTest.OtherClass">
<level value="DEBUG"/>
<appender-ref ref="ConsoleAppender"/>
</logger>

注意这里的 logger名称是带命名空间的完整的类名称。如果你想监测整个命名空间,这里只要改成命名空间名称即可. 我不建议在多个logger中复用appenders ,这可能会带来不可预知的后果。

ConfigSections

因为在一个配置文件中,除了log4net相关的配置信息外,往往还有其他的配置信息。所以你需要指定一个配置段用来标识log4net配置所在位置。下面是一个例子用来表示log4net的相关配置信息位于"log4net"这个XML标签下:

<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>

Appender (General)

appender用来配置日志的相关信息.用来指定日志信息写在哪里,如何写,以及在什么情况下写日志. 不同的appender 可能会有不同的parameters ,但有一些是共性的元素,首先是名字(name)和类型( type)。 每个appender 必须被命名,且必须指定一种类型,下面是一个appender实例:

<appender name="LogFileAppender" type="log4net.Appender.FileAppender" >
<param name="File" value="log-file.txt" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n"/>
<param name="Footer" value="[Footer]\r\n"/>
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="DEBUG" />
<param name="LevelMax" value="WARN" />
</filter>
</appender>

Layout

在每个appender中必须要有一个layout配置. 使用不同类型的Layout可能会有一点不同,但是基本的东西都是一样的. 你需要指定一种类型来指明如何写日志。有多种选择,但是为我建议你使用PatternLayout. This will allow you to specify how you want your data written to the data repository. 如果你使用PatternLayout,你需要定义一个子项,来指定转换格式.下面我和会针对转换格式进行详细讲解, 这里是一个关于 pattern layout 的简单例子:

<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc]
- %message%newline"/>
</layout>

转换格式(Conversion Patterns)

就如为上面所提到的,转换格式是用于针对PatternLayout类型的appender ,指定如何存储信息. 转换格式中有很多关键字,就像字符串转义符一样。这里我将针对几个我认为比较有用和重要的进行介绍. 完整的转换格式列表可以看log4net 文档.

%date - 将时间以本地时区格式输出. 例如可以用带“{}”的格式描述符%date{MMMM dd, yyyy HH:mm:ss, fff} 输出时间字符串: "January 01, 2011 14:15:43, 767". 但是一般建议使用log4net  自动的时间格式 (ABSOLUTE, DATE, or ISO8601) ,因为性能会更好.

%utcdate -这个与 %date 基本相同,但是它输出的是通用时间(格林威治时),其他的都一样.

%exception - If an exception is passed in, it will be entered and a new line will be placed after the exception. If no exception is passed in, this entry will be ignored and no new line will be put in. This is usually placed at the end of a log entry, and usually a new line is placed before the exception as well.

%level - 用于指定时间的等级 (DEBUG, INFO, WARN, etc.).

%message - 输出的日志消息,如ILog.Debug(…)输出的一条消息.

%newline - 这是新增一行,基于应用程序运行的平台,他将转换为指定指定平台的换行符。使用这个转换符和使用特定平台的的换行符相比,没有性能差异。

%timestamp - 从应用程序启动起的毫秒数.

%thread - 发起写日志的线程名称(线程如果没有命名,则显示线程ID)

除了以上这些,还有一些比较有用但是要慎用的转换符。这些转换符的使用可能会给性能带来负面影响,如下:

%identity - 当前使用 Principal.Identity.Name 方法的用户标识.

%location - 在Debug  模式下特别有用,用来显示在哪里调用写日志的的方法(行数,方法名等). 但是在Release模式下,信息会变少.

%line - 调用的代码行号.

%method -调用的方法名.

%username - 输出 WindowsIdentity 属性.

你可能会发现有些是采用字母而不是名称(如:%m 对应%message),这是一种简写,我们在这里不做展开了. 另外需要注意每一个转换格式都可以指定输出长度,为了长度规整,会自动增加空格或对内容进行截取.基本的语法是在%符号和名字中加填写一个数字:

  • X - 指定最小字符数,不足指定最小字符数的将自动在字符串左边补足空字符串. For example, %10messagewill give you " hi".
  • -X -与上面一样,只不过是补在字符串右边, %-10message will give you "hi ".
  • .X - 指定最大字符数 ,注意如果超过最大字符串,则从字符串的头部截取而不是尾部. 例如,如果输入"Error entry" , %.10message返回  "rror entry" 。

你也可以将两种组合起来使用,像这样: "%10.20message",如果输入不足10,则在左边补空格,如果超过20个字节,则从起始位置截取.

过滤器(Filters)

filter是appender中的一个重要部分,通过Filters,你可以指定日志等级,甚至可以查找消息中的关键字。Filters 可以混合使用,但是使用时需要小心. 当一个消息符合过滤器条件的时候,它将被写入日志,并且当前这个过滤器的处理就结束了。因此当你做一个复杂过滤的时候,过滤器的顺序就变得尤为重要.

字符串匹配过滤器(StringMatchFilter)
 字符串匹配过滤器用于在输入日志消息中查找指定的字符串. 你可能指定了多个字符串匹配过滤器。他们是以OR 的逻辑进行工作的。过滤器会查找第一个字符串,然后查找第二个字符串,直到找到匹配的字符串。然而这里需要注意的是,如果没有找到指定的字符串,并不意味着整条就是排除了(因为需要传到下一个字符串匹配过滤器去)。这就意味着,当你遇到没有找指定字符串的情况,默认的处理是会是记录整条日志消息。因此,在字符串匹配过滤器集合的最后需要放置一个DenyAllFilter 过滤器,防止这种情况。如下例:
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
级别范围过滤器(LevelRangeFilter)

一个级别范围过滤器用来告诉系统,只有在指定范围内的级别日志才会记录,因此,在下面的例子中 INFO, WARN, ERROR, 或 FATAL级别的日志将被记录。但是DEBUG 级别的日志将被忽略。这里不需要在最后添加DenyAllFilter 过滤器,因为它隐含表示 不在该范围内被拒绝记录。

<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
级别匹配过滤器(LevelMatchFilter)

级别匹配过滤器和级别范围过滤器一样,只不过它仅针对单个级别进行匹配。但是它并不隐含不匹配就拒绝记录日志的规则。所以我们需要在最后加上DenyAllFilter 过滤器

<filter type="log4net.Filter.LevelMatchFilter">
<levelToMatch value="ERROR"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter"/>
拒绝所有过滤器(DenyAllFilter)

如果忘记了这个过滤器,你的appender 将不能按预想的逻辑工作. 这个过滤器的目的就是指定所有内容都不做日志记录.如果只有这一个过滤器,那么什么内容都不会记录日志.

<filter type="log4net.Filter.DenyAllFilter" />

Appenders

每一类appender 因为其存储数据的位置不同,都有其自己的语法集合。其中记录到数据库的方式是最不同寻常的。我将列出一些我认为最常用的类型进行介绍。然而,通过上面讲的这些信息,你可能已经能使用网上给出的例子.  log4net官方网站有很多针对不同类型appenders的例子.就像我之前所说的,通过阅读log4net 的文档,一般都不会有什么问题.我一般都拷贝他们的例子,然后根据自己的需要进行修改.

Console Appender

我通常在测试中使用这类appender ,但是它在实际产品中也是很有用的。它将日志写到输出窗口,如果是控制台程序,则写到命令窗口。下面的例子会输出这样的日志: "2010-12-26 15:41:03,581 [10] WARN Log4NetTest.frmMain - This is a WARN test." 并在最后有一个换行.

<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{ABSOLUTE}
[%thread] %level %logger - %message%newline"/>
</layout>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>

File Appender

这类appender 将把日志写到text文件中。这里我们需要注意的是,我们必须指定text文件的名字(在下面这个例子中,日志文件的名字是mylogfile.txt  这个文件将保存在应用程序同一目录下) ,我们已经指定了追加的模式(而非覆盖模式),我们还指定了Minimal Lock ,以确保这个文件不会被多个 appenders影响。

<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="mylogfile.txt" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
</appender>

Rolling File Appender

这个appender 可以用在所有使用File appender 的地方。其功能和File appender 基本一样,只不过额外增加了设置文件大小的功能,超过文件大小则新写一个文件。 这样你就不需要当心长时间运行的日志文件过载问题。如果没有采用这种appender ,只要写一个文件的时间足够长,甚至一个小应用程序都可能压垮操作系统的文件系统。下面的例子中,我将记录与上面file appender类似式样的日志。但是我指定了日志文件的大小为10MB,并且在我删除之前,指定保留存储5个归档文件。这归档文件的名字和日志文件名相同,只不过后面跟了“.”和数字(例如: mylogfile.txt.2 表示第二个归档文件).staticLogFileName 属性确保当前日志文件以 file 标签里定义的命名。(在我的例子中是: mylogfile.txt).

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="mylogfile.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>

示例代码

public sealed class Logger
{ #region [ 单例模式 ]
private static readonly Logger _logger = new Logger();
private static readonly log4net.ILog _Logger4net = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// <summary>
/// 无参私有构造函数
/// </summary>
private Logger()
{
} /// <summary>
/// 得到单例
/// </summary>
public static Logger Singleton
{
get
{
return _logger;
}
}
#endregion #region [ 参数 ]
public bool IsDebugEnabled
{
get { return _Logger4net.IsDebugEnabled; }
}
public bool IsInfoEnabled
{
get { return _Logger4net.IsInfoEnabled; }
}
public bool IsWarnEnabled
{
get { return _Logger4net.IsWarnEnabled; }
}
public bool IsErrorEnabled
{
get { return _Logger4net.IsErrorEnabled; }
}
public bool IsFatalEnabled
{
get { return _Logger4net.IsFatalEnabled; }
}
#endregion #region [ 接口方法 ] #region [ Debug ]
public void Debug(string message)
{
if (this.IsDebugEnabled)
{
this.Log(LogLevel.Debug, message);
}
} public void Debug(string message, Exception exception)
{
if (this.IsDebugEnabled)
{
this.Log(LogLevel.Debug, message, exception);
}
} public void DebugFormat(string format, params object[] args)
{
if (this.IsDebugEnabled)
{
this.Log(LogLevel.Debug, format, args);
}
} public void DebugFormat(string format, Exception exception, params object[] args)
{
if (this.IsDebugEnabled)
{
this.Log(LogLevel.Debug, string.Format(format, args), exception);
}
}
#endregion #region [ Info ]
public void Info(string message)
{
if (this.IsInfoEnabled)
{
this.Log(LogLevel.Info, message);
}
} public void Info(string message, Exception exception)
{
if (this.IsInfoEnabled)
{
this.Log(LogLevel.Info, message, exception);
}
} public void InfoFormat(string format, params object[] args)
{
if (this.IsInfoEnabled)
{
this.Log(LogLevel.Info, format, args);
}
} public void InfoFormat(string format, Exception exception, params object[] args)
{
if (this.IsInfoEnabled)
{
this.Log(LogLevel.Info, string.Format(format, args), exception);
}
}
#endregion #region [ Warn ] public void Warn(string message)
{
if (this.IsWarnEnabled)
{
this.Log(LogLevel.Warn, message);
}
} public void Warn(string message, Exception exception)
{
if (this.IsWarnEnabled)
{
this.Log(LogLevel.Warn, message, exception);
}
} public void WarnFormat(string format, params object[] args)
{
if (this.IsWarnEnabled)
{
this.Log(LogLevel.Warn, format, args);
}
} public void WarnFormat(string format, Exception exception, params object[] args)
{
if (this.IsWarnEnabled)
{
this.Log(LogLevel.Warn, string.Format(format, args), exception);
}
}
#endregion #region [ Error ] public void Error(string message)
{
if (this.IsErrorEnabled)
{
this.Log(LogLevel.Error, message);
}
} public void Error(string message, Exception exception)
{
if (this.IsErrorEnabled)
{
this.Log(LogLevel.Error, message, exception);
}
} public void ErrorFormat(string format, params object[] args)
{
if (this.IsErrorEnabled)
{
this.Log(LogLevel.Error, format, args);
}
} public void ErrorFormat(string format, Exception exception, params object[] args)
{
if (this.IsErrorEnabled)
{
this.Log(LogLevel.Error, string.Format(format, args), exception);
}
}
#endregion #region [ Fatal ] public void Fatal(string message)
{
if (this.IsFatalEnabled)
{
this.Log(LogLevel.Fatal, message);
}
} public void Fatal(string message, Exception exception)
{
if (this.IsFatalEnabled)
{
this.Log(LogLevel.Fatal, message, exception);
}
} public void FatalFormat(string format, params object[] args)
{
if (this.IsFatalEnabled)
{
this.Log(LogLevel.Fatal, format, args);
}
} public void FatalFormat(string format, Exception exception, params object[] args)
{
if (this.IsFatalEnabled)
{
this.Log(LogLevel.Fatal, string.Format(format, args), exception);
}
}
#endregion
#endregion #region [ 内部方法 ]
/// <summary>
/// 输出普通日志
/// </summary>
/// <param name="level"></param>
/// <param name="format"></param>
/// <param name="args"></param>
private void Log(LogLevel level, string format, params object[] args)
{
switch (level)
{
case LogLevel.Debug:
_Logger4net.DebugFormat(format, args);
break;
case LogLevel.Info:
_Logger4net.InfoFormat(format, args);
break;
case LogLevel.Warn:
_Logger4net.WarnFormat(format, args);
break;
case LogLevel.Error:
_Logger4net.ErrorFormat(format, args);
break;
case LogLevel.Fatal:
_Logger4net.FatalFormat(format, args);
break;
}
} /// <summary>
/// 格式化输出异常信息
/// </summary>
/// <param name="level"></param>
/// <param name="message"></param>
/// <param name="exception"></param>
private void Log(LogLevel level, string message, Exception exception)
{
switch (level)
{
case LogLevel.Debug:
_Logger4net.Debug(message, exception);
break;
case LogLevel.Info:
_Logger4net.Info(message, exception);
break;
case LogLevel.Warn:
_Logger4net.Warn(message, exception);
break;
case LogLevel.Error:
_Logger4net.Error(message, exception);
break;
case LogLevel.Fatal:
_Logger4net.Fatal(message, exception);
break;
}
}
#endregion
}//end of class #region [ enum: LogLevel ]
/// <summary>
/// 日志级别
/// </summary>
public enum LogLevel
{
Debug,
Info,
Warn,
Error,
Fatal
}
#endregion

日志助手类

别忘了,需要在类定义前加上一句:

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

否则什么都不会写哦。

在app.config/web.config以外进行配置

你可能想在独立的配置文件中进行log4net 的配置.事实上, 你可能会发现这是一种最佳的配置方式,因为你可以在你的不同的项目中形成不同的标准配置文件。这有助于减少开发时间,及是配置文件标准化。要实现这一目的,你只要在你的程序的两个地方进行修改即可。首先,你需要将你的配置文件保存成另一个文件。 除了不在app.config 或web.config 文件中外,其他的格式都一样。第二点需要改变的是在setup调用的地方,如下:

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "MyStandardLog4Net.config", Watch = true)]

在上面这行代码中,你也可以用"ConfigFileExtension"后缀代替 "ConfigFile"。这样,你需要把你的配置文件名称改成你的assembly 名称。如下所示:

[assembly: log4net.Config.XmlConfigurator(ConfigFileExtension = "mylogger", Watch = true)]

在上面这个例子中,如果我们的应用程序名字叫 test.exe,那么log4net 配置文件的名字为 :text.exe.mylogger.

配置文件模版

下面是一个空白的配置文件模版

<!--This is the root of your config file-->
<configuration> <!-- Level 0 -->
<!--This specifies what the section name is-->
<configSections> <!-- Level 1 -->
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,
log4net"/> <!-- Level 2 -->
</configSections>
<log4net> <!-- Level 1 -->
<appender> <!-- Level 2 -->
<layout> <!-- Level 3 -->
<conversionPattern /> <!-- Level 4 -->
</layout>
<filter> <!-- Level 3 -->
</filter>
</appender>
<root> <!-- Level 2 -->
<level /> <!-- Level 3 -->
<appender-ref /> <!-- Level 3 -->
</root>
<logger> <!-- Level 2 -->
<level /> <!-- Level 3 -->
<appender-ref /> <!-- Level 3 -->
</logger>
</log4net>
</configuration>
上一篇:【Leetcode】【Medium】Unique Paths II


下一篇:【Leetcode】【Medium】Unique Paths