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种等级你可以通过代码调用。他们是下面几种 (等级从高到低):
- OFF - 不会产生日志 (不能被调用)
- FATAL
- ERROR
- WARN
- INFO
- DEBUG
- ALL - 所有的操作都会产生日志 (不能被调用)
配置
通常建立一个log4net 日志器的标准方法,在桌面程序中在app.config文件中配置,web程序则在web.config文件中配置. 为了能让log4net正常工作,需要在配置文件增加几项配置,下面章节将详细说明相关配置,修改配置文件后,无需重新编译.
<Root>
<root>
<level value="INFO"/>
<appender-ref ref ="FileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
<!--Additivity的值缺省是true-->
<logger name="testApp.Logging" additivity="false">
</logger>
<Loggers>
<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,%10message
will 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)
<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