Log4Net结构详解

当我们在描述为系统做日志这个动作的时候,实际上描述了3个点;做日志,其实就是在规定,在什么地方 用什么日志记录器 以什么样的格式做日志。把三个最重要的点抽取出来,即什么地方,日志记录器,什么格式。在Log4net中,就使用了三个最重要的组件来描述这三个要素,即Logger:日志记录器、Appender:什么地方、 Layout:什么格式。 下面我们就分别来看看这三个对象在Log4net中起的重要作用和一些基本的规则用法。

1、Logger

之前我们在创建Logger的时候,都是使用LogManager.GetLogger("seven");方法来得到一个类绑定的日志记录器的。实际上,当我们说把一个日志记录器绑定在一个类上,这种说法是不准确的,正确的说,我们仅仅是使用给定的类的全限定名为Logger取了一个名字。这里请大家注意一下,Logger都是有名字的,假如我们除开rootLogger不谈,我们可以把Logger想象成一张表里面的数据,Logger对应的名字就是其主键,当两个Logger的名字相同,这两个Logger就是同一个Logger实例。我们可以简单的通过一个实例来验证:

 static void Main(string[] args)
{
log4net.Config.BasicConfigurator.Configure();
ILog log = LogManager.GetLogger("seven");
ILog log2 = LogManager.GetLogger("seven"); log.Debug(log == log2);
Console.ReadKey();
}

控制台打印:30 [9] DEBUG  seven<null>  - true;说明Logger自身维护着每一个名字的Logger实例的引用,保证相同名字的Logger在不同地方获取到的实例是一致的,这样就允许我们在统一的代码中配置不同Logger的特性。

另外,Logger的层次结构,也是靠Logger的名字来区分的,比如:名称为DoNet的Logger就是DoNet.Seven的父Logger;DoNet.Seven是DoNet.Seven.Song的父Logger;Logger的体系结构和命名空间的结构划分类似,使用.来区分;所以我们前面才说,使用类的全限定名是最简单,也是最符合logger的体系结构的命名方式。当然,你也可能使用任何的能想到的方式去处理Logger的命名;这也是可以的。

看到这里,可能有的人会提出疑问,那既然Logger的层次结构是按照Logger的名字来创建的,那在创建Logger的时候,是否必须按照其结构来顺序创建Logger?比如:

ILog log = LogManager.GetLogger("DoNet");

ILog log = LogManager.GetLogger("DoNet.Seven");

ILog log = LogManager.GetLogger("DoNet.Seven.Song");

是否必须要按照这个顺序创建呢?不需要。在做前面的示例的时候,我们说到,大家可能认为当我们通过ILog log = LogManager.GetLogger("DoNet.Seven.Song");的时候,Log4J其实为我们创建了DoNet;DoNet.Seven和DoNet.Seven.Song这三个Logger;其实不然,如果是这样的话,Log4net可能会产生非常多不必要的Logger。所以,真正的方式应该是,当我通过ILog log = LogManager.GetLogger("DoNet.Seven.Song");的时候,Log4net仅仅为我们创建了DoNet.Seven.Song这个Logger;而当我们再次使用ILog log = LogManager.GetLogger("DoNet.Seven");的时候,Log4net才会为我们创建DoNet.Seven这个Logger,并且Log4net会自动的去寻找这个Logger的上下级关系,并自动的把这个新创建的Logger添加到已有的Logger结构体系中。

在Logger中,非常重要的一个组件,就是Logger的日志级别,即Level。关于Level的划分,使用,我们在前面的例子中已经大概了解到了,下面来看看完整的Level的定义和其在Logger体系中的继承方式,这是一个很简单的概念。
首先,在Log4net中,为我们默认的定义了7种不同的Level级别,即all<debug<info<warn<error<fatal<off;而这7中Level级别又刚好对应着Level类中的7个默认实例:Level.ALL;Level.DEBUG;Level.INFO;Level.WARN;Level.ERROR;Level.FATAL和Level.OFF。我们在之前也只看到了其中的debug到fatal这5种;这五种日志级别正好对应着Logger中的五个方法,即:
 void Debug(object message);

void Info(object message);

void Warn(object message);

void Error(object message);

void Fatal(object message);

当然也对应着一些扩展方法,方便使用,这里不列举了。Log4Net学习【一】我们已经介绍了Logger的基本使用,以及Level的使用和限制。其实,在真正使用Log4net的时候,我们一般都不需要使用代码的方式去配置Level,这些配置更多的时候是使用配置文件来完成的,这个后面会详细介绍。但是,不管使用什么样的配置文件,最终也会解释成这样的配置代码,所以,理解了这些代码,再去使用配置文件,会更加清楚到底配置文件在干什么

2、Appender

使用Logger的日志记录方法,仅仅是发出了日志记录的事件,具体日志要记录到什么地方,需要Appender的支持。在Log4net中,Appender定义了日志输出的目的地。在上面所有的示例当中,我们日志输出的目的地都是控制台,在Log4j中,还有非常多的Appender可供选择,可以将日志输出到文件,网络,数据库等等,这个后面再介绍。说到这里,可能有人就已经会思考,既然Logger对象的info()等方法仅仅是发出了日志记录的事件,还需要指定输出目的地;那么我们之前的示例代码也并没有为任何一个Logger设置Appender啊?其实这很好理解,我们回顾一下之前的Level,按道理,也应该为每一个Logger指定对应的日志输出级别,但是我们也并没有这样做,正是因为Logger本身存在一个完整的体系结构,而Level能够在这个结构中自下而上的继承。同理,Appender也具有这种继承的特性.

 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;
SimpleLayout layout=new SimpleLayout();
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();

结果如下:

这里文件中的输出明显已经和控制台的输入不一致了,在这里说明了两个问题:

1、在Log4Net中,这个特性叫做Appender的追加性。默认情况下,所有的Logger都自动具有追加性,通过一个表来说明

 

Logger addAppender 起作用的Appender
Root A1 A1
DoNet A2 A2,A1
DoNet.Seven null A2,A1
DoNet.Seven.Song A3 A3,A2,A1

 

但是,在某些情况下,这样做反而会引起日志输出的混乱。有些时候,我们并不希望Logger具有追加性。比如在上面这张表中,我们想让DoNet.Seven.Song只需要继承A2和自己的A3Appender,而不想使用root上面的A1 Appender,又该怎么做呢?
 其实很简单,在Logger上,都有一个additivity属性,如果设置additivity为false,则该logger的子类停止追加该logger之上的Appender;如果设置为true,则具有追加性。修改一下上表:

Logger addAppender 起作用的Appender additivity
Root A1 A1 true
DoNet A2 A2 false
DoNet.Seven null A2 true
DoNet.Seven.Song A3 A3,A2 true

3、 Layout

Logger规定了输出什么日志,Appender规定了日志输出到哪里,当然,我们还会奢望,以什么样的方式输出日志。这就涉及到之前我们在观察Appender的时候创建ConsoleAppender和FileAppender都需要传入的Layout。在Log4net中,Layout对象提供了以什么样的方式格式化日志。这个对象是绑定在Appender之上的,一般在Appender创建的时候指定。

下面就简单看一个最常使用的Layout:PatternLayout。PatternLayout允许使用标准的输出格式来指定格式化日志消息的样式。举个简单的例子,可能之前大家看到的使用BasicConfigurator配置的输出的日志样式和我们使用SimpleLayout输出的样式不一样,这些Layout的输出样式都是默认的,我们也可以手动置顶一些format,正如下面这样:

 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");

文件中的样式是我们通过 PatternLayout layout = new PatternLayout("%d[%t]%-5p %c [%x] - %m%n");

这个Layout控制的,那么我们在创建Layout的时候传递了一个字符串,是什么意思呢?在这个模式中,有很多以%开头的参数,每一个特定的参数代表着一种日志的内容,这就是log4net.Layout.PatternLayout中的转换模式(ConversionPattern)

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

%n(new line):换行

%d(datetime):输出当前语句运行的时刻

%r(run time):输出程序从运行到执行到当前语句时消耗的毫秒数

%t(thread id):当前语句所在的线程ID

%p(priority): 日志的当前优先级别,即DEBUG、INFO、WARN…等

%c(class):当前日志对象的名称

%L:输出语句所在的行号

%F:输出语句所在的文件名

%-数字:表示该项的最小长度,如果不够,则用空格填充

例如,转换模式为%r [%t]%-5p %c - %m%n 的 PatternLayout 将生成类似于以下内容的输出:

176 [main] INFO  org.foo.Bar - Located nearest gas station.

4、组件的使用

前面简单的了解了Log4J中最重要的3个组件,下面我们来看看Log4j是怎么使用这3个组件完成当我们调用logger.debug()方法能在控制台上打印出日志信息的。
    第一步,继承数体系上的门槛检查:首先当调用info()方法后,Log4J会立刻使用该Logger所在的体系结构中设置的门槛去检查当前日志的级别。如果级别不够,立刻作废当前日志请求。
    第二步,Level级别检查:使用当前Logger上设置的或者继承的Level级别来检查当前的日志级别。如果当前日志级别不够,立刻作废当前日志请求。
    第三步,创建LoggingEvent对象:当日志审核通过,Log4J就会创建一个LoggingEvent对象(即日志事件对象)。在该对象中,会保存和本次日志相关的所有参数信息,包括日志内容,日志时间等。
    第四步,执行Appender:当创建完成LoggingEvent对象时候,会该对象交给当前logger上起作用的所有的Appender对象,并调用这些对象的doAppend方法来处理日志消息。
    第五步,格式化日志消息:接下来,会使用每一个Appender绑定的Layout对象(如果有)来格式化日志消息。Layout对象会把LoggingEvent格式化成最终准备输出的String。
    第六步,输出日志消息:当得到最终要输出的String对象之后,appender会把字符输出到最终的目标上,比如控制台或者文件。
    三个主要组件的组成结构:
    
    日志执行流程图:
    

Log4Net学习【二】的更多相关文章

  1. Log4Net学习【三】

    Log4Net配置详解 配置方式一 在相应的应用程序的配置文件中配置,(WinForm对应的是*.exe.config,WebForm对应的是*.config),本实例是Web应用程序,以Web.co ...

  2. emberjs学习二(ember-data和localstorage_adapter)

    emberjs学习二(ember-data和localstorage_adapter) 准备工作 首先我们加入ember-data和ember-localstorage-adapter两个依赖项,使用 ...

  3. ReactJS入门学习二

    ReactJS入门学习二 阅读目录 React的背景和基本原理 理解React.render() 什么是JSX? 为什么要使用JSX? JSX的语法 如何在JSX中如何使用事件 如何在JSX中如何使用 ...

  4. [转载]Log4net学习笔记

    Log4net 学习笔记: 主要是根据apache站点整理的: 原文链接:http://logging.apache.org/log4net/release/sdk/ http://logging.a ...

  5. TweenMax动画库学习(二)

    目录            TweenMax动画库学习(一)            TweenMax动画库学习(二)            TweenMax动画库学习(三)            Tw ...

  6. Hbase深入学习(二) 安装hbase

    Hbase深入学习(二) 安装hbase This guidedescribes setup of a standalone hbase instance that uses the local fi ...

  7. Struts2框架学习(二) Action

    Struts2框架学习(二) Action Struts2框架中的Action类是一个单独的javabean对象.不像Struts1中还要去继承HttpServlet,耦合度减小了. 1,流程 拦截器 ...

  8. Python学习二:词典基础详解

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7862377.html 邮箱:moyi@moyib ...

  9. Quartz学习--二 Hello Quartz! 和源码分析

    Quartz学习--二  Hello Quartz! 和源码分析 三.  Hello Quartz! 我会跟着 第一章 6.2 的图来 进行同步代码编写 简单入门示例: 创建一个新的java普通工程 ...

随机推荐

  1. (笔记)angular选项卡变色

  2. 基于s5pv210嵌入式linux系统sqlite3数据库移植

    基于s5pv210嵌入式linux系统sqlite3数据库移植 1.下载源码 http://www.sqlite.org/download.html 最新源码为3080100 2.解压 tar xvf ...

  3. ios球体弹跳游戏源码

    一款耐玩的ios游戏源码,画面上有很多小星星,球体落下的时候,你需要在画面上画出一条条的线条让球体弹跳起来然后吃掉小星星,如果没借助球体就失败了.游戏有很多关卡.注意: <ignore_js_o ...

  4. [视频]ARM告诉你物联网怎么玩,mbed 6LoWPan demo

    该视频演示了基于arm mbed的物联网设备间的6LoWPAN应用,如连接家里的土壤湿度传感器,灯光控制,安防联动等应用. 演示视频       原创文章,转载请注明: 转载自 http://www. ...

  5. 《深入剖析Tomcat》读书笔记(二)

    三.容器Container Container 是容器的父接口,所有子容器都必须实现这个接口.Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine. ...

  6. POJ C程序设计进阶 编程题#3:运算符判定

    编程题#3:运算符判定 来源: POJ (Coursera声明:在POJ上完成的习题将不会计入Coursera的最后成绩.) 注意: 总时间限制: 1000ms 内存限制: 65536kB 描述 两个 ...

  7. varnish状态引擎1

    vcl: state engine:各引擎之间存一定程度上的相关性:前一个engine如果可以有多种下游engine,则上游engine需要用return指明 要转移的下游engine vcl_rec ...

  8. 删除linux系统服务

    #删除服务的命令,[ServiceName]需要替换为实际的服务名称 sudo update-rc.d [ServiceName] remove 有时候安装sysv-rc-conf进行服务控制,但是在 ...

  9. C++ 里 构建动态二维数组

    //****动态二维数组 /* int m=3; int **data; int n=2; data=new int*[m]; for(int j=0;j<m;j++) { data[j]=ne ...

  10. WIN10 64位下VS2015 MFC直接添加 halcon 12的CPP文件实现视觉检测

    近段时间开始接触halcon,但是在VS2015里面使用,无论是配置还是生产EXE文件,都不如意. 加上网上的教程很多,经过多次测试,其实有很多地方无需修改,如果修改的太多也失去了直接添加封装的意义. ...