You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

JBoss EAP7日志重复及栈追踪拆分问题求助(Log4j1.2)

JBoss EAP 7: 重复日志输出与异常栈每行独立ERROR的问题分析与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));
    }
}

然后在应用初始化时调用这个方法,比如在ServletContextListenercontextInitialized方法里执行:

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.

火山引擎 最新活动