Node.js Express 日志记录与性能监控技巧
日志记录基础
在 Node.js Express 应用中,日志记录是追踪应用运行状况、排查问题的重要手段。日志可帮助我们了解请求的处理流程、错误发生的位置以及应用的运行状态。
1. 简单的控制台日志
在 Express 应用中,最基础的日志记录方式就是使用 console.log
。例如,我们可以在处理请求的路由中记录一些信息:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log('收到根路径的请求');
res.send('Hello, World!');
});
const port = 3000;
app.listen(port, () => {
console.log(`应用在端口 ${port} 上运行`);
});
这种方式简单直接,但在生产环境中存在一些局限性。console.log
的输出会直接打印到终端,不利于集中管理和长期存储,而且在应用复杂后,大量的日志输出会导致终端信息杂乱无章。
2. 使用 Winston 进行日志记录
Winston 是一个功能强大的日志记录库,可用于 Node.js 应用。它支持多种日志级别、不同的日志传输方式(如文件、控制台等),还提供了灵活的日志格式设置。
首先,安装 Winston:
npm install winston
然后,在 Express 应用中使用它:
const express = require('express');
const winston = require('winston');
const app = express();
// 创建 Winston 日志记录器
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transport.Console(),
new winston.transport.File({ filename: 'app.log' })
]
});
app.get('/', (req, res) => {
logger.info('收到根路径的请求');
res.send('Hello, World!');
});
const port = 3000;
app.listen(port, () => {
logger.info(`应用在端口 ${port} 上运行`);
});
在上述代码中,我们创建了一个 Winston 日志记录器 logger
。它将日志同时输出到控制台和 app.log
文件中。level
设置为 info
,表示记录 info
级别及以上的日志。format.json()
定义了日志的格式为 JSON,这样在后续分析日志时会更加方便。
3. 日志级别
Winston 支持多种日志级别,从低到高依次为 silly
、debug
、verbose
、info
、warn
、error
。合理使用不同的日志级别可以帮助我们在不同场景下记录合适的信息。
例如,在开发过程中,我们可能会使用 debug
级别记录详细的调试信息:
app.get('/debug', (req, res) => {
logger.debug('这是一条调试信息,在开发中很有用');
res.send('调试页面');
});
而在生产环境中,我们可能更关注 error
和 warn
级别的日志,以发现应用中的潜在问题:
app.get('/error', (req, res) => {
try {
// 模拟一个错误
throw new Error('这是一个故意抛出的错误');
} catch (err) {
logger.error('发生错误', err);
res.status(500).send('服务器内部错误');
}
});
通过这种方式,我们可以根据不同的环境和需求,灵活控制日志的记录内容。
高级日志记录技巧
1. 日志格式化
除了 JSON 格式,Winston 还支持自定义日志格式。我们可以通过 winston.format.printf
来创建自定义格式。例如,我们想要在日志中添加时间戳和请求 ID:
const { format } = winston;
const { combine, timestamp, label, printf } = format;
const myFormat = printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
label({ label: 'MyApp' }),
timestamp(),
myFormat
),
transports: [
new winston.transport.Console(),
new winston.transport.File({ filename: 'app.log' })
]
});
在这个自定义格式中,timestamp
会添加当前时间,label
可以用于标识应用或模块,level
是日志级别,message
是具体的日志内容。这样的格式使得日志更加易读和便于分析。
2. 按请求记录日志
在 Express 应用中,每个请求都有其独特的生命周期。我们可以通过中间件来为每个请求记录完整的日志,包括请求方法、URL、响应状态码等信息。
首先,创建一个日志记录中间件:
const requestLogger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const end = Date.now();
const duration = end - start;
logger.info(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
});
next();
};
app.use(requestLogger);
在这个中间件中,我们在请求开始时记录时间 start
,在响应完成时记录时间 end
,并计算请求处理的时长 duration
。然后,使用 logger
记录请求的方法、URL、响应状态码和处理时长。这样,我们可以清晰地了解每个请求的处理情况,对于性能分析和问题排查非常有帮助。
3. 日志轮换
在生产环境中,随着应用的运行,日志文件会不断增大。为了避免日志文件过大影响系统性能,我们需要进行日志轮换。Winston 提供了 winston-daily-rotate-file
这个插件来实现日志轮换。
首先,安装插件:
npm install winston-daily-rotate-file
然后,修改日志记录器的配置:
const { createLogger, transports } = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const logger = createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new DailyRotateFile({
filename: 'app-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
})
]
});
在这个配置中,filename
定义了日志文件的命名规则,datePattern
表示按小时进行日志轮换。zippedArchive
设置为 true
表示将旧的日志文件压缩,maxSize
限制每个日志文件的大小为 20MB,maxFiles
表示只保留 14 天内的日志文件。通过这种方式,我们可以有效地管理日志文件,确保系统的稳定性和性能。
性能监控基础
性能监控对于 Express 应用的优化至关重要。它可以帮助我们发现应用中的性能瓶颈,及时调整代码和配置,以提供更好的用户体验。
1. 使用 Node.js 内置的 console.time()
和 console.timeEnd()
这是一种简单的性能测量方法,可以用于测量一段代码的执行时间。例如,我们可以在路由处理函数中测量处理请求的时间:
app.get('/performance', (req, res) => {
console.time('request-handling');
// 模拟一些耗时操作
for (let i = 0; i < 1000000; i++);
console.timeEnd('request-handling');
res.send('性能测试页面');
});
在上述代码中,console.time('request - handling')
开始计时,console.timeEnd('request - handling')
结束计时,并在控制台输出这段代码的执行时间。虽然这种方法简单,但它只能在开发环境中进行简单的性能测量,对于生产环境的复杂应用来说,功能有限。
2. 使用 node - prof
进行性能分析
node - prof
是一个用于 Node.js 应用性能分析的工具。它可以生成火焰图,直观地展示应用中函数的调用关系和执行时间,帮助我们找出性能瓶颈。
首先,安装 node - prof
:
npm install -g node - prof
然后,在启动 Express 应用时使用 node - prof
:
node -prof app.js
应用运行一段时间后,使用 node -prof-process
工具来生成火焰图:
node -prof-process `ls -1tr /tmp/node -prof/*.log | tail -1` > profile.svg
这样会生成一个 profile.svg
文件,用浏览器打开这个文件,就可以看到火焰图。火焰图中越宽的部分表示函数执行时间越长,通过分析火焰图,我们可以找出应用中哪些函数是性能瓶颈,从而进行针对性的优化。
高级性能监控技巧
1. 使用 Prometheus 和 Grafana 进行性能监控
Prometheus 是一个开源的系统监控和警报工具包,Grafana 是一个可视化平台,两者结合可以为 Express 应用提供强大的性能监控功能。
首先,安装 prom-client
这个 Node.js 库,用于在 Express 应用中生成 Prometheus 格式的指标数据:
npm install prom-client
然后,在 Express 应用中添加以下代码:
const promClient = require('prom-client');
const app = express();
const collectDefaultMetrics = promClient.collectDefaultMetrics;
const appMetrics = new promClient.Registry();
collectDefaultMetrics({ register: appMetrics });
const httpRequestDurationMicroseconds = new promClient.Histogram({
name: 'http_request_duration_microseconds',
help: 'HTTP 请求处理时间(微秒)',
labelNames: ['method', 'route', 'code'],
buckets: [100, 200, 300, 400, 500, 1000, 2000, 5000]
});
appMetrics.registerMetric(httpRequestDurationMicroseconds);
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const end = Date.now();
const duration = end - start;
httpRequestDurationMicroseconds.labels(req.method, req.url, res.statusCode).observe(duration);
});
next();
});
app.get('/metrics', (req, res) => {
res.set('Content - Type', appMetrics.contentType);
res.end(appMetrics.metrics());
});
在这段代码中,我们创建了一个 appMetrics
注册表,并收集了一些默认指标。同时,定义了一个 httpRequestDurationMicroseconds
直方图,用于记录 HTTP 请求的处理时间。在 Express 中间件中,我们记录每个请求的处理时间,并将数据添加到直方图中。最后,通过 /metrics
路由暴露 Prometheus 格式的指标数据。
接下来,配置 Prometheus 来收集这些指标数据。在 Prometheus 的配置文件 prometheus.yml
中添加以下内容:
scrape_configs:
- job_name: 'express - app'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/metrics'
启动 Prometheus 后,它会定期从 Express 应用的 /metrics
路由收集指标数据。
最后,配置 Grafana 来可视化这些指标数据。在 Grafana 中添加 Prometheus 作为数据源,然后创建仪表盘,通过编写 PromQL 查询语句来展示我们关心的性能指标,如请求平均处理时间、不同状态码的请求数量等。
2. 内存监控
在 Node.js Express 应用中,内存泄漏是一个常见的性能问题。我们可以使用 node - heapdump
这个工具来进行内存监控和分析。
首先,安装 node - heapdump
:
npm install node - heapdump
然后,在 Express 应用中添加以下代码:
const heapdump = require('node - heapdump');
app.get('/heapdump', (req, res) => {
heapdump.writeSnapshot((err, filename) => {
if (err) {
return res.status(500).send('生成堆快照失败');
}
res.send(`堆快照已保存到 ${filename}`);
});
});
当访问 /heapdump
路由时,会生成一个堆快照文件。我们可以使用 Chrome DevTools 等工具来分析这个堆快照文件,找出内存占用过高的对象和潜在的内存泄漏点。
例如,在 Chrome DevTools 中,打开 chrome://inspect
,选择 Open dedicated DevTools for Node
,然后在 Profiles
面板中加载生成的堆快照文件。通过分析对象的引用关系和内存占用情况,我们可以找到哪些对象没有被正确释放,从而进行内存优化。
3. 负载测试
负载测试是评估 Express 应用在高并发情况下性能的重要手段。我们可以使用 Artillery
这个工具来进行负载测试。
首先,安装 Artillery
:
npm install -g artillery
然后,创建一个 test.yml
文件,定义负载测试场景:
config:
target: 'http://localhost:3000'
phases:
- duration: 60
arrivalRate: 10
scenarios:
- flow:
- get:
url: '/'
在这个配置中,target
定义了要测试的 Express 应用地址,phases
定义了测试阶段,这里表示在 60 秒内,以每秒 10 个请求的速率发送请求。scenarios
定义了具体的测试场景,这里是对根路径发送 GET 请求。
运行负载测试:
artillery run test.yml
Artillery 会在测试结束后输出详细的性能报告,包括平均响应时间、每秒请求数、错误率等指标。根据这些指标,我们可以评估应用在高并发情况下的性能,并进行相应的优化,如调整数据库连接池大小、优化代码逻辑等。
通过上述日志记录与性能监控技巧,我们可以更好地管理和优化 Node.js Express 应用,提高应用的稳定性和性能,为用户提供更优质的服务。在实际应用中,需要根据具体的业务需求和场景,灵活选择和组合这些技巧,以达到最佳的效果。同时,随着应用的发展和业务的变化,持续进行日志分析和性能监控,及时发现和解决潜在的问题,是保证应用长期稳定运行的关键。