Tomcat 下多个 war 包部署引发 log 4j 组件异常分析

背景

一个简单的列表查询应用,使用了 Log4jConfigListener 监听器,但是客户现场是一个 Tomcat 下同时部署多个应用的方式使用该 war 包的,虽然本地部署都没问题,但是现场却一直启动失败,Catalina.log 日志中只有一行报错信息:

org.apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file

看不到任何异常堆栈日志。

排查过程

最麻烦的是看不到异常堆栈日志,而应用日志显示应用刚启动又停止了,所以第一步就是打印出异常日志。

Tomcat 使用的是 logging 日志框架,所以需要提供 logging.properties 文件。所以先准备一个日志配置文件放入应用的类路径下,内容如下:

handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
############################################################
Handler specific properties.
Describes specific configuration info for Handlers.
############################################################
org.apache.juli.FileHandler.level = FINE
org.apache.juli.FileHandler.directory = …/logs
org.apache.juli.FileHandler.prefix = error-debug.
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

症结

添加日志配置后,重启 Tomcat 此时 catalian.log 日志中出现了异常信息:

SEVERE: Exception sending context initialized event to listener instance of class [org.springframework.web.util.Log4jConfigListener]
java.lang.IllegalStateException: Web app root system property already set to different value: 'webapp.root' = [/opt/apache-tomcat-8.5.20/webapps/xxx/] instead of [/opt/apache-tomcat-8.5.20/webapps/xxx/] - Choose unique values for the 'webAppRootKey' context-param in your web.xml files!
        at org.springframework.web.util.WebUtils.setWebAppRootSystemProperty(WebUtils.java:162)
        at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:119)
        at org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:49)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4745)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:752)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:728)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:988)
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1860)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

根源是 Log4jConfigListener 配置监听器启动异常导致应用启动失败,而这个异常根源是两个 war 包应用中都没有配置 webAppRootKey 参数。

解决办法

单独为在每个项目 web.xml 中配置各自的该参数:

<context-param>
		<param-name>webAppRootKey</param-name>
		<param-value>xxx.root</param-value>
	</context-param>

延伸思考

延伸思考一 :这个 webAppRootKey 配置的作用是什么呢,为什么多项目部署时都不配会导致 Log4jConfigListener 监听器启动失败呢?

webAppRootKey 是指项目部署的路径的 key ,Spring 提供了默认值为 webapp.root ,作为系统环境变量,运行过程中通过 System.getProperty("webapp.root") 来动态获项目的运行路径,该路径为项目部署根路径。

跟踪了一下 Log4jConfigListener 源码,找到了根源。

它的 initLogging 方法需要使用项目部署根目录以便确定 log4j 配置文件的绝对路径,由于是一个 Tomcat 启动后是作为一个 JVM 实例运行的,所有的系统参数都是共享的,如果不同的项目都使用了 Log4jConfigListener 它们会找各自应用的配置文件,并且限制了系统变量获取到的根目录不能相同。

initLogging 方法:
在这里插入图片描述
setWebAppRootSystemProperty 方法:
在这里插入图片描述
这里一旦报错,就会抛出异常,导致该 Linstener 启动失败,进而该应用启动失败。

延伸思考二:Tomcat 的日志配置文件的作用,为什么明明有异常,而且是 Serve 最高级别的,为什么没有显示在容器的启动日志中呢?

跟了下 Tomcat 的源码看到部分 logging 的过程,以前编译过 Tomcat 源码,本地有源码跟踪环境。

首先,在 Eclipse 中全局搜索异常信息 “Exception sending context initialized event to listener instance of class” ,发现他是在一个本地化配置文件 LocalStrings.properties 中定义的,内容为:

standardContext.listenerStart=Exception sending context initialized event to listener instance of class {0}

这里只是配置日志格式,占位符处是输出具体 Linstener 名称的,继续找引用该配置的地方,全局搜索 “ standardContext.listenerStart ” 找到调用者:
在这里插入图片描述
继续看它是怎么用的,日志打印方法:
在这里插入图片描述
这里的水还是不浅的,最终找到 getLogger 是一个 org.apache.juli.logging.Log 接口,只有一个实现类 DirectJDKLog ,它承担了 Tomcat 的日志工作。

这里有一个疑问:Tomcat 配置 conf 目录下的 logging.properties 和 某一应用类路径下同时都具有 logging.properties ,那么 Tomcat 是以哪个为主的?Tomcat 的 conf 中的日志配置级别应该是能输出异常的为什么没有呢?

没搜到结果,改天研究下。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Tomcat可以通过两种方式来部署多个war。一种方式是通过在Tomcat的webapps目录下放置多个war来实现。每个war将会被Tomcat解压并部署为一个独立的Web应用程序。这种方式简单方便,适用于少量的Web应用程序。 另一种方式是修改Tomcat的配置文件server.xml,定义多个Connector。每个Connector都可以配置一个独立的端口,并将其映射到一个特定的war。这样,不同的war可以通过不同的端口访问。这种方式适用于需要更灵活的部署和管理多个Web应用程序的场景。 无论是哪种方式,一旦部署完成,Tomcat会自动解压和部署war,并将其转换为运行中的Web应用程序,可以通过相应的URL访问。这样,您就可以在同一个Tomcat服务器上同时运行多个独立的Web应用程序了。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Tomcat部署多个war的方法步骤](https://download.csdn.net/download/weixin_38534444/12898843)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [多war部署在一个tomcat中](https://blog.csdn.net/xiaokanfuchen86/article/details/111184846)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值