ErrorWatch
ErrorWatch captures every script error, deduplicates identical ones, and reports each unique error once per window with an occurrence count — a crash loop becomes one line with (x4000), not four thousand lines. It’s a shared module: it attaches to a server kernel or a client kernel the same way. For the sibling module that derives GC health instead of error telemetry, see GcWatch.
Mental model
Section titled “Mental model”ErrorWatch merges two error streams into one deduplicated report:
ScriptContext.Error— every unhandled script error on this machine, whether or not the code involves the kernel.Scheduler.OnError— task crashes the Scheduler caught and isolated. These never reachScriptContext(the scheduler pcalls its tasks), so without this second tap a crash-looping scheduled task would be invisible to error telemetry. The error’s first line becomes the message, the full text the trace, and the source readsKernelScheduler.
Both streams funnel through the same dedup:
- Key = the error message plus the first line of the traceback. Two different call sites raising the same message count separately; the same line crashing 4000 times counts once.
- Window (default 30s): the first report of a key logs immediately — through Logger scope
ErrorWatchat error level, formatted{message} [{source}] (x{count})— and publishesKernel.ScriptErroron the bus. Repeats within the window increment the count silently. The first report after the window logs and publishes again, carrying the accumulated count. - Eviction: interpolated values (names, ids, coordinates) mint a unique key each, and without eviction the
Seentable would grow for the server’s whole lifetime. At most once per window, entries unseen forWindowSeconds * 10(default 300s) are pruned.
The result: sinks and the bus see at most one line per unique error per window, while nothing is ever dropped from the count.
-- src/Server/Bootstrap.luaulocal ReplicatedStorage = game:GetService("ReplicatedStorage")local ErrorWatch = require(ReplicatedStorage.ChloeKernel.ErrorWatch)
return function(kernel) local Watcher = ErrorWatch.attach(kernel)
-- Ship every deduplicated occurrence to your telemetry kernel.Bus:subscribe("Kernel.ScriptError", function(_, message, trace, source, count) warn(`[ops] {source}: {message} (x{count})`) end)endFor webhook alerting on errors generally (not just script errors), add a Logger sink filtered to Logger.Level.Error — ErrorWatch’s windowing means the sink receives one line per unique error per window, so the webhook cannot be flooded by a crash loop.
API reference
Section titled “API reference”| Member | Description |
|---|---|
ErrorWatch.attach(kernel?, options?) → watcher |
Connects ScriptContext.Error, and kernel.Scheduler.OnError when a kernel is given. kernel is optional: without one, errors still log through the Logger, but nothing publishes on the bus. |
watcher:counts() → { [key]: count } |
Snapshot of occurrence counts, keyed by the dedup key (message .. "\n" .. firstTraceLine). |
watcher:destroy() |
Disconnects both captures and clears the seen table. |
Options (all optional):
| Field | Default | Description |
|---|---|---|
WindowSeconds |
30 |
Dedup window. One log line and one bus publish per unique error per window. Eviction horizon is WindowSeconds * 10. |
SkipBind |
false |
Do not connect ScriptContext.Error (specs drive reports directly). |
Bus topics
Section titled “Bus topics”| Topic | Payload | Fired |
|---|---|---|
Kernel.ScriptError |
(message, trace, sourceName, count) |
On the first occurrence of a unique error, then at most once per window with the accumulated count. sourceName is the erroring script’s GetFullName(), "?" when unknown, or "KernelScheduler" for scheduler-caught task crashes. |
ErrorWatch consumes Scheduler.OnError (a signal on the scheduler, not a bus topic) when attached with a kernel.
Design notes
Section titled “Design notes”attachrequires the Logger from its own kernel clone (a relative require) — a cloned kernel gets its own ErrorWatch logging rather than silently sharing the original’s sinks.- ErrorWatch is fully spec-verified with a
SkipBinddouble driving reports directly — the dedup windows and bus payloads above are asserted behavior, not documentation optimism.