001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.epilogue.logging.errors;
006
007import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
008import java.util.HashMap;
009import java.util.Map;
010
011/**
012 * An error handler that disables loggers after too many exceptions are raised. Useful when playing
013 * in matches, where data logging is less important than reliable control. Setting the threshold to
014 * ≤0 will cause any logger that encounters an exception whilst logging to immediately be disabled.
015 * Setting to higher values means your program is more tolerant of errors, but takes longer to
016 * disable, and therefore may have more sets of partial or incomplete data and may have more CPU
017 * overhead due to the cost of throwing exceptions.
018 */
019public class LoggerDisabler implements ErrorHandler {
020  private final int m_threshold;
021  private final Map<ClassSpecificLogger<?>, Integer> m_errorCounts = new HashMap<>();
022
023  /**
024   * Creates a new logger-disabling error handler.
025   *
026   * @param threshold how many errors any one logger is allowed to encounter before it is disabled.
027   */
028  public LoggerDisabler(int threshold) {
029    this.m_threshold = threshold;
030  }
031
032  /**
033   * Creates a disabler that kicks in after a logger raises more than {@code threshold} exceptions.
034   *
035   * @param threshold the threshold value for the maximum number of exceptions loggers are permitted
036   *     to encounter before they are disabled
037   * @return the disabler
038   */
039  public static LoggerDisabler forLimit(int threshold) {
040    return new LoggerDisabler(threshold);
041  }
042
043  @Override
044  public void handle(Throwable exception, ClassSpecificLogger<?> logger) {
045    var errorCount = m_errorCounts.getOrDefault(logger, 0) + 1;
046    m_errorCounts.put(logger, errorCount);
047
048    if (errorCount > m_threshold) {
049      logger.disable();
050      System.err.println(
051          "[EPILOGUE] Too many errors detected in "
052              + logger.getClass().getName()
053              + " (maximum allowed: "
054              + m_threshold
055              + "). The most recent error follows:");
056      System.err.println(exception.getMessage());
057      exception.printStackTrace(System.err);
058    }
059  }
060
061  /** Resets all error counts and reenables all loggers. */
062  public void reset() {
063    for (var logger : m_errorCounts.keySet()) {
064      // Safe. This is a no-op on loggers that are already enabled
065      logger.reenable();
066    }
067    m_errorCounts.clear();
068  }
069}