Case study · Open source

Sauron

Error tracking and product analytics on one timeline

Teams usually run one tool for crashes and another for product analytics, and the two never meet. Sauron puts Sentry-style issue grouping and PostHog-style events behind a single SDK: when an error fires you see what that person did before it, and when you open a person you see their errors. It is self-hosted and runs on nothing more than PostgreSQL and Redis.

Role
Primary author
Organization
EDRAJ
Since
July 2026
License
AGPL-3.0

Architecture

The write path and the read path are separate services over shared crates. Ingest is fire-and-forget, authenticated by the environment's public key, so the environment an event belongs to cannot be spoofed by a client. The dashboard API uses JWT and resolves permissions down the org → project → app tree.

Write path
SDKs
Browser · Flutter · Node · Python · C#
gzip envelope, retry, bounded disk queue
sauron-ingest
axum edge
DSN auth → rate limit → validate → 202
Redis stream
co-located workers
enrich → fingerprint → group → write to Postgres
Read path
Dashboard
Svelte 5 SPA
English + Arabic (RTL)
sauron-api
axum + JWT
RBAC checked on every request
Storage
PostgreSQL (hot) + Parquet (cold)
cross-tier reads via DuckDB
Background services
sauron-alerts
metric rules + notification digests
sauron-monitor
uptime probes
sauron-tier
Postgres → Parquet tiering
sauron-storesync
Play + App Store installs
sauron-inspector
PII scans + masking
crebain
load generator

What it does

Error tracking

  • › Fingerprinting and grouping into issues
  • › Stack traces, breadcrumbs, tags and context
  • › Source-map symbolication
  • › Release tracking and crash-free rate

Product analytics

  • › Event explorer with payload search
  • › Funnels with saved, shareable templates
  • › User journeys as Sankey diagrams
  • › Retention cohorts and DAU / WAU / MAU

People, sessions and devices

  • › One timeline per person: errors and events together
  • › Guest → identified identity merge
  • › Session timelines and duration stats
  • › Device groups across environments

Screens and performance

  • › Per-screen views, users and dwell time
  • › Transactions with p50 / p95 latency
  • › Drill-down by operation with deep links
  • › Opt-in auto screen tracking for web and Flutter

Alerting and uptime

  • › Error spike, threshold and latency rules
  • › HTTP uptime probes with SSRF guards
  • › Email, Slack, Discord, Telegram and webhooks
  • › Per-user subscriptions with digest batching

Access control

  • › Org → project → app scope tree
  • › Fine-grained RBAC with custom roles
  • › Privilege-escalation guards on every grant
  • › Audit log of administrative actions

Privacy

  • › Scans telemetry for developer-supplied PII
  • › Reports findings without keeping a second copy
  • › Retro-masks hot data after a typed confirmation
  • › Enforces the mask on all future ingest

Data lifecycle

  • › Range-partitioned event tables
  • › Aged partitions moved to compressed Parquet
  • › Transparent queries across both tiers
  • › Retention policies and admin purge

Search

  • › Query language over tags and JSON payloads
  • › Saved views per list page
  • › Filter chips on list pages
  • › Custom date ranges with server-side limits

SDKs

Every SDK is written in its language's own idiom, sends the same wire format, and supports scope, breadcrumbs, transactions, before-send hooks, gzip, retry with backoff, and a bounded queue that can spill to disk.

Shipping it

  • Docker Compose for evaluation
  • Native RPM (Fedora / RHEL) and .deb (Debian / Ubuntu) packages driven by systemd
  • Migrations run before every service start; a daemon refuses to boot on a failed migration
  • OpenAPI / Swagger docs, with a parity test against the router
  • CycloneDX SBOMs tracked in Dependency-Track
  • GitHub Actions CI and tagged releases

Stack

Rustaxumtokiodiesel-asyncPostgreSQLRedis StreamsDuckDBParquetSvelte 5TypeScriptViteFlutterDartNode.jsPythonC#DockersystemdGitHub Actions