Skip to content

systems audit trail

Magnus Hedemark edited this page Jun 16, 2026 · 1 revision

Audit trail

Active contributors: Magnus Hedemark

Purpose

Durable query audit trail that records every search query and its dispatch results in a Valkey stream. Used for operational analysis, debugging, capacity planning, and understanding which queries hit which engines.

Key abstractions

Type File Description
QueryAuditLogger slopsearx/audit.py Fire-and-forget audit writer. Each call to record_query() writes a field-value entry to a daily Valkey stream keyed by date. Valkey failure is a silent no-op.
_AUDIT_STREAM_MAXLEN slopsearx/audit.py Stream cap constant (10,000 entries). Older entries are trimmed when the stream exceeds this length.
_AUDIT_TTL slopsearx/audit.py Stream TTL constant (90 days / 7,776,000 seconds). Streams auto-expire after this period.

How it works

Stream schema

Each audit entry is a set of field-value pairs written to a daily Valkey stream:

query_audit:{YYYY-MM-DD}  →  XADD MAXLEN ~10000 * field1 value1 field2 value2 ...

Entry fields:

Field Value
query Raw search query string
client_ip Requesting client IP address
timestamp ISO 8601 timestamp with milliseconds
engines Comma-separated list of dispatched engine names
engines_ok Count of engines that returned successful results
engines_error Count of engines that errored or were blocked
engines_timeout Count of engines that timed out
total_results Total results returned across all engines
latency_ms Total request latency in milliseconds

Dispatch flow

  1. After a search request completes (success or failure), the server calls _audit_logger.record_query() as a fire-and-forget background task
  2. record_query() derives aggregate statistics from the per-engine AdapterResponse objects: ok count, error count, timeout count, total results
  3. The entry is written to the daily stream via XADD stream_key MAXLEN ~10000 * with EXPIRE stream_key 7776000
  4. The write is wrapped in a try/except — Valkey unavailability is logged at DEBUG level and ignored

Bounded resource usage

Each daily stream is capped at ~10,000 entries via Valkey's MAXLEN directive (approximate, using ~). Streams auto-expire after 90 days. At ~10K queries/day, this is roughly 90 streams of ~10K entries each, keeping total memory footprint predictable and bounded.

Graceful degradation

If Valkey is unavailable, QueryAuditLogger silently skips the write. The audit trail is an observability convenience, not a correctness requirement. No search request is blocked or delayed by audit failures.

Integration points

  • Server startup: QueryAuditLogger is instantiated in startup() and stored in the module-level _audit_logger global. It receives a reference to SearchCache for access to the Valkey connection.
  • Server search handler: After building the response, the server spawns a background asyncio.create_task() calling _audit_logger.record_query() with the query, client IP, per-engine results, and total latency.
  • Valkey connection: Shared with SearchCache — no separate Valkey connection is opened for audit purposes.

Entry points for modification

  • Adding fields to audit entries: modify record_query() to derive and write new field-value pairs
  • Changing stream cap: modify _AUDIT_STREAM_MAXLEN constant
  • Changing retention: modify _AUDIT_TTL constant
  • Changing key format: modify _stream_key() method

Key source files

File Description
slopsearx/audit.py QueryAuditLogger class, stream key construction, ISO timestamp helper
slopsearx/server.py _audit_logger initialization and record_query() call site

Clone this wiki locally