1. Structured Logging (Pino)
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty', options: { colorize: true } }
: undefined,
redact: ['req.headers.authorization', 'password'],
})
// Context logging
const reqLogger = logger.child({ requestId: crypto.randomUUID() })
reqLogger.info({ userId: '123', action: 'login' }, 'User logged in')
reqLogger.error({ err, orderId: '456' }, 'Payment failed')
// HTTP request logging (middleware)
import pinoHttp from 'pino-http'
const httpLogger = pinoHttp({ logger })
2. OpenTelemetry
// tracing.ts — import first!
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://jaeger:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true },
'@opentelemetry/instrumentation-redis': { enabled: true },
}),
],
})
sdk.start()
process.on('SIGTERM', () => sdk.shutdown())
node --require ./tracing.js app.js
3. Prometheus Metrics
import { Registry, Counter, Histogram, collectDefaultMetrics } from 'prom-client'
const register = new Registry()
collectDefaultMetrics({ register })
const httpRequests = new Counter({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'path', 'status'],
registers: [register],
})
const httpDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration',
labelNames: ['method', 'path'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
registers: [register],
})
// Middleware
function metricsMiddleware(req, res, next) {
const end = httpDuration.startTimer({ method: req.method, path: req.path })
res.on('finish', () => {
httpRequests.inc({ method: req.method, path: req.path, status: res.statusCode })
end()
})
next()
}
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType)
res.end(await register.metrics())
})
4. Health Checks
app.get('/health', async (req, res) => {
const checks = {
uptime: process.uptime(),
timestamp: Date.now(),
status: 'ok' as string,
checks: {} as Record<string, string>,
}
try {
await pool.query('SELECT 1')
checks.checks.database = 'ok'
} catch {
checks.checks.database = 'error'
checks.status = 'degraded'
}
try {
await redis.ping()
checks.checks.redis = 'ok'
} catch {
checks.checks.redis = 'error'
checks.status = 'degraded'
}
const statusCode = checks.status === 'ok' ? 200 : 503
res.status(statusCode).json(checks)
})
5. Graceful Shutdown
async function gracefulShutdown(signal: string) {
logger.info(`Received ${signal}. Starting graceful shutdown...`)
// Stop accepting new connections
server.close()
// Drain existing connections (max 10s)
const timeout = setTimeout(() => {
logger.error('Forced shutdown after timeout')
process.exit(1)
}, 10000)
try {
await pool.end() // Close DB connections
await redis.quit() // Close Redis
clearTimeout(timeout)
logger.info('Shutdown complete')
process.exit(0)
} catch (err) {
logger.error({ err }, 'Error during shutdown')
process.exit(1)
}
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
process.on('SIGINT', () => gracefulShutdown('SIGINT'))
6. Production Checklist
| Hạng mục | Checklist |
|---|---|
| Logging | Pino structured, request IDs, log levels |
| Monitoring | Prometheus + Grafana, OpenTelemetry |
| Error Tracking | Sentry, unhandledRejection handler |
| Health Checks | /health endpoint, readiness probe |
| Graceful Shutdown | SIGTERM handler, connection draining |
| Security | Helmet, CORS, rate limiting, input validation |
| Performance | Cluster/PM2, connection pooling, caching |
🎉 Hoàn thành series Node.js Core: Từ Cơ bản đến Nâng cao!