A log ingestion and observability dashboard. Ingest structured log events via a REST API and explore them through a real-time filterable dashboard.
┌─────────────────┐ HTTP ┌─────────────────┐ SQL ┌─────────────────┐
│ Svelte 5 + │ ────────────► │ FastAPI + │ ──────────► │ PostgreSQL 16 │
│ Vite │ │ Python 3.12 │ │ │
│ :5173 │ │ :8000 │ │ :5432 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
All three services run as Docker containers orchestrated with Docker Compose. The frontend talks directly to the API; the API holds a connection pool to Postgres.
Requirements: Docker Desktop
git clone https://github.com/full-Stack-Hacks/log-pulse.git
cd log-pulse
docker-compose up --build
That's it. On first run, Postgres initializes the schema from db/init.sql and the API container automatically seeds 100K rows of sample data — this takes about 30 seconds. The dashboard will be available at http://localhost:5173 once seeding completes and you see Application startup complete. in the logs. Subsequent restarts detect the existing data and skip seeding.
| Service | URL |
|---|---|
| Dashboard | http://localhost:5173 |
| API | http://localhost:8000 |
| API docs | http://localhost:8000/docs |
| Postgres | localhost:5432 |
To stop everything: docker-compose down
To wipe the database and start fresh: docker-compose down -v
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check |
POST |
/logs |
Ingest a log entry |
GET |
/logs |
Query logs with filters + pagination |
GET |
/logs/stats |
Count by level and service |
GET |
/logs/timeline |
Hourly log counts for charting |
GET /logs query parameters| Param | Type | Description |
|---|---|---|
level |
string | Filter by level: DEBUG, INFO, WARN, ERROR |
service |
string | Filter by service name |
search |
string | Full-text search on message |
start / end |
ISO datetime | Time range filter |
limit |
int (max 500) | Page size, default 50 |
offset |
int | Pagination offset |
POST /logs body{
"level": "ERROR",
"service": "auth-service",
"message": "Login failed for user 42",
"metadata": { "user_id": 42, "ip": "1.2.3.4" }
}
CREATE TYPE log_level AS ENUM ('DEBUG', 'INFO', 'WARN', 'ERROR');
CREATE TABLE logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
level log_level NOT NULL,
service TEXT NOT NULL,
message TEXT NOT NULL,
metadata JSONB
);
CREATE INDEX idx_logs_timestamp ON logs (timestamp DESC);
CREATE INDEX idx_logs_level ON logs (level);
All dashboard queries measured against 100K rows with EXPLAIN ANALYZE:
| Query | Execution time |
|---|---|
| Fetch logs (no filter) | 1.4ms |
| Fetch logs (level filter) | 4ms |
| Count by level (stats) | 13ms |
| Timeline (24h buckets) | 18ms |
The idx_logs_timestamp index drives the main log fetch and timeline queries. The idx_logs_level index enables index-only scans for the stats aggregation.
ENUM for log level — rejects invalid values at the database layer rather than the application layer. Tradeoff: adding a new level requires a schema migration.
JSONB for metadata — allows arbitrary structured context per log (stack traces, request IDs, user IDs) without requiring schema changes for each new field. Tradeoff: querying inside metadata is slower than a dedicated column.
psycopg2 connection pool — keeps a pool of 10 reusable Postgres connections rather than opening a new connection per request. Simpler than an async driver (asyncpg) with negligible performance difference at this scale.
Svelte 5 runes — uses $state, $derived, and $effect throughout, the modern Svelte 5 reactivity model. The search input is debounced (300ms) to avoid hammering the API on every keystroke.
Seed script as a one-off command — intentionally not part of the container startup. Running it on every boot would wipe and repopulate the database, which is destructive in a real environment.
timestamp + id) would be more efficient for deep pages.(level, timestamp DESC) to speed up filtered queries at larger table sizesManifests for all three services are in the k8s/ directory. Before applying, build and push the images to a registry:
docker build -t your-registry/log-pulse-api:latest ./backend
docker push your-registry/log-pulse-api:latest
docker build -t your-registry/log-pulse-frontend:latest ./frontend
docker push your-registry/log-pulse-frontend:latest
Update the image: fields in k8s/api.yaml and k8s/frontend.yaml with your registry paths, then apply:
kubectl apply -f k8s/db.yaml
kubectl apply -f k8s/api.yaml
kubectl apply -f k8s/frontend.yaml
The database credentials are stored in a Kubernetes Secret defined in k8s/db.yaml. The frontend is exposed via NodePort on port 30573. The API and database use ClusterIP and are internal to the cluster only.
log-pulse/
├── backend/
│ ├── Dockerfile
│ ├── main.py # FastAPI app and all endpoints
│ ├── seed.py # 100K row seed script
│ └── requirements.txt
├── frontend/
│ ├── Dockerfile
│ ├── src/
│ │ ├── App.svelte
│ │ └── lib/
│ │ ├── api.js # All fetch calls
│ │ ├── StatsBar.svelte
│ │ ├── Timeline.svelte
│ │ └── LogTable.svelte
│ └── package.json
├── db/
│ └── init.sql # Schema and indexes
├── k8s/
│ ├── db.yaml # Postgres deployment, PVC, and secret
│ ├── api.yaml # FastAPI deployment
│ └── frontend.yaml # Svelte deployment (NodePort)
└── docker-compose.yml