Skip to main content

log

The event pipeline behind ModuleLog (Architecture › Rule 7): every event a module submits is redacted, sequenced, hash-chained (audit only) and fanned out to sinks on a background thread. Source in libs/log/. The kernel module that attaches the sinks is logging.

Types​

TypeRole
EventPipelineImplements EventLog; bounded queue, single consumer thread, addSink / removeSink, flush(timeout), counters (accepted, pending, dropped), chainHead()
RedactorRefuses secret-looking detail keys (password, token, api_key, …) and masks emails, IPs, URLs and long secrets in free text
HashChainSHA-256 chain over the canonical form of every AUDIT event; verify(rows) finds the first broken link
StoredEventAn event plus seq, prevHash, hash; serialises to/from the log_events row (JSONEachRow)
LogSinkaccept(List<StoredEvent>) + status(); sinks must never throw into the pipeline (it isolates them anyway)
ConsoleLogSinkSLF4J per category/severity
ClickHouseLogSinkInserts into log_events. While the database is down, AUDIT events are spilled to <data>/log/audit-spill.jsonl and replayed later; other categories are retried, then dropped with a count
DiscordWebhookSinkOne webhook per category (SHATTERVEIL_DISCORD_WEBHOOK_AUDIT / _GAME / _SYSTEM) plus _ALERTS for ALERT severity; summaries only, never details; rate-limited

Guarantees​

  • Never blocks, never throws. If the queue is full the event is dropped and counted.
  • Audit events are never silently lost. No database means a spill file, and the chain continues from the spill on the next start.
  • Secrets don't get in. Redactor runs before anything is stored or sent.

Submitting an event​

Modules don't touch the pipeline directly; they use the ModuleLog from their context, which stamps the module id:

ctx.log().audit("role.set")
.actor(actorName) // a name, never an IP or token
.target(player.getUsername())
.summary(player.getUsername() + " is now " + newRole)
.detail("from", oldRole).detail("to", newRole)
.submit();

Entry also offers severity(...) and security() (flags the event as a security event); game(action) and system(action) build the other two categories the same way.

Tests​

LogLibraryTest covers redaction, the hash chain, row round-trips, the pipeline's ordering and sink isolation, the ClickHouse sink's spill path (no database) and the Discord sink's configuration and formatting. Nothing needs a network.