如何只记录一次重复警告

时间:2011-12-02 16:58:49

标签: java logging log4j warnings

偶尔会出现一种模式。我有一个多次调用的方法,它包含以下代码段:

Foo foo = getConfiguredFoo();
if (foo == null) {
  logger.warn("Foo not configured");
  foo = getDefaultFoo();
}

然后我的日志文件被这个警告混乱了一百次。我知道我可以grep出来,但我想知道是否有更好的方法只能看一次此警告。

注意:默认情况下,邮件重复是正确的行为,因此不是avoiding unintentional duplicate log message。我将我的问题标记为log4j,但我对其他java日志框架开放。

4 个答案:

答案 0 :(得分:4)

这是我能想到的:一个积累警告的类,可以在最后抛弃。它很时髦,但你可以明白这一点。当然,转储部件可以定制为使用记录器。

class BadNews {
  static Map<String,List<Object>> warnings = [:];

  static void warn(String key, Object uniqueStuff) {
    def knownWarnings = warnings[key]
    if (! knownWarnings) {
      knownWarnings = []
      warnings[key] = knownWarnings
    }
    knownWarnings << uniqueStuff
  }

  static void dumpWarnings(PrintStream out) {
    warnings.each{key, stuffs ->
      out.println("$key: " + stuffs.size())
      stuffs.each{
        out.println("\t$it")
      }
    }
  }
}

class SomewhereElse {
  def foo(Bar bar) {
    if (! bar)
      BadNews.warn("Empty bar", this)
  }
}

答案 1 :(得分:2)

我前段时间遇到过类似的问题,但在Log4J中无法找到解决这个问题的方法。 我终于做了以下事情:

Foo foo = getConfiguredFoo();
if (foo == null) {
  if(!warningLogged)logger.warn("Foo not configured");
  warningLogged = true
  foo = getDefaultFoo();
}

如果您有一个或两个日志语句,您不希望在日志中重复出现但是没有使用更多日志语句进行扩展(对于记录的每条消息都需要布尔值),此解决方案就可以了。

答案 2 :(得分:1)

您可以在日志记录周围编写一个包装器来存储记录的最后一行。根据您的实现方式,您可以添加某种计数器来记录记录的次数,或者您可以选择将Logger子类化而不是使用外部包装器。如果你需要,也可以使用布尔值suppressDuplicates进行配置。

public class LogWrapper{
    Logger logger = Logger.getLogger("your logger here");
    String lastLine = new String();

    public void warn(String s){
        if (lastLine.compareToIgnoreCase(s) == 0)
            return;
        else {
            lastLine = s;
            logger.warn(s);
        }
    }
}

答案 3 :(得分:0)

如果这是你想要打印一次的唯一东西,那么使用保存的布尔值将是你最好的选择。如果你想要在整个项目中使用的东西,我创造了一些可能有用的东西。我刚刚创建了一个使用log4j logger实例的Java类。当我想记录消息时,我只是做这样的事情:

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

而不是:

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

这使得它每5秒最多记录一次,并打印它应记录的次数(例如| x53 |)。显然,你可以做到这一点,这样你就没有多少参数,或者通过log.warn或其他东西来提升水平,但这适用于我的用例。

对于你(如果你只想每次打印一次),这是过度的,但你仍然可以通过传递类似:Long.MAX_LONG in作为第三个参数来实现。我喜欢能够灵活地确定每个特定日志消息的频率(因此参数)。例如,这可以实现您的目标:

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

这是LogConsolidated类:

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;
        }
    }
}