上一篇里,笔者将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的用法还是这样:
- 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 的方法可以将配置重定向了。果不其然,找到了,方法如下:
- Logger logger = LogManager.getLogger(this.getClass());
- LoggerContext loggerContext =(LoggerContext)LogManager.getContext();
- loggerContext.setConfiguration(new BaseConfiguration(){});
找起来很费劲的说,毕竟框架它做为通用性的库,考虑的东西需要很周到,我们目前要用的就只有这么些,直接贴上来为大家理解与使用更方便快捷。至于想更深层次看清它的朋友们,就不扰乱你们在Debug过程中的乐趣了。
上文BaseConfiguration类体中,直接那样匿名实现它就行,至于为什么用它而不用接口Configuration,想来你看了就知道,DefaultConfiguration很简单地写了构造方法继承了它就可以用,而如果匿名实现Configuration接口,会比较繁琐。 笔者这样匿名写了个config()方法,直接return this , 在类体后面直接.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,面向切面编程。
本文出自 “掌心童林” 博客,转载请与作者联系!