Java 如何只记录一次重复警告

Java 如何只记录一次重复警告,java,logging,log4j,warnings,Java,Logging,Log4j,Warnings,有一种模式时常发生。我有一个多次调用的方法,它包含以下代码段: Foo foo = getConfiguredFoo(); if (foo == null) { logger.warn("Foo not configured"); foo = getDefaultFoo(); } 然后我的日志文件被这个警告弄得乱七八糟。我知道我可以grepit out,但我想知道是否有更好的方法只查看一次此警告 注意:默认情况下,复制消息是正确的行为,因此这与此无关。我将我的问题标记为log4j,但我

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

Foo foo = getConfiguredFoo();
if (foo == null) {
  logger.warn("Foo not configured");
  foo = getDefaultFoo();
}
然后我的日志文件被这个警告弄得乱七八糟。我知道我可以
grep
it out,但我想知道是否有更好的方法只查看一次此警告


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

我之前遇到过类似的问题,但在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);
        }
    }
}

下面是我能想到的:一个积累警告的类,可以在最后转储。它是用groovy编写的,但你可以理解要点。当然,可以自定义转储部件以使用记录器

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)
  }
}
class坏消息{
静态映射警告=[:];
静态无效警告(字符串键、对象唯一性){
def KnownWarning=警告[按键]
如果(!知道警告){
知识警告=[]
警告[键]=已知警告
}
知识警告
out.println(“$key:+stuffs.size())
每件物品{
out.println(“\t$it”)
}
}
}
}
到别的地方上课{
def foo(条形){
如果(!巴)
坏消息。警告(“空栏”,此)
}
}

如果这是您希望一次打印的唯一内容,那么使用保存的布尔值将是您的最佳选择。如果你想在整个项目中使用一些东西,我已经创建了一些可能有用的东西。我刚刚创建了一个使用log4j记录器实例的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\u Long作为第三个参数传入来实现。我喜欢能够确定每个特定日志消息的频率(因此参数)的灵活性。例如,这将实现您想要的:

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

这永远不会起作用,因为大多数情况下,您会在这两者之间得到不同的日志语句,尤其是在多线程环境中。@GETah,实际上这种方法可以很好地工作,因为它取决于日志、生成日志的频率以及生成其他日志的频率。我有一个软件,可以从设备故障中生成大量重复的日志,而其余的等待用户输入或其他事件,因此不生成日志。由于设备故障,结果是一个接一个的重复日志。我以前的回答被删除,因为它与我已经回答的另一个类似。这一次,我根据具体问题定制了它。:)