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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 17:00:11