Skip to main content

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

LayerWhatWhereFor
Activity logbusiness events (org, domains, scans, feedback)per-org activity_log tablethe UI audit trail (GET /org/activity)
SIEM streamthe same events as structured JSONstdout, one line per event (log_type: easm.activity)central SIEM via a log shipper
Webhook (optional)direct push of each eventSIEM_WEBHOOK_URLSIEMs 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" }
}
FieldMeaning
event / actionwhat happened
actor + actor_kindwho (admin integration, customer, or system)
admin_keywhich admin key (prefix only)
org + customer_idwhich org (for per-org alerting)
ts / occurred_atwhen
ip, target_type, target_id, targetwhere / on what
outcomeok | error | denied
levelseverity for alert routing (see below)
detailevent-specific context (counts, old/new status, delta, reason)

Severity → alert tiers

The level drives routing:

LevelEventsAlert tier
ERRORscan.failed, any outcome:"error"Dev / DevOps (something broke)
WARNINGscan.cancelled, scan.stopped, org.key_rotated, any outcome:"denied"DevOps / Security (attention)
INFOnormal business activity (org.created, domain.*, scan.started/completed, finding.feedback)audit / dashboards

Example alert rules a SIEM can express on the stream:

  • DevOps: level >= ERROR and event = scan.failed → page on-call (group by org).
  • DevOps: rate of outcome = error over 5 min above threshold → degraded service.
  • Security: outcome = denied or event = org.key_rotated → review.
  • Per-customer SLA: event = scan.completed not seen for customer_id in N days.

Because every line carries org + customer_id, all of the above can be scoped per organization.

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.