`
agapple
  • 浏览: 1595530 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

纠结的velocity log那些事(出现Permission denied)

阅读更多

背景  

  最近开始使用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个关于日志记录的参数:

  • runtime.log.logsystem       (对应的logsystem实例)
  • runtime.log.logsystem.class (对应的logsystem实现类)
  • runtime.log  (日志文件名称)

备注: 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

  • runtime.log.logsystem.log4j.logger (对应于log4j.xml配置中的Logger name,如果没有就使用class获取Logger,同时获取runtime.log属性作为日志输出的文件)
  • runtime.log.logsystem.log4j.logger.level (转化log4j的level到velocity log中,可覆盖Logger)

 

再来理一下,velocity整个初始化日志过程:

  1. new RuntimeInstance(),属性Log log = new Log(), 默认创建一个HoldingLogChute()做为LogChute,(该LogChute临时记录日志到内存对象上)
  2. RuntimeInstance.init() 进行velocity系统初始化
  3. 顺序调用initializeProperties(), 读取velocity.properties默认配置,合并自定义的properties。
  4. 顺序调用initializeLog() ,调用LogManager.updateLog(),进行Log初始化
  5. LogManager.createLogChute()会首先读取runtime.log.logsystem配置,看看是否有存在自定义的LogChute实例对象,如果有则直接使用,并返回
  6. 在没有对应的LogChute实例对象配置,继续读取runtime.log.logsystem.class,看看似乎否有logsystem的配置,就是前面类图中的一对LogChute,LogSystem的实现类。
    runtime.log.logsystem.class = org.apache.velocity.runtime.log.AvalonLogChute,org.apache.velocity.runtime.log.Log4JLogChute,org.apache.velocity.runtime.log.CommonsLogLogChute,org.apache.velocity.runtime.log.ServletLogChute,org.apache.velocity.runtime.log.JdkLogChute
     按照顺序,逐一加载LogChute实现类,如果class装载成功,则进行初始化,并返回
  7. LogManager,针对createLogChute,将系统初始时HoldingLogChute记录的内容,输出到新的LogChute上,最后完成了log的初始化

 

了解了velocity的整套log机制后,再来看该问题: 

  • 使用时没有设置velocity log的任何参数,因为系统中存在Log4j的包,所以会使用Log4jChute做为Log记录的对象返回。
  • 在初始化Log4jChute时,没有设置logger.name,初始化Logger时,会使用默认的velocity.log做为文件输出路径
  • File file = new File("velocity.log"),大家知道这样的文件创建,是基于当前jvm的current work,也就是user.dir属性。(可以通过jinfo $pid | grep user.dir进行查看)

ok,现在的问题已经很明了,异常中提示velocity.log无权限,只需要check一下当前jvm进程的user.dir属性。

最后检查结果:

  • jboss作为web容器时,user.dir=/home/ljh/web-deploy/bin  (当前的启动脚本所在目录)
  • jetty作为web容器时,user.dir = /usr/local/program/jetty-7.2.0 (所指定的jetty.home变量路径)

刚好我用的是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();
 }

 
说明:针对应用中的渲染引擎,可以直接使用NullLogChute(),不做任何的日志输出。

 

方案二:统一型,融合到现有的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

  • 大小: 13.4 KB
分享到:
评论
4 楼 56553655 2011-09-14  
分析的很好,学习了
3 楼 agapple 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则是当前进程执行。一些潜规则挺多的。
2 楼 wlhok 2010-11-19  
有个问题,为什么jetty的run.sh执行的jvm,其user.dir会是jetty的路径?
1 楼 agapple 2010-11-19  
网上查了下,java.io.FileNotFoundException: velocity.log (Permission denied)

这样的问题还不少stackoverflow上也有人提问,http://stackoverflow.com/questions/849710/error-in-velocity-and-log4j

看来j2se和j2ee模式下的确会出现一些问题,普通jar运行好好的代码,在运行于容器中就会出现一些莫名的问题

单元测试很重要,集成测试也还是很有必要,这次就犯了这么个错误,改完提测被退回

相关推荐

    Velocity 入门

    **Velocity 入门** Velocity 是一个基于 Java 的模板引擎,它是 Apache 软件基金会的顶级项目之一。它被广泛用于 Web 开发中,尤其是在 MVC(Model-View-Controller)架构中,作为视图层的技术,帮助开发者将静态...

    Springmvc4.0.6+mybatis3.2.7+velocity1.7+log4j2

    框架采用最流行技术springmvc4.0.6,和最流行持久层框架mybatis3.2.7,还有Velocity - 模板引擎,还有最新日志输出log4j2的配置并且分级别输出到不同文件, 感兴趣赶紧下载吧

    Velocity所需jar包

    velocity所需jar下载 commons-beanutils.jar commons-collections-3.1.jar commons-digester-1.8.jar velocity-1.7.jar velocity-1.7-dep.jar velocity-tools-2.0.jar

    velocity的jar包

    **Velocity简介** Velocity是Apache软件基金会的一个开源项目,它是一个基于Java的模板引擎,用于生成动态网站内容。Velocity被设计成一个轻量级的、快速的、非侵入式的模板语言,使得开发者能够将HTML页面的展示...

    velocity所需的jar包

    在使用Velocity时,可能会遇到需要调试或记录错误的情况,这时Log4j可以帮助我们跟踪和记录这些问题。 `eclipse相关插件`,虽然不是必需的jar包,但对于使用Eclipse作为IDE的开发者来说,可能需要安装Velocity支持...

    velocity-1.5.jar,velocity-1.6.2-dep.jar,velocity-tools-1.3.jar

    Velocity是Apache软件基金会的一个开源项目,它是一款强大的模板引擎,主要用于生成动态Web内容。 Velocity的主要优点在于其简单易用和高度可扩展性,使得开发者能够将业务逻辑与表现层分离,提高代码的可读性和维护...

    velocity插件

    【 Velocity 插件详解】 Velocity 是一款基于 Java 的模板引擎,它被广泛应用于 Web 开发中,特别是作为 Apache Struts 和 Spring MVC 框架的一部分。Velocity 插件则是为了在开发环境中提供对 Velocity 模板语言的...

    模板:velocity和freemarker的比较

    Velocity和Freemarker模板技术比较 模板技术在现代软件开发中扮演着重要角色,而在目前最流行的两种模板技术中, Velocity 和 Freemarker 独占鳌头。在 WebWork2 中,我们可以随意选择使用 Freemarker 或 Velocity ...

    velocity为空判断

    这两个指令属于Velocity Tools的扩展,如果你的项目中引入了Velocity Tools,可以通过在`velocity.properties`文件中添加以下配置来启用它们: ```properties userdirective = org.apache.velocity.tools.generic....

    Velocity 和 FreeMarker区别

    ### Velocity与FreeMarker的区别 在IT领域特别是Java开发中,模板引擎是不可或缺的一部分,它们用于将数据模型转换为HTML、PDF、Word文档等格式。在众多模板引擎中,Velocity和FreeMarker是两种非常受欢迎的选择。...

    velocity jar包

    3. **velocity-dep.jar**:这个文件包含了Velocity运行所依赖的其他第三方库,如log4j(日志框架)、commons-lang(Apache Commons Lang库)等。这些依赖库为Velocity提供了一些基础功能,如日志记录、字符串操作等...

    Velocity资料

    文档提供了如何配置和使用Velocity日志系统(LogSystem)的示例,这对于调试和记录运行时信息非常重要。 14. Velocity资源装载器: 资源装载器(Resource Loaders)是Velocity用来加载模板和资源的机制,文档中提到...

    Velocity1.4

    Velocity Velocity Velocity Velocity Velocity Velocity Velocity Velocity Velocity Velocity

    velocity的所有jar包

    2. **velocity-1.4.jar**:这是Velocity的主要库文件,包含了 Velocity Engine的核心实现。它负责解析模板,执行嵌入的Java代码,并生成最终的输出。版本1.4是一个较早的版本,但依然稳定且广泛使用。其中包含了许多...

    velocity入门使用教程

    Velocity是Apache软件基金会下的一个开源模板引擎,用于生成动态Web页面。它基于Java语言编写,可以被集成到多种Java应用程序中,如Java EE应用服务器和Java Web应用框架。Velocity模板语言(Velocity Template ...

    eclipse中velocity插件

    在Eclipse中集成Velocity插件,可以方便地进行Velocity模板的编写和调试,提高开发效率。 首先,让我们深入了解一下Velocity插件在Eclipse中的作用。Velocity插件提供以下功能: 1. **代码提示和自动完成**:在...

    JAVAEE Velocity例子工程

    `JAVAEE Velocity例子工程` 是一个用于展示如何在Java企业版(JAVAEE)环境中使用Velocity模板引擎的示例项目。Velocity是Apache软件基金会的一个开源项目,它是一个轻量级的、基于Java的模板引擎,常用于生成动态...

    eclipse编辑velocity插件velocitysite-2.0.8

    Eclipse编辑Velocity插件VelocitySite-2.0.8是一款专为Java开发人员设计的工具,旨在提升在Eclipse集成开发环境中编辑Velocity模板语言(VTL)的效率和体验。Velocity是一个快速、轻量级的Java模板引擎,常用于Web...

    Velocity_API

    props.setProperty("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"); props.setProperty("log4j.logger.org.apache.velocity", "DEBUG"); Velocity.init(props); ``` ##...

    Velocity初始化过程解析

    首先,它创建一个HoldingLogChute实例作为临时存储,然后根据配置文件中的runtime.log.logsystem或runtime.log.logsystem.class属性来创建实际的日志系统。 2. **ResourceManager**:负责加载和管理模板资源。它...

Global site tag (gtag.js) - Google Analytics