Skip to content

SierraSoftworks/analytics

Repository files navigation

Analytics

Lightweight, privacy-preserving analytics for your websites and applications.

A self-hosted analytics service written in Rust. It collects useful product analytics without compromising your users' privacy — no cookies, no IP addresses, no personally identifiable information. Multiple sources (a marketing site, its docs, a paired application) can be grouped into a project and viewed in aggregate, alongside a global overview across every project.

It also doubles as a lightweight, privacy-preserving error tracker, grouping client-side exceptions much like Sentry.

Privacy model

Inspired by medama, the service deliberately collects only broad, non-identifying signals:

  • No cookies, no IP storage, no PII. Client IPs are used transiently only as rate-limit keys and are never logged or persisted.
  • Daily unique visitors are counted with the HTTP conditional-request cache trick (If-Modified-Since vs UTC midnight), so uniqueness resets every day without any client-side identifier.
  • The User-Agent and Accept-Language headers are parsed into broad classes (browser / OS / device, primary language) at the edge — the raw values are never stored.
  • Country is derived from the browser's reported timezone, not IP geolocation.
  • DNT / Sec-GPC signals are honored.

Features

  • Projects & sources — group multiple hostnames (and applications) into a project; filter to subsets; auto-register new reporting hostnames.
  • Metrics — visitors, page views, bounce rate, median time on page, time series, and breakdowns by page, referrer, browser, OS, device, country, language, and source.
  • Tracking pixels — admin-created, project-bound tracking GIFs (e.g. for email opens) with attached metadata. Unknown pixel ids are rejected — there is no open pixel endpoint.
  • Exception tracking — capture unhandled errors and rejections, grouped by a Sentry-style fingerprint, with triage state (unresolved / resolved / ignored).
  • OIDC authentication — the dashboard and management API are gated by a server-driven OIDC flow with a configurable filter-expression ACL. The public tracking endpoints need no authentication.
  • Rate limiting — per-IP token-bucket limits on both the public tracking endpoints and unauthenticated hits to protected endpoints.
  • Append-only, write-optimized storage — events are appended to an redb hot store, compacted into date-partitioned Parquet, and queried with polars.

Architecture

A Cargo workspace, mirroring grey and automate:

  • api/ — framework-free serde DTOs shared by the server and the WebAssembly frontend.
  • agent/ — the actix-web server: clap CLI, YAML config, OIDC auth, the ingest pipeline, storage, and the polars query layer. The compiled frontend is embedded into the binary via include_dir!.
  • ui/ — a client-side-rendered Yew dashboard, built with Trunk.
  • tracker/ — the tracking beacon: a dependency-free, pure-JavaScript snippet built into a single heavily-minified artifact with esbuild and served at /tracker.js. One build, no variants — behaviour is toggled by data-* attributes at runtime. Unit-tested with Vitest.

Quick start

# 1. Build the tracking beacon (embedded into the server binary).
cd tracker && npm install && npm run build && cd ..

# 2. Build the frontend bundle (embedded into the server binary).
cd ui && trunk build --release && cd ..

# 3. Build and run the server.
cargo build --release -p analytics
cp config.example.yaml config.yaml   # then edit to taste
./target/release/analytics --config config.yaml

The dashboard is served at the configured address (default http://127.0.0.1:8080).

Tracking a website

Add the tracker script to your pages, pointing data-api at your server:

<script
  async
  src="https://analytics.example.com/tracker.js"
  data-api="https://analytics.example.com"
  data-auto-capture-exceptions="true"
></script>

The script reports page views (and, with data-auto-capture-exceptions, unhandled errors and promise rejections). It follows SPA navigations automatically by intercepting the History API; add data-hash if your app routes with the URL hash instead. It also exposes window.analytics.event(name, data) and window.analytics.captureException(error, meta) for manual reporting. Sources are identified purely by their hostname — no per-site key to embed.

Tracking pixels

Create a pixel in the dashboard (under a project) to get an embeddable URL such as https://analytics.example.com/track/gif/<id>.gif for contexts where JavaScript can't run (email opens, RSS, docs).

Configuration

All configuration lives in a YAML file (see config.example.yaml). Secrets can be injected from the environment with ${{ env.VAR_NAME }} placeholders.

Omitting the web.admin.oidc block disables the sign-in flow, but the dashboard is still gated by the web.admin.acl filter expression — which defaults to "false" (deny all). To run locally without authentication, omit OIDC and set an allow-all ACL so the API is reachable:

web:
  admin:
    acl: "true"   # local development only — grants everyone full access

With the default deny-all ACL and no OIDC, the dashboard cannot be signed into (the sign-in page explains this rather than looping).

API

  • Public (no auth): GET /tracker.js, GET /track/ping, POST /track/hit, POST /track/exception, GET /track/gif/{id}.gif, GET /api/v1/health.
  • Protected (OIDC + ACL): everything else under /api/v1 — projects, sources, pixels, overview, per-project stats, and exception groups/triage.

License

MIT — see LICENSE.

About

Lightweight and privacy preserving analytics for your website(s)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors