Java log4j:防止重复日志消息的标准方法?

Java log4j:防止重复日志消息的标准方法?,java,logging,log4j,Java,Logging,Log4j,我们的生产应用程序在无法建立TCP/IP连接时记录错误。由于它不断地重试连接,因此会一遍又一遍地记录相同的错误消息。同样,如果某个实时资源在一段时间内不可用,应用程序中其他正在运行的组件可能会进入错误循环 是否有任何标准方法来控制记录相同错误的次数?(我们使用的是log4j,所以如果log4j有任何扩展来处理这个问题,那就太完美了。)通过每次记录错误时记录一个时间戳,然后在下次某个时间段过去后才记录,来控制这一点是相当简单的 理想情况下,这将是log4j中的一个特性,但在应用程序中对其进行编码并

我们的生产应用程序在无法建立TCP/IP连接时记录错误。由于它不断地重试连接,因此会一遍又一遍地记录相同的错误消息。同样,如果某个实时资源在一段时间内不可用,应用程序中其他正在运行的组件可能会进入错误循环


是否有任何标准方法来控制记录相同错误的次数?(我们使用的是log4j,所以如果log4j有任何扩展来处理这个问题,那就太完美了。)

通过每次记录错误时记录一个时间戳,然后在下次某个时间段过去后才记录,来控制这一点是相当简单的

理想情况下,这将是log4j中的一个特性,但在应用程序中对其进行编码并不太糟糕,您可以将其封装在helper类中,以避免在整个代码中使用样板文件


显然,每个重复的日志语句都需要某种唯一的ID,以便可以合并来自同一源的语句。

我刚刚创建了一个Java类,它使用log4j解决了这个问题。当我想记录一条消息时,我只需执行以下操作:

LogConsolidated.log(logger, Level.WARN, 5000, "File: " + f + " not found.", e);
而不是:

logger.warn("File: " + f + " not found.", e);
这使得它每5秒最多记录1次,并打印它应该记录的次数(例如| x53 |)。显然,您可以通过log.warn之类的操作使其不具有那么多的参数,或者将级别拉出,但这适用于我的用例

import java.util.HashMap;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class LogConsolidated {

    private static HashMap<String, TimeAndCount> lastLoggedTime = new HashMap<>();

    /**
     * Logs given <code>message</code> to given <code>logger</code> as long as:
     * <ul>
     * <li>A message (from same class and line number) has not already been logged within the past <code>timeBetweenLogs</code>.</li>
     * <li>The given <code>level</code> is active for given <code>logger</code>.</li>
     * </ul>
     * Note: If messages are skipped, they are counted. When <code>timeBetweenLogs</code> has passed, and a repeat message is logged, 
     * the count will be displayed.
     * @param logger Where to log.
     * @param level Level to log.
     * @param timeBetweenLogs Milliseconds to wait between similar log messages.
     * @param message The actual message to log.
     * @param t Can be null. Will log stack trace if not null.
     */
    public static void log(Logger logger, Level level, long timeBetweenLogs, String message, Throwable t) {
        if (logger.isEnabledFor(level)) {
            String uniqueIdentifier = getFileAndLine();
            TimeAndCount lastTimeAndCount = lastLoggedTime.get(uniqueIdentifier);
            if (lastTimeAndCount != null) {
                synchronized (lastTimeAndCount) {
                    long now = System.currentTimeMillis();
                    if (now - lastTimeAndCount.time < timeBetweenLogs) {
                        lastTimeAndCount.count++;
                        return;
                    } else {
                        log(logger, level, "|x" + lastTimeAndCount.count + "| " + message, t);
                    }
                }
            } else {
                log(logger, level, message, t);
            }
            lastLoggedTime.put(uniqueIdentifier, new TimeAndCount());
        }
    }

    private static String getFileAndLine() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        boolean enteredLogConsolidated = false;
        for (StackTraceElement ste : stackTrace) {
            if (ste.getClassName().equals(LogConsolidated.class.getName())) {
                enteredLogConsolidated = true;
            } else if (enteredLogConsolidated) {
                // We have now file/line before entering LogConsolidated.
                return ste.getFileName() + ":" + ste.getLineNumber();
            }
        }
        return "?";
    }       

    private static void log(Logger logger, Level level, String message, Throwable t) {
        if (t == null) {
            logger.log(level, message);
        } else {
            logger.log(level, message, t);
        }
    }

    private static class TimeAndCount {
        long time;
        int count;
        TimeAndCount() {
            this.time = System.currentTimeMillis();
            this.count = 0;
        }
    }
}

查看这个链接@SajanChandran:我想我可以“自己动手”,但我希望这是一个足够普遍的问题,已经有了标准的解决方案/最佳实践。如果我这样编码,我很可能会扩展一个log4j类,使其成为一个配置任务,而不是编码。这可能是一个很好的第一步:-也许您可以编写自己的过滤器,类似于集成此答案代码的过滤器:请注意,映射访问不是线程安全的。另外,执行实际日志记录的else情况可能会导致两个线程同时记录相同的错误。的确,两个线程从完全相同的代码行调用记录器可能会导致记录器记录相同的内容两次。我可以通过使对map线程的访问变得安全来解决这个问题,但是我将放弃对性能的影响,以避免出现重复日志消息的可能性。这整件事的主要思想是在应用程序进入不良状态时,删除垃圾邮件,以便更容易消化日志。谢谢你指出这些问题,我真的很感激!错误警报:lastTimeAndCount.time应在每条日志消息之后重置,否则在time+delta之后-您将记录所有消息。