An end-to-end, fault-tolerant log ingestion and observability pipeline designed to handle massive concurrent traffic.
Built using an event-driven architecture, LogStream decouples ingestion, processing, storage, and visualization layers to ensure scalability, resilience, and near real-time observability under heavy load.
- High-throughput asynchronous log ingestion
- Redis Streams-based event pipeline
- Consumer Group architecture for concurrent workers
- Real-time observability dashboard using WebSockets
- Infinite scrolling searchable log explorer
- SHA-256 based deduplication
- Batch database insertion using
executemany - Fault-tolerant replay handling using Redis Pending Entries List (PEL)
- PostgreSQL-backed persistent storage
- Full-text log searching
- Live ingestion velocity monitoring
- Horizontally scalable worker architecture
- API-key authenticated & rate-limited ingestion endpoint
- Structured, request-correlated logging
- Liveness/readiness health probes and Prometheus metrics
┌────────────────────┐
│ Client Apps │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ FastAPI Ingestion │
│ Layer │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ Redis Streams │
│ Consumer Groups │
└─────────┬──────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Worker #1 │ │ Worker #2 │
│ asyncio │ │ asyncio │
└──────┬───────┘ └──────┬───────┘
│ │
└──────────┬───────────┘
▼
┌────────────────────┐
│ PostgreSQL │
│ Persistent DB │
└────────────────────┘
│
▼
┌────────────────────┐
│ Redis Pub/Sub │
│ Live Metrics Bus │
└─────────┬──────────┘
▼
┌────────────────────┐
│ React Dashboard │
│ WebSocket Client │
└────────────────────┘
- FastAPI
- Python 3.11+
- SQLAlchemy
- asyncpg
- asyncio
- Redis Streams
- Redis Pub/Sub
- PostgreSQL
- Alembic
- pydantic-settings
- prometheus-fastapi-instrumentator
- uv
- React
- TypeScript
- Tailwind CSS
- Recharts
- Lucide Icons
Instead of writing directly to PostgreSQL during ingestion, logs are first pushed into Redis Streams.
This decouples:
- API responsiveness
- database writes
- processing workload
Result:
- sub-millisecond ingestion latency
- higher throughput
- resilient backpressure handling
Every log message is hashed using SHA-256 before insertion.
Combined with PostgreSQL:
ON CONFLICT DO NOTHINGworkers can safely replay failed batches without corrupting the database.
This makes recovery from worker crashes deterministic and safe.
Workers collect logs in batches of 100 and use:
executemany()instead of individual insert queries.
This reduces:
- database round trips
- transaction overhead
- disk sync pressure
Result:
- significantly higher throughput
- lower database CPU utilization
Redis Consumer Groups allow multiple workers to process logs concurrently without duplication.
Scaling becomes simple:
uv run processor.pyRun multiple instances across machines or containers.
Instead of continuously polling PostgreSQL for updates:
- workers publish lightweight metrics to Redis Pub/Sub
- FastAPI listens to Pub/Sub
- WebSocket broadcasts updates to connected clients
Result:
- near-zero database load during live monitoring
- efficient real-time dashboard streaming
Beyond the core pipeline, the backend includes the operational scaffolding a real deployment needs:
- Centralized configuration - one
Settingsobject (app/core/config.py) validated at startup, instead ofos.getenvsprinkled across modules. - Structured logging with request correlation - every HTTP request gets an
X-Request-ID(generated or forwarded), bound to acontextvarscontext so all log lines emitted while handling it share the same id (app/core/logging.py,app/core/middleware.py). - Global error handling - a single exception handler returns a consistent JSON error body and logs the full traceback server-side, instead of duplicating
try/exceptper endpoint. - Liveness vs. readiness -
GET /healthis a cheap liveness probe;GET /health/readyactually pings Postgres and Redis and returns503if either is down, the distinction Kubernetes (and any real load balancer) expects. - Authenticated, rate-limited ingestion -
POST /api/ingestrequires anX-API-Keyheader and is rate-limited per key/IP via a Redis fixed-window counter, so a public log-ingestion endpoint can't be used to spam the pipeline or run up database writes. The read-only analytics/dashboard endpoints stay open. - Metrics -
GET /metricsexposes Prometheus-formatted request latency/count metrics plus a customlogs_ingested_totalcounter. - Security headers - baseline headers (
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-Policy) applied to every response. - Real horizontal scaling - each worker process now derives a unique Redis consumer name (hostname + random suffix, or
CONSUMER_NAMEif pinned) instead of a hardcodedconsumer1. Previously every scaled-out worker replica would have collided under the same consumer identity, silently defeating the consumer-group fan-out the architecture is built around. Workers also shut down gracefully onSIGTERM/SIGINTafter finishing their current batch.
Copy log_processor/.env.example to log_processor/.env and fill in real values:
REDIS_HOST=localhost
REDIS_PORT=6379
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=your_password
DB_NAME=logger
DATABASE_URL=postgresql://postgres:root@localhost:5432/logger
TABLE_NAME=logs
# Comma-separated list of origins allowed to call the API
CORS_ORIGINS=http://localhost:5173
# Required header (X-API-Key) for POST /api/ingest - generate a real secret
# before deploying, e.g. `python -c "import secrets; print(secrets.token_hex(32))"`
INGESTION_API_KEY=dev-only-change-me
# Fixed-window rate limit applied to POST /api/ingest, keyed by API key/IP
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_WINDOW_SECONDS=60All configuration is loaded once at startup through a single Settings object (app/core/config.py, backed by pydantic-settings) instead of os.getenv calls scattered across the codebase - a missing/misnamed variable fails fast at boot rather than silently at request time.
pip install uvuv venvActivate environment:
source .venv/bin/activate.venv\Scripts\activateIf using pyproject.toml:
uv syncInitialize Alembic:
uv run alembic init alembicCreate migration:
uv run alembic revision --autogenerate -m "create logs table"Run migrations:
uv run alembic upgrade headuv run uvicorn app.main:app --reload --port 8000uv run processor.pyRun multiple workers in separate terminals for horizontal scaling.
Navigate into frontend directory:
npm install -g pnpm
cd clientInstall dependencies:
pnpm installRun development server:
pnpm run devThe easiest way to run LogStream is using Docker Compose. This starts all required services automatically, including PostgreSQL, Redis, the backend API, frontend, worker, and database migrations.
- Docker
- Docker Compose
Verify your installation:
docker --version
docker compose versiongit clone https://github.com/RahulGIT24/Scalable-Log-Processor
cd Scalable-Log-ProcessorInside the log_processor directory, create a file named:
log_processor/.docker.env
Add the following configuration:
REDIS_HOST=redis
REDIS_PORT=6379
DB_HOST=postgres
DB_PORT=5432
DB_USER=postgres
DB_PASS=root
DB_NAME=logger
DATABASE_URL=postgresql://postgres:root@postgres:5432/logger
TABLE_NAME=logsNote: Inside Docker containers, services communicate using the Docker Compose service names (
postgres,redis) rather thanlocalhost.
docker compose builddocker compose upRun in detached mode if preferred:
docker compose up -dDocker Compose automatically starts:
- PostgreSQL
- Redis
- FastAPI Backend
- React Frontend (served using Nginx)
- Background Log Processing Worker
- Alembic Migration Service
No additional setup is required.
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend API | http://localhost:8000 |
Stop all containers:
docker compose downTo also remove the PostgreSQL volume:
docker compose down -vIf Dockerfiles or dependencies change:
docker compose build --no-cache
docker compose upDocker Compose runs the following services:
- frontend — React application served via Nginx
- backend — FastAPI REST API
- worker — Redis Streams consumer responsible for processing logs
- migrate — Alembic migration runner
- postgres — PostgreSQL database
- redis — Redis Streams and Pub/Sub server
cd log_processor
uv sync
uv run pytest -vTests for ingestion, the pydantic Log model, and the worker's process_batch/make_group logic are fully hermetic (Redis and the DB are mocked) and always run.
Tests for the /api/analytics endpoints need a real Postgres database (SQLAlchemy's ARRAY column type isn't supported by SQLite). They only run when TEST_DATABASE_URL is set, and are skipped otherwise:
docker run -d --rm -p 5432:5432 \
-e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=logstream_test \
postgres:15
TEST_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/logstream_test uv run pytest -vTEST_DATABASE_URL is intentionally separate from DATABASE_URL so tests never accidentally run against whatever database your local .env points at.
cd client
pnpm install
pnpm run test # single run
pnpm run test:watch # watch mode
pnpm run test:coverage # with coverage reportEvery push and pull request to master/main runs .github/workflows/ci.yml, which:
- spins up Postgres and Redis service containers, runs Alembic migrations, and runs the backend pytest suite
- installs frontend dependencies, lints, runs the Vitest suite, and builds the client
A stress testing script is included to simulate high concurrent traffic.
Run:
uv run stress_test.pyDefault configuration:
- 500 concurrent connections
- 5000 log messages
Expected behavior:
- API accepts requests immediately
- Redis buffers ingestion spikes
- workers batch-process logs asynchronously
- dashboard displays live ingestion spikes in real time
Interactive OpenAPI docs are available at /docs (Swagger UI) and /redoc while the backend is running.
Push a log into the Redis Stream. Requires an X-API-Key header matching INGESTION_API_KEY, and is rate-limited to RATE_LIMIT_MAX_REQUESTS requests per RATE_LIMIT_WINDOW_SECONDS per key (401 if the key is missing/wrong, 429 if the limit is exceeded).
Example request:
curl -X POST http://localhost:8000/api/ingest/ \
-H "Content-Type: application/json" \
-H "X-API-Key: dev-only-change-me" \
-d '{"type": "ERROR", "message": "Database connection FAILED"}'Tags shown in the dashboard aren't submitted by the client - the worker derives them from any ALL-CAPS words in message (e.g. FAILED above) before storing the log.
Returns aggregated counts grouped by log level.
Search historical logs with pagination.
Example:
/api/analytics/search?search=database&limit=50&offset=0Streams:
- ingestion metrics
- live logs
- processing statistics
Liveness probe - always returns 200 if the process is up.
Readiness probe - pings Postgres and Redis, returns 200 ({"status": "ok"}) only if both respond, otherwise 503 with per-component detail.
Prometheus-formatted metrics: HTTP request count/latency plus logs_ingested_total.
- Live WebSocket updates
- Infinite scroll log explorer
- Debounced full-text search
- Real-time ingestion velocity graph
- Log level aggregation cards
- Responsive observability UI
- Search without database polling
Enjoy Stress Testing LogStream in action:
Under stress testing:
- ingestion remains non-blocking
- Redis absorbs traffic spikes
- workers process asynchronously
- PostgreSQL write amplification remains low due to batching
The architecture is designed around:
- backpressure tolerance
- fault recovery
- horizontal scalability
- observability-first design