Log4j: Стандартный способ предотвращения повторяющихся сообщений журнала?

Наше производственное приложение регистрирует ошибку, когда не удается установить соединение TCP/IP. Поскольку он постоянно повторяет соединение, он регистрирует одно и то же сообщение об ошибке снова и снова. Аналогично, другие запущенные компоненты приложения могут попасть в цикл ошибок, если некоторый ресурс реального времени недоступен в течение некоторого периода времени.

Существует ли какой-либо стандартный подход к управлению количеством раз, когда одна и та же ошибка регистрируется? (Мы используем log4j, поэтому, если для обработки этого файла существует какое-либо расширение для log4j, оно будет идеально.)

Ответ 1

Я только что создал класс Java, который решает эту точную проблему, используя log4j. Когда я хочу записать сообщение, я просто делаю что-то вроде этого:

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

Вместо:

logger.warn("File: " + f + " not found.", e);

Заставляет его регистрировать максимум 1 раз за 5 секунд и печатает сколько раз он должен был регистрироваться (например, | 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;
        }
    }
}

Ответ 2

Было бы довольно просто контролировать это, записывая временную метку каждый раз, когда вы регистрируете ошибку, а затем регистрируете ее только в следующий раз, если истекает определенный период.

В идеале это будет функция в log4j, но кодирование ее в вашем приложении не так уж плохо, и вы можете инкапсулировать ее в класс-помощник, чтобы избежать шаблона во всем коде.

Ясно, что для каждого повторяющегося оператора журнала потребуется какой-то уникальный идентификатор, чтобы вы могли объединять операторы из одного источника.