I built LogSight to understand how log monitoring tools work, how you ingest thousands of logs per minute, query them efficiently, and surface meaningful patterns to users.
Send logs via HTTP API → See error trends on a dashboard → Click "Analyze" to get AI explanations.
Live: https://logsight.onrender.com
I was curious about how companies handle log monitoring at scale:
- How do you detect errors from thousands of services simultaneously?
- How do you pinpoint the exact place where something broke?
- How do you query billions of logs efficiently?
- How do monitoring tools like Datadog and New Relic actually work under the hood?
This project taught me:
- How to handle real-time data ingestion
- Why database queries are the bottleneck (not the API)
- What JWT auth needs that API keys don't (and vice versa)
- How to write SQL that doesn't destroy your database under load
- Why testing actually matters (caught me breaking auth 3 times)
- Node.js v24+
- PostgreSQL 18 (local) or Supabase (cloud)
- Groq API key (free)
git clone https://github.com/Vrishali34/logsight.git
cd logsight
# Install dependencies
npm install
cd client && npm install && cd ..
# Configure environment
cp .env.example .env
# Edit: DATABASE_URL, JWT_SECRET, GROQ_API_KEY
# Start development
npm run dev # Backend @ localhost:3000
npm run client # Frontend @ localhost:5173
# Run tests
npm test # 26 tests passingYour app makes an HTTP request:
curl -X POST https://logsight.onrender.com/api/logs \
-H "x-api-key: ls_2xf1d8f8d372d08a381fa1b42b69a8256c5fcf87a" \
-H "Content-Type: application/json" \
-d '{
"level": "error",
"message": "Payment gateway timeout after 30s",
"service": "checkout",
"metadata": {"user_id": 123, "amount": 99.99, "gateway": "stripe"}
}'Dashboard queries aggregated data:
SELECT
COUNT(*) as total_logs,
COUNT(*) FILTER (WHERE level = 'error') as error_count,
ROUND(100.0 * COUNT(*) FILTER (WHERE level = 'error') / COUNT(*), 2) as error_rate
FROM logs
WHERE app_id = 1 AND created_at > NOW() - INTERVAL '24 hours';Result: "50% error rate. 7 errors in the last 24 hours."
Click "Analyze":
curl https://logsight.onrender.com/api/ai/insights?app_id=1&hours=24 \
-H "Authorization: Bearer your-jwt-token"Groq analyzes the logs and responds:
"Your payment service is timing out waiting for Stripe. The timeouts spike at 3 AM UTC — likely a batch job running. You're not retrying failed payments. Recommend: (1) increase timeout to 60s, (2) add exponential backoff, (3) queue failed payments for async retry."
This diagram shows how the application layers communicate:
graph TB
subgraph Browser["Client Layer"]
UI["React + Vite<br/>Dashboard, Auth UI"]
end
subgraph API["Express API Layer"]
Auth["Auth Service<br/>JWT, bcrypt"]
Logs["Log API<br/>Ingestion"]
Analysis["Analytics Service<br/>Trends, Summary"]
Alerts["Alert Service<br/>Threshold Check"]
AI["AI Service<br/>Groq Integration"]
end
subgraph DB["Data Layer"]
Users[("users")]
Apps[("apps")]
LogsDB[("logs")]
AlertsDB[("alert_rules")]
end
subgraph External["External Services"]
Groq["Groq API<br/>Llama 3.3 70B"]
end
UI -->|POST /auth| Auth
UI -->|GET /analysis| Analysis
UI -->|POST /alerts| Alerts
Auth -->|Read/Write| Users
Auth -->|Read/Write| Apps
Logs -->|INSERT| LogsDB
Logs -->|Verify| Apps
Analysis -->|SELECT| LogsDB
Analysis -->|SELECT| Apps
Alerts -->|SELECT| LogsDB
Alerts -->|Read/Write| AlertsDB
AI -->|SELECT| LogsDB
AI -->|Request| Groq
Browser -->|API Key| Logs
style Browser fill:#6366f1,color:#fff,stroke:#4f46e5
style API fill:#10b981,color:#fff,stroke:#059669
style DB fill:#f59e0b,color:#fff,stroke:#d97706
style External fill:#ef4444,color:#fff,stroke:#dc2626
Raw SQL instead of ORM: I wanted to practice writing complex queries myself. An ORM would've been faster, but I needed to understand DATE_TRUNC for hourly bucketing and FILTER for conditional counts — things most ORMs hide. Raw SQL makes it obvious when a query is slow.
JWT + API keys (two auth systems): User login (JWT) and log ingestion (API keys) have different needs. JWT is short-lived, revocable, session-specific. API keys are long-lived, server-to-server, bulk operations. Splitting them makes security clearer.
PostgreSQL for time-series logs: A real system would use a time-series database (ClickHouse, TimescaleDB). PostgreSQL works fine for demos but starts struggling above ~1k logs/minute. This tradeoff helped me understand why specialized databases exist.
Groq for AI: It's cheap ($0.10 per 1M tokens) and fast (1-3 seconds). Easy to swap to OpenAI or Anthropic later.
| Component | Technology | Why |
|---|---|---|
| Backend | Node.js + Express 5 | Lightweight, good async error handling |
| Frontend | React 18 + Vite | Fast, familiar, works with dashboards |
| Database | PostgreSQL 18 | Mature, good for time-series queries |
| Auth | JWT + bcrypt | Stateless, no session storage needed |
| Validation | Zod | Type-safe input validation |
| Charts | Recharts | Simple line/bar charts for trends |
| AI | Groq SDK + Llama 3.3 70B | Cheap and surprisingly good at log analysis |
| Testing | Jest + Supertest | 26 tests covering all critical paths |
| Deployment | Render + Supabase | Both have free tiers, work together well |
# Register
curl -X POST https://logsight.onrender.com/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"Secure@123"}'
# Login (returns JWT)
curl -X POST https://logsight.onrender.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"Secure@123"}'TOKEN="your-jwt-token"
# Create app (auto-generates API key)
curl -X POST https://logsight.onrender.com/api/apps \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"My Service"}'API_KEY="your-api-key"
# Send a log
curl -X POST https://logsight.onrender.com/api/logs \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"level": "error",
"message": "Database connection failed",
"service": "auth-service",
"metadata": {"user_id": 123, "retry_count": 3}
}'TOKEN="your-jwt-token"
# Summary metrics (24 hours)
curl "https://logsight.onrender.com/api/analysis/summary?app_id=1&hours=24" \
-H "Authorization: Bearer $TOKEN"
# Hourly trends
curl "https://logsight.onrender.com/api/analysis/trends?app_id=1&hours=24" \
-H "Authorization: Bearer $TOKEN"
# Per-service breakdown
curl "https://logsight.onrender.com/api/analysis/services?app_id=1&hours=24" \
-H "Authorization: Bearer $TOKEN"# Create alert (trigger when error_count > 10)
curl -X POST https://logsight.onrender.com/api/alerts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"app_id": 1,
"metric": "error_count",
"threshold": 10,
"cooldown_minutes": 15
}'
# Get alerts
curl "https://logsight.onrender.com/api/alerts?app_id=1" \
-H "Authorization: Bearer $TOKEN"
# Delete alert
curl -X DELETE "https://logsight.onrender.com/api/alerts/1?app_id=1" \
-H "Authorization: Bearer $TOKEN"curl "https://logsight.onrender.com/api/ai/insights?app_id=1&hours=24" \
-H "Authorization: Bearer $TOKEN"| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
| POST | /api/auth/register |
None | Create user |
| POST | /api/auth/login |
None | Get JWT token |
| GET | /api/auth/verify |
JWT | Verify token |
| POST | /api/apps |
JWT | Create monitored app |
| GET | /api/apps |
JWT | List apps |
| POST | /api/logs |
API Key | Ingest log |
| GET | /api/logs |
JWT | Query logs |
| GET | /api/analysis/summary |
JWT | Error rate, counts |
| GET | /api/analysis/trends |
JWT | Hourly distribution |
| GET | /api/analysis/services |
JWT | Per-service breakdown |
| POST | /api/alerts |
JWT | Create alert rule |
| GET | /api/alerts |
JWT | List alert rules |
| DELETE | /api/alerts/:id |
JWT | Delete alert rule |
| GET | /api/ai/insights |
JWT | AI analysis |
-- Users (authentication)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR UNIQUE NOT NULL,
password_hash VARCHAR NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Apps (monitored applications)
CREATE TABLE apps (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
name VARCHAR NOT NULL,
api_key VARCHAR UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Logs (immutable event stream)
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
app_id INT REFERENCES apps(id),
level VARCHAR NOT NULL,
message TEXT NOT NULL,
service VARCHAR,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Key index: (app_id, created_at) for time-series queries
-- Alert Rules (thresholds)
CREATE TABLE alert_rules (
id SERIAL PRIMARY KEY,
app_id INT REFERENCES apps(id),
metric VARCHAR NOT NULL,
threshold INT NOT NULL,
cooldown_minutes INT,
last_triggered_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);All analytics computed in single round-trip:
SELECT
COUNT(*) as total_logs,
COUNT(*) FILTER (WHERE level = 'error') as error_count,
COUNT(*) FILTER (WHERE level = 'warning') as warning_count,
ROUND(
100.0 * COUNT(*) FILTER (WHERE level = 'error') / COUNT(*),
2
) as error_rate
FROM logs
WHERE app_id = $1
AND created_at > NOW() - INTERVAL '1 hour' * $2;Why single query?
- Minimize database round-trips
- Atomic snapshot of metrics
- Lower latency for dashboard
| Operation | Latency | Notes |
|---|---|---|
| API request | <100ms | After warm-up (excludes AI) |
| Summary query | 5–20ms | Indexed on (app_id, created_at) |
| Authorization check | 2–5ms | Single index lookup |
| Trends query | 10–30ms | GROUP BY with window functions |
| AI analysis | 1–3s | Groq network latency |
- Indexes:
(app_id, created_at)on logs table (time-series pattern) - Strategy: GROUP BY with FILTER aggregates (no temporary tables)
- Window functions: For percentiles without joining back
- Partitioning: Ready for future scaling (by month)
Every endpoint that returns user data checks: "Does this user own this app?"
const ownershipCheck = await pool.query(
'SELECT id FROM apps WHERE id = $1 AND user_id = $2',
[appId, req.user.userId]
);
if (ownershipCheck.rows.length === 0) {
return res.status(403).json({ error: 'Access denied' });
}Tests verify:
- User A cannot see User B's logs ✅
- Brute force attacks are rate-limited (10 attempts per 15 minutes) ✅
- Passwords are bcrypt-hashed, never logged ✅
- Invalid input is rejected (Zod validation) ✅
- API keys can be revoked per app ✅
npm testResults:
- ✅ Auth (register, login, JWT verification)
- ✅ Apps (create, list apps, API key generation)
- ✅ Logs (ingest, query, filter by level)
- ✅ Analysis (summary, trends, service breakdown)
- ✅ Alerts (create, trigger on threshold, delete)
- ✅ Security (ownership checks, rate limiting)
26 tests, 0 failures.
logsight/
├── client/ # React + Vite frontend
│ └── src/
│ ├── Dashboard.jsx # Main layout
│ ├── Summary.jsx # Error rate metrics
│ ├── TrendsChart.jsx # Hourly line chart
│ ├── ServicesTable.jsx # Per-service breakdown
│ ├── LogViewer.jsx # Raw logs view
│ ├── AlertsPanel.jsx # Alert rule management
│ └── AIInsights.jsx # AI analysis display
│
├── src/ # Node.js + Express backend
│ ├── features/
│ │ ├── auth/ # Login, JWT, password hashing
│ │ ├── apps/ # App CRUD, API key generation
│ │ ├── logs/ # Log ingestion, query, filter
│ │ ├── analysis/ # Summary, trends, services queries
│ │ ├── alerts/ # Alert rule creation & checking
│ │ └── ai/ # Groq integration
│ ├── config/
│ │ └── db.js # PostgreSQL connection pool
│ ├── middleware/
│ │ └── errorHandler.js # Global error handling
│ └── utils/
│ └── logger.js # Structured logging
│
├── tests/ # Jest test suite (26 tests)
│ ├── auth.test.js
│ ├── apps.test.js
│ ├── logs.test.js
│ ├── analysis.test.js
│ └── alerts.test.js
│
├── migrations/ # Database schema
├── app.js # Express app setup
├── server.js # Entry point
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Local development
├── .env.example # Environment template
├── package.json # Dependencies
├── LICENSE # MIT
├── docs/
│ └── screenshots/ # Project screenshots
└── README.md # This file
Create .env file:
# Server
NODE_ENV=development
PORT=3000
# Database (get from Supabase)
DATABASE_URL=postgresql://postgres:password@aws-0-region.pooler.supabase.com:6543/postgres
# JWT (generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
JWT_SECRET=your-32-character-hex-secret-key-here
JWT_EXPIRES_IN=7d
# AI (get from console.groq.com)
GROQ_API_KEY=gsk_your_key_here
# Frontend
FRONTEND_URL=http://localhost:5173npm testDatabase queries are the bottleneck. I spent 3 weeks optimizing queries before even touching Express. The difference between a 500ms query and a 20ms query is bigger than anything else.
Raw SQL taught me things. I learned FILTER, DATE_TRUNC, window functions, and index strategy by necessity. An ORM would've hidden that. Now I write faster queries.
JWT vs API keys is a useful split. I initially tried to use JWT for everything. Turned out log ingestion needs different properties (fire-and-forget, no session state, revocable per app).
Testing is real. The 26 tests caught me breaking authorization checks 3 times. After that, I won't ship without tests.
Groq is actually good. I expected the AI analysis to be generic. It's not — it catches patterns I'd miss manually (like "this service has 0 logs but high error rate" = something is silently failing).
MIT




