如果项目上过线的话,那你一定知道Log是多么重要。为什么说Log重要呢?因为上线项目不允许你调试,你只能通过Log来分析问题。这时打一手好Log的重要性绝不亚于写一手好代码。项目出问题时,你要能拿出Log证明自己负责的部分没有问题,如果是自己的问题,要从Log里快速找出错误原因。如果没有从Log里找出错误原因,那一定是一件很悲催的事情,特别是在bug不容易重现的情况下。那简直就是叫天天不灵,叫地地不应啊。
Log4Net简介
1、百科
log4net库是Apache log4j框架在Microsoft .NET平台的实现,是一个帮助程序员将日志信息输出到各种目标(控制台、文件、数据库等)的工具,Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。关于对Log4j详细的介绍,请点这里。
2、优点
几乎所有的大型应用都会有自己的用于跟踪调试的API。因为一旦程序被部署以后,就不太可能再利用专门的调试工具了。然而一个管理员可能需要有一套强大的日志系统来诊断和修复配置上的问题。经验表明,日志记录往往是软件开发周期中的重要组成部分。它具有以下几个优点:它可以提供应用程序运行时的精确环境,可供开发人员尽快找到应用程序中的Bug;一旦在程序中加入了Log 输出代码,程序运行过程中就能生成并输出日志信息而无需人工干预。另外,日志信息可以输出到不同的地方(控制台,文件等)以备以后研究之用。Log4net就是为这样一个目的设计的,用于.NET开发环境的日志记录包。
3、结构
log4net 有四种主要的组件,分别是Logger(记录器), Repository(库), Appender(附着器)以及 Layout(布局).
关于结构的详情讲解,将在下文中进行。
4、使用
有些朋友估计已经看过很多Log4Net的文章了,说必须如何如何配置,为了纠正这个观点,下面一个简单的例子,不做任何配置,直接使用Log4Net。
可以去http://logging.apache.org/log4net/ 下载Log4Net的源代码,下载之后把log4net.dll引入项目即可。
现在我们尝试使用一下log4net,新建一个【控制台应用程序】
示例1-----日志输出到控制台
namespace DoNet.Seven.ConsoleApplicationTest
{
class Program
{
static void Main(string[] args)
{
log4net.Config.BasicConfigurator.Configure();
ILog log = LogManager.GetLogger("DoNet.Seven.ConsoleApplicationTest.Propram");
log.Debug("debug");
log.Info("info");
Console.ReadKey();
}
}
}
运行一下代码
示例2-----日志输出到文件
class Program
{
static void Main(string[] args)
{
//log4net.Config.BasicConfigurator.Configure();
log4net.Config.BasicConfigurator.Configure(new log4net.Appender.FileAppender(new log4net.Layout.PatternLayout("%d[%t]%-5p %c [%x] - %m%n"),"test.log"));
ILog log = LogManager.GetLogger("DoNet.Seven.ConsoleApplicationTest.Program");
log.Debug("debug");
log.Info("Info");
Console.ReadKey();
}
}
运行一下代码
我们在bin/Debug文件夹中发现了test.log这个文件。文件内容如下:
5、总结
OK,就是这么简单,通过这个例子我们已经演示了如何使用log4net,只需要这么几步:
1、添加对log4net.dll的引用
2、完成log4net的配置,在上面的应用中,我们仅仅使用最简单也是最缺乏灵活性的log4net.Config.BasicConfigurator.Configure来完成配置,后面我们会逐步看到更灵活的配置方式。
3、对于每一个需要日志输出的类来说,通过LogManager.GetLogger方法得到一个专属这个类的日志记录器。
4、调用Debug和Info方法可以完成日志信息输出,在上面的例子中我们仅仅看到了Debug和Info方法,后面会看到更多的日志记录方式。
Log4Net日志级别
1、概念
在Log4net中,内置了几种日志级别,分别是All、Debug、Info、Warn、Error、Fatal、Off,优先级顺序为:All<Debug<Info<Warn<Error<Fatal<Off;
Log最常用的级别就是DEBUG,INFO,WARN,ERROR,其他的很少用。如何运用合适的Log级别也是非常重要的,在不该用ERROR的地方用了ERROR,可能会给你带来额外的麻烦。下边仅根据自己的使用习惯,分别说一下我对各种级别的理解。
1.1 ERROR:
ERROR是错误的意思,但不代表出现异常的地方就该打ERROR。我认为ERROR是相对程序正确运行来说的,如果出现了ERROR那就代表出问题了,开发人员必须要查一下原因,或许是程序问题,或许是环境问题,或许是理论上不该出错的地方出错了。总之,如果你觉得某个地方出问题时需要解决,就打ERROR,如果不需要解决就不要打ERROR。
举例来说,如果有一个接口。调用者传过来的参数不在你的接受范围内,在这种情况下你不能打ERROR,因为传什么值是用户决定的,并不影响程序正确运行。想象一下,如果你的服务器上有监控程序的话,检测到ERROR或WARN就报警,参数错误你也打ERROR,那运维人员会疯掉的。
如果做一个对讲机,在解析语音数据包时出错了,那就要打ERROR了,因为这个是理论上不该出错的地方,要不就是你的解析代码有问题,要不就是开发人员在拼凑语音包时存在问题,这个时候需要你来找出问题的原因。所以应该打ERROR。
1.2 WARN:
WARN是指出现了不影响程序正确运行的问题,WARN也是问题但不影响程序正常运行,如果WARN出现的过于频繁或次数太多,那就代表你要检查一下程序或环境或依赖程序是否真的出问题了。
假如你访问一个接口,设置了一个超时,超时之后会抛异常,你在try块里不该打ERROR也不该打INFO来无视它,这时你应该打WARN,紧紧是警告一下,如果超时过多那就该检查一下了,是不是对方接口有问题了或者是网络环境出问题了。1.
1.3 INFO和DEBUG:
ERROR和WARN是指有问题,而INFO和DEBUG就是指一般的信息了。在程序出问题时,如果这条log可以帮助你分析问题或查看程序的运行情况,那就应该打个INFO。如果仅仅是为了在调试阶段查看程序是否运行正确那就要打DEBUG。前边讨论的接口参数错误问题,就应该打个INFO了,调用者说你的接口总是返回错误代码,你可以告诉他,是他的哪个参数传错了。
2、日志级别应用
前面讲到日志级别对我们来说其实是很重要的,那么现在看看如何控制日志级别。
2.1 示例一【默认情况】
static void Main(string[] args)
{
log4net.Config.BasicConfigurator.Configure();
ILog log = LogManager.GetLogger("DoNet.Seven");
log.Debug("debug");
log.Info("Info");
log.Error("Error");
Console.ReadKey();
}
很简单,不解释。
2.2 示例二【设置日志级别】
static void Main(string[] args)
{
log4net.Config.BasicConfigurator.Configure();
ILog log = LogManager.GetLogger("DoNet.Seven");
log.Logger.Repository.Threshold = Level.Info;
log.Debug("debug");
log.Info("Info");
log.Error("Error");
Console.ReadKey();
}
我们设置了日志级别为Info,那么只有优先级大于等于Info的才能被记录【这里不妨猜想想为什么这样呢?Log4net在记录日志之前首先会检查日志级别的,IsDebugEnabled、IsInfoEnabled、IsWarnEnabled、IsErrorEnabled、IsFatalEnabled】
这里不做过多的解释,可以看下源码,在记录日志之前就是先进行日志级别的过滤。
Log4Net的继承特性
示例一
static void Main(string[] args)
{
log4net.Config.BasicConfigurator.Configure();
ILog log = LogManager.GetLogger("DoNet.Seven");
log.Logger.Repository.Threshold = Level.Info;
ILog log2 = LogManager.GetLogger("DoNet.Seven.Song"); log.Debug("debug");
log.Info("Info");
log.Error("Error"); log2.Debug("Debug2");
log2.Info("Info2");
log2.Error("Error2"); Console.ReadKey();
}
这个结果和我们想象的好像有点不一样,log2我们没有做任何限制,那么为什么log2.Debug("Debug2");没有输出呢?如果我们把log.Logger.Repository.Threshold = Level.Info;去掉,那么结果会是怎样呢?
这里我直接说结果就行了,代码和上面一样,结果就是log和log2的Debug信息都可以输出。
这里我们可以得出结论,Log4Net是有继承特性的,可以看到是类似命名空间的写法:DoNet.Seven.Song的父Log是DoNet.Seven,如果还有DoNet这个实例,那么DoNet是DoNet.Seven的父Log,所有日志实例都继承RootLog。
示例二
static void Main(string[] args)
{
log4net.Config.BasicConfigurator.Configure();
ILog log = LogManager.GetLogger("DoNet.Seven");
log.Logger.Repository.Threshold = Level.Info; #region 给log添加一个Append
IBasicRepositoryConfigurator configurableRepository = log.Logger.Repository as IBasicRepositoryConfigurator;
PatternLayout layout = new PatternLayout("%d[%t]%-5p %c [%x] - %m%n");
layout.ConversionPattern = PatternLayout.DefaultConversionPattern;
FileAppender appender = new FileAppender(layout, "test.log");
configurableRepository.Configure(appender);
#endregion ILog log2 = LogManager.GetLogger("DoNet.Seven.Song"); log.Debug("debug");
log.Info("Info");
log.Error("Error"); log2.Debug("Debug2");
log2.Info("Info2");
log2.Error("Error2"); Console.ReadKey();
}
运行结果
这个示例我们给Log添加了一个Append对象【上文中提到了,这里先知道有这个对象存在就行了】,Append是控制Log输出媒体的,比如输出到控制台、文件、数据库等等。
log4net.Config.BasicConfigurator.Configure();默认情况是指定了控制台作为输出的默认Append,我们这里为log添加了一个文件Append【每个Append要指定一个Layout对象来格式化输出的日志】,这是我们看到的结果是控制台、文件【在项目的更目录下会生成test.log这个文件】都输出了日志。
这就说明Append是相加的,指定多可Append,日志会向多个“媒体”输出日志。
我们需要说明的一点也很清楚了,没有为log2指定Append,但是控制台和文件同时记录了log2的日志,这就更加说明了我们上面提到的继承特性。