You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中SLF4J封装Log4j时,日志warn/error触发计数的简洁实现方法

实现方案

方案一:自定义Log4j2 Appender(最简洁干净)

这是最优解,完全基于日志框架原生扩展,不侵入业务代码,逻辑清晰且性能开销低。

步骤1:编写自定义Appender类

继承Log4j2的AbstractAppender,在append方法中判断日志级别,触发计数逻辑:

import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;

@Plugin(name = "CounterAppender", category = Appender.CATEGORY, elementType = Appender.ELEMENT_TYPE)
public class CounterAppender extends AbstractAppender {

    private final LogCounter logCounter = LogCounter.getInstance();

    protected CounterAppender(String name, Layout<?> layout) {
        super(name, null, layout, true, Property.EMPTY_ARRAY);
    }

    @Override
    public void append(LogEvent event) {
        // 拦截WARN和ERROR级别日志
        if (event.getLevel().isWarnOrHigher()) {
            if (event.getLevel() == org.apache.logging.log4j.Level.WARN) {
                logCounter.incrementWarnCount();
            } else if (event.getLevel() == org.apache.logging.log4j.Level.ERROR) {
                logCounter.incrementErrorCount();
            }
        }
        // 若需要保留原有日志输出,可调用super.append(event)或转发到其他Appender
    }

    @PluginFactory
    public static CounterAppender createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Layout") Layout<?> layout) {
        if (name == null) {
            LOGGER.error("CounterAppender name cannot be null");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new CounterAppender(name, layout);
    }
}

步骤2:实现计数工具类

用单例模式封装计数逻辑,支持线程安全的递增操作:

public class LogCounter {
    private static final LogCounter INSTANCE = new LogCounter();
    private int warnCount = 0;
    private int errorCount = 0;

    private LogCounter() {}

    public static LogCounter getInstance() {
        return INSTANCE;
    }

    public void incrementWarnCount() {
        synchronized (this) {
            warnCount++;
        }
        // 如需上报监控(如Prometheus),可在此添加对应逻辑
    }

    public void incrementErrorCount() {
        synchronized (this) {
            errorCount++;
        }
    }

    // 提供计数查询方法
    public int getWarnCount() {
        synchronized (this) {
            return warnCount;
        }
    }

    public int getErrorCount() {
        synchronized (this) {
            return errorCount;
        }
    }
}

步骤3:配置Log4j2注册Appender

在log4j2.xml中添加自定义Appender,并关联到目标Logger:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <!-- 原有控制台输出Appender -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <!-- 注册计数Appender -->
        <CounterAppender name="CounterAppender">
            <PatternLayout pattern="%msg%n"/>
        </CounterAppender>
    </Appenders>
    <Loggers>
        <Root level="info">
            <!-- 同时启用原有输出和计数逻辑 -->
            <AppenderRef ref="Console"/>
            <AppenderRef ref="CounterAppender"/>
        </Root>
        <!-- 若只需给特定Logger计数,可单独配置 -->
        <!-- <Logger name="com.yourpackage" level="info">
            <AppenderRef ref="CounterAppender"/>
        </Logger> -->
    </Loggers>
</Configuration>

方案二:SLF4J Logger包装(轻量临时场景)

若不想修改Log4j配置,可包装SLF4J的Logger,重写warn/error方法,但需替换所有业务中的Logger获取方式,侵入性较强:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CountingLogger implements Logger {
    private final Logger delegate;
    private final LogCounter logCounter = LogCounter.getInstance();

    private CountingLogger(Logger delegate) {
        this.delegate = delegate;
    }

    public static Logger getLogger(Class<?> clazz) {
        return new CountingLogger(LoggerFactory.getLogger(clazz));
    }

    // 重写所有warn重载方法
    @Override
    public void warn(String msg) {
        delegate.warn(msg);
        logCounter.incrementWarnCount();
    }

    @Override
    public void warn(String format, Object arg) {
        delegate.warn(format, arg);
        logCounter.incrementWarnCount();
    }

    // ... 其他warn重载方法(共6个)需逐一重写

    // 重写所有error重载方法
    @Override
    public void error(String msg) {
        delegate.error(msg);
        logCounter.incrementErrorCount();
    }

    @Override
    public void error(String format, Object arg) {
        delegate.error(format, arg);
        logCounter.incrementErrorCount();
    }

    // ... 其他error重载方法(共6个)需逐一重写

    // 其余Logger接口方法直接委托给原Logger
    @Override
    public String getName() {
        return delegate.getName();
    }

    @Override
    public void trace(String msg) {
        delegate.trace(msg);
    }

    // ... 其余trace、debug、info方法逐一委托
}

业务代码中使用:

private static final Logger log = CountingLogger.getLogger(YourService.class);

方案三:AOP切面(不推荐)

Spring AOP无法拦截SLF4J的静态方法调用,需依赖AspectJ编译时/加载时织入,配置复杂且性能开销高于Appender方案,仅在特殊场景考虑。


内容的提问来源于stack exchange,提问作者ranban282

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 10:01:17