Log4J实现每日仅记录一次相同异常的集中式方案问询
Log4J实现每日相同异常仅记录一次的集中处理方案
核心思路
利用Log4J的自定义Appender机制,在日志输出的最后环节拦截ERROR级别的日志,结合你已有的缓存管理器做每日去重判断,无需修改现有LOGGER.error代码。
方案一:Log4J 2.x 自定义去重Appender
1. 编写自定义Appender类
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; import java.time.LocalDate; // Log4J2插件声明,用于配置文件识别 @Plugin(name = "DeduplicateErrorAppender", category = Appender.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) public class DeduplicateErrorAppender extends AbstractAppender { private final CacheManager cacheManager; // 注入你已有的缓存管理器实例 protected DeduplicateErrorAppender(String name, Layout<?> layout, CacheManager cacheManager) { super(name, null, layout, true, Property.EMPTY_ARRAY); this.cacheManager = cacheManager; } @PluginFactory public static DeduplicateErrorAppender createAppender( @PluginAttribute("name") String name, @PluginElement("Layout") Layout<?> layout) { if (name == null) { LOGGER.error("DeduplicateErrorAppender name cannot be null"); return null; } if (layout == null) { layout = PatternLayout.createDefaultLayout(); } // 此处根据你的缓存管理器获取方式调整(比如Spring注入、单例) CacheManager cacheManager = CacheManager.getInstance(); return new DeduplicateErrorAppender(name, layout, cacheManager); } @Override public void append(LogEvent event) { // 仅处理ERROR级别日志 if (!event.getLevel().equals(org.apache.logging.log4j.Level.ERROR)) { super.append(event); return; } Throwable throwable = event.getThrown(); if (throwable == null) { // 无异常对象的ERROR日志,直接输出(或按消息去重,按需调整) super.append(event); return; } // 生成当日唯一的异常标识:异常类名+堆栈顶部方法(可根据业务调整规则) String exceptionKey = throwable.getClass().getName() + "_" + throwable.getStackTrace()[0].toString(); String dailyKey = LocalDate.now().toString() + "_" + exceptionKey; // 缓存判断:当日未记录则输出日志并缓存 if (!cacheManager.exists(dailyKey)) { super.append(event); // 设置缓存过期时间为当日剩余秒数 long expireSeconds = LocalDate.now().plusDays(1).atStartOfDay().toEpochSecond(java.time.ZoneOffset.systemDefault()) - System.currentTimeMillis()/1000; cacheManager.put(dailyKey, "recorded", expireSeconds); } // 已记录则跳过输出 } }
2. 修改Log4J2配置文件
将自定义Appender加入配置,替换或关联原有ERROR级别的Appender:
<Configuration status="WARN"> <Appenders> <!-- 自定义去重Appender --> <DeduplicateErrorAppender name="DeduplicateError"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </DeduplicateErrorAppender> <!-- 原有输出Appender(如控制台、文件) --> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="DeduplicateError"/> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
方案二:Log4J 1.x 自定义去重Appender
1. 编写自定义Appender类
import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; import java.time.LocalDate; public class DeduplicateErrorAppender1x extends AppenderSkeleton { private CacheManager cacheManager; // 配置文件注入缓存管理器 public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } @Override protected void append(LoggingEvent event) { if (!event.getLevel().equals(org.apache.log4j.Level.ERROR)) { super.append(event); return; } Throwable throwable = event.getThrowableInformation() != null ? event.getThrowableInformation().getThrowable() : null; if (throwable == null) { super.append(event); return; } String exceptionKey = throwable.getClass().getName() + "_" + throwable.getStackTrace()[0].toString(); String dailyKey = LocalDate.now().toString() + "_" + exceptionKey; if (!cacheManager.exists(dailyKey)) { super.append(event); long expireSeconds = LocalDate.now().plusDays(1).atStartOfDay().toEpochSecond(java.time.ZoneOffset.systemDefault()) - System.currentTimeMillis()/1000; cacheManager.put(dailyKey, "recorded", expireSeconds); } } @Override public void close() { // 清理资源(如缓存连接) } @Override public boolean requiresLayout() { return true; } }
2. 修改Log4J 1.x配置文件
log4j.rootLogger=ERROR, DeduplicateError, Console # 自定义去重Appender配置 log4j.appender.DeduplicateError=com.yourpackage.DeduplicateErrorAppender1x log4j.appender.DeduplicateError.layout=org.apache.log4j.PatternLayout log4j.appender.DeduplicateError.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n log4j.appender.DeduplicateError.cacheManager=com.yourpackage.CacheManagerImpl # 原有输出Appender配置 log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
关键注意事项
- 异常标识规则:可根据业务调整,比如结合异常消息、具体参数等,避免过度去重(如不同场景的同一异常类被误判为重复)。
- 缓存过期策略:必须设置为当日结束,确保次日重新记录相同异常。
- 无异常的ERROR日志:若无需去重可直接输出,如需去重则按日志消息生成唯一key即可。
内容的提问来源于stack exchange,提问作者Sunny
相关产品推荐
相关产品推荐

