Keen Kestrel
v0.2 “analytics” · early release

Know what broke.
See how it’s used.

Keen Kestrel is self-hosted error tracking and privacy-friendly analytics in one small Rust binary. Point the Sentry SDKs you already use at your own server, and count your users without cookies.

One binary and SQLite. No Kafka, ClickHouse, Redis or Postgres.

MIT licensedBuilt in RustSibling of Vigilant Vole

Concept illustration with made-up data. The real interface follows the Vigilant Vole design system.

Two questions, one place.

Most small teams need a crash reporter and an analytics service, and end up with two hosted accounts, two dashboards and two places their users’ data goes. Kestrel puts both on your own server, in the same project.

  • What broke?

    Errors and crashes from the official Sentry SDKs, grouped into issues, with readable stack traces, iOS symbolication, release health and alerts.

    Error tracking
  • How is it used?

    Signals from your apps and pageviews from your sites, without cookies or stored IP addresses: users, versions, devices, pages and referrers.

    Analytics

Better together One project is one app or site, with the same keys and releases for both. A Sentry release like notes@1.4.0+52 matches the app version your analytics see, so a release page can show how many people have updated next to its crash-free rate and new issues.

Error tracking

Crashes you can read, issues you can act on.

Kestrel speaks the Sentry protocol, so the SDKs you know send their events here without a fork or a plugin. It is built to cover what a small team uses every day, and to leave out the rest.

Readable Swift stacks from real crashes.

Upload your dSYMs with the official sentry-cli, so the Xcode build phase and fastlane keep working. Kestrel turns each one into a symbol cache once, then maps every frame of an iOS or macOS crash back to function, file and line, expands inlined frames and demangles Swift.

No dSYM yet? The frames keep their image and offset and the issue is marked not symbolicated, so you know what’s missing. Rust, Python, Go and JavaScript SDKs send readable frames already; Kestrel highlights your own code.

Grouped into issues, not a flood of events.

Events are grouped by the SDK’s fingerprint when you set one, otherwise by the exception type and your own frames, with addresses, generics and hash suffixes stripped. Each issue can show why its events were grouped together.

Resolve, resolve in the next release, ignore or merge. When a resolved issue comes back it is marked regressed and you hear about it. Search the way you think: is:unresolved release:1.4 os:iOS.

Alerts where you already are.

Per-project rules for a new issue, a regression, a spike above the usual hourly level, and the first crash in a new release. A cooldown per issue and a digest mode keep it calm.

Delivery uses the same notification providers as Vigilant Vole: about 40 of them, from Discord and Slack to ntfy, email and webhooks.

And the rest of a proper error tracker.

  • Release health

    Crash-free sessions and crash-free users per release, rolled up from the sessions your SDK already sends.

  • Scrubbed before it’s stored

    No IP is ever derived. Authorization and cookie headers, secret-looking keys and card numbers are removed; emails and usernames are hashed unless you opt in.

  • Feedback and screenshots

    User feedback from the SDK, and screenshots and view hierarchies attached to an event, shown with the issue.

  • API and MCP

    A REST API with OpenAPI docs, and MCP tools so a coding agent can list issues, read a stack trace and resolve what it fixed.

  • Retention you choose

    By default, event details for 30 days and issues until 90 days after they were last seen, while counters and rollups stay. Per project.

  • 26 languages

    The same interface languages as Vigilant Vole, with a glossary per language so every term says the same thing.

Analytics

How your apps and sites are used, and nothing more.

Counts and trends, not profiles. Kestrel answers “how many people use version 1.4?” and “where do visitors come from?”, and it is built so it can’t answer “what did this person do?”.

App signals from a small Swift package.

KestrelKit is a Swift package for iOS and macOS with no dependencies. Send a signal such as App.launched or Paywall.shown; it queues on disk when you’re offline and sends in batches.

Dashboards show daily, weekly and monthly users, sessions, version adoption, OS versions, devices and locales, with one filter bar for date, version, platform and signal type.

Website pageviews from a script under 2 KB.

Add one small script to your site. It follows single-page apps, sends with sendBeacon, and never sets a cookie or stores anything in the visitor’s browser.

See visitors and pageviews over time, top pages, referrers, UTM campaigns, browsers, operating systems and device classes.

Counting without cookies.

To count unique visitors, a site has to recognise a repeat visit, but it doesn’t have to remember who you are. Here is the whole trick, in plain words.

  1. A visit arrives

    The server briefly sees the visitor’s IP address and browser, as every web server does.

  2. It becomes a scrambled number

    IP, browser, site and today’s secret salt go through a one-way hash. Only the short result is kept.

  3. The originals are dropped

    The IP address and the browser string are never written to disk. Neither is a cookie set.

  4. At midnight, the salt is gone

    The salt lives only in memory and is replaced every day at midnight UTC. Tomorrow, the same visitor is a new, unrelated number.

Never stored

  • IP addresses, for analytics and for errors
  • Cookies, or anything else kept in the visitor’s browser
  • The full user-agent string
  • Advertising or vendor ids (IDFA, IDFV)
  • An app’s raw install id
  • Values that look like an email, phone or card number

What is kept

  • The page path, without its query string except utm_*
  • The referring domain and a screen-width bucket
  • Browser, OS and device class, worked out on the server
  • App version, OS, device model and locale for app signals
  • For apps, a keyed hash of a random install id, so weekly and monthly users can be counted. A strict mode rotates it daily too.

Setup

Works with the SDKs you already use.

Kestrel ships no error SDK of its own. Keep the official Sentry SDK in your app and change one thing: the DSN, which points at your own server instead.

  • iOS and macOS (Swift)
  • JavaScript
  • Rust
  • Python
  • Go
  • …and other official Sentry SDKs

For analytics, KestrelKit (Swift) and the k.js web script are Kestrel’s own, and small.

Point a Sentry SDK at Kestrel Works today
// The DSN comes from your project’s settings in Kestrel
// https://<public key>@kestrel.example.com/<project id>

// Swift
SentrySDK.start { options in
    options.dsn = "https://abc123@kestrel.example.com/1"
}

// JavaScript
Sentry.init({ dsn: "https://abc123@kestrel.example.com/2" });

# Python
sentry_sdk.init(dsn="https://abc123@kestrel.example.com/3")
Upload dSYMs with sentry-cli Works today
export SENTRY_URL=https://kestrel.example.com
export SENTRY_AUTH_TOKEN=kk_…   # a Kestrel API token with difs:write
sentry-cli debug-files upload --org any --project notes build/

Errors and analytics today Error tracking arrived in v0.1, and analytics with KestrelKit and k.js in v0.2.

Architecture

One binary. SQLite. That’s the stack.

Error trackers and analytics tools often need a queue, a column store, a cache and a database before they show a single event. Kestrel is one Rust process with two SQLite files, like Vigilant Vole.

In

  • Sentry SDKs
  • sentry-cli (dSYMs)
  • KestrelKit · k.js
kestrel one Rust binary
  1. Ingest
  2. Bounded queue
  3. One batch writer
kestrel.sqlite Issues, releases, rollups, settings
store.sqlite Event details and raw signals; safe to throw away

Out

  • Web interface (live updates)
  • REST API · OpenAPI
  • MCP tools
  • Alerts to ~40 channels
Diagram: Sentry SDKs, sentry-cli and Kestrel’s own analytics SDKs send to one Kestrel binary. Inside, ingest feeds a bounded queue and a single batch writer, which writes to two SQLite files: kestrel.sqlite for issues, releases, rollups and settings, and store.sqlite for event details and raw signals. Out come the web interface, the REST API, MCP tools and alerts.

Not needed:

  • Kafka
  • ClickHouse
  • Redis
  • Postgres
  • Extra containers
  • Designed for 2vCPU 2GB A small VPS or home server
  • Memory when idle <150MB Target
  • Container image <40MB Target, amd64 and arm64
  • Runs on Docker · Unraid · Proxmox · Linux Like Vigilant Vole

These are design targets from the plan, not measurements. They’ll be load-tested before v1.0.

Why Kestrel, and why not.

Sentry is an excellent, far bigger product, and the hosted analytics services are good at what they do. Kestrel is for when you’d rather run something small yourself. Here is the honest version, comparing Kestrel’s plan for v1 with what exists today.

Comparison of Keen Kestrel’s planned v1 with hosted services and self-hosted Sentry
Topic Keen Kestrel planned v1 Hosted Sentry and analytics Self-hosted Sentry
Where the data livesYour serverThe provider’s cloudYour server
What you runOne binary and SQLiteNothingMany containers, including Kafka, ClickHouse, Redis and Postgres
Official Sentry SDKsUnchangedYesYes
Errors and analytics togetherSame projects and releasesUsually two servicesAnalytics needs another tool
Cookieless analytics, no stored IPsBy designDepends on the serviceNot applicable
Performance tracing, profiling, session replayNot in v1YesYes
JavaScript source mapsPlanned for v1.1YesYes
Android and native minidumpsNot in v1YesYes
Funnels, cohorts, countriesNot in v1Depends on the serviceNot applicable
Organisations, SSO, billingOne owner or team per instanceYesYes

When something else is the better fit If you need tracing, replay, profiling, Android crashes or source maps, or you’re a larger organisation, use Sentry. And today, use anything that exists: Kestrel is still a design. Keen Kestrel is not affiliated with Sentry; it implements the open Sentry protocol so their SDKs can talk to it.

Where it stands.

Kestrel is built in phases, riskiest part first. There are no dates: each step is done when it works, and the phase table in the design says what “works” means.

  1. Done

    Design

    Scope, data model, Sentry protocol coverage, privacy model and phases, written down and approved.

  2. Done

    Spike: symbolication first

    A throwaway spike proved it: real sentry-cocoa crashes, dSYMs uploaded with sentry-cli, symbolicated with Swift names, files, lines and inlined frames, also in a 5.5 MiB static Rust image.

  3. Done

    Shared foundation: vole-kit

    Sign-in, users, API tokens, settings, translations, notifications and the design system moved out of Vigilant Vole into a shared crate, with Vigilant Vole’s behaviour unchanged.

  4. Done

    Skeleton, ingest and issues

    Projects and DSN keys, Sentry-compatible ingest with scrubbing and grouping, and pages to search, resolve, ignore and merge issues, tested with the official Python, JavaScript, Rust and Cocoa SDKs.

  5. Released

    v0.1 “errors”

    Ingest, grouping, issues and search, symbolication from dSYMs, alerts to ~40 channels, release health, the REST API and MCP tools. An early release, as a Docker image.

  6. Released

    v0.2 “analytics”

    Web analytics with k.js, app signals with the KestrelKit Swift package, and release pages that show adoption next to crashes.

  7. Then

    v1.0

    Load tests against the targets, installers, Unraid and Proxmox templates, and all 26 languages.

Later, from v1.1: JavaScript source maps, Apple .ips and MetricKit reports, minidumps, funnels and retention cohorts, countries from your own GeoIP database, and performance tracing.

Questions.

  • When can I use it?

    Now. v0.2 “analytics” is an early release as a Docker image (ghcr.io/ferdinand99/keen-kestrel) with error tracking and web and app analytics; the README shows how to run it, and an Unraid template is in unraid-templates. Installers and more templates come by v1.0. Watch or star the repository to hear about new releases.

  • Is it open source?

    Yes, under the MIT license, like Vigilant Vole and Nimble Newt. No paid tier, no account.

  • Which SDKs will work?

    For errors, the official Sentry SDKs, unchanged: Swift (sentry-cocoa), JavaScript, Rust, Python, Go and others that speak the same protocol. Kestrel is tested against recorded requests from real SDKs, and a nightly job will run current SDKs and sentry-cli against it.

    Kestrel keeps errors, sessions, user feedback and attachments. Traces, profiles, replays, logs and metrics are accepted and dropped, so an SDK with those switched on keeps working.

    For analytics: KestrelKit for Swift apps and the k.js script for websites.

  • How much server does it need?

    It is designed to run comfortably on 2 vCPU and 2 GB of memory, as one container or one binary, with data in SQLite. The targets for that size are 500 events and 3,000 signals a second and under 150 MB of memory when idle. They are targets until the load tests say otherwise.

  • Is it a fork of Sentry?

    No. It is a new Rust program that speaks the Sentry protocol, so Sentry’s SDKs and sentry-cli can talk to it. For symbolication it uses symbolic, the open-source library Sentry publishes for that job.

  • Do I need a cookie banner for the analytics?

    Kestrel sets no cookies, stores nothing in the visitor’s browser and keeps no IP addresses, which is what usually makes a banner necessary. Whether you need one in the end depends on your site and your jurisdiction, so check your own situation.

  • How can I help?

    Read the design and open an issue with questions or objections. If you speak one of the 26 languages, the draft glossaries would benefit from a native speaker’s eye.