JBoss EAP7日志重复及栈追踪拆分问题求助(Log4j1.2)
咱们先拆解你遇到的两个核心问题:同一错误同时出现在server.log和应用日志,以及应用日志里异常栈的每一行都被标记成独立的ERROR条目。我来帮你梳理成因,再给出Log4j1.2下的具体解决办法。
问题成因
1. 重复日志的根源
JBoss EAP7内置的Undertow容器会自动捕获Servlet请求处理过程中的异常,并将其写入server.log。而你的应用日志之所以也会出现相同异常,是因为Spring框架在处理请求失败时,默认会把完整的异常栈打印到System.err(标准错误输出)。如果你的应用日志配置了捕获System.err的输出,就会导致同一份异常被Undertow和应用日志系统各记录一次,造成重复。
2. 异常栈每行独立ERROR的原因
异常栈的打印是通过System.err.println()逐行输出的,而你的日志配置(比如用Log4j1.2的ConsoleAppender绑定System.err)会把每一行输出都单独当成一条ERROR级别的日志记录,而不是将整个异常栈合并为一条完整的日志条目。这就导致了每一行栈追踪都显示为独立的ERROR。
Log4j1.2 解决方案
1. 从根源阻止Spring输出异常到System.err
最彻底的解决方式是让Spring不再把异常打印到System.err,改用Log4j直接记录完整的异常栈。
步骤1:配置Spring的DispatcherServlet
在你的web.xml里,给Spring的DispatcherServlet添加初始化参数,关闭默认的异常打印行为:
<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>throwExceptionIfNoHandlerFound</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
步骤2:自定义全局异常处理器
创建一个全局异常处理器,用Log4j直接记录完整的异常栈(这会生成一条单独的ERROR日志,包含整个栈追踪):
import org.apache.log4j.Logger; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class GlobalExceptionHandler implements HandlerExceptionResolver { private static final Logger logger = Logger.getLogger(GlobalExceptionHandler.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 用Log4j记录完整异常栈,这会是一条单独的ERROR日志 logger.error("Request processing failed for URI: " + request.getRequestURI(), ex); // 这里可以添加返回错误页面或JSON响应的逻辑,根据你的需求调整 ModelAndView errorView = new ModelAndView("error"); errorView.addObject("message", "An error occurred while processing your request"); return errorView; } }
步骤3:注册异常处理器
在Spring的XML配置文件里注册这个全局异常处理器:
<bean id="globalExceptionHandler" class="com.your.package.GlobalExceptionHandler"/>
2. 修正应用日志对System.err的捕获逻辑
如果你的应用里有第三方库必须输出到System.err,可以调整Log4j1.2的配置,让多行异常栈被识别为一条日志,而不是每行单独标记为ERROR。
你可以在应用启动时,把System.err重定向到Log4j的日志流,这样所有stderr输出都会被Log4j统一处理:
import org.apache.log4j.Logger; import org.apache.log4j.Level; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; public class StdErrToLog4jRedirector { public static void init() { Logger stderrLogger = Logger.getLogger("stderr"); stderrLogger.setLevel(Level.ERROR); // 创建一个自定义输出流,将stderr内容转发到Log4j OutputStream logStream = new OutputStream() { private StringBuilder buffer = new StringBuilder(); @Override public void write(int b) throws IOException { if (b == '\n') { stderrLogger.error(buffer.toString()); buffer.setLength(0); } else { buffer.append((char) b); } } }; // 替换System.err System.setErr(new PrintStream(logStream, true)); } }
然后在应用初始化时调用这个方法,比如在ServletContextListener的contextInitialized方法里执行:
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class AppInitListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { StdErrToLog4jRedirector.init(); } @Override public void contextDestroyed(ServletContextEvent sce) { // 清理资源(如果需要) } }
最后在web.xml里注册这个监听器:
<listener> <listener-class>com.your.package.AppInitListener</listener-class> </listener>
3. 可选:调整JBoss EAP7的server.log配置
如果你不想让Undertow把请求异常输出到server.log,可以修改JBoss的standalone.xml(或domain.xml),调整io.undertow.request日志类别的级别,比如改成WARN:
<subsystem xmlns="urn:jboss:domain:logging:8.0"> <!-- 其他配置 --> <logger category="io.undertow.request"> <level name="WARN"/> </logger> </subsystem>
注意:这个操作要谨慎,可能会错过Undertow容器层面的其他重要错误信息,建议只在确认重复日志确实影响排查时使用。
总结
解决这个问题的核心逻辑是:
- 阻止Spring默认将异常打印到
System.err,改用Log4j直接记录完整异常栈,从根源避免重复日志 - 正确处理
System.err的输出,让多行异常栈被合并为一条日志 - 按需调整JBoss的容器日志配置,进一步优化日志输出
内容的提问来源于stack exchange,提问作者Rocco N.




