Vigil is a local-first observability box for side projects. It gives you a built-in UI for logs, traces, and stats, stores data locally, and uses project-scoped ingest keys so each app can send events without a larger observability stack.
Vigil is early alpha. It is designed first for local and trusted-network use, not internet-exposed production deployments.
- Roadmap - where the project is going
- Contributing - local setup and contribution guidance
- Security - how to report vulnerabilities and current security notes
Install the latest GitHub Release:
curl -fsSL https://raw.githubusercontent.com/M-A-D-A-R-A/Vigil/main/scripts/install.sh | shThen start Vigil:
vigil serveVigil listens on http://localhost:8080 by default.
In your app or project directory, initialize Vigil:
vigil init -project my-appvigil init creates or selects a project, stores your local CLI config, writes SDK-ready values into .env, and prints a ready-to-run ingest command.
Send your first event:
vigil ingest-commandRun the printed curl command, then open http://localhost:8080 and watch the event appear in Logs, Traces, and Stats.
Apps that already use OpenTelemetry can also send OTLP/HTTP protobuf payloads to /v1/logs, /v1/traces, and /v1/metrics with the same Vigil ingest key. Native JSON ingest remains the easiest manual path.
Example OpenTelemetry exporter environment:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8080
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20vigil_...
OTEL_SERVICE_NAME=my-appvigil- start the servervigil serve- explicitly start the servervigil init [-project NAME]- create or select a project and write app env valuesvigil status- show the active server, project, key state, and healthvigil projects- list projects and mark the active onevigil use PROJECT_ID_OR_NAME- switch active projectvigil key rotate- create and store a fresh ingest key for the active projectvigil ingest-command- print a ready-to-run curl commandvigil logs- inspect recent logs for the active project
Useful log commands:
vigil logs --since 1h
vigil logs --errors --q checkout
vigil logs --query 'level = "error" && source = "api"'
vigil logs --stats
vigil logs --fields
vigil logs --json
vigil logs --tailThe CLI defaults to http://localhost:8080, then reuses the saved server URL. Set VIGIL_CONFIG_PATH to use a specific config file.
vigil init manages only this block in your app .env and preserves the rest of the file:
# BEGIN VIGIL
VIGIL_BASE_URL=http://localhost:8080
VIGIL_PROJECT_ID=proj_...
VIGIL_INGEST_KEY=vigil_...
# END VIGILUse vigil init -env-file PATH to write a different env file. Server runtime settings, such as retention and data directories, stay in the server environment.
The Python SDK is published on PyPI as vigil-observability. It reads the env values from vigil init and sends events to Vigil:
pip install vigil-observabilityFor Python apps that already use OpenTelemetry:
pip install "vigil-observability[otel]"Package page: https://pypi.org/project/vigil-observability/
from vigil_sdk import VigilClient
vigil = VigilClient.from_env()
vigil.log("request.completed", message="request completed", attrs={"route": "/health"})
vigil.trace("llm.completed", trace_id="trace-123", attrs={"total_tokens": 42})
vigil.metric("queue.depth", value=7, unit="count", attrs={"queue": "jobs"})OpenTelemetry helper:
from vigil_sdk import configure_vigil_otel
configure_vigil_otel(service_name="my-app")The TypeScript SDK lives in sdk/typescript. Server-side JavaScript/TypeScript runtimes use the private-key VigilClient:
import { VigilClient } from "vigil-observability";
const vigil = VigilClient.fromEnv();
await vigil.log("request.completed", {
message: "request completed",
attrs: { route: "/health" },
});
await vigil.trace("llm.completed", {
traceId: "trace-123",
spanId: "span-1",
parentSpanId: "span-root",
attrs: { total_tokens: 42 },
});
await vigil.metric("queue.depth", {
value: 7,
unit: "count",
attrs: { queue: "jobs" },
});Do not bundle VIGIL_INGEST_KEY into browser code. Browser apps should use a browser-safe ingest key with BrowserVigilClient or startVigilBrowserCapture:
import { startVigilBrowserCapture } from "vigil-observability";
startVigilBrowserCapture({
baseUrl: "http://localhost:8080",
browserIngestKey: import.meta.env.VITE_VIGIL_BROWSER_INGEST_KEY,
});The capture helper records safe summaries for page views, route changes, console.error, uncaught errors, unhandled promise rejections, and fetch calls. It can attach W3C traceparent headers to outgoing fetch requests so backend logs can keep the same trace_id. It does not capture cookies, local/session storage, auth headers, request bodies, response bodies, password fields, full DOM, screenshots, or HAR files.
Use W3C traceparent helpers when you want to connect a frontend action to backend logs:
import { VigilClient, continueTraceContext, parseTraceparent, traceparentHeaders } from "vigil-observability";
const vigil = VigilClient.fromEnv();
export async function handleRequest(req: Request) {
const span = continueTraceContext(parseTraceparent(req.headers.get("traceparent")));
await vigil.log("api.request.started", {
traceId: span.traceId,
spanId: span.spanId,
parentSpanId: span.parentSpanId,
attrs: { route: "/api/checkout" },
});
await fetch("http://worker.local/jobs", {
method: "POST",
headers: traceparentHeaders(span),
});
}Query trace_id = "..." in logs or traces to see the flow across browser, API, workers, and downstream services. Product-specific fields still belong in attrs; Vigil core only needs generic trace context.
Browser ingest keys are public, project-scoped, ingest-only keys for frontend telemetry. They are separate from private server ingest keys, require exact origin allowlists, and can only write to POST /api/browser/ingest.
Browser keys do not secure the whole Vigil server. The current project, query, and key-management APIs are still intended for local-admin/trusted-network use until Vigil grows auth and RBAC.
Create one for a project:
curl -X POST http://localhost:8080/api/projects/proj_.../browser-keys \
-H 'Content-Type: application/json' \
-d '{"name":"local web","allowed_origins":["http://localhost:3000"]}'The response returns browser_ingest_key once. Use it from browser code with an allowed Origin; the browser endpoint infers the project from the key, so project_id may be omitted from the event body.
Send a browser event:
await fetch("http://localhost:8080/api/browser/ingest", {
method: "POST",
headers: {
Authorization: `Bearer ${VIGIL_BROWSER_INGEST_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
schema_version: 1,
kind: "log",
ts: new Date().toISOString(),
source: "browser",
level: "error",
name: "frontend.error",
attrs: { path: location.pathname },
body: { message: "client error" },
}),
});vigil init does not choose where log files live. It configures your app to send data to a running Vigil server. The server owns the on-disk data directory.
By default, vigil serve writes data under ./vigil-data relative to the directory where the server is started:
vigil-data/
├── logs/
│ └── <project_id>/
│ └── <YYYY-MM-DD>/
│ └── *.ndjson
└── index/
└── vigil.dbThe raw NDJSON files are the source of truth. vigil.db is the SQLite read model used by the UI and query APIs, and Vigil can rebuild it from the remaining NDJSON data when needed.
Vigil redacts obvious secrets before raw NDJSON append by default. This covers sensitive key names such as password, token, authorization, and cookie, plus common secret-looking values such as bearer tokens, JWTs, provider keys, connection strings with credentials, high-entropy strings, and emails. Set VIGIL_REDACTION_ENABLED=false only for local debugging where storing raw secrets is acceptable.
Use VIGIL_DATA_DIR to put server data somewhere specific:
VIGIL_DATA_DIR=/path/to/vigil-data vigil serveInstall Go and Bun, then build and run locally:
make ui-install
make ui-build
make build
bin/vigilUseful development commands:
make run- run the Go server locallymake ui-dev- start the Vite dev servermake test- run backend testsmake test-python- run Python SDK testsmake test-typescript- run TypeScript SDK testsmake smoke- build the UI and run the browser first-run/explorer smoke testmake bench- ingest synthetic logs and time log queriesmake size-release- print stripped release binary size plus embedded UI sizemake build-linux-arm64- build a Raspberry Pi 4 friendly Linux ARM64 binary
For local frontend development without rebuilding the embedded UI:
make run
make ui-devOpen http://localhost:5173.
Vigil keeps data forever unless retention is explicitly enabled.
Set these environment variables to turn it on:
VIGIL_RETENTION_ENABLED=trueVIGIL_RETENTION_DAYS=30VIGIL_RETENTION_SWEEP_INTERVAL=1hVIGIL_RETENTION_DRY_RUN=false
Retention works on raw UTC day folders. When a sweep removes expired raw folders, Vigil clears and rebuilds the SQLite read model from the remaining NDJSON so query surfaces stay consistent.
Latest baseline: Vigil accepted 100,000 log events through the public API with 32 parallel ingest workers and no event loss. Batched SQLite indexing made all 100k logs queryable after about 20.52s total. See benchmark.md for the full readout.
Run a local isolated benchmark:
make bench ARGS="-events 5000 -concurrency 16 -query-runs 25"cmd/vigil- binary entrypointinternal/- backend application codeui/- React + TypeScript + Vite frontendsdk/python- Python SDK packagesdk/typescript- TypeScript SDK packageweb/dist- built frontend assetsdocs/- usage and operations docstest/- integration, e2e, and fixtures