我们的生产应用程序在无法建立TCP/IP连接时记录错误。由于它不断地重试连接,它会一遍又一遍记录相同的错误消息。同样,如果一段时间内某些实时资源不可用,应用程序中的其他运行组件可能会进入错误循环。log4j:防止重复日志消息的标准方法?
是否有任何标准方法来控制相同错误记录的次数? (我们使用的是log4j,所以如果有任何log4j的扩展来处理这个问题,这将是完美的。)
我们的生产应用程序在无法建立TCP/IP连接时记录错误。由于它不断地重试连接,它会一遍又一遍记录相同的错误消息。同样,如果一段时间内某些实时资源不可用,应用程序中的其他运行组件可能会进入错误循环。log4j:防止重复日志消息的标准方法?
是否有任何标准方法来控制相同错误记录的次数? (我们使用的是log4j,所以如果有任何log4j的扩展来处理这个问题,这将是完美的。)
通过记录每次记录错误时的时间戳来控制这一点相当简单,如果一段时间已过,则记录下一次。
理想的情况下,这将是的log4j内的功能,但你的应用程序中的编码是不是太糟糕,你可以一个辅助类内封装,以避免整个代码样板。显然,每个重复的日志语句都需要某种唯一的ID,这样就可以合并来自同一个源的语句。
我刚刚创建了一个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;
}
}
}
请注意,地图访问不是线程安全的。 此外,执行实际日志记录的其他情况可能会导致两个线程同时记录相同的错误。 – 2016-09-15 08:30:10
确实,可能有两个线程从完全相同的代码行调用记录器,可能会导致记录器记录两次相同的事件。我可以通过访问地图线程来安全地解决这个问题,但我会放弃性能打击,以便有可能发生重复的日志消息。这整个事情的主要想法是在应用程序进入不良状态时切断垃圾邮件消息,以便消化日志。感谢您指出问题,我真的很感激! – 11101101b 2016-09-19 14:02:48
错误警报:lastTimeAndCount.time应该在每个日志消息之后重置,否则在时间+ delta之后 - 最终会记录所有消息。 – rjha94 2017-04-19 17:33:14
检查此链接 http://stackoverflow.com/questions/8359839/how-to-log-repeated-warnings-only-once – 2012-02-03 17:31:45
@SajanChandran:我想我可以 “推出自己的”,但有人希望这是一个普遍的问题,已经有了标准的解决方案/最佳实践。如果我这样做代码,我很可能会扩展一个log4j类,以便它是一个配置任务,而不是编码。 – 2012-02-03 19:11:09
这可能是一个很好的第一步:http://logging.apache.org/log4j/2.x/manual/filters.html#BurstFilter/ - 也许你可以编写你自己的过滤器,类似于这个集成这个代码的回答:https://stackoverflow.com/a/37619797/1520422 – 2017-11-20 15:08:56