通过日志优化 Ubuntu 上 Node.js 应用响应时间的实操方案
一 目标与关键指标
二 埋点与日志配置
// 假设已使用 uuid/v4 生成 traceId
const uuid = require('uuid').v4;
const express = require('express');
const app = express();
app.use((req, res, next) => {
const start = process.hrtime.bigint();
const traceId = req.headers['x-trace-id'] || uuid();
req.traceId = traceId;
res.setHeader('x-trace-id', traceId);
res.on('finish', () => {
const [sec, ns] = process.hrtime.bigint().split(',');
const durationMs = Number(sec * 1n + ns / 1_000_000n);
// 建议接入结构化日志库(winston/pino),此处用 console 示意
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'info',
service: 'my-api',
env: process.env.NODE_ENV,
host: os.hostname(),
traceId,
msg: 'http_request',
method: req.method,
url: req.url,
route: req.route?.path,
statusCode: res.statusCode,
durationMs,
userAgent: req.get('user-agent'),
ip: req.ip
}));
});
next();
});
// const winston = require('winston');
// const DailyRotateFile = require('winston-daily-rotate-file');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new DailyRotateFile({
filename: '/var/log/nodejs/app-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '100m',
maxFiles: '14d'
}),
new winston.transports.Console({ format: winston.format.simple() })
]
});
// const morgan = require('morgan');
// const logger = winston.createLogger({...}); // 如上
app.use(morgan('combined', { stream: { write: msg => logger.info(msg.trim()) } }));
// const { Pool } = require('pg'); const pool = new Pool();
const start = Date.now();
pool.query('SELECT * FROM users WHERE id = $1', [userId], (err, res) => {
const duration = Date.now() - start;
logger.info('db_query', {
query: 'SELECT * FROM users WHERE id = $1', // 生产请脱敏
durationMs: duration,
rows: res?.rowCount,
error: err?.message
});
});
三 采集存储与可视化告警
# /etc/logrotate.d/nodejs
/var/log/nodejs/*.log {
daily
rotate 7
missingok
notifempty
compress
delaycompress
sharedscripts
create 0640 www-data www-data
}
四 从日志定位瓶颈与闭环优化
grep "ERROR" combined.log | wc -lawk '/2025-11-28 10:00:00/,/2025-11-28 11:00:00/' combined.logjq -s 'sort_by(-.durationMs) | .[0:10]' combined.logawk '{sum+=$2; count++; arr[NR]=$2} END {asort(arr); p95=arr[int(count*0.95)]; print "avg:", sum/count, "ms; p95:", p95, "ms"}' durations.txtlog_min_duration_statement(阈值如 100ms);pg_stat_statements 找出最耗时语句并优化(索引、重写、分页/游标、批量)。