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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

背景  

  最近开始使用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


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
5月前
|
关系型数据库 MySQL 数据库
MySQL 启动日志报错: File /mysql-bin.index not found (Errcode: 13 - Permission denied)
MySQL 启动日志报错: File /mysql-bin.index not found (Errcode: 13 - Permission denied)
241 2
|
消息中间件
failed to open log file at ‘/var/log/rabbitmq/rabbit@9f987b50c687_upgrade.log‘, reason: permission d
failed to open log file at ‘/var/log/rabbitmq/rabbit@9f987b50c687_upgrade.log‘, reason: permission d
316 0
|
关系型数据库 MySQL
MySQL - File /var/log/mysql/mysql-bin.index not found (Errcode 13 - Permission denied)
MySQL - File /var/log/mysql/mysql-bin.index not found (Errcode 13 - Permission denied)
339 0
|
监控 Shell
项目采坑日志——zabbix agent执行脚本,提示 Permission denied
项目采坑日志——zabbix agent执行脚本,提示 Permission denied
844 0
|
Web App开发 监控 应用服务中间件
|
1月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
223 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
2月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
279 3
|
10天前
|
存储 监控 安全
什么是事件日志管理系统?事件日志管理系统有哪些用处?
事件日志管理系统是IT安全的重要工具,用于集中收集、分析和解释来自组织IT基础设施各组件的事件日志,如防火墙、路由器、交换机等,帮助提升网络安全、实现主动威胁检测和促进合规性。系统支持多种日志类型,包括Windows事件日志、Syslog日志和应用程序日志,通过实时监测、告警及可视化分析,为企业提供强大的安全保障。然而,实施过程中也面临数据量大、日志管理和分析复杂等挑战。EventLog Analyzer作为一款高效工具,不仅提供实时监测与告警、可视化分析和报告功能,还支持多种合规性报告,帮助企业克服挑战,提升网络安全水平。
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1674 14
|
22天前
|
存储 监控 安全
什么是日志管理,如何进行日志管理?
日志管理是对IT系统生成的日志数据进行收集、存储、分析和处理的实践,对维护系统健康、确保安全及获取运营智能至关重要。本文介绍了日志管理的基本概念、常见挑战、工具的主要功能及选择解决方案的方法,强调了定义管理目标、日志收集与分析、警报和报告、持续改进等关键步骤,以及如何应对数据量大、安全问题、警报疲劳等挑战,最终实现日志数据的有效管理和利用。