Class ErrorReportCollector

java.lang.Object
com.ultikits.ultitools.manager.ErrorReportCollector

@Internal public class ErrorReportCollector extends Object
Collects, deduplicates, and rate-limits error reports before they reach the WebSocket.

Three-layer protection against DB bloat: 1. Fingerprint dedup within 5-minute window 2. Max 10 unique errors per batch cycle 3. Sampling after 10 total occurrences of same error

This class NEVER uses java.util.logging.Logger to prevent circular logging. All internal error output uses System.err.

On Paper, System.err does not bypass the logger: SysoutCatcher re-logs a plugin's System.err line through that plugin's logger, so it can reach SystemLogHandler (#584). That is safe here because of where the two lines run: reportError(java.lang.Throwable, java.lang.String, com.ultikits.ultitools.manager.TriggerContext) is called from SystemLogHandler (inside its same-thread re-entry guard, which drops the re-logged copy), from command execution, the cooldown validator, the declarative GUI scheduler and the AOP exception interceptor, and the configuration is loaded at start-up and reload. Only the first can run on a thread holding a lock of the log stream, and there the re-entry guard drops the copy before the transmitter is reached (and UltiPanelLogTransmitter never takes its batch lock on a thread holding the drain lock); from every other caller the line at worst reaches the stream as an ordinary line.

Since:
6.2.3
  • Field Details

  • Constructor Details

    • ErrorReportCollector

      public ErrorReportCollector()
  • Method Details

    • init

      public void init()
      Initialize the collector and start the dedup window reset scheduler.
    • shutdown

      public void shutdown()
      Shutdown the collector.
    • loadConfiguration

      public void loadConfiguration()
      Load configuration from UltiTools config.yml.
    • reportError

      public void reportError(Throwable throwable, String moduleName, TriggerContext ctx)
      Report an error. Thread-safe. Returns immediately if disabled.
      Parameters:
      throwable - the exception
      moduleName - which UltiTools module caused it (e.g., "UltiChat")
      ctx - trigger context (who/what caused this)
    • drainErrors

      public com.google.gson.JsonArray drainErrors(int max)
      Drain errors into a JsonArray for batch_update. Layer 2: returns at most maxErrorsPerBatch, sorted by count descending. Performs a final dedup pass to merge entries with the same fingerprint.
      Parameters:
      max - maximum number of errors to drain
      Returns:
      JsonArray of error reports
    • drainErrorReportPayloads

      List<com.google.gson.JsonObject> drainErrorReportPayloads(int max)
      Drains the pending reports as v2 error.report payloads (plan 22-22, S-55), with the same limit, merge and order as drainErrors(int). Each payload is built by AgentEventEncoders.errorReport(com.ultikits.ultitools.manager.AgentEventEncoders.ErrorReportView) from ErrorReportCollector.ErrorReport.view(), so the message, stack trace and trigger detail are redacted exactly as the v1 report is, and the fingerprint is this collector's group key padded to eight hex digits. A report the encoder cannot make valid is dropped. A repeat of a report already drained inside the dedupe window is counted on it and not sent again, as before.
      Parameters:
      max - the most payloads to return
      Returns:
      the payloads, most frequent first; empty while error reporting is off
    • drainReports

      private List<ErrorReportCollector.ErrorReport> drainReports(int max)
    • resetDedupWindow

      private void resetDedupWindow()
      Reset the dedup window — allows the same error to be reported again.
    • fingerprintOf

      static String fingerprintOf(Throwable throwable)
      Parameters:
      throwable - an exception
      Returns:
      the group key this collector files it under; the panel's error group id is this key padded to eight hex digits (S-55)
    • computeFingerprint

      private static String computeFingerprint(Throwable throwable)
      Compute a fingerprint for dedup: hash of (exception class + top N stack frame method:line).
    • createReport

      private ErrorReportCollector.ErrorReport createReport(Throwable throwable, String fingerprint, String moduleName, TriggerContext ctx)
      Create an ErrorReport from a throwable.
    • captureServerState

      private void captureServerState(ErrorReportCollector.ErrorReport report)
      Capture current server state (TPS, memory, player count).
    • formatStackTrace

      private String formatStackTrace(Throwable throwable)
      Format stack trace to string, truncated to MAX_STACK_TRACE_LENGTH.
    • computeIsFramework

      private boolean computeIsFramework(Throwable throwable, String moduleName)
      Determine whether an error originates from the UltiTools framework itself (as opposed to a plugin module loaded by the framework).

      A framework error satisfies BOTH: 1. The top stack frame's class is in the com.ultikits.ultitools package. 2. The module name indicates the framework (null, empty, or "UltiTools").

    • moduleIdentifierOf

      static String moduleIdentifierOf(String moduleName)
      The UltiCloud identifier of the loaded module a report names (22-26 fix round, backend contract answer of 2026-10-27): the server groups an error.report to a module by matching its moduleName against the catalogue identifier, so the v2 report carries the module's identify-string, lower-case (ModuleIds), never free text. A name is attributed to a loaded module when it equals that module's name or identify-string, ignoring case; anything else -- the framework itself, a Bukkit plugin, the server, a command description, a bean name -- gives null, which the contract allows. Never throws.
      Parameters:
      moduleName - the name a producer passed, possibly null
      Returns:
      the identifier, or null when the report is not attributable to a loaded module
    • redact

      private static String redact(String text)
      Redacts registered commands' arguments from a field bound for UltiCloud. Without a framework instance nothing can have been registered, so the text is returned unchanged.
    • isEnabled

      public boolean isEnabled()
      Check if error reporting is enabled.
      Returns:
      true if enabled