Fast, lightweight, OTEL-native observability — logs, traces, metrics, APM, RUM, uptime, and host/GPU monitoring in a single Rust binary.
A self-hosted alternative to SigNoz, Datadog, Sentry, and Grafana that aims to be better, faster, and cheaper: one process, one binary, your disk, your data.
Modern observability makes you choose between two bad options: a sprawling self-hosted stack (SigNoz needs ClickHouse + collectors; Grafana's "LGTM" is Loki + Tempo + Mimir + Grafana, each a separate service) or a metered SaaS bill (Datadog and Sentry price per host, per seat, per GB, per million spans — and it compounds). Photon takes a third path.
- One binary, one process. The OTLP receivers, the storage/query engine, the REST API, and the
Vue UI are all compiled into a single ~30 MB Rust binary (the UI is embedded at build time). No
ClickHouse, no Zookeeper, no Kafka, no sidecars.
docker compose up— or just run the binary. - 10× lighter storage. No full-text inverted index (the thing that makes log stores as big as
the logs themselves). Photon prunes with skip indexes — per-row-group min/max stats + token
bloom filters, kilobytes per file — over zstd-compressed Parquet sorted by
(service, timestamp). - 10× faster queries by layout, not luck. Arrow-native end to end (Arrow → Parquet → DataFusion), local NVMe is the primary store (no network hop on the query path), and search is two-pass late-materialized so wide attribute maps are only decoded for rows that survive.
- 10× cheaper to run and to own. Self-hosted on one well-resourced node. No per-seat, per-host, or per-GB metering. The durable tier is optional and any S3-compatible bucket (Garage recommended — also a single Rust binary).
- OTEL-native. Ingest is standard OTLP over gRPC (
:4317) and HTTP (:4318). Point your existing OpenTelemetry Collector or SDKs at it; nothing proprietary on the wire. - A RUM SDK that doesn't slow your site down. The browser agent is < 5 KB gzipped — versus Sentry's ~20 KB+ and Datadog RUM's ~25–40 KB.
Photon is a full-signal platform, not just a log store. Every section below is live in the UI:
| Section | What it does |
|---|---|
| Home | A cross-signal landing dashboard that stitches recent activity from every signal into one at-a-glance overview (the default view). |
| Logs | OTLP log explorer with a small query grammar (field:value, OR-lists, negation, numeric compare, free-text), facets, severity histogram, and live tail. |
| Traces | Distributed-trace explorer + waterfall detail view, with log↔trace↔span correlation. |
| Services (APM) | Per-service RED metrics (Rate/Errors/Duration), Apdex, health-first service list, and per-service dashboards derived from trace spans. |
| Metrics | OTLP metrics explorer (gauges, sums, histograms/summaries) with a query builder and a metric catalog. |
| RUM | Real-User Monitoring: Core Web Vitals (LCP/INP/CLS/FCP/TTFB) + JS error tracking from a tiny browser SDK, sliceable per app / route / device / browser. |
| Infrastructure | Host & NVIDIA-GPU resource monitoring — CPU / RAM / disk / network + GPU util / memory / temperature — pushed by a tiny standalone photon-agent, with a per-host dashboard. |
| Uptime | Always-on synthetic HTTP/ping monitors with per-monitor intervals, timeouts, and webhook alerts. |
| Data | Data-usage and storage monitoring + retention management, so you can see and bound what you're keeping. |
All signals share one storage/query spine, so cross-signal correlation (a log line → its trace → the service's RED metrics → the errors on that page) is a click, not an integration.
| Photon | SigNoz | Grafana (LGTM) | Datadog | Sentry | |
|---|---|---|---|---|---|
| Deployment | 1 binary / 1 container | ClickHouse + collector + UI | Loki + Tempo + Mimir + Grafana | SaaS agent | SaaS SDK |
| Storage engine | Embedded (Parquet + DataFusion) | ClickHouse server | 3 separate TSDB/stores | Proprietary cloud | Proprietary cloud |
| Signals | Logs, traces, metrics, APM, RUM, uptime, infra (host/GPU) | Logs, traces, metrics | Logs, traces, metrics | All + more | Errors, some tracing/RUM |
| Log index | Skip index (min/max + bloom) | Inverted / ClickHouse | Loki labels | Proprietary | — |
| RUM SDK size | < 5 KB gzip | n/a | Faro (~larger) | ~25–40 KB | ~20 KB+ |
| Cost model | Self-host, flat | Self-host | Self-host | Per-host/GB/seat | Per-event/seat |
| Your data | On your disk | On your disk | On your disk | Their cloud | Their cloud |
Photon targets medium scale — dozens of services, tens of GB/day of logs, meaningful trace and metric volume — on a single well-resourced node. Clustering/horizontal scale-out is deliberately not built; the crate boundaries are kept clean so pieces could split into separate processes later without a rewrite.
Photon's frontend is embedded into the binary at build time, so build the UI first (the package manager is bun, never npm):
# 1. Build the Vue UI into frontend/dist (embedded by photon-api)
cd frontend && bun install && bun run build && cd ..
# 2. Build & run the server
cp photon.example.toml photon.toml # then edit the secrets
cargo run -p photon-server -- photon.tomlOpen http://localhost:8080, complete the one-time "create your account" onboarding, and log in.
- UI + REST API:
:8080 - OTLP gRPC:
:4317· OTLP HTTP::4318
Send data with your OTEL Collector/SDK pointed at http://<host>:4318/v1/{logs,traces,metrics}
(header Authorization: Bearer <your ingest token>).
Keep your existing Prometheus (or Grafana Agent / Alloy / OTel Collector) and re-point its
remote_write at Photon — no changes to your exporters or scrape config. Photon stores and
visualizes the data, so you can switch off Grafana:
# prometheus.yml
remote_write:
- url: http://<photon-host>:<ingest-http-port>/api/v1/write
authorization:
credentials: <ingest token> # matches [ingest].tokenRun the standalone photon-agent on any box to stream host CPU / RAM / disk / network — and NVIDIA
GPU util / memory / temperature — as OTLP system.* metrics tagged with host.name. They surface in
the Infrastructure → Hosts UI (/infra) with a per-host dashboard:
cargo run -p photon-agent -- --endpoint http://127.0.0.1:4318/v1/metrics # $PHOTON_INGEST_TOKEN read from envIt's an ordinary metrics producer — no new storage engine — see
docs/subsystems/infra.md.
make install-tools # one-time: installs process-compose (dev TUI)
make dev # backend + Vite dev server together; UI at :5173, API at :8080On first run this generates a throwaway photon.dev.toml (local data dir, dev secrets,
admin/admin login) — it works out of the box. make help lists every target.
cp .env.example .env # fill in PHOTON_INGEST_TOKEN + PHOTON_SESSION_SECRET
make docker-up # single non-root distroless containerAdd the opt-in Garage durable-S3 tier with make docker-up-durable. See
deploy/README.md for the full env-var reference and durability model.
cargo run -p photon-loadgen -- logs --rate 5000 --services 10
cargo run -p photon-loadgen -- traces --rate 2000 --services 10 --spans-per-trace 4..20A < 5 KB gzipped agent (CI-enforced) that captures Core Web Vitals and JS errors and beacons them to Photon. One call, never throws into your app, no cookies:
import { initPhoton } from "@photon/rum";
initPhoton({
app: "web-storefront", // becomes service.name in Photon
endpoint: "https://photon.example.com",
key: "pk_live_…", // public app-key (safe to ship in client JS)
attribution: true, // opt-in per-page "why is LCP slow" breakdown
});Ships as ESM (npm, tree-shakeable) and a drop-in IIFE <script> build for no-bundler /
HTTP-only apps. Vitals become gauge metrics (web_vitals.*); errors become ERROR logs
grouped into issues by a server-computed fingerprint — no new storage signal. Source in
sdk/rum/; design in docs/subsystems/rum.md.
Photon is a Cargo workspace of small crates — the crate graph is the architecture, with
dependencies pointing one way toward photon-core (the compiler forbids cycles).
photon-core domain types only, no I/O: Arrow schemas, config, query grammar, PhotonError
photon-wal group-commit write-ahead log (the durability boundary)
photon-index skip-index format: token bloom + min/max stats (pure)
photon-storage hot + durable object_store wrappers + background replicator
photon-compact closed WAL segment → sort → Parquet + skip index → replicate → manifest
photon-query manifest + skip-index pruning, then DataFusion (search, facets, RED, RUM, …)
photon-ingest OTLP gRPC + HTTP receivers, OTLP→record mapping, token auth
photon-uptime always-on synthetic HTTP/ping monitors
photon-api axum REST API + session auth + embedded Vue UI
photon-server the binary: config, wiring, background-task supervision
photon-loadgen dev-only OTLP load generator
photon-agent standalone host/GPU resource-metrics agent (its own binary)
Write path (one signal): OTLP request → token check → map to Arrow record → WAL append
(group-commit fsync = the ack/durability boundary) → background compactor drains closed segments
into sorted Parquet + .idx skip index → async replicate to the durable store → manifest update.
Read path: manifest prunes candidate files by time overlap → skip index prunes by min/max + token bloom → DataFusion scans only the survivors. Bloom filters may false-positive (extra scan) but never false-negative — pruning can only add work, never drop a real result.
Full detail lives in docs/ ↓
The docs/ directory is written to get both humans and AI agents to the right context fast:
docs/architecture.md— the crate graph, per-signal write/read data flow, storage/durability model, and the load-bearing invariants you must not break.docs/frontend.md— the Vue 3 app: routes, views, theui/primitive library, and the Ky → TanStack Query → components data flow.docs/subsystems/— one doc per feature: logs, traces, services/APM, metrics, rum (incl. the@photon/rumSDK), uptime, data, auth — each with its key files, endpoints, and UI.docs/conventions.md— coding conventions and gotchas (thePhotonErrorenum, DataFusion column access, frontend timestamp units, dependency co-pinning).
CLAUDE.md is the concise, always-loaded entry point that indexes into these docs.
crates/ the Rust workspace (see the crate graph above)
frontend/ Vue 3 + Vite + Tailwind + Reka UI SPA (embedded into the binary)
sdk/rum/ the @photon/rum browser SDK (TypeScript, ESM + IIFE)
deploy/ Dockerfile, docker-compose, Garage durable-tier profile
docs/ architecture & subsystem reference (this project's knowledge base)
photon.example.toml annotated example config
Makefile dev / build / docker / bench targets (make help)
cargo test # whole Rust workspace
cargo test -p photon-query # one crate
cargo fmt && cargo clippy --all-targets
cd frontend && bun run test # frontend unit tests (vitest)
cd sdk/rum && bun run test # SDK tests + bun run size (bundle-size gate)Photon began as an internal SigNoz replacement and is under active development. It is a single-node, single-role (all authenticated users share one role) platform today — RBAC and multi-tenancy are explicitly out of scope until there's demand.
It does federate: a central Photon can register your own Photon installs in other regions, clusters or environments (sites) and aggregate what they push, so one team can watch the whole fleet while each site team watches their own. That is fleet aggregation, not clustering — there is no shared cluster state and no node serves another's data.
License: TBD.