使用log4j的DailyRollingFileAppender时,日志rolling不成功调查
https://blog.csdn.net/lmmzsn/article/details/77988716?utm_source=blogxgwz0
解决 log4j:ERROR Failed to rename,不需要修改源码。同时解决工程重复加载的问题。
很多人解决这个问题的办法就是修改log4j源码,将文件重命名的代码改为文件复制的代码。其实找到根本原因,就不需要修改源码了。
问题现象:
在控制台可以看到日志:log4j:ERROR Failed to renameDailyRollingFileAppender没有周期性的生成日志文件,只生成了一个日志文件。Tomcat启动慢并且占内存大(修改log4j源码是不能解决这个问题的)
问题发生的环境:
Log4j使用DailyRollingFileAppender周期性生成日志使用Tomcat服务器在tomcat的默认的server.xml里面,错误的配置了Host或者Context标签
问题发生的直接原因:
当DailyRollingFileAppender对日志文件进行重命名时,因为其他程序占用了该日志文件,导致重命名失败而报错。
问题发生的根本原因:
在tomcat的默认的server.xml里面,错误的配置了Host或者Context标签,这导致了Tomcat多次加载Web工程,进而导致多个DailyRollingFileAppender对象持有相同日志文件。
问题的解决方案:
我们在配置域名访问的时候一般会在server.xml里配置新的host,以此为例,其中最关键的地方就是为Context配置name属性
对下面配置的说明:
path=””时,可以直接用域名(www.xxx.com)来访问工程:web-syspath=”/web-sys”时,只能通过域名+path(www.xxx.com/web-sys)来访问工程:web-sysmywebapps与webapps是同一目录的文件夹,在正确配置里,我们的工程web-sys是放在mywebapps里面的(webapps里面千万不能有web-sys)到底有几个对象占用了文件,可以通过资源监视器来查看。
错误的配置1:这个配置会导致三个对象同时持有日志文件,可以直接用域名访问工程:web-sys
<Host name=”www.xxx.com” appBase=”webapps” autoDeploy=”true” unpackWARs=”true”><Alias>www.xxx.cn</Alias> <Alias>www.xxx.com.cn</Alias> <Context docBase=”web-sys” path=””/></Host>
错误的配置2:这个配置会导致二个对象同时持有日志文件,可以直接用域名访问工程:web-sys
<Host name=”www.xxx.com” appBase=”mywebapps” autoDeploy=”true” unpackWARs=”true”><Alias>www.xxx.cn</Alias> <Alias>www.xxx.com.cn</Alias> <Context docBase=”web-sys” path=””/></Host>
正确的配置1:只有一个对象持有日志文件,不能直接用域名访问工程:web-sys
<Host name=”www.xxx.com” appBase=”mywebapps” autoDeploy=”true” unpackWARs=”true”><Alias>www.xxx.cn</Alias> <Alias>www.xxx.com.cn</Alias> <Context docBase=”web-sys” path=”/web-sys”/></Host>
正确的配置2:只有一个对象持有日志文件,可以直接用域名访问工程:web-sys
<Host name=”www.xxx.com” appBase=”mywebapps” autoDeploy=”true” unpackWARs=”true”><Alias>www.xxx.cn</Alias> <Alias>www.xxx.com.cn</Alias> <Context docBase=”web-sys” path=”” name=”/web-sys”/></Host>
关于server.xml配置的总结:
如果新增了Host,那Host/appBase的值是不能和其他Host/appBase的值一样的。比如appBase都是webapps,那么两个Host会导致webapps下面的工程被加载两遍。如果配置了Context标签那么就一定要配置name属性值为:工程名(/web-sys),或者配置path值为工程名(/web-sys),否则就会引起重复加载工程的问题。<Alias>标签,允许我们把多个域名配置在一个Host下面,例如:xxx.com、xxx.cn、xxx.com.cn,都可以配置在一个Host下面到这,问题已经解决。推荐使用上面的【正确的配置2】。下面再分享一下我分析这个问题的过程,不想看的就可以跳过了。
——————————
分析过程
查看log4j源码,发现log4j:ERROR Failed to rename这个日志是在org.apache.log4j.DailyRollingFileAppender.rollOver()里面输出的找到DailyRollingFileAppender输出日志文件的变量是父类:WriterAppender中的:QuietWriter qw。找到设定qw的地方:org.apache.log4j.FileAppender.setQWForFiles(Writer)在setQWForFiles里设置断点,发现有两个不同的Trace,他们都是用来初始化DispatcherServlet(Spring框架),不同点在Tomcat源码里通过分析Tomcat源码,发现这两个Trace是由两个StandardContext对象:StandardContext[]和StandardContext[/web-sys])发起的。StandardContext[]来自于server.xml配置的Context;standardContext[/web-sys]是根据工程(web-sys),自动生成的。StandardContext[]和standardContext[/web-sys]其实代表的都是一个工程(web-sys),但是因为有两个对象,所以工程(web-sys)也被加载了两次。分析StandardContext的生成及调用过程,发现StandardContext[]和StandardContext[/web-sys]最终都会被存储在StandardHost的属性children(HashMap)里,children的KEY是用StandardContext的name属性来标识的分析StandardContext[/web-sys]的生成过程,其中有个判断:如果children里存在相同的StandardContext,就跳过该过程。到这里我们发现了关键的地方,StandardContext[]和standardContext[/web-sys]他们的不同点就在于name属性。StandardContext[]的name属性值就来自于server.xml里的Context标签,standardContext[/web-sys]的name属性值来自于工程的名字(/web-sys)如何才能改变StandardContext[]的name属性值呢?这就要从Context标签上面下功夫。在Tomcat官方文档中,Context标签的说明里并没有提到name这个属性,而在StandardContext.setPath(String)里我们发现下面代码,就是说如果没有name,那就把name设定为path的值。 if (getName() == null) { setName(this.path); }我以为只有Context标签的path属性才能改变StandardContext的name属性值。但是为了直接用域名访问工程,path只能为空。分析StandardContext加载Context标签的过程,发现他是通过反射进行设值的。比如Context标签配置了path,它就调用了StandardContext.setPath。那如果Context标签配置了name,它是不是也会调用StandardContext.setName呢?试着在Context标签里配置name属性值为工程名:”/web-sys”,发现StandardContext[]的name值变了,而且standardContext[/web-sys]也没有被加载。到此成功。
———————
GOOOOOOOOD
Tomcat组成与工作原理
https://juejin.im/post/58eb5fdda0bb9f00692a78fc
Web 应用的部署方式
注:catalina.home:安装目录;catalina.base:工作目录;默认值 user.dir
- Server.xml 配置 Host 元素,指定 appBase 属性,默认\$catalina.base/webapps/
- Server.xml 配置 Context 元素,指定 docBase,元素,指定 web 应用的路径
- 自定义配置:在\$catalina.base/EngineName/HostName/XXX.xml 配置 Context 元素
HostConfig 监听了 StandardHost 容器的事件,在 start 方法中解析上述配置文件:
- 扫描 appbase 路径下的所有文件夹和 war 包,解析各个应用的 META-INF/context.xml,并 创建 StandardContext,并将 Context 加入到 Host 的子容器中。
- 解析$catalina.base/EngineName/HostName/下的所有 Context 配置,找到相应 web 应 用的位置,解析各个应用的 META-INF/context.xml,并创建 StandardContext,并将 Context 加入到 Host 的子容器中。
注:
- HostConfig 并没有实际解析 Context.xml,而是在 ContextConfig 中进行的。
- HostConfig 中会定期检查 watched 资源文件(context.xml 配置文件)
ContextConfig 解析 context.xml 顺序:
- 先解析全局的配置 config/context.xml
- 然后解析 Host 的默认配置 EngineName/HostName/context.xml.default
- 最后解析应用的 META-INF/context.xml
ContextConfig 解析 web.xml 顺序:
- 先解析全局的配置 config/web.xml
- 然后解析 Host 的默认配置 EngineName/HostName/web.xml.default 接着解析应用的 MEB-INF/web.xml
- 扫描应用 WEB-INF/lib/下的 jar 文件,解析其中的 META-INF/web-fragment.xml 最后合并 xml 封装成 WebXml,并设置 Context
注:
- 扫描 web 应用和 jar 中的注解(Filter、Listener、Servlet)就是上述步骤中进行的。
- 容器的定期执行:backgroundProcess,由 ContainerBase 来实现的,并且只有在顶层容器 中才会开启线程。(backgroundProcessorDelay=10 标志位来控制)
也可能是在Tomcat的同一个host下有两个App,每个app的lib下都有一个log4j的jar包,而导致了两个jar包同时占有log文件。致使log文件rename不成功。可以把两个app的jar包放入tomcat的公用lib下,可以解决此问题。