Monitoring & Logging
Structured Logging
// JSON structured logs
interface LogEntry {
timestamp: string;
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
requestId?: string;
userId?: string;
duration?: number;
error?: string;
metadata?: Record<string, unknown>;
}
function log(entry: LogEntry): void {
console.log(JSON.stringify({
...entry,
timestamp: new Date().toISOString(),
}));
}
// Usage
log({
level: 'info',
message: 'User login successful',
requestId: 'req-123',
userId: 'user-456',
metadata: { ip: '192.168.1.1', userAgent: 'Mozilla/5.0' },
});
log({
level: 'error',
message: 'Database query failed',
requestId: 'req-123',
error: 'Connection timeout',
metadata: { query: 'SELECT * FROM users', duration: 5000 },
});
Python Logging
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
}
if hasattr(record, "request_id"):
log_entry["requestId"] = record.request_id
if record.exc_info:
log_entry["exception"] = self.formatException(record.exc_info)
return json.dumps(log_entry)
# Setup
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger = logging.getLogger("app")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
Prometheus Metrics
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
const registry = new Registry();
const httpRequestDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests',
labelNames: ['method', 'route', 'status'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
registers: [registry],
});
const httpRequestTotal = new Counter({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'route', 'status'],
registers: [registry],
});
const activeConnections = new Gauge({
name: 'active_connections',
help: 'Number of active connections',
registers: [registry],
});
// Middleware
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer();
res.on('finish', () => {
end({ method: req.method, route: req.route?.path || req.path, status: res.statusCode });
httpRequestTotal.inc({ method: req.method, route: req.route?.path || req.path, status: res.statusCode });
});
next();
});
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', registry.contentType);
res.end(await registry.metrics());
});
Health Checks
// Basic health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Deep health check
app.get('/health/ready', async (req, res) => {
const checks = {
database: await checkDatabase(),
redis: await checkRedis(),
external_api: await checkExternalAPI(),
};
const healthy = Object.values(checks).every(c => c.status === 'ok');
res.status(healthy ? 200 : 503).json({
status: healthy ? 'ready' : 'not_ready',
checks,
});
});
async function checkDatabase(): Promise<{ status: string; latency?: number }> {
try {
const start = Date.now();
await db.query('SELECT 1');
return { status: 'ok', latency: Date.now() - start };
} catch (error) {
return { status: 'error' };
}
}
Distributed Tracing (OpenTelemetry)
import { NodeSDK } from '@opentelemetry/sdk-node';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
const sdk = new NodeSDK({
serviceName: 'my-service',
traceExporter: new JaegerExporter(),
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
],
});
sdk.start();
// Custom span
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('my-service');
async function processOrder(orderId: string) {
return tracer.startActiveSpan('processOrder', async (span) => {
try {
span.setAttribute('order.id', orderId);
// ... process logic
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
throw error;
} finally {
span.end();
}
});
}
Alert Rules (Prometheus)
# prometheus.yml
groups:
- name: app-alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"
- alert: HighLatency
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
- alert: ServiceDown
expr: up{job="my-service"} == 0
for: 1m
labels:
severity: critical
Log Levels
| Level | When to Use |
|---|
| DEBUG | Detailed diagnostic info |
| INFO | Normal operations |
| WARN | Unexpected but handled |
| ERROR | Failures requiring attention |
Best Practices
- Use structured JSON logs
- Include request ID for tracing
- Never log secrets, passwords, or PII
- Use appropriate log levels
- Keep log messages concise
- Include context (user ID, request ID)