Implements WP3.2 stage (b) per docs/plans/2026-08-15-site-events-policy-design.md.
- Per-run instance-script Started/Completed Info site events are now off by
default (SiteRuntimeOptions.PerRunScriptEvents=false) instead of firing on
every run, closing the dominant site_events writer. Gated at the ScriptRunLauncher
call sites (moved there from ScriptExecutionActor by WP3.1). Error-level events
(timeout/failure/stuck-watchdog/recursion-limit) remain unconditional.
- ScriptRunSummaryRecorder accumulates per-(instance, script) run counters and a
new site-only ScriptRunSummaryFlushService emits one aggregate "script" Info
site event per ScriptRunSummaryIntervalSeconds (default 300s), top-50-script
breakdown with an "others" rollup, zero-activity intervals emit nothing.
- Per-script opt-in via PerRunScriptEventScripts ("Instance/Script" exact or
"Instance/*" wildcard), matched by the new pure ScriptRunEventPolicy. All three
options are read from IOptionsMonitor<SiteRuntimeOptions> per run, so the
policy is hot-togglable without a restart.
- Fixed the stale "event log is not replicated" comment at AkkaHostedService.cs
(~905): site_events IS registered in SiteLocalDbSetup.ReplicatedTables — the
singleton is what makes queries always hit the actively-written copy;
replication is what gives the singleton history to read after a failover
(memo Decision (b)). site_events replication itself is unchanged (still
registered) and already pinned by
tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SiteLocalDbCdcRegistrationTests.cs.
- Updated Component-SiteEventLogging.md (Volume Policy section, corrected
Storage/replication rationale) and Component-SiteRuntime.md (Script Run
Launch + Error Handling sections).
8.9 KiB
Component: Site Event Logging
Purpose
The Site Event Logging component records operational events at each site cluster, providing a local audit trail of runtime activity. Events are queryable from the central UI for remote troubleshooting.
Location
Site clusters (event recording and storage). Central cluster (remote query access via UI).
Responsibilities
- Record operational events from all site subsystems.
- Persist events to local SQLite.
- Enforce 30-day retention policy with automatic purging.
- Respond to remote queries from central for event log data.
Events Logged
| Category | Events |
|---|---|
| Script Executions | Script started, completed (sampled/opt-in — see Volume Policy below); failed, timed out, recursion limit exceeded (with error details; always logged) |
| Script Run Summary | Interval aggregate replacing the per-run rows by default — one Info row per flush interval summarizing every instance script run since the last flush (see Volume Policy) |
| Alarm Events | Alarm activated, alarm cleared (which alarm, which instance), alarm evaluation error |
| Deployment Events | Configuration received from central, scripts compiled, applied successfully, apply failed |
| Data Connection Status | Connected, disconnected, reconnected (per connection) |
| Store-and-Forward | Message queued, delivered, retried, parked |
| Instance Lifecycle | Instance enabled, disabled, deleted |
| Notification | Site→central forward failure, long-buffered notification (still in the site buffer past a threshold) |
Volume Policy — Script Run Events (WP3.2, 2026-08-15)
Per-run instance-script Started/Completed Info events were, before this policy, the dominant
site_events writer: two rows per run, translating to roughly a dozen physical row-writes
across a replicated site pair once CDC oplog/row-version rows and their eventual purge
tombstones are counted — for content nobody looks at unless something went wrong. The policy
below (design memo: docs/plans/2026-08-15-site-events-policy-design.md) keeps the signal
while removing the volume.
- Off by default.
ScadaBridge:SiteRuntime:PerRunScriptEvents(defaultfalse) is the global switch back to the legacy per-run Started/Completed rows for every script. - Interval aggregate instead.
ScriptRunSummaryRecorderaccumulates per-(instance, script)counters (started, completed, failed, timed out, total/max duration) from the same call sites that used to emit the per-run rows. A hosted flush service emits one "script" Info row per interval —ScadaBridge:SiteRuntime:ScriptRunSummaryIntervalSeconds(default300, i.e. 5 minutes;0disables the flush) — with headline totals in the message (e.g. "1,243 runs: 1,240 completed, 2 failed, 1 timed out across 17 scripts") and a per-script breakdown inDetails, capped at the top 50 scripts by run activity with an "others" rollup entry so a pathological deployment cannot mint an oversized row. An interval with zero activity emits no row — the standby, which never runs scripts, therefore never produces a summary row naturally. Source is"ScriptRunSummary"; Instance ID isNULL(the row spans every instance). - Per-script opt-in for debugging.
ScadaBridge:SiteRuntime:PerRunScriptEventScripts(default empty) restores the legacy per-run rows for named scripts only, without paying the volume cost for the rest of the deployment. Entries are"{InstanceName}/{ScriptName}"for an exact match or"{InstanceName}/*"for every script on that instance. Effective only while the global switch is off. - Hot-togglable. Both keys are read from the live options snapshot at the top of every
run (
IOptionsMonitor<SiteRuntimeOptions>.CurrentValue), so an operator can flip either one — including narrowing an opt-in to a single misbehaving script — without a restart or a redeploy. - Error-level events are unconditional. Timeout, failure, stuck-watchdog, and
recursion-limit Error rows are unaffected by any of the above — they always fire, regardless
of
PerRunScriptEventsor the opt-in list.
Event Entry Schema
Each event entry contains:
- Timestamp: When the event occurred.
- Event Type: Category of the event (script, alarm, deployment, connection, store-and-forward, instance-lifecycle, notification).
- Severity: Info, Warning, or Error.
- Instance ID (optional): The instance associated with the event (if applicable).
- Source: The subsystem that generated the event (e.g., "ScriptActor:MonitorSpeed", "AlarmActor:OverTemp", "DataConnection:PLC1").
- Message: Human-readable description of the event.
- Details (optional): Additional structured data (e.g., exception stack trace, alarm name, message ID, compilation errors).
Storage
- Events are stored in the consolidated site LocalDb SQLite file (
site_eventstable; see CLAUDE.md → Consolidated site database). Only the active node generates and stores events (a standby runs no scripts, subsystems, or deployments to log). site_eventsIS replicated (SiteLocalDbSetup.ReplicatedTables) on a site pair with replication configured — this reverses the component's pre-LocalDb-Phase-1 behavior, and the reason is failover history continuity, not either-node queries: the query handler (EventLogHandlerActor) is a cluster singleton, so a central query always routes to the actively-written copy regardless of which node answers the gRPC call — replication is never needed to make queries work. What replication buys is content to read after a failover: when the active node dies, the singleton restarts on the survivor, and its query service reads the survivor's local copy — which has history only because it was replicated. Without replication, a failover would start the event log empty at precisely the moment an operator is investigating an incident (WP3.2 design memo, Decision (b):docs/plans/2026-08-15-site-events-policy-design.md). On a site pair running without replication configured (by deliberate choice, e.g. the rig's site-b/site-c), the log stays node-local and a failover does start it fresh — the documented trade of not configuring a peer, unchanged by this policy.- Retention: 30 days. A daily background job runs on the active node and deletes all events older than 30 days. Hard delete — no archival. (Today, retention/cap deletes on a replicated node are captured by CDC like any other write — see the Volume Policy section above for why this is a small residual cost now that per-run rows are off by default.)
- Storage cap: A configurable maximum database size (default: 1 GB) is enforced. If the storage cap is reached before the 30-day retention window, the oldest events are purged first. This prevents disk exhaustion from alarm storms, script failure loops, or connection flapping.
Central Access
- The central UI can query site event logs remotely via the Communication Layer.
- Queries support filtering by:
- Event type / category
- Time range
- Instance ID
- Severity
- Keyword search: Free-text search on message and source fields (SQLite LIKE query). Useful for finding events by script name, alarm name, or error message across all instances.
- Results are paginated with a configurable page size (default: 500 events). Each response includes a continuation token for fetching additional pages. This prevents broad queries from overwhelming the communication channel.
- The site processes the query locally against SQLite and returns matching results to central.
Dependencies
- SQLite: Local storage on each site node.
- Communication Layer: Handles remote query requests from central.
- Site Runtime: Generates script execution events, alarm events, deployment application events, and instance lifecycle events.
- Data Connection Layer: Generates connection status events.
- Store-and-Forward Engine: Generates buffer activity events, including notification-category forward failures and long-buffered notifications on the site→central notification path.
Interactions
- All site subsystems: Event logging is a cross-cutting concern — any subsystem that produces notable events calls the Event Logging service.
- Communication Layer: Receives remote queries from central and returns results.
- Central UI: Site Event Log Viewer displays queried events.
- Store-and-Forward Engine: Its notification path (the site→central forward of script-generated notifications) reports forward failures and long-buffered notifications as Notification-category events. Routine enqueue and forward-success events are deliberately not logged — central's authoritative
Notificationstable (owned by the Notification Outbox component) is the audit record of record; site-side logging covers only the in-transit blind spot when central is unreachable. - Health Monitoring: Script error rates and alarm evaluation error rates can be derived from event log data.