? ? ? ? 上一篇里,笔者将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中的方式,当然就是编码方式,二话不说,直接按其所指,看看ExtendingLog4j 2 里怎么说。一堆诸如@Plugin的注解式配置,大喜。不过整了半天,没奏效。想想即使好用,配置到Spring中也是一件费力的事儿,还是去Debug吧。Log的用法还是这样:
?
Logger logger = LogManager.getLogger(this.getClass());
?
? ? ? ? 顺着这条藤,自是能摸到瓜的。LogManager中有Log4jContextFactory,用来选择生成LoggerContext.我们看到Context这个词,很容易就想到它是要干什么的:一个装载了很多通过名字得到的唯一单例的容器,所说的单例自然是Logger。正因如此,LogManager里的这个LoggerContext可以通过类名将logger一一对应。OK,不废话了,继续:在LoggerContext被实例化时,它有个属性Configuration也被同时实例化,这个实例是DefaultConfiguration类型.实例化后Facotry又将Context启动,调用了LoggerContext.start()方法,该方法会reconfiguraion()。这个reconfiguration()会进行系统下的配置找寻,也就是按照我们看到的官网中AutomaticConfiguration里所写的配置顺序进行找寻。(多一嘴:这里Debug过程中也确认了它在找系统默认ClassLoader中去找被注解所注的配置,而且看到我注解的类确实在ClassLoader里,至于为什么没有被加载,始终搞不明白)。继续Debug会看到最后它什么也没找到又加载一遍DefaultConfiguration。
?
? ? ? ? 然后笔者就开动脑筋,用Eclipse的代码提示功能去找用没有public的方法可以将配置重定向了。果不其然,找到了,方法如下:
?
Logger logger = LogManager.getLogger(this.getClass()); LoggerContext loggerContext =(LoggerContext)LogManager.getContext(); loggerContext.setConfiguration(new BaseConfiguration(){});
?
? ? ? ? 找起来很费劲的说,毕竟框架它做为通用性的库,考虑的东西需要很周到,我们目前要用的就只有这么些,直接贴上来为大家理解与使用更方便快捷。至于想更深层次看清它的朋友们,就不扰乱你们在Debug过程中的乐趣了。
?
? ? ? ?上文BaseConfiguration类体中,直接那样匿名实现它就行,至于为什么用它而不用接口Configuration,想来你看了就知道,DefaultConfiguration很简单地写了构造方法继承了它就可以用,而如果匿名实现Configuration接口,会比较繁琐。笔者这样匿名写了个config()方法,直接returnthis ,在类体后面直接.config()调用会简易明了的展现这主要就是在做Configuration。不过试了一下,还是没好用。再去看DefaultConfiguration是如何做到的,如法炮制吧:覆写doConfigure()为空,因为在Configuration.start()后,它会被调用从而加载了很多appender把自己配制的都盖掉了。综上所述,上面代码中的最后一句就变成
?
loggerContext.setConfiguration(new BaseConfiguration(){ public BaseConfiguration config(){ setName("MyConfig"); Layout<?> layout = PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n",null, null, null); Appender<?> appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "true"); appender.start(); addAppender(appender); LoggerConfig root = getRootLogger(); root.addAppender(appender, null, null); String levelName = System.getProperty("org.apache.logging.log4j.level"); Level level = levelName != null && Level.valueOf(levelName) != null ? Level.valueOf(levelName) : Level.ERROR; root.setLevel(level); return this; } @Override protected void doConfigure() {} }.config());
?
? ? ? ? 测试一下,对了,前面一直说某些操作没好用,是如何看出?答案是只需将所写代码的layout的Patten中的一些格式化参数换一下位置看看输出,是否按所配Layout输出来的就可以了。因为上文中的config方法内的代码完全是DefaultConfiguration构造方法中的语句,所以变一下layout如果控制台输出的字符串还是按原来的顺序打出,那它的配置还是DefaultConfiguration.以下四行代码放到普通JUnit4的@Test方法中,可以测试出logger确实是一个类名永远只得到一个实例,并且控制台输出的字符按照自己配置的layout输出来了。证明配置成功。
Logger logger = loggerContext.getLogger(this.getClass().getName()); Logger logger0 = loggerContext.getLogger(this.getClass().getName()); Assert.assertSame(logger,logger0); logger.error("Test Error2");
?
? ? ? ? 下面就是将这个log4j2的配置放到Spring中了,我想看了第一篇的话,这个配置应该是很简单了,在Spring的@Configuration注解的ApplicationContext.java中,写一个@Bean注解的方法,Return出一个LoggerContext即可,这个LoggerContex就可以注入到任意Spring的Bean中以供该Bean作Log用。当然还可以生成个通用的Log Bean. 那就贴上代码段如下:
@Bean public LoggerContext log4j2Context() { LoggerContext loggerCtx = (LoggerContext) LogManager.getContext(); loggerCtx.setConfiguration(new BaseConfiguration() { public BaseConfiguration config() { setName("webmodel-log-Config"); Layout<?> consolelayout = PatternLayout.createLayout( "%d{HH:mm:ss.SSS} [%thread] %logger{36} %-5level - %msg%n",null, null, null); Appender<?> consoleAppender = ConsoleAppender.createAppender(consolelayout,null, "SYSTEM_OUT", "Console","true"); Layout<?> fileLayout = HTMLLayout.createLayout("true", "Webmodel Error Log", "text/html", null, "x-small", "arial,serif"); String fileName =this.getClass().getResource("/").getFile().replace("/classes/", "/log/")+"systemErrorLog.html"; Appender<?> fileAppender = FileAppender.createAppender(fileName, "true", "false", "errorLog","true","true", "true", fileLayout, null); this.addAppender(consoleAppender); this.addAppender(fileAppender); LoggerConfig root = getRootLogger(); root.setLevel(Level.ALL); root.addAppender(consoleAppender, Level.ERROR, null); root.addAppender(fileAppender, Level.INFO, null); return this; } @Override protected void doConfigure() {} }.config()); return loggerCtx; } @Configuration static class LoggerConfiguration{ private LoggerContext lctx; public LoggerContext getLctx() { return lctx; } @Resource public void setLctx(LoggerContext lctx) { this.lctx = lctx; } @Bean public Logger commonLogger(){ return lctx.getLogger("com.gxino.webmodel.CommonLogger"); } }
?
? ? ? ? 这段代码的配置较上面有所update,因为想要做一个在控制台只输出Error以上级别的,而保存一个系统日志文件以输出INFO以上级别的日志,所以又做了很多Debug工作。首先一点,Appender不用start(),因为BaseConfiguration.start()时会去做;其次,并不是root.addAppender(appender,appenderLevel,appenderFilter);后,log就能按这个Level去做日志。因为在一个logEvent被响应时,logger会isEnable来判断这个响应奏不奏效,这里面就会根据LoggerConfig即root去判断,然后在logEvent被响应时,才去看每个Appender的具体Level. 而不管是哪里,所有的Level都默认是Level.ERROR,所以我们需要把root的Level调到ALL,让它过了isEnable,再去看具体的Appender的Level.最后,Html的文件日志还是不错嘛。贴个图以结束这一篇内容。
? ? ? ? 有老手们应该已经发觉,到处充斥着的日志,加起来很繁琐,或说总是忘记去写,有没有方法可以解决?也就是说,在每一个方法的固定位置让它自动写一段日志。那这个固定位置可不可以看成是切面Aspect呢?
?
? ? ? ? OK,下一篇,就引入AOP,面向切面编程。
?
?
?
?