Skip to content

Passive events

Active polling asks a device how it is doing. Passive events are the opposite direction: the device speaks first — a syslog line, an SNMP trap, a webhook from another system — and Yagra listens, correlates the message to the node that sent it, and decides whether it warrants an alert.

Three intake paths feed one event pipeline:

Path How it arrives Received by
syslog UDP datagrams (host port 514 by default) a poller
SNMP traps UDP datagrams (host port 162 by default) — v1/v2c traps and informs a poller
Webhooks POST /api/v1/ingest/webhook/<source-id> with a per-source bearer token core, on the API port

Pollers receive syslog and traps close to the devices and forward them to core over the bus, so passive intake works in the same distributed topology as polling — a branch-site poller receives the branch’s events locally and they ride the poller’s existing bus connection home. Webhooks skip the poller entirely and land on the core API.

Core matches every received event against your event rules (below). A matching event gets a severity and can raise an alert; a non-matching event is still stored and browsable, so you can see what your fleet is saying before you have written a rule for it. Alerts raised from events join the same pipeline as threshold alerts — the same notification channels, including PagerDuty (Events API v2) and Jira Service Management (Alerts API) with a native fire/resolve lifecycle.

The syslog and trap listeners are opt-in: each exists only while its bind variable is set. The compose files enable both by default.

Listener Enable with Container bind Host port (compose)
syslog YAGRA_SYSLOG_BIND (e.g. 0.0.0.0:1514) 1514/udp 514 (YAGRA_SYSLOG_PORT)
SNMP traps YAGRA_TRAP_BIND (e.g. 0.0.0.0:1162) 1162/udp 162 (YAGRA_TRAP_PORT)
Terminal window
# .env — these are the compose defaults; set a bind empty to disable that listener
YAGRA_SYSLOG_BIND=0.0.0.0:1514
YAGRA_TRAP_BIND=0.0.0.0:1162
YAGRA_SYSLOG_PORT=514 # host port devices send to
YAGRA_TRAP_PORT=162

To disable one under compose, set its bind variable to an empty string in .env; outside compose, leaving the variable unset means the listener never starts.

Two deployment details matter more than anything else on this page:

  • Event→node correlation uses the datagram’s source IP. An event is attributed to the node whose address sent it. If Docker’s bridge networking rewrites source addresses on your host, run the poller with network_mode: host so the device’s real address survives — the remote-site poller composition already does. Anything else that rewrites the source address (NAT between device and poller) breaks attribution too, which is one more reason to place pollers close to their devices. If the host already runs a syslog daemon on port 514, remap the published port.
  • The poller cannot bind privileged ports. It runs as a non-root container, so it binds the unprivileged ports 1514 and 1162; compose maps the standard low ports on the host for it. With host networking or a native install there is no port mapping — either point devices at the high ports directly or redirect 514/162 to them in the firewall. The exact iptables REDIRECT rules are in Ports & firewall.

The syslog listener accepts both RFC 5424 (modern, structured) and RFC 3164 (legacy BSD) messages, so routers, switches, firewalls, and Unix hosts can all point their existing syslog configuration at it unchanged. Parsing extracts the facility, severity, hostname, application name, and message text — the fields event rules match on, and the same fields forwarding filters can select on later.

There is nothing to configure per device on the Yagra side: any device whose source address matches a monitored node is correlated automatically, and messages from addresses Yagra does not monitor are still received and browsable.

To confirm the listener is reachable, send a test line from any Linux host and look for it under Alerts ▸ Events:

Terminal window
logger --server <yagra-host> --port 514 --udp "test: hello from yagra docs"

The trap listener speaks SNMP v1 and v2c, and handles both notification forms:

  • Traps — fire-and-forget notifications, received and parsed.
  • Informs — confirmed notifications: Yagra sends the acknowledgement the sender is waiting for, so devices configured for informs do not sit in retransmit loops.

Incoming traps resolve their trap OID to a human-readable name, so a trap arrives as a named event rather than a raw OID, with a trap badge in the event log. A set of built-in trap event rules ships with Yagra, so common traps are classified out of the box.

An optional community filter drops traps whose community string does not match:

Terminal window
YAGRA_TRAP_COMMUNITY=mycommunity # unset = accept all communities

The configured value is never logged. SNMPv3 traps are not supported yet — the trap listener is v1/v2c only.

To confirm trap reception end to end, send a standard linkDown trap from any host with Net-SNMP installed and watch it arrive as a named event:

Terminal window
snmptrap -v 2c -c public <yagra-host>:162 '' 1.3.6.1.6.3.1.1.5.3 1.3.6.1.2.1.2.2.1.1 i 2

The third intake path lets other systems push events over HTTPS — a backup job, a CI pipeline, a cloud service’s alerting hook. Create webhook sources under Alerts ▸ Event sources: each source names the node its events belong to and gets its own bearer token.

Senders then post to the source’s endpoint on the core API port:

Terminal window
curl -X POST "http://<yagra-host>:8080/api/v1/ingest/webhook/<source-id>" \
-H "Authorization: Bearer <source-token>" \
-H "Content-Type: application/json" \
-d '{"message": "nightly backup failed on db-01"}'

The payload is deliberately permissive: if the body is a JSON object, its message, text, or summary field becomes the event text; any other body is taken as-is. The response codes are deliberately distinct, so an automated sender can tell what went wrong: 202 accepted (with the event id), 401 for a wrong token, 404 for an unknown or disabled source, 413 for an oversized body, and 429 when the source is over its rate limit. A 503 means event ingestion is not available on this core — either it is not configured, or in a high-availability pair the request landed on the standby.

Rules live under Alerts ▸ Event rules and turn raw events into classified, alertable signals. A rule is built from:

  • A match — substring or regex against the event text, optionally scoped to a single stream (syslog, traps, or webhooks) so a pattern written for one source cannot fire on another.
  • A severity — the matched event is classified, which drives the event log, the dashboard widgets, and any alert the rule raises.
  • An alert with auto-clear — a rule can raise a real alert on the node the event came from. Event alerts carry a TTL and clear automatically when it lapses; the TTL is how an event alert closes.
  • A clear pattern — an optional second pattern that clears the alert early, so a link up message closes the alert its link down counterpart raised, without waiting out the TTL.
  • A fire threshold — “N events in M seconds”, so a pattern that is noise in isolation (an occasional authentication failure) alerts only when it storms.

Two behaviors worth knowing:

  • Event-raised alerts skip dependency suppression on purpose — a device that just emitted an event is demonstrably reachable, so there is no upstream outage to roll it into.
  • A rule scoped to a stream kind this core does not recognize (possible mid-upgrade, when a newer WebUI writes a rule an older core has not learned) is left out of the matching engine and logged — it will not silently widen to every stream.

Event floods are bounded at the edge, on the poller, before anything reaches the bus or the database. Syslog and trap intake share a token-bucket limiter keyed by source IP:

Limit Default Variable
Per source IP 200 events/s YAGRA_EVENT_RATE_PER_SOURCE
Global (all sources) 5000 events/s YAGRA_EVENT_RATE_GLOBAL

Both allow bursts of up to the sustained rate, so a device that batches a few seconds of messages is not clipped, while a genuinely looping device is. A single misbehaving source hits its own 200/s ceiling long before it can crowd out the rest of the fleet’s 5000/s budget.

Webhook sources are rate-limited separately, per source, at the API — an over-rate sender gets 429 and can back off. Flow datagrams have their own, separate budget (see Traffic flow).

Both event limits are tunable; see the configuration reference.

Alerts ▸ Events shows everything received — matched or not. Filter by stream kind (syslog / trap / webhook), by matched vs. unmatched, by node, and by time range, with a text search that has a regex toggle. Unmatched events are worth a periodic look: they are the messages your fleet sends that no rule classifies yet.

The same event stream also appears where you triage:

  • Every node’s detail page has an Events tab scoped to that node.
  • The dashboard has a passive-events widget section — event feed, volume, kind mix, top traps, triage, noisy sources, and rule coverage.

By default events are stored and searched in PostgreSQL, which is fine for moderate volumes and needs nothing extra. For serious event volumes, point YAGRA_LOGS_URL at a VictoriaLogs instance and event storage and search move onto a dedicated log store built for full-text queries over large histories. The single-node compose stack already ships a VictoriaLogs service with the URL set, so it is the default experience there; on a hand-rolled deployment it is opt-in. The UI and API are the same either way.

AI clients see the same stream: the MCP tools search_events and event_stats expose event search and triage statistics (noisiest nodes, severity mix, unmatched signatures) to an MCP-capable assistant.

Search, sort, and paging are all based on when an event happened, not when Yagra ingested it. One consequence is inherent to time-ordered logs: when a remote poller reconnects after an outage and replays what it buffered (store-and-forward), those older events insert at their original position — which can be behind a page you have already scrolled past.

Everything the listeners receive can also be relayed to a SIEM or another collector — byte-for-byte where the original datagram exists — or streamed into BigQuery as normalized rows for long-term querying. Forwarding is a tee, not a diversion: Yagra keeps matching, alerting, and storing regardless of where else the data goes. See Forwarding.

  • Traffic flow — the other passive intake: NetFlow, IPFIX, and sFlow collection.
  • Ports & firewall — listener ports, the REDIRECT rules for unprivileged binds, and what must stay internal.
  • Configuration reference — every YAGRA_* variable named above.