当前位置: 代码迷 >> Web前端 >> Java Web Application 从架构 四 Log4j2日志管理
  详细解决方案

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

热度:470   发布时间:2013-01-23 10:44:49.0
Java Web Application 自架构 四 Log4j2日志管理

? ? ? ? 上一篇里,笔者将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());

?

? ? ? ? 测试一下,对了,前面一直说某些操作没好用,是如何看出?答案是只需将所写代码的layoutPatten中的一些格式化参数换一下位置看看输出,是否按所配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就可以注入到任意SpringBean中以供该BeanLog用。当然还可以生成个通用的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被响应时,loggerisEnable来判断这个响应奏不奏效,这里面就会根据LoggerConfigroot去判断,然后在logEvent被响应时,才去看每个Appender的具体Level. 而不管是哪里,所有的Level都默认是Level.ERROR,所以我们需要把rootLevel调到ALL,让它过了isEnable,再去看具体的AppenderLevel.最后,Html的文件日志还是不错嘛。贴个图以结束这一篇内容。


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

?

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

?



?

?

?


  相关解决方案