Skip to content

Audit log

The audit log is Sondar’s own record of what happened on the server: every sign-in, every change and every search, written by the server as it happens. It lives in its own index, _audit, which nobody can delete, write into or cut short, and each record is chained to the one before it so that a record edited or removed afterwards shows up when the log is verified.

Every edition records the audit log and can view, filter, export and verify it. Pro adds keeping it for longer than 90 days (Licensing).

KindWritten whenWhat it says
Requestan HTTP request that changes something is answered; or a read of the audit log itself, a read that takes data out (an export or download, an uploaded or lookup file), or a sign-in or sign-outwho (user), what (action, module, the handler that served it), the method and path, the status the server sent and whether that was a success, the TCP peer address and, separately, the address the client claimed in X-Forwarded-For
Searcha search endsthe job, the user, where it came from (search, dashboard, alert_rule, export_task, …), the query, its time range, how it ended (completed, failed, terminated, timeout), how long it took, how many events and rows it returned, whether a row limit cut it, and the error; what it read — the indexes searched, any it named but may not read, which were masked, the row-level limits applied, and whether the search time limit cut its range; and, for a search the system ran, who for (schedule:…, alert:…, export:…)
Loss noticerecords could not be writtenhow many were lost

Not recorded: reads that change nothing and take no data out — the license, settings, job progress, message and status reads the pages repeat while they are open, which would otherwise fill the log with the product talking to itself (who searched what is the search record, written once when the search ends); the bodies of requests the product does not audit by name (a body can carry a password), data sent to the ingest endpoints (HEC, OTLP, /api/v1/data, Prometheus remote write), and the product’s own housekeeping searches (run as its internal user with no more specific source, such as metric synchronization and log-pattern lookups). Searches an export runs are recorded, and say which one. The searches a rule or a schedule runs — alert, aggregation and metric-generation rules, scheduled searches — are recorded when something changes: the first run of each of its searches, any run that ends differently from the last (completed, failed, terminated, timeout), every run that fails, and at least one run a day for each rule, so the log shows the rule is still running. A run that repeats the last one exactly within the day is not recorded. Searches that run without a job are recorded too — Prometheus remote read, the gRPC query stream, the data-masking report — under part: query, without a row count.

Settings → Advanced Settings → Audit Log. Filter by time range, user, action, kind and outcome; open a row to see the whole record. It needs the Audit Log permission: List to read and verify, Download to export. The admin role has it; analysts do not.

Read-only access for auditors (Pro). The built-in auditor role holds that permission and nothing else: an auditor sees the audit log — every page of it, export and verification — and no data, no settings and no other page. Giving it to someone needs Pro, by any route: the user’s roles, a role’s member list, a group that carries it, or a sign-in setting (an SSO group mapping or default role) that hands it out. Taking it away is always allowed (it takes effect within 15 seconds for someone already signed in, as any role change does), and an assignment made under Pro keeps working when Pro ends.

Without Pro, an admin can still hand an auditor the evidence: a JSON Lines export, which re-verifies outside Sondar, and the last hash from Verify.

The same records are in SonQL: repo="_audit".

The API:

Endpoint
GET /api/v1/audit/recordsnewest first; from, to (epoch ms), user, action, kind (request, search, notice), outcome (ok, failed, lost), size (up to 500), cursor (the previous page’s next)
GET /api/v1/audit/export?format=jsonlevery matching record, whole, with its chain fields — at most 100,000 per export; the X-Audit-Export-Truncated header says when more matched. format=csv gives the summary columns for a spreadsheet
GET /api/v1/audit/verifyeach node’s chain over from/to

Reading, exporting and verifying the audit log are themselves recorded: each page of records someone opens is one record (the page never reloads on its own).

  • Nobody deletes the index. Deleting the _audit repo, deleting or closing its indices through the internal admin API, removing its alias, and SonQL | delete over it are all refused.
  • Nobody writes into it. Only the server writes the audit log: ingest into _audit and SonQL | export repo="_audit" are refused.
  • Nobody cuts it short. It is deleted by age only, never by a capacity limit; it keeps at least 7 days; it cannot follow the global retention setting; it keeps its raw text; and under disk pressure it stops and alerts rather than deleting old records to make room.
  • Losses are recorded. A record that cannot be written is retried with the same chain position; one that never makes it leaves a gap in the numbers and a loss notice saying how many were lost. Up to 200,000 records wait in memory between writes; past that the overflow is counted in the next notice.

Shortening the retention is always allowed, down to 7 days.

Each node numbers its records 1, 2, 3, … and links each to the one before: hash = SHA-256(previous hash + "\n" + the record's raw text). Verification reads every record in order and reports, per node:

  • Edited — a record whose hash no longer matches its text;
  • Missing — numbers with no record (deleted, or lost before they were written; a loss also left a notice);
  • Repeated and broken links.

It also counts records in the window that carry no chain fields at all: the server chains every record it writes, so one without was not written by the server’s audit writer, and the log is reported as not intact.

Verification also returns each node’s last hash. Keep it somewhere outside Sondar: a later verification that still reaches that hash shows that nothing before it changed. A JSON Lines export carries prev, hash and raw for every record, so the same check can be run outside Sondar.

What this is not: tamper-proof. Someone able to rewrite the underlying index directly could rewrite a node’s chain from an edit onwards; the last hash you kept elsewhere is what exposes that. And deleting the newest records leaves no gap until the node writes again.

90 days by default. Every edition can set anything from 7 to 90 days, or keep a longer setting that Pro made; raising it above 90 days needs Pro. Set it in the _audit index’s settings, or over the API in milliseconds: PUT /api/v1/repos/_audit with {"retention": 34560000000} is 400 days (a string such as "400d" is refused, and the refusal names the unit). When a Pro subscription or trial ends, a longer retention already set keeps working; it can be shortened but not raised again.

How much it holds. Each change, each read that takes data out or opens the audit log, and each search a person runs is one record (a search is two: it is submitted, and it ends). The pages’ own status reads add nothing, and a rule whose runs keep ending the same way adds one record a day, not one per run. Measured 12 Oct 2026 on a fresh install with its two built-in alert rules (every 3 minutes) and an admin signed in: 70 of the status reads the pages repeat, sent over the API, wrote no records (the previous release wrote 70); five minutes on the search page wrote only its two searches; and five rule runs wrote two — the first run of each rule.