Log collection
Operators can query a device's logs on demand from the web UI. The agent runs journalctl locally with the requested filter and ships the output back over the mTLS stream. The control server stores the result keyed by a query ID; the UI polls until it arrives.
On-demand pull, not push. The agent does not tail logs continuously or ship them to a central store. If a device is offline when a query is dispatched, the query sits queued and runs when it reconnects.
What's supported today
- Backend:
journalctlonly — the agent always queries journald. Devices without journald (Alpine running OpenRC + syslog, etc.) can't currently answer log queries. The proto'sLogSourceenum already defines aLOG_SOURCE_SYSLOGvalue (plain/var/logsyslog files) and the SDK ships a syslog source, but the agent doesn't wire it up yet. - Filters: unit name, time range (
since/until), priority floor, free-formgreppattern, "kernel messages only" flag, and a line cap. - Output cap: the agent truncates results at 1 MB, keeping the tail. There is no truncation marker and no second page — refine the filter if you hit the cap.
How a query flows
- Operator submits a query from the device-detail page → control server's
QueryDeviceLogsRPC validates inputs and enqueues an Asynq task with a generatedquery_id. - The gateway dequeues and sends a
LogQuerymessage over the agent's stream. - The agent runs
journalctlwith the validated args, captures stdout, truncates if needed, and returns aLogQueryResultcarrying(query_id, success, error, logs). - The gateway proxies the result back to control via
InternalService; control stores it keyed byquery_id. - The web UI polls
GetDeviceLogResult(query_id)until the result lands.
Safety guards
- grep is ReDoS-guarded. Before the pattern reaches journalctl, the SDK rejects regex shapes known to backtrack catastrophically — nested quantifiers, overlapping alternation under a quantifier, too many unbounded quantifiers — plus over-length patterns.
- No raw shell. The journalctl argv is constructed programmatically, every dynamic value the argument of a dedicated flag — there is no
bash -cinterpolation of operator input. - No unbounded output. The 1 MB truncation is enforced at the agent, not just the server, so a misbehaving device can't blow up the control server's memory by returning a 10 GB result.
When to use
- Triage: "what did sshd say on this device in the last hour?"
- Incident response: "show me the kernel messages around the time the disk filled up."
- Compliance fishing: "did anyone log in as root yesterday?" (probably better answered by an osquery query against
last, but log collection works too).
For long-term log retention or fleet-wide search, you want a syslog forwarder + SIEM. Log collection is for "I have a device and a question right now."