# 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.
