背景  

  最近开始使用jetty做为我们的应用web容器,在迁移过程中发现一个比较隐晦的问题,原本在jboss容器跑的好好的应用,换到jetty容器上,直接不可用。出现一些莫名奇妙的错误。

现象

说明:我们应用中有代码使用了velocity处理一些业务,比如模板输出,自定义渲染引擎等。

使用例子:

1.RuntimeInstance  ri    = new RuntimeInstance();
2.
3......
4.ri.parse(new StringReader(script), name); //进行渲染脚本处理

换成jetty后,会莫名的出现一个异常信息,截取了一个异常描述:

1.caused by: java.lang.RuntimeException: Error configuring Log4JLogChute :
2.    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
3.    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
4.    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
5.    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
6.    at org.apache.velocity.util.ExceptionUtils.createWithCause(ExceptionUtils.java:67)
7.    at org.apache.velocity.util.ExceptionUtils.createRuntimeException(ExceptionUtils.java:45)
8.    at org.apache.velocity.runtime.log.Log4JLogChute.initAppender(Log4JLogChute.java:133)
9.    at org.apache.velocity.runtime.log.Log4JLogChute.init(Log4JLogChute.java:85)
10.    at org.apache.velocity.runtime.log.LogManager.createLogChute(LogManager.java:157)
11.    ... 33 more
12.Caused by: java.io.FileNotFoundException: velocity.log (Permission denied)
13.    at java.io.FileOutputStream.openAppend(Native Method)
14.    at java.io.FileOutputStream.(FileOutputStream.java:177)
15.    at java.io.FileOutputStream.(FileOutputStream.java:102)
16.    at org.apache.log4j.FileAppender.setFile(FileAppender.java:290)
17.    at org.apache.log4j.RollingFileAppender.setFile(RollingFileAppender.java:194)
18.    at org.apache.log4j.FileAppender.(FileAppender.java:109)
19.    at org.apache.log4j.RollingFileAppender.(RollingFileAppender.java:72)
20.    at org.apache.velocity.runtime.log.Log4JLogChute.initAppender(Log4JLogChute.java:118)
21.    ... 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的实现类。
    1.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  
  1. 按照顺序,逐一加载LogChute实现类,如果class装载成功,则进行初始化,并返回
  2. 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机制后,解决方案就有很多种了

方案一:暴力型,啥都不输出

1.RuntimeInstance       ri            = new RuntimeInstance();
2........
3.if (!ri.isInitialized()) {
4.            // 设置空的log,避免使用velocity默认的veloicyt.log
5.           ri.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute());
6.           ri.init();
7. }

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

方案二:统一型,融合到现有的log框架

1.RuntimeInstance       ri            = new RuntimeInstance();
2.......
3.if (!ri.isInitialized()) {
4.        .......
5.            // 自定义LogChute,代理到应用的Log对象上,统一使用Log4j.xml进行管理
6.            ri.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new LogChute() {
7.            public void init(RuntimeServices runtimeServices) {
8.            }
9.
10.            public void log(int level, String message) {
11.                log(level, message, null);
12.            }
13.
14.            public void log(int level, String message, Throwable t) {
15.                switch (level) {
16.                    case TRACE_ID:
17.                        getLogger().trace(message, t);
18.                        break;
19.
20.                    case DEBUG_ID:
21.                        getLogger().debug(message, t);
22.                        break;
23.
24.                    case INFO_ID:
25.                        getLogger().info(message, t);
26.                        break;
27.
28.                    case WARN_ID:
29.                        getLogger().warn(message, t);
30.                        break;
31.
32.                    case ERROR_ID:
33.                        getLogger().error(message, t);
34.                        break;
35.
36.                    default:
37.                }
38.            }
39.
40.            public boolean isLevelEnabled(int level) {
41.                switch (level) {
42.                    case TRACE_ID:
43.                        return getLogger().isTraceEnabled();
44.
45.                    case DEBUG_ID:
46.                        return getLogger().isDebugEnabled();
47.
48.                    case INFO_ID:
49.                        return getLogger().isInfoEnabled();
50.
51.                    case WARN_ID:
52.                        return getLogger().isWarnEnabled();
53.
54.                    case ERROR_ID:
55.                        return getLogger().isErrorEnabled();
56.
57.                    default:
58.                        return false;
59.                }
60.            }
61.        });
62.
63.        ri.init();
64. }

说明:使用内部匿名类,将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。

纠结的velocity log那些事(出现Permission denied)相关推荐

  1. 【MySQL 17】安装异常:Could not open file ‘/var/log/mysql/mysqld.log‘ for error logging: Permission denied

    MySQL安装时出现异常: Jul 24 09:15:34 linux-3v74 mysqld[3259]: 2022-07-24T01:15:34.845330Z 0 [System] [MY-01 ...

  2. 故障分析 | MySQL 启动遭遇 Permission denied 失败案例一则

    作者:任坤 现居珠海,先后担任专职 Oracle 和 MySQL DBA,现在主要负责 MySQL.mongoDB 和 Redis 维护工作. 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授 ...

  3. velocity模板引擎 -- java.io.FileNotFoundException: velocity.log (Permission denied)

    问题原因是velocity的日志框架导致(velocity是使用自己封装的日志框架记录日志的),velocity在初始化Logger时,如果没有读取到配置文件,则会使用默认的velocity.log做 ...

  4. log.php(157),Log出现permission Denied的错误

    我之前是可以写log的,最近发现写不了了,我删掉重来也不可以.请问这个改怎么处理! ( ! ) Fatal error: Uncaught exception 'think\exception\Err ...

  5. java io .log_namenode无法启动:java.io.FileNotFoundException: .log (Permission denied)

    在启动集群的时候,namenode无法启动,用tail命令查看log,可以看到出现如下错误:[hadoop@master hadoop]$ tail -100f /usr/local/hadoop/l ...

  6. Spark On Kubernetes报错Permission denied: user=root, access=WRITE, inode=/user/spark/log:hadoop:supe

    Spark On Kubernetes 提交测试任务,driver pod 报错日志,如下: 19/11/06 07:38:05 INFO OutputCommitCoordinator$Output ...

  7. selinux运行程序Permission denied,无avc log

    当在宽容模式下运行程序,解决了avc的遇到denied信息后,在强制模式下运行同一个程序却发生Permission denied,且无AVC denied信息产生. 最后发现是AVC msg没有打印出 ...

  8. Android已申请动态权限报错,Android 读取或者写入U盘时,报错:Permission denied

    ** 以下所有操作的前提是: android 系统已经root ** 最近在将文件拷贝到U盘时总是 写入失败(Permission denied) 程序清单中已经加入了权限 并且动态申请了权限 pri ...

  9. java.io.FileNotFoundException: /storage/emulated/0/ (Permission denied)

    最近在做项目时保存文件时老是抛java.io.FileNotFoundException: /storage/emulated/0/ (Permission denied)异常 说什么权限不够,我在这 ...

最新文章

  1. 从一个数组中寻找出现奇数次的数字
  2. PTA数据结构与算法题目集6-4 6-3 6-8
  3. oracle 游标中抛出异常的处理方式
  4. labview曲线上两点画延长线_教你用直尺画各种几何图形
  5. python多边形裁剪
  6. zap支持php,golang的zap怎么使用
  7. iOS 9 学习系列:UIStack View
  8. linux加密格式化吗,linux环境下给文件加密/解密的方法
  9. Linux网络流量监控Iftop安装
  10. OC__part11.3
  11. #pragma multi_compile_fwdbase会增加很多个shader variants
  12. key value vue 输出_Vue2.0 v-for 中 :key 到底有什么用?
  13. win7系统中出现“ 驱动器c中的卷没有标签...”的问题
  14. L2-025 分而治之-PAT团体程序设计天梯赛GPLT
  15. 3802.消灭数组-AcWing题库
  16. uml 9图不同的角度品种分类
  17. matlab 电力电子仿真电路,基于Matlab的电力电子电路仿真方法
  18. solidworks电气元件3d库_60套机械设计三维选型软件 非标自动化 电气选型SolidWorks标准件...
  19. 「名企直推,人才优聘」计划启动,寻找掌握 TiDB 技能的「你」
  20. dotnet core error 0x80070057

热门文章

  1. shell脚本传递参数,并且判断参数是否为空
  2. 创新、颠覆、引领,从 0 到百亿美元市值的创操作指南
  3. 金融业如何更好地利用大数据实现突破性变革?(实例解读)
  4. 互联网运营人员不得不知的14款工具神器
  5. 为什么单例模式需要double check
  6. OC高效率52之理解OC错误模型
  7. Android总结篇系列:Activity Intent Flags及Task相关属性
  8. 在golang编程中总结的基础语法及常见的问题
  9. 如何发送Head请求
  10. key to ribbon chart in powerbi: time, category, value