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
| Type | Role |
|---|---|
EventPipeline | Implements EventLog; bounded queue, single consumer thread, addSink / removeSink, flush(timeout), counters (accepted, pending, dropped), chainHead() |
Redactor | Refuses secret-looking detail keys (password, token, api_key, …) and masks emails, IPs, URLs and long secrets in free text |
HashChain | SHA-256 chain over the canonical form of every AUDIT event; verify(rows) finds the first broken link |
StoredEvent | An event plus seq, prevHash, hash; serialises to/from the log_events row (JSONEachRow) |
LogSink | accept(List<StoredEvent>) + status(); sinks must never throw into the pipeline (it isolates them anyway) |
ConsoleLogSink | SLF4J per category/severity |
ClickHouseLogSink | Inserts 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 |
DiscordWebhookSink | One 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.
Redactorruns 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.