c254d0740e
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).
125 lines
8.9 KiB
Markdown
125 lines
8.9 KiB
Markdown
# 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` (default `false`) is the
|
|
global switch back to the legacy per-run Started/Completed rows for every script.
|
|
- **Interval aggregate instead.** `ScriptRunSummaryRecorder` accumulates 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` (default
|
|
`300`, i.e. 5 minutes; `0` disables 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 in `Details`, 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 is `NULL`
|
|
(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 `PerRunScriptEvents` or 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_events` table;
|
|
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_events` IS 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 `Notifications` table (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.
|