我們的生產應用程序在無法建立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