Monitoring
System, app & DB metrics + alerts
The monitoring block collects operational metrics — host resources, application traffic, database health — persists a rolling history, and raises alerts when thresholds are breached. It exposes everything over HTTP, including a live SSE stream you can wire straight into a dashboard.
Each collector is independently toggleable, so you pay only for what you watch. Alerts add email fan-out with a cooldown so a flapping metric doesn't bury your inbox.
Collectors#
Four metric sources, each switched on independently with its own sub-metric checklist. collectInterval controls how often samples are taken.
1{2 "monitoring": {3 "enabled": true,4 "system": {5 "enabled": true,6 "collectInterval": "30s",7 "metrics": { "cpu": true, "memory": true, "disk": true, "process": true }8 },9 "application": {10 "enabled": true,11 "metrics": { "requests": true, "responseTime": true, "errors": true, "rateLimits": true }12 },13 "database": {14 "enabled": true,15 "metrics": { "connections": true, "queryTime": true, "slowQueryThreshold": "500ms" }16 },17 "redis": { "enabled": true },18 "persistence": { "enabled": true, "flushInterval": "60s", "retentionDays": 30 },19 "alerts": {20 "enabled": true,21 "email": { "enabled": true, "recipients": ["[email protected]"] },22 "thresholds": { "cpuPercent": 80, "memoryPercent": 85, "errorRatePercent": 5, "responseTimeMs": 1000 },23 "cooldown": "10m"24 },25 "endpoints": {26 "enabled": true,27 "basePath": "/monitoring",28 "stream": { "enabled": true, "path": "/stream", "interval": "5s" },29 "snapshot": { "enabled": true, "path": "/snapshot" },30 "history": { "enabled": true, "path": "/history", "maxMinutes": 1440 }31 }32 }33}enabledbooleanOptionalMaster switch for the monitoring subsystem. Note: a configured Redis (config.redis) is a HARD precondition — the entire block (MonitoringService construction, collectors, alerts and the HTTP endpoints) is gated on `redis && monitoring.enabled`, so with no Redis wired the subsystem silently does not start even when enabled is true. (config.liveMonitoring is gated independently on its own enabled flag and needs no Redis.)
falsesystemobject (enabled default true)OptionalHost metrics (on by default). collectInterval sets the sampling cadence (default 10s — the 30s in the sample above is just an override example); metrics toggles cpu, memory, disk and process (all on by default) plus network (opt-in, defaults false — collected only when explicitly set true, whereas the other four are collected unless explicitly set false).
applicationobject (enabled default true)OptionalIn-process metrics (on by default). metrics toggles requests, responseTime, errors and rateLimits — your real-time picture of API behaviour.
databaseobject (enabled default false)OptionalPostgres health — OPT-IN (default off). metrics toggles connections and queryTime, with slowQueryThreshold (default 100ms — the 500ms in the sample above is just an override example) marking the boundary for the slow-query count.
redis{ enabled?: boolean } (default false)OptionalToggle collection of Redis health metrics — OPT-IN (default off).
Persistence#
Metrics are buffered in memory and periodically flushed to the monitoring_metrics system table so you keep history across restarts. Persistence is ON by default: with a database wired and persistence.enabled not explicitly false, monitoring adds monitoring_metrics to the generated schema definition (feature_set 'monitoring') — but the table is NOT auto-created at runtime. You must re-run nucleus-generate (and migrate) for it to exist; until then the plugin logs a startup warning and keeps metrics in Redis only, skipping the DB flush. The table is exposed read-only through the standard entity routes — GET only, mutating methods are not generated. Tune the flush cadence and how long history is retained.
persistenceobjectOptionalHistory storage configuration.
enabledbooleanOptionalPersist collected metrics to the database. Defaults to true — persistence is ON whenever monitoring is enabled with a DB wired. This adds monitoring_metrics to the generated schema, but the table is only created when you re-run nucleus-generate; if it is still missing at startup the plugin warns and keeps metrics in Redis only (no DB flush). Set it false to opt out, which keeps monitoring_metrics out of the generated schema and skips writing to it.
trueflushIntervalstringOptionalHow often buffered metrics are written (e.g. 60s).
1mretentionDaysnumberOptionalHow many days of history to keep before pruning.
30Alerts#
Fire notifications when a metric crosses a threshold. Email recipients receive the alert, and a cooldown prevents alert storms from a metric hovering at the boundary.
alertsobjectOptionalThreshold-based alerting.
enabledbooleanOptionalMaster switch for threshold alerting — OFF by default even when monitoring collection is enabled; set true to run AlertService after each collect (checkAndAlert early-returns while this is false).
falseemail{ enabled?; recipients?: string[] }OptionalEmail delivery of alerts to a recipient list.
thresholdsobjectOptionalThe trip points: cpuPercent, memoryPercent, diskPercent, errorRatePercent, responseTimeMs and rateLimitBlocksPerMinute.
cooldownstringOptionalMinimum gap between repeat alerts for the same condition.
Endpoints#
Monitoring is exposed over HTTP under a base path, with a live stream, a point-in-time snapshot, a history query and an alerts feed.
endpointsobjectOptionalHTTP surface for metrics.
enabledbooleanOptionalGates the whole monitoring HTTP surface — defaults OFF and must be set to true explicitly. When false (the default) the routes are not mounted at all (checked before createMonitoringRoutes runs), so enabling monitoring collection alone exposes no HTTP endpoints. The stream/snapshot/history/alerts sub-endpoints (which each default on) are only reachable once this parent gate is truthy.
basePathstringOptionalPrefix for all monitoring routes.
stream{ enabled?; path?; interval? }OptionalServer-Sent Events live feed; interval sets push frequency. enabled defaults on — the route is skipped only when explicitly set to false.
snapshot{ enabled?; path? }OptionalGET {basePath}{snapshot.path} (default GET /monitoring/snapshot) returns the current metric values in a single response. enabled defaults on — the route is skipped only when explicitly set to false.
history{ enabled?; path?; maxMinutes? }OptionalGET /history serves the in-Redis rolling window, capped at roughly the last hour — so maxMinutes effectively tops out around 60 and a larger value (e.g. 1440) yields no extra data. enabled defaults on — the route is skipped only when explicitly set to false. Long-term history persisted in monitoring_metrics is queried via the read-only entity routes on that table.
alerts{ enabled?; path? }OptionalGET {basePath}{alerts.path} (default GET /monitoring/alerts) reads the ever-triggered alerts (latest event per type). Note active alerts NEVER auto-clear: once a metric type breaches it stays in the in-memory list — and keeps streaming over SSE — for the process lifetime, even after the metric normalizes (getActiveAlerts returns every type ever fired; the clearAlert method that would remove one is unused dead code). Enabling it also mounts a companion POST {basePath}{alerts.path}/:alertId/acknowledge (default POST /monitoring/alerts/:alertId/acknowledge) that returns isSuccess:false with 'Alert not found' when the alertId is unknown — there is no generated client action, call it by method+path. Acknowledge only sets acknowledged:true on the matching alert; it is a no-op for delivery — the alert is neither removed nor stopped from streaming, so a dashboard cannot dismiss an alert through this endpoint. enabled defaults on — both routes are skipped only when explicitly set to false.
From the frontend (live-monitoring actions)#
These four actions belong to the live-monitoring subsystem (config.liveMonitoring), not the persisted metrics feature. MONITORING_GET_LOGS returns the live in-memory activity feed (memory/CPU/Dapr/WebSocket/request), NOT persisted metric history for charts; MONITORING_GET_SETTINGS / MONITORING_CHANGE_SETTINGS read and patch the live-monitoring settings; MONITORING_HEALTH_CHECK hits its health route. The live SSE feed is read straight from the stream path.
MONITORING_HEALTH_CHECKGET · /monitoring/healthA simple status + timestamp probe for an uptime badge.
MONITORING_GET_LOGSGET · /monitoring/logsPull the recent live in-memory activity feed (memory/CPU/Dapr/WebSocket/request) — this is the live-monitoring feed, not persisted metric history for charts.
MONITORING_GET_SETTINGS / MONITORING_CHANGE_SETTINGSGET|PATCH · /monitoring/settingsRead or tune which feeds are captured and at what cadence, from an admin panel.
Under the hood — the collect/flush loop#
MonitoringService runs two timers: one collects a snapshot on collectInterval, the other flushes accumulated metric points to the database on flushInterval. Redis holds the hot data; the database holds history.
collectorssystem · application · database · redisOptionalSystemCollector reads CPU/memory/disk/network/process (network via /proc/net/dev on Linux); ApplicationCollector tallies requests/response-time/errors/rate-limit blocks (fed by recordRequest / recordRateLimitBlock from the middleware); DatabaseCollector queries pg_stat_activity/pg_stat_database for connections and query timings/slow-query count (avg query time on PG14+); RedisCollector parses INFO for memory/clients/ops and hit rate (direct connection mode only — skipped under Dapr). Each enabled metric is flattened into dotted MetricPoints (e.g. system.memory.usagePercent).
hot data in Redislatest + 1h historyOptionalEvery snapshot is written to monitoring:{appId}:latest (1h TTL) and appended to a monitoring:{appId}:history list trimmed to the last hour — so the stream/snapshot/history endpoints get instant recent data without touching the database. (This Redis cache is separate from the live-monitoring GET_LOGS feed, which reads its own in-memory store, not these keys.)
persistence flushflushToDb + retentionDaysOptionalCollected MetricPoints buffer in memory and are flushed in batches into the monitoring_metrics table on flushInterval (default 1m); a failed flush re-queues them. A daily prune deletes rows older than retentionDays.
alertsthresholds + cooldownOptionalAfter each collect, AlertService compares the snapshot against thresholds (cpu 80%, memory 85%, disk 90%, error-rate 5%, response-time 1000ms, rate-limit blocks 100/min by default). A breach raises an alert and, if configured, emails the recipients — then a cooldown (default 5m) suppresses re-alerting of that type for the window. The cooldown throttles the ENTIRE re-alert, not just email: within the window createAlert returns null before building the event, so neither the activeAlerts entry is refreshed nor an email is sent. Streaming persists purely because the alert entry is never auto-cleared, so getActiveAlerts (backing GET /alerts and the SSE 'alerts' event) keeps returning it for the process lifetime even after the metric normalizes. Note the retained entry is NOT frozen at the first breach: each breach outside the cooldown window overwrites the map entry with a fresh value/timestamp (createAlert sets lastAlertTime[type] and activeAlerts.set(type, alert)), while breaches inside the window early-return null without updating. So the surviving entry reflects the LAST breach before normalization — equal to the first breach only when the metric normalizes within a single cooldown window. Acknowledge flips acknowledged:true but does not remove it, and clearAlert (the only method that deletes an entry) is unused.
Related sections