精华帖 (0) :: 良好帖 (9) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-11-19
最后修改:2011-03-25
背景最近开始使用jetty做为我们的应用web容器,在迁移过程中发现一个比较隐晦的问题,原本在jboss容器跑的好好的应用,换到jetty容器上,直接不可用。出现一些莫名奇妙的错误。
现象说明:我们应用中有代码使用了velocity处理一些业务,比如模板输出,自定义渲染引擎等。
使用例子: RuntimeInstance ri = new RuntimeInstance(); ..... ri.parse(new StringReader(script), name); //进行渲染脚本处理
换成jetty后,会莫名的出现一个异常信息,截取了一个异常描述: caused by: java.lang.RuntimeException: Error configuring Log4JLogChute : at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at org.apache.velocity.util.ExceptionUtils.createWithCause(ExceptionUtils.java:67) at org.apache.velocity.util.ExceptionUtils.createRuntimeException(ExceptionUtils.java:45) at org.apache.velocity.runtime.log.Log4JLogChute.initAppender(Log4JLogChute.java:133) at org.apache.velocity.runtime.log.Log4JLogChute.init(Log4JLogChute.java:85) at org.apache.velocity.runtime.log.LogManager.createLogChute(LogManager.java:157) ... 33 more Caused by: java.io.FileNotFoundException: velocity.log (Permission denied) at java.io.FileOutputStream.openAppend(Native Method) at java.io.FileOutputStream.(FileOutputStream.java:177) at java.io.FileOutputStream.(FileOutputStream.java:102) at org.apache.log4j.FileAppender.setFile(FileAppender.java:290) at org.apache.log4j.RollingFileAppender.setFile(RollingFileAppender.java:194) at org.apache.log4j.FileAppender.(FileAppender.java:109) at org.apache.log4j.RollingFileAppender.(RollingFileAppender.java:72) at org.apache.velocity.runtime.log.Log4JLogChute.initAppender(Log4JLogChute.java:118) ... 35 more
换回jboss容器后,一切正常,没有出现任何异常。
分析查找问题最好的利器就是debug,我也不例外。开启remote debug,一步步跟踪代码,发现最后的问题出在RuntimeInstance.init()调用initializeLog()方法上,说白了就是velocity日志处理上。
展开分析之前,先大致了解下velocity的日志处理。
velocity的3个关于日志记录的参数:
备注: logsystem是velocity 1.5版本以前早期的log一套实现接口,现在1.5以后建议都使用logchute,而且早的logsystem一套也不建议被使用,@deprecated Use LogChute instead!
类图:Log : 对LogChute的一个delegate,提供一些遍历的方法,比如info(),warn()不同级别的日志记录方法。 LogChute: velocity中定义的日志处理接口,目前支持各种类型的Log三方包,同时支持System.out,NullLog等特殊类型。 LogMananger : velocity管理Log对象的入口,里面有个方法updateLog(Log log, RuntimeServices rsvc),就是本次出问题的点。
针对每种LogChute实现,都有自己一些特殊的配置项, (大家都知道velocity配置项可以是通过velocity.properties进行配置)
比如log4j的配置项,代码:Log4JLogChute
再来理一下,velocity整个初始化日志过程:
了解了velocity的整套log机制后,再来看该问题:
ok,现在的问题已经很明了,异常中提示velocity.log无权限,只需要check一下当前jvm进程的user.dir属性。 最后检查结果:
刚好我用的是linux系统,软件安装路径的权限都是root用户,运行web应用的都是普通用户,所以也让我撞上了这个问题。
说明:jboss和jetty我都是通过调用自带的run.sh和jetty.sh进行启动,存在这样的差异也是让我很无语的,几点建议。
解决深入了解了velocity log机制后,解决方案就有很多种了
方案一:暴力型,啥都不输出 RuntimeInstance ri = new RuntimeInstance(); ....... if (!ri.isInitialized()) { // 设置空的log,避免使用velocity默认的veloicyt.log ri.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute()); ri.init(); }
方案二:统一型,融合到现有的log框架 RuntimeInstance ri = new RuntimeInstance(); ...... if (!ri.isInitialized()) { ....... // 自定义LogChute,代理到应用的Log对象上,统一使用Log4j.xml进行管理 ri.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new LogChute() { public void init(RuntimeServices runtimeServices) { } public void log(int level, String message) { log(level, message, null); } public void log(int level, String message, Throwable t) { switch (level) { case TRACE_ID: getLogger().trace(message, t); break; case DEBUG_ID: getLogger().debug(message, t); break; case INFO_ID: getLogger().info(message, t); break; case WARN_ID: getLogger().warn(message, t); break; case ERROR_ID: getLogger().error(message, t); break; default: } } public boolean isLevelEnabled(int level) { switch (level) { case TRACE_ID: return getLogger().isTraceEnabled(); case DEBUG_ID: return getLogger().isDebugEnabled(); case INFO_ID: return getLogger().isInfoEnabled(); case WARN_ID: return getLogger().isWarnEnabled(); case ERROR_ID: return getLogger().isErrorEnabled(); default: return false; } } }); ri.init(); }
说明:使用内部匿名类,将LogChute代理到当前class类的getLogger对象上,这样实现和当前web容器的整合,可以统一使用log4j.xml进行管理,只需要设置好Velocity 包装class的logger即可。
现在还有比较流行Slf4jLog,同样可以写一个Slf4jLogChute。一个小建议:对WARN以下level不记录异常的详细stack详情 ── 避免Velocity在找不到资源时会打印异常,直接打印e.getMessage()
异常的stack打印还是比较消耗性能的,具体可以看下我同事的一篇分析文章,使用异常耗性能到底耗在哪一块http://www.blogjava.net/stone2083/archive/2010/07/09/325649.html。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-11-19
网上查了下,java.io.FileNotFoundException: velocity.log (Permission denied)
这样的问题还不少stackoverflow上也有人提问,http://stackoverflow.com/questions/849710/error-in-velocity-and-log4j 看来j2se和j2ee模式下的确会出现一些问题,普通jar运行好好的代码,在运行于容器中就会出现一些莫名的问题 单元测试很重要,集成测试也还是很有必要,这次就犯了这么个错误,改完提测被退回 |
|
返回顶楼 | |
发表时间:2010-11-19
有个问题,为什么jetty的run.sh执行的jvm,其user.dir会是jetty的路径?
|
|
返回顶楼 | |
发表时间:2010-11-19
wlhok 写道 有个问题,为什么jetty的run.sh执行的jvm,其user.dir会是jetty的路径?
很简单,打开jetty.sh一看,你就发现了,它有这么一段脚本 ################################################## # No JETTY_HOME yet? We're out of luck! ################################################## if [ -z "$JETTY_HOME" ]; then echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location" exit 1 fi cd "$JETTY_HOME" JETTY_HOME=$PWD 执行了一次cd动作,真当是 还有jetty.sh脚本,选择jetty.sh start会被强制使用--daemon,jetty.sh run则是当前进程执行。一些潜规则挺多的。 |
|
返回顶楼 | |
浏览 3996 次