如何只记录一次重复警告

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

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

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

注意:默认情况下,重复的消息是正确的行为,因此这不是为了避免无意的重复日志消息 。 我将我的问题标记为log4j,但我对其他java日志框架持开放态度。

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

 class BadNews { static Map> 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) } } 

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

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

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

您可以在日志记录周围编写一个包装器来存储记录的最后一行。 根据您的实现方式,您可以添加某种计数器来记录记录的次数,或者您可以选择将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); } } } 

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

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

代替:

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

这使得它每5秒最多记录1次,并打印它应记录的次数(例如| x53 |)。 显然,你可以创建它,这样你就没有那么多参数,或者通过log.warn或其他东西来提升级别,但这适用于我的用例。

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

 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 lastLoggedTime = new HashMap<>(); /** * Logs given message to given logger as long as: * 
    *
  • A message (from same class and line number) has not already been logged within the past timeBetweenLogs.
  • *
  • The given level is active for given logger.
  • *
* Note: If messages are skipped, they are counted. When timeBetweenLogs 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; } } }