Java Web Application 自架构 四 Log4j2日志管理

时间:2021-12-11 21:49:33

       上一篇里,笔者将DAO做了一个通用的实现,不过在继续之前,我们好像忘了些什么。就是做任何程序都不可缺少的东东,一个对程序的功能没什么用,很容被遗忘,但是每个方法里都需要有的东西,那就是日志。

       笔者以往的经验都是将Log4j配置到Spring 中去用,顺着这一点,访问了下log4j的官网, 进而看到了新东东 log4j2,粗略地看了一下介绍:比log4j更好的性能,做了些logback的实现甚至还解决了些logback的固有的问题,支持多种facade框架。听起来还不错,于是动了把这玩意儿加到Spring 中的想法,到网上去搜娄了一翻后。。。 好吧,我承认东西很少,只能是苦读官方的英文文档加debug状态看源码了。

       在log4j2的官网http://logging.apache.org/log4j/2.x/上,瞅一眼左侧的导航栏,大致都点进去看一下:API里说的需要JDK1.5以上,Architecture里的类架构图,以及与log4j的集成或转换等等。这些都不重要,因为目前没有用到,需要时候再来看不迟。我们的重点是 Configuration. 进入Configuration页面,细读一下吧。

       看到Configuration 中所述,配置可以是xml形式,可以是json形式,也可以是编码的方式(Programmatically), 我们要的是可以配到前面所写的由Spring@Configuration标注的ApplicationContext.java中的方式,当然就是编码方式,二话不说,直接按其所指,看看Extending Log4j 2 里怎么说。 一堆诸如@Plugin的注解式配置,大喜。不过整了半天,没奏效。想想即使好用,配置到Spring中也是一件费力的事儿,还是去Debug吧。Log的用法还是这样:     

  
 
 
  1. Logger logger = LogManager.getLogger(this.getClass()); 

       顺着这条藤,自是能摸到瓜的。LogManager中有Log4jContextFactory, 用来选择生成LoggerContext. 我们看到Context这个词,很容易就想到它是要干什么的:一个装载了很多通过名字得到的唯一单例的容器,所说的单例自然是Logger。正因如此,LogManager里的这个LoggerContext可以通过类名将logger一一对应。 OK,不废话了,继续:在LoggerContext被实例化时,它有个属性Configuration也被同时实例化,这个实例是DefaultConfiguration类型. 实例化后Facotry又将Context启动,调用了LoggerContext.start()方法,该方法会reconfiguraion()。这个reconfiguration()会进行系统下的配置找寻,也就是按照我们看到的官网中Automatic Configuration里所写的配置顺序进行找寻。(多一嘴:这里Debug过程中也确认了它在找系统默认ClassLoader中去找被注解所注的配置,而且看到我注解的类确实在ClassLoader里,至于为什么没有被加载,始终搞不明白)。继续Debug会看到最后它什么也没找到又加载一遍DefaultConfiguration

       然后笔者就开动脑筋,用Eclipse的代码提示功能去找用没有public 的方法可以将配置重定向了。果不其然,找到了,方法如下:

  
 
 
  1. Logger logger = LogManager.getLogger(this.getClass()); 
  2. LoggerContext loggerContext =(LoggerContext)LogManager.getContext(); 
  3. loggerContext.setConfiguration(new BaseConfiguration(){}); 

       找起来很费劲的说,毕竟框架它做为通用性的库,考虑的东西需要很周到,我们目前要用的就只有这么些,直接贴上来为大家理解与使用更方便快捷。至于想更深层次看清它的朋友们,就不扰乱你们在Debug过程中的乐趣了。

        上文BaseConfiguration类体中,直接那样匿名实现它就行,至于为什么用它而不用接口Configuration,想来你看了就知道,DefaultConfiguration很简单地写了构造方法继承了它就可以用,而如果匿名实现Configuration接口,会比较繁琐。 笔者这样匿名写了个config()方法,直接return this , 在类体后面直接.config()调用会简易明了的展现 这主要就是在做Configuration。 不过试了一下,还是没好用。再去看DefaultConfiguration是如何做到的,如法炮制吧:覆写doConfigure()为空,因为在Configuration.start()后,它会被调用从而加载了很多appender把自己配制的都盖掉了。综上所述,上面代码中的最后一句就变成

  
 
 
  1. loggerContext.setConfiguration(new BaseConfiguration(){ 
  2.     public BaseConfiguration config(){ 
  3.         setName("MyConfig"); 
  4.         Layout<?> layout = PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n",nullnullnull); 
  5.         Appender<?> appender = ConsoleAppender.createAppender(layout, null"SYSTEM_OUT""Console""true"); 
  6.         appender.start(); 
  7.         addAppender(appender); 
  8.         LoggerConfig root = getRootLogger(); 
  9.         root.addAppender(appender, nullnull); 
  10.         String levelName = System.getProperty("org.apache.logging.log4j.level"); 
  11.                 Level level = levelName != null && Level.valueOf(levelName) != null ? Level.valueOf(levelName) : Level.ERROR; 
  12.         root.setLevel(level); 
  13.         return this
  14.     } 
  15.          
  16.     @Override 
  17.     protected void doConfigure() {} 
  18. }.config()); 

 

       测试一下,对了,前面一直说某些操作没好用,是如何看出? 答案是只需将所写代码的layoutPatten中的一些格式化参数换一下位置看看输出,是否按所配Layout输出来的就可以了。因为上文中的config方法内的代码完全是DefaultConfiguration构造方法中的语句,所以变一下layout如果控制台输出的字符串还是按原来的顺序打出,那它的配置还是DefaultConfiguration. 以下四行代码放到普通JUnit4@Test方法中,可以测试出 logger确实是一个类名永远只得到一个实例,并且控制台输出的字符按照自己配置的layout输出来了。证明配置成功。

  
 
 
  1. Logger logger = loggerContext.getLogger(this.getClass().getName()); 
  2. Logger logger0 = loggerContext.getLogger(this.getClass().getName()); 
  3. Assert.assertSame(logger,logger0); 
  4. logger.error("Test Error2"); 

 

      下面就是将这个log4j2的配置放到Spring中了,我想看了第一篇的话,这个配置应该是很简单了,在Spring@Configuration注解的ApplicationContext.java中,写一个@Bean注解的方法,Return出一个LoggerContext即可,这个LoggerContex就可以注入到任意SpringBean中以供该BeanLog用。当然还可以生成个通用的Log Bean. 那就贴上代码段如下:

  
 
 
  1. @Bean 
  2. public LoggerContext log4j2Context() { 
  3.     LoggerContext loggerCtx = (LoggerContext) LogManager.getContext(); 
  4.     loggerCtx.setConfiguration(new BaseConfiguration() { 
  5.         public BaseConfiguration config() { 
  6.             setName("webmodel-log-Config"); 
  7.             Layout<?> consolelayout = PatternLayout.createLayout( 
  8.                         "%d{HH:mm:ss.SSS} [%thread] %logger{36} %-5level - %msg%n",nullnullnull); 
  9.             Appender<?> consoleAppender = ConsoleAppender.createAppender(consolelayout,null"SYSTEM_OUT""Console","true"); 
  10.                  
  11.             Layout<?> fileLayout = HTMLLayout.createLayout("true""Webmodel Error Log""text/html"null"x-small""arial,serif"); 
  12.             String fileName =this.getClass().getResource("/").getFile().replace("/classes/""/log/")+"systemErrorLog.html"
  13.             Appender<?> fileAppender = FileAppender.createAppender(fileName, "true""false""errorLog","true","true""true", fileLayout, null); 
  14.                  
  15.             this.addAppender(consoleAppender); 
  16.             this.addAppender(fileAppender); 
  17.                  
  18.             LoggerConfig root = getRootLogger(); 
  19.             root.setLevel(Level.ALL); 
  20.             root.addAppender(consoleAppender, Level.ERROR, null); 
  21.             root.addAppender(fileAppender, Level.INFO, null); 
  22.                  
  23.             return this
  24.         } 
  25.  
  26.         @Override 
  27.         protected void doConfigure() {} 
  28.     }.config()); 
  29.     return loggerCtx; 
  30.     } 
  31.      
  32.     @Configuration 
  33.     static class LoggerConfiguration{ 
  34.         private LoggerContext lctx; 
  35.  
  36.         public LoggerContext getLctx() { 
  37.             return lctx; 
  38.         } 
  39.         @Resource 
  40.         public void setLctx(LoggerContext lctx) { 
  41.             this.lctx = lctx; 
  42.         } 
  43.          
  44.         @Bean  
  45.         public Logger commonLogger(){ 
  46.             return lctx.getLogger("com.gxino.webmodel.CommonLogger"); 
  47.         } 
  48.     } 
       

        这段代码的配置较上面有所update, 因为想要做一个在控制台只输出Error以上级别的,而保存一个系统日志文件以输出INFO以上级别的日志,所以又做了很多Debug工作。首先一点,Appender不用start(),因为BaseConfiguration.start()时会去做;其次,并不是root.addAppender(appender,appenderLevel,appenderFilter);后,log就能按这个Level去做日志。因为在一个logEvent被响应时,loggerisEnable来判断这个响应奏不奏效,这里面就会根据LoggerConfig root去判断,然后在logEvent被响应时,才去看每个Appender的具体Level. 而不管是哪里,所有的Level都默认是Level.ERROR, 所以我们需要把rootLevel调到ALL, 让它过了isEnable, 再去看具体的AppenderLevel. 最后,Html的文件日志还是不错嘛。贴个图以结束这一篇内容。

 

Java Web Application 自架构 四 Log4j2日志管理

      有老手们应该已经发觉,到处充斥着的日志,加起来很繁琐,或说总是忘记去写,有没有方法可以解决?也就是说,在每一个方法的固定位置让它自动写一段日志。那这个固定位置可不可以看成是切面Aspect呢?

      OK,下一篇,就引入AOP,面向切面编程。

 

 

 

 

本文出自 “掌心童林” 博客,转载请与作者联系!