Skip to content

Commit 93e887f

Browse files
authored
feat: add monitoring, structured logging, metrics, and health checks (#40)
- Migrate all console.log/console.error to Pino via nestjs-pino - Add correlation IDs on every request via middleware - Redact sensitive data (auth, passwords, tokens) from logs - Add GET /metrics with Prometheus format via @willsoto/nestjs-prometheus - Add HTTP request counters and latency histograms - Add BullMQ queue depth, indexer lag, Horizon health, DB pool gauges - Expand GET /health with checks for DB, Horizon, indexer lag, BullMQ, Redis - Create docs/ops/monitoring.md with alert thresholds and scrape config - Create grafana/dashboard.json with 9 monitoring panels
1 parent 658cd40 commit 93e887f

17 files changed

Lines changed: 1355 additions & 42 deletions

docs/ops/monitoring.md

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Monitoring & Alerting Configuration
2+
3+
## Overview
4+
5+
StepFi API exposes three observability endpoints:
6+
7+
| Endpoint | Purpose | Public | Route |
8+
|------------|-------------------------------|--------|-------------------|
9+
| `/health` | Comprehensive health check | Yes | `GET /health` |
10+
| `/metrics` | Prometheus metrics | No | `GET /metrics` |
11+
| `/docs` | Swagger/OpenAPI documentation | Yes | `GET /api/v1/docs`|
12+
13+
> **`/metrics` must never be exposed publicly.** Block it at the reverse proxy (Nginx,
14+
> Cloudflare, or Render's network layer). Only the internal Prometheus server may
15+
> scrape this endpoint.
16+
17+
---
18+
19+
## Prometheus Metrics
20+
21+
All metrics use the `stepfi_` prefix:
22+
23+
### HTTP Request Metrics
24+
- `stepfi_http_requests_total` — Counter, labels: `method`, `status`, `path`
25+
- `stepfi_http_request_duration_seconds` — Histogram, labels: `method`, `status`, `path`
26+
- Buckets: 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s
27+
28+
### Application Metrics
29+
- `stepfi_bullmq_queue_depth` — Gauge, labels: `queue`
30+
- `stepfi_indexer_lag_ledgers` — Gauge (indexer ledger lag behind network tip)
31+
- `stepfi_horizon_up` — Gauge (1 = reachable, 0 = down)
32+
- `stepfi_db_pool_open` — Gauge (open database connections)
33+
34+
### Default Node.js Metrics
35+
Provided by `prom-client` default metrics:
36+
- `process_cpu_user_seconds_total`
37+
- `process_resident_memory_bytes`
38+
- `nodejs_heap_size_used_bytes`
39+
- `nodejs_eventloop_lag_seconds`
40+
41+
---
42+
43+
## Uptime Monitors
44+
45+
### Recommended service (Render)
46+
47+
If deployed on Render, configure a **cron job** health check:
48+
49+
```yaml
50+
healthCheckPath: /health
51+
healthCheckInterval: 30 # seconds
52+
```
53+
54+
### Recommended service (external — Pingdom / Better Uptime)
55+
56+
| Setting | Value |
57+
|------------------|--------------------------------|
58+
| Check URL | `https://api.stepfi.app/health`|
59+
| Check interval | 1 minute |
60+
| Timeout | 10 seconds |
61+
| Retries | 2 |
62+
| Regions | US East, US West, EU West |
63+
64+
**Alert triggers:**
65+
- 2 consecutive failures → Pager (PagerDuty / Opsgenie)
66+
- Any single 5xx response → Slack notification
67+
68+
---
69+
70+
## Prometheus & Alertmanager
71+
72+
### Scrape configuration (`prometheus.yml`)
73+
74+
```yaml
75+
scrape_configs:
76+
- job_name: 'stepfi-api'
77+
scrape_interval: 30s
78+
scrape_timeout: 10s
79+
metrics_path: /metrics
80+
static_configs:
81+
- targets: ['api.stepfi.app:443']
82+
scheme: https
83+
authorization:
84+
credentials: '<bearer-token-if-used>'
85+
```
86+
87+
### Alert rules (`alerts.yml`)
88+
89+
```yaml
90+
groups:
91+
- name: stepfi
92+
rules:
93+
94+
# API down
95+
- alert: StepFiAPIDown
96+
expr: probe_success{job="stepfi-api"} == 0
97+
for: 1m
98+
labels:
99+
severity: critical
100+
annotations:
101+
summary: 'StepFi API is unreachable'
102+
103+
# HTTP 5xx rate
104+
- alert: StepFiHighErrorRate
105+
expr: |
106+
rate(stepfi_http_requests_total{status=~"5.."}[5m])
107+
/
108+
rate(stepfi_http_requests_total[5m])
109+
> 0.05
110+
for: 5m
111+
labels:
112+
severity: warning
113+
annotations:
114+
summary: 'Error rate > 5% over 5m'
115+
116+
# p99 latency
117+
- alert: StepFiHighLatency
118+
expr: |
119+
histogram_quantile(
120+
0.99,
121+
rate(stepfi_http_request_duration_seconds_bucket[5m])
122+
) > 3
123+
for: 5m
124+
labels:
125+
severity: warning
126+
annotations:
127+
summary: 'p99 latency > 3s over 5m'
128+
129+
# Indexer lag
130+
- alert: StepFiIndexerStalled
131+
expr: stepfi_indexer_lag_ledgers > 500
132+
for: 2m
133+
labels:
134+
severity: warning
135+
annotations:
136+
summary: 'Indexer lag > 500 ledgers'
137+
138+
- alert: StepFiIndexerCritical
139+
expr: stepfi_indexer_lag_ledgers > 2000
140+
for: 1m
141+
labels:
142+
severity: critical
143+
annotations:
144+
summary: 'Indexer lag > 2000 ledgers — data may be stale'
145+
146+
# BullMQ queue depth
147+
- alert: StepFiQueueBacklog
148+
expr: stepfi_bullmq_queue_depth > 1000
149+
for: 5m
150+
labels:
151+
severity: warning
152+
annotations:
153+
summary: 'BullMQ queue {{ $labels.queue }} has > 1000 waiting jobs'
154+
155+
# Horizon down
156+
- alert: StepFiHorizonDown
157+
expr: stepfi_horizon_up == 0
158+
for: 1m
159+
labels:
160+
severity: critical
161+
annotations:
162+
summary: 'Stellar Horizon endpoint is unreachable'
163+
```
164+
165+
### Alert thresholds summary
166+
167+
| Metric | Warning | Critical |
168+
|--------------------|----------------|----------------|
169+
| Error rate | > 5% | > 10% |
170+
| p99 latency | > 3s | > 5s |
171+
| Indexer lag | > 500 ledgers | > 2000 ledgers |
172+
| Queue depth | > 1000 jobs | > 5000 jobs |
173+
| Horizon up | — | 0 (down) |
174+
175+
---
176+
177+
## Grafana
178+
179+
The dashboard JSON is at [`grafana/dashboard.json`](../grafana/dashboard.json).
180+
Import it into Grafana via **Dashboards → Import**.
181+
182+
### Panels included
183+
184+
1. **Request rate** — Rate of HTTP requests per second by status class
185+
2. **Error rate** — Percentage of 5xx responses
186+
3. **Latency (p50 / p95 / p99)** — Request duration quantiles
187+
4. **Indexer lag** — Ledger lag gauge & timeseries
188+
5. **Queue depths** — BullMQ waiting job counts per queue
189+
6. **Horizon health** — Up/down status
190+
7. **Database pool** — Open connections
191+
8. **Node.js resource usage** — Memory, CPU, event loop lag
192+
193+
---
194+
195+
## Logging
196+
197+
- **Format:** JSON structured logs in production, pretty-print in development
198+
- **Levels:** `trace`, `info`, `warn`, `error`, `fatal`
199+
- **Correlation:** Every log line includes a `correlationId` — pass the
200+
`x-correlation-id` or `x-request-id` header from the client
201+
- **Redaction:** The following fields are automatically redacted:
202+
`authorization`, `cookie`, `x-api-key`, `password`, `secret`, `token`, `refreshToken`
203+
204+
**Log level configuration:**
205+
```
206+
LOG_LEVEL=info # default: info in prod, trace in dev
207+
LOG_PRETTY=false # default: false in prod, true in dev
208+
```
209+
210+
---
211+
212+
## Sentry (optional)
213+
214+
`@sentry/nestjs` is installed but not configured. To enable:
215+
216+
1. Set `SENTRY_DSN` in environment
217+
2. Initialize Sentry in `main.ts`:
218+
219+
```typescript
220+
import * as Sentry from '@sentry/nestjs';
221+
222+
Sentry.init({
223+
dsn: process.env.SENTRY_DSN,
224+
environment: process.env.NODE_ENV || 'development',
225+
});
226+
```

0 commit comments

Comments
 (0)