Comment enregistrer les avertissements répétés une seule fois

il y a un schéma qui se produit de temps en temps. J'ai une méthode appelée de nombreuses fois, et il contient ce morceau de code:

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

alors mon fichier journal est encombré de cet avertissement une centaine de fois. Je sais que je peux grep, mais je me demande si il ya une meilleure façon de voir cet avertissement qu'une seule fois.

Note: la duplication des messages est un comportement correct par défaut, donc il ne s'agit pas de éviter les doublons non intentionnels. J'ai marqué ma question en tant que log4j, mais je suis ouvert à d'autres cadres de journalisation java.

15
demandé sur Community 2011-12-02 20:58:48

4 réponses

voici ce que je peux trouver: une classe qui accumule les avertissements qui peuvent être jetés à la fin. C'est dans groovy, mais tu peux comprendre. La partie dumping peut être personnalisée pour utiliser un logger, bien sûr.

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)
  }
}
4
répondu Adam Schmideg 2011-12-03 23:26:22

j'ai fait face à un problème similaire il y a quelque temps mais je n'ai pas pu trouver un moyen de traiter cela dans Log4J. Je l'ai enfin fait le code suivant:

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

Cette solution est OK si vous avez une ou deux déclarations de log que vous ne voulez pas voir répétées dans vos logs mais ne met pas à l'échelle avec plus de déclarations de log (vous avez besoin d'un booléen pour chaque message enregistré)

2
répondu GETah 2011-12-02 17:12:07

Vous pouvez écrire un wrapper autour de votre journalisation pour stocker la dernière ligne enregistrée. Selon la façon dont vous mettez en œuvre, vous pouvez ajouter une sorte de compteur pour enregistrer le nombre de fois qu'il a été enregistré ou vous pouvez choisir de sous-classe Logger au lieu d'avoir une enveloppe externe. Pourrait être configurable avec un booléen suppressDuplicates si vous aviez besoin que trop.

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);
        }
    }
}
1
répondu Thomas 2011-12-02 17:40:15

si c'est la seule chose que vous voulez imprimer une seule fois, alors utiliser un booléen enregistré serait votre meilleur pari. Si vous vouliez quelque chose que vous pourriez utiliser tout au long de votre projet, j'ai créé quelque chose qui peut être utile. Je viens de créer une classe Java qui utilise une instance Logger log4j. Quand je veux enregistrer un message, je fais juste quelque chose comme ceci:

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

au Lieu de:

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

ce qui le rend log un maximum de 1 fois toutes les 5 secondes, et imprime combien de fois il aurait dû se connecter (par exemple |x53|). Évidemment, vous pouvez faire en sorte que vous n'avez pas autant de paramètres ou de tirer le niveau en faisant le journal.prévenez ou quelque chose, mais ça marche pour mon étui.

Pour vous (si vous souhaitez imprimer un temps, tout le temps) c'est exagéré, mais vous pouvez toujours le faire en passant par quelque chose comme: Long.MAX_LONG est le troisième paramètre. J'aime la flexibilité de pouvoir déterminer la fréquence pour chaque message log spécifique (d'où le paramètre). Par exemple, ce permettrait de réaliser ce que vous voulez:

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

Voici la classe Logconsolidée:

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;
        }
    }
}
0
répondu 11101101b 2016-06-08 19:56:25