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
相关产品推荐
相关产品推荐

