Initializes local configuration and runs database migrations.
raglogs init
raglogs init --db-url postgresql+psycopg://user:pass@host/raglogs
raglogs init --no-migrate # skip migrationsIngests logs into the database from local files, a pull adapter (CloudWatch, Datadog, Loki), or Kubernetes log exports. File mode supports JSON and plain-text formats, single files, directories, and glob patterns.
raglogs ingest ./logs/app.log
raglogs ingest ./logs/
raglogs ingest ./logs/*.log
raglogs ingest ./logs/ --recursive
raglogs ingest ./logs/ --service api --env production
raglogs ingest ./logs/ --format json
# CloudWatch Logs (AWS credential chain; no keys on the CLI)
raglogs ingest --adapter cloudwatch --param log_group=/aws/lambda/my-service --since 1h
# Datadog Logs Search API (keys via env; see Configuration)
raglogs ingest --adapter datadog --param query='service:billing-worker status:error' --since 1hKubernetes log exports (--adapter k8s) — concatenated kubectl logs, Fluent Bit / Vector JSON lines, CRI (kubelet) node logs, .gz files, and tarballs. Namespace / pod / container map to environment / host / service.
# kubectl capture (prefix + timestamps give pod/container and event time)
kubectl logs -n production -l app=billing-worker --all-containers \
--prefix --timestamps --since=1h > /tmp/billing-export.log
raglogs ingest --adapter k8s /tmp/billing-export.log
# kubelet /var/log/pods dump (path supplies namespace, pod, container)
raglogs ingest --adapter k8s --recursive ./var/log/pods
raglogs ingest --adapter k8s ./node-logs.tar.gz| Flag | Description |
|---|---|
--recursive / -r |
Recurse into subdirectories |
--source-name |
Logical name for this ingestion source |
--service |
Default service name when not in logs |
--env |
Default environment |
--format |
json, text, or auto (default) |
--with-embeddings |
Persist pgvector embeddings on log_embeddings for semantic ask (requires EMBEDDINGS_PROVIDER) |
--adapter |
file (default), cloudwatch, datadog, loki, or k8s |
--param |
Adapter param as key=value (repeatable) |
--since |
Window for pull adapters, e.g. 30m, 1h, 24h (default last 1h) |
--from / --to |
Explicit ISO 8601 window bounds (pull adapters) |
--resume-job |
Prior ingestion job UUID to resume pagination cursors from |
--scope |
Isolation scope (CLI default default). Service requests require a resolvable scope. |
Loki
Pull a bounded window from Grafana Loki via LogQL — no intermediate files.
Auth and the Loki origin come from env (LOKI_*); tenant and query can be
passed per ingest. Stream labels are mapped onto service / environment /
host (app/service/job, namespace/env, pod/instance).
query_range's limit is global across matching streams. Paginating by
advancing start to the latest timestamp in a full page can skip earlier
lines from other streams in a wide selector — prefer a narrow LogQL query
when completeness matters.
export LOKI_URL=http://localhost:3100
raglogs ingest --adapter loki --param query='{app="api"}' --since 1h
raglogs ingest --adapter loki --param query='{namespace="prod"}' \
--from 2026-03-12T22:00:00+00:00 --to 2026-03-12T22:30:00+00:00curl -X POST http://localhost:8000/v1/ingestions \
-H "Content-Type: application/json" \
-d '{"adapter":"loki","params":{"query":"{app=\"api\"}"},"since":"1h"}'| Param | Description |
|---|---|
query / queries |
LogQL selector (required unless LOKI_QUERY is set) |
tenant |
Override LOKI_TENANT (X-Scope-OrgID) |
limit |
Page size for query_range (default 5000, Loki max) |
Output
Ingestion complete
Files processed: 3
Lines read: 464
Parsed logs: 461
Skipped/errors: 3
Services detected: api, billing-worker, deployment-controller
Duration: 0.4s
The main command. Analyzes a time window, clusters the logs, compares against a baseline, and produces a structured incident summary.
raglogs explain --since 30m
raglogs explain --since 2h --service billing-worker
raglogs explain --from 2026-03-12T22:00:00Z --to 2026-03-12T22:30:00Z
raglogs explain --since 1h --no-llm
raglogs explain --since 1h --format json
raglogs explain --since 1h --format markdown
raglogs explain --since 1h --format markdown > postmortem.md
raglogs explain --since 1h --baseline-window 7d| Flag | Description |
|---|---|
--since |
Relative window: 30m, 1h, 24h, 7d |
--from |
Start of window (ISO 8601) |
--to |
End of window (ISO 8601) |
--service |
Filter to one service |
--env |
Filter to one environment |
--no-llm |
Skip LLM, use deterministic templates |
--max-clusters |
Max clusters to analyze (default: 10) |
--baseline-window |
How far back to compare (default: 24h) |
--format |
text, json, or markdown |
Markdown incident report
--format markdown writes a paste-ready GitHub-flavored markdown file for tickets and postmortems. Redirect stdout to save it:
raglogs explain --since 1h --format markdown > postmortem.mdThe report includes:
- Title (from the primary cluster message, or the first useful summary line)
- Metadata: window (ISO start/end + duration), services, environment (if filtered), total logs, confidence, mode (
rules/llm) - Summary (the same explanation text as
text/ JSON — not re-parsed) - Primary cluster (fingerprint, count, importance, representative message) when present
- Secondary clusters and trigger candidates when present
- Evidence bullets
- A Reproduce footer with the exact
raglogs explain ... --format markdowninvocation
Output is written as raw markdown on stdout (Rich markup is not applied), so shell redirection stays valid GFM. Progress and errors go to stderr.
Output structure (default --format text)
Incident summary
Window: ...
Services affected: ...
Primary issue: ...
Secondary effects: ...
Likely trigger: ...
Evidence:
- ...
Confidence: low | medium | medium-high | high
Confidence is computed from cluster volume, baseline change ratio, trigger correlation, secondary cluster agreement, and service spread. It is never invented.
No-LLM mode produces the same structure from deterministic templates. Slightly less polished, zero hallucination risk, works fully offline.
Reconstructs the causal sequence of events in an incident window. Shows deploys, service restarts, the primary error spike, downstream effects, and system-level symptoms — sorted chronologically and grouped by causal role.
raglogs timeline --since 30m
raglogs timeline --since 2h
raglogs timeline --from 2026-03-12T22:00:00Z --to 2026-03-12T22:30:00Z
raglogs timeline --since 2h --service billing-worker
raglogs timeline --since 1h --format json| Flag | Description |
|---|---|
--since |
Relative window: 30m, 1h, 24h, 7d |
--from |
Start of window (ISO 8601) |
--to |
End of window (ISO 8601) |
--service |
Filter to one service |
--env |
Filter to one environment |
--format |
text or json |
Event categories
| Label | Meaning |
|---|---|
deploy |
Deploy, release, or rollout event |
startup |
Service start or port binding |
trigger |
Other pre-error event (config change, migration) |
error ↑ |
Primary error cluster — the root cause |
effect |
Downstream failure caused by the primary error |
symptom |
System-level degradation (queue growth, backlog) |
Output
Incident timeline 2026-03-12 21:58:00 UTC → 2026-03-12 23:58:00 UTC
21:58:14 deploy Deploy completed for billing-worker version v2.4.1 · deployment-controller
21:58:15 startup Application started billing-worker v2.4.1 on port 8080 · billing-worker
22:00:10 error ↑ Stripe signature verification failed for endpoint /webhooks/stripe
184 events · billing-worker · 49 min span
22:01:27 effect POST /api/checkout 200 OK latency=<duration> (high latency detected)
25 events · api · 43 min span
22:01:49 effect Webhook retries (2 retry events)
2 events · billing-worker
22:02:56 effect POST /api/checkout 500 Internal Server Error — upstream billing error
39 events · api · 45 min span
22:11:25 symptom Webhook queue growing, 251 events pending processing
2 events · billing-worker · 30 min span
Point-in-time events (deploys, startups) show the service inline. Volumetric events show a sub-line with event count, service, and cluster duration. Blank lines separate events more than 60 seconds apart. Repeated webhook retry events are deduplicated into a single line.
No LLM required. The timeline is assembled entirely from cluster timestamps and causal classification.
Diffs two time windows by their cluster sets. Shows exactly which error patterns appeared, disappeared, intensified, or resolved between a current window and a baseline.
raglogs compare --since 30m --baseline 24h
raglogs compare --since 1h --baseline 7d
raglogs compare --since 2h --baseline 24h --service billing-worker
raglogs compare \
--window-a-from 2026-03-16T14:00:00Z --window-a-to 2026-03-16T14:30:00Z \
--window-b-from 2026-03-15T14:00:00Z --window-b-to 2026-03-15T14:30:00Z
raglogs compare --since 30m --baseline 24h --format json--since 30m --baseline 24h compares the last 30 minutes against the equivalent 30-minute window from 24 hours ago — the most useful form during an active incident.
| Flag | Description |
|---|---|
--since |
Incident window size, e.g. 30m, 1h |
--baseline |
Offset to baseline window, e.g. 24h, 7d |
--window-a-from/to |
Explicit start/end for window A (ISO 8601) |
--window-b-from/to |
Explicit start/end for window B (ISO 8601) |
--service |
Filter both windows to one service |
--env |
Filter both windows to one environment |
--format |
text or json |
Output sections
| Symbol | Meaning |
|---|---|
+ |
New cluster — present in A, absent in B |
- |
Disappeared — present in B, gone in A |
↑ |
Increased — in both, count grew by more than 50% |
↓ |
Decreased — in both, count shrank by more than 50% |
+⚡ |
New trigger — deploy or restart only seen in A |
-⚡ |
Dropped trigger — deploy or restart only seen in B |
Output
Incident comparison
Window A (now): 2026-03-16 15:17:42 UTC → 2026-03-16 15:47:42 UTC
Window B (baseline): 2026-03-15 15:17:42 UTC → 2026-03-15 15:47:42 UTC
New error clusters
+ Stripe signature verification failed for endpoint /webhooks/stripe 86 events
+ POST /api/checkout 500 Internal Server Error — upstream billing error 20 events
+ Webhook retries (24 distinct events, 24 total) 24 events
+ Webhook queue growing 13 events
Triggers in A not seen in B
+⚡ Deploy completed for billing-worker version v2.4.1 · deployment-controller
Individual webhook retry events (evt_XXXXXX) and queue-depth lines are deduplicated into single entries before diffing. No LLM required.
Lists the top log clusters in a time window ranked by importance score. Useful for exploration and understanding dominant event families without running a full explain.
raglogs clusters --since 1h
raglogs clusters --since 30m --service api
raglogs clusters --since 1h --top 20
raglogs clusters --since 1h --format jsonExample output
Top clusters — 2026-03-12 22:00:00 UTC → 2026-03-12 23:00:00 UTC
3 clusters found
# Count Chg Level Service(s) Message
1 184 184x error billing-worker Stripe signature verification failed for endpoint /webhooks/stripe
2 39 39x error api POST /api/checkout 500 Internal Server Error — upstream billing error
3 10 1.0x info deployment-ctrl Deploy completed for billing-worker version <token> ⚡
⚡ = likely trigger event Chg = change vs baseline
| Flag | Description |
|---|---|
--since |
Relative window |
--from / --to |
Explicit range |
--service |
Filter by service |
--env |
Filter by environment |
--top / -n |
Number of clusters to show (default: 15) |
--format |
text or json |
Answer a natural language question about your logs. Retrieval is semantic-first when embeddings exist and a provider is enabled, then keyword search, then a window-level error/warn fallback.
raglogs ask "why did login fail?"
raglogs ask "what changed before latency increased?" --since 2h
raglogs ask "what happened in billing?" --since 1h
raglogs ask "why are checkouts failing?" --format json
raglogs ask "why did login fail?" --ingestion-job <uuid>
raglogs ask "why did login fail?" --all-ingestionsLike explain / timeline / compare, ask defaults to the latest completed ingestion. Pass --ingestion-job to target a specific job, or --all-ingestions to search every ingested log.
| Flag | Description |
|---|---|
--since |
Relative window: 30m, 1h, 24h |
--service |
Filter to one service |
--format |
text or json |
--ingestion-job |
Scope to a specific ingestion job UUID |
--all-ingestions |
Search all historical ingestions, not just the latest |
How retrieval works
- Semantic — if
EMBEDDINGS_PROVIDERisopenaiorlocaland log lines were ingested with--with-embeddings, the question is embedded and nearest neighbors are fetched from pgvector (ASK_SEMANTIC_TOP_K,ASK_SEMANTIC_MIN_SIMILARITY). Paraphrases that keyword search would miss (e.g. "why are payments being declined?" vs "Stripe signature verification failed") can still match. - Keyword — if embeddings are disabled, the provider errors, or semantic search returns nothing above the similarity threshold,
askmatches tokens againstnormalized_messageas before. - Fallback — if keyword search is also empty, the top error/warn lines in the window are used.
Hits are still grouped with cluster_logs so answers stay grounded in counts, first_seen, and last_seen. JSON output includes retrieval_mode (semantic | keyword | fallback). Default EMBEDDINGS_PROVIDER=disabled keeps ask fully keyword-based — no API key required.
Populate vectors at ingest time:
raglogs ingest ./logs --with-embeddingsVectors are stored in log_embeddings (1536-d). Because that column width is fixed, a non-disabled provider with EMBEDDINGS_DIMENSIONS ≠ 1536 — or a local model whose native width isn't 1536 — is a configuration error that can never persist a vector. It is surfaced, not silently skipped: ingest --with-embeddings hard-exits (non-zero) with an actionable message before ingesting, and API startup logs the misconfig at error and keeps serving the non-embedding features (explain / timeline / compare / keyword ask) — coupling the whole API's availability to an optional feature would be worse, especially during an incident. Transient provider failures during ingest are still skipped fail-open; the log lines land regardless. (With EMBEDDINGS_PROVIDER=local, both the startup check and ingest load the SentenceTransformer model, which may download it on first use.)
Example output
Most likely cause related to 'why did the webhook fail?':
Stripe signature verification failed for endpoint /webhooks/stripe
In service: billing-worker
Evidence:
- 184 events: 'Stripe signature verification failed...' in billing-worker
- 39 events: 'POST /api/checkout 500...' in api
Total matching log events: 184
Shows database connectivity, log counts, and provider status.
raglogs statusDatabase: connected
Log entries: 464
Sources: 1
Ingestion jobs: 1
LLM provider: disabled
LLM model: gpt-4.1-mini
Embeddings: disabled
Inspect the current effective configuration.
raglogs config # show all
raglogs config llm_providerExpire raw log rows (and cascaded log-line embeddings / cluster membership) while keeping cluster summaries and cluster_embeddings so POST /v1/query/similar still works. After RETENTION_SUMMARY those summaries expire too. Per-scope TTLs override env defaults via the scope_retention table; missing override → RETENTION_RAW / RETENTION_SUMMARY. 0, empty, or off skips that tier.
The background worker (raglogs worker) also enqueues a purge job on idle poll about every PURGE_INTERVAL_SECONDS (default 3600). Purge uses SELECT FOR UPDATE SKIP LOCKED like ingest and deletes in created_at-ordered chunks (time-in-store) so ingest is not starved. Raw expiry uses log_entries.created_at, not the event timestamp, so historical dumps are not wiped on ingest.
raglogs purge # every scope with data
raglogs purge --scope default
raglogs purge --dry-run # count onlyMint, list, revoke, and set per-key query defaults for HTTP API keys. The API bearer token is printed once at create time and is never stored or logged — only an argon2 hash and a short prefix are kept. A separate webhook signing secret (whsec_…) is also printed once; it is stored server-side so ingest completion callbacks can be HMAC-signed. It is not the bearer token. raglogs keys list shows whsec_**** when a signing secret exists (legacy keys minted before this column fall back to WEBHOOK_SECRET).
raglogs keys create --role query --scope default --name "ci"
raglogs keys create --role query --scope incident:INC-9 --allow-scope-override --name "ci-override"
raglogs keys create --role query --max-clusters 5 --baseline-window 12h
raglogs keys set-defaults <key-uuid> --max-clusters 8 --max-evidence-items 4 --llm-provider ollama
raglogs keys set-defaults <key-uuid> --clear
raglogs keys list
raglogs keys revoke <key-uuid>| Flag | Description |
|---|---|
--role |
ingest, query, or admin (default query) |
--scope |
Pin the key to this isolation scope (enforced on every service read/write). Default default. Convention: incident:<id>, service:<name>, env:<name>. |
--allow-scope-override |
Allow the caller to pass a request scope other than the key's pin. Pinned by default. |
--name |
Optional label |
--max-clusters |
Per-key default max_clusters (1–100) stored on api_keys.config_json |
--max-evidence-items |
Per-key default max_evidence_items (1–50) |
--baseline-window |
Per-key default baseline duration (e.g. 24h) |
--llm-provider |
Per-key default openai / ollama / claude / disabled |
--llm-enabled / --no-llm-enabled |
Per-key default for whether the LLM is used |
raglogs keys set-defaults merges flags into the key's config_json. --clear removes all per-key query defaults. Requires a migrated database (raglogs init). See HTTP API authentication and per-request overrides.