Activity Logging & SIEM Streaming
Every meaningful action in the platform is logged for who did what, when, where, and the outcome — and streamed to a central SIEM so DevOps and Dev alert rules can be built per org.
Three layers
| Layer | What | Where | For |
|---|---|---|---|
| Activity log | business events (org, domains, scans, feedback) | per-org activity_log table | the UI audit trail (GET /org/activity) |
| SIEM stream | the same events as structured JSON | stdout, one line per event (log_type: easm.activity) | central SIEM via a log shipper |
| Webhook (optional) | direct push of each event | SIEM_WEBHOOK_URL | SIEMs that prefer push (Splunk HEC, Elastic, Logz.io) |
api_audit (raw HTTP-level audit, key prefixes only) and FindingEvent (a single
finding's status timeline) remain; the activity log is the org-wide business feed.
Events (the "what")
org.created · org.key_rotated · domain.added · domain.changed ·
domain.removed · scan.started · scan.stopped · scan.completed ·
scan.failed · scan.cancelled · finding.feedback
Event shape (SIEM JSON line)
{
"ts": "2026-06-09T05:53:52Z",
"level": "INFO",
"name": "easm.activity",
"log_type": "easm.activity",
"event": "scan.started",
"outcome": "ok",
"org": "cust_act",
"customer_id": "CUST-ACT",
"actor": "ui-backend",
"actor_kind": "admin",
"admin_key": "adm_MPGUXcjy",
"target_type": "scan",
"target_id": "43274175-...",
"target": null,
"ip": "172.23.0.1",
"detail": { "module": "easm", "scanners": "full" }
}
| Field | Meaning |
|---|---|
event / action | what happened |
actor + actor_kind | who (admin integration, customer, or system) |
admin_key | which admin key (prefix only) |
org + customer_id | which org (for per-org alerting) |
ts / occurred_at | when |
ip, target_type, target_id, target | where / on what |
outcome | ok | error | denied |
level | severity for alert routing (see below) |
detail | event-specific context (counts, old/new status, delta, reason) |
Severity → alert tiers
The level drives routing:
| Level | Events | Alert tier |
|---|---|---|
ERROR | scan.failed, any outcome:"error" | Dev / DevOps (something broke) |
WARNING | scan.cancelled, scan.stopped, org.key_rotated, any outcome:"denied" | DevOps / Security (attention) |
INFO | normal business activity (org.created, domain.*, scan.started/completed, finding.feedback) | audit / dashboards |
Example alert rules a SIEM can express on the stream:
- DevOps:
level >= ERRORandevent = scan.failed→ page on-call (group byorg). - DevOps: rate of
outcome = errorover 5 min above threshold → degraded service. - Security:
outcome = deniedorevent = org.key_rotated→ review. - Per-customer SLA:
event = scan.completednot seen forcustomer_idin N days.
Because every line carries org + customer_id, all of the above can be scoped
per organization.
Shipping the stream to a SIEM (recommended)
The app writes the events to stdout as JSON. Ship them with any log collector. Example Docker Compose sidecar with Vector:
vector:
image: timberio/vector:latest-alpine
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./vector.toml:/etc/vector/vector.toml:ro
# vector.toml
[sources.api]
type = "docker_logs"
include_images = ["scanners-api", "scanners-worker"]
[transforms.activity]
type = "filter"
inputs = ["api"]
condition = '.log_type == "easm.activity"' # only activity events
[sinks.siem]
type = "elasticsearch" # or splunk_hec, http, datadog_logs, loki ...
inputs = ["activity"]
endpoint = "https://siem.internal:9200"
(Fluent Bit / Filebeat work the same way — tail container logs, filter on
log_type = easm.activity, forward.)
Direct webhook push (optional)
For SIEMs you'd rather push to directly, set:
SIEM_WEBHOOK_URL=https://collector.internal/ingest
SIEM_WEBHOOK_TOKEN=... # sent as Authorization: Bearer
Each event is then POSTed as JSON, fire-and-forget and fail-open (a SIEM outage never affects a request). The stdout stream is still emitted, so you can run both.
Reading the activity log from the UI
GET /api/v1/org/activity?action=&actor_kind=&target_type=&outcome=&limit=&offset=
Org-scoped (org key header). Returns the events newest-first for that org — the data behind an "Activity" / audit-trail screen.
Notes
- Logging is best-effort: a logging or SIEM failure never breaks the action.
- The stream is JSON lines (NDJSON on stdout), not the API response format — API
responses remain
application/json. - In production, turn off SQL echo (
APP_DEBUG=false) so stdout carries clean application + activity JSON only.