各位朋友,大家好!我是你们的老朋友,今天咱们来聊聊 Node.js 里的那些“小秘密”,也就是日志管理和监控。这可不是什么枯燥乏味的东西,而是你代码健康的关键!你想想,你的应用就像一辆跑车,日志和监控就像是仪表盘和维护手册,没有它们,你咋知道啥时候该加油,啥时候该修车呢?
一、日志:给你的代码装上“摄像头”
日志,简单来说,就是你的程序运行过程中发生的事情的记录。这可不是简单的“我开始运行了”、“我结束了”这么简单,而是要记录足够的信息,让你在出现问题的时候,能像侦探一样,根据线索找到真凶。
1. 为什么需要日志库?
你可能会说,console.log
不就够了吗?嗯,在小规模项目或者调试的时候,console.log
确实挺方便的。但你想想,如果你的项目越来越大,日志越来越多,console.log
就会变成一场灾难:
- 缺乏分级: 所有信息都混在一起,难以区分重要程度。
- 难以过滤: 你想只看错误信息?对不起,翻到天荒地老吧。
- 缺乏格式化: 日志格式混乱,难以阅读和分析。
- 难以持久化: 只能在控制台看到,重启服务就没了。
- 性能问题: 在生产环境大量使用
console.log
可能会影响性能。
所以,我们需要专业的日志库来解决这些问题。
2. 日志库的选择:Winston vs Pino
Node.js 生态中有很多优秀的日志库,其中最受欢迎的莫过于 Winston 和 Pino。它们各有千秋,咱们来简单对比一下:
特性 | Winston | Pino |
---|---|---|
性能 | 较低 | 极高 |
易用性 | 较高,配置灵活 | 稍低,配置相对简单 |
功能 | 丰富,支持多种 Transport,灵活定制 | 精简,专注于高性能日志输出 |
格式化 | 支持多种格式化方式,例如 JSON, simple | 默认 JSON,可定制 |
使用场景 | 对性能要求不高,需要灵活配置的项目 | 对性能要求极高,需要快速输出日志的项目 |
个人理解 | 功能强大,像瑞士军刀,但有点重 | 轻量级,专注于速度,像一把锋利的匕首 |
3. Winston 的使用:像调酒一样配置你的日志
Winston 就像一个调酒师,你可以根据自己的口味,调制出各种各样的日志。
-
安装:
npm install winston
-
基本使用:
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', // 日志级别,可选:error, warn, info, verbose, debug, silly format: winston.format.json(), // 日志格式,这里使用 JSON transports: [ new winston.transports.Console(), // 输出到控制台 new winston.transports.File({ filename: 'combined.log' }) // 输出到文件 ] }); logger.info('This is an info log'); logger.warn('This is a warning log'); logger.error('This is an error log');
这段代码创建了一个 logger 实例,它会将日志输出到控制台和一个名为
combined.log
的文件中。 注意level
参数,只有高于或者等于level
级别的日志才会被输出。 -
自定义格式:
Winston 提供了强大的格式化功能,你可以自定义日志的输出格式,例如添加时间戳、日志级别等。
const { createLogger, format, transports } = require('winston'); const { combine, timestamp, label, printf } = format; const myFormat = printf(({ level, message, label, timestamp }) => { return `${timestamp} [${label}] ${level}: ${message}`; }); const logger = createLogger({ format: combine( label({ label: 'My Application' }), timestamp(), myFormat ), transports: [new transports.Console()] }); logger.info('Hello, Winston!');
这段代码定义了一个名为
myFormat
的格式,它会将日志格式化成timestamp [label] level: message
的形式。 -
使用 Transport 输出到不同的地方:
Winston 支持多种 Transport,可以将日志输出到不同的地方,例如:
Console
: 输出到控制台File
: 输出到文件HTTP
: 发送到 HTTP 服务器MongoDB
: 存储到 MongoDB 数据库Syslog
: 发送到 Syslog 服务器
你可以根据自己的需求选择合适的 Transport。
-
集成 Morgan 中间件记录 HTTP 请求:
const morgan = require('morgan'); const winston = require('winston'); // 创建一个 Winston logger const logger = winston.createLogger({ format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'http.log' }) ] }); // 创建一个 Morgan 中间件,将日志输出到 Winston logger const stream = { write: message => logger.info(message.trim()) }; const morganMiddleware = morgan( ':method :url :status :res[content-length] - :response-time ms', { stream } ); // 在 Express 应用中使用中间件 const express = require('express'); const app = express(); app.use(morganMiddleware); app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(3000, () => { console.log('Server listening on port 3000'); });
这样,你就可以将 HTTP 请求的详细信息记录到日志文件中了。
4. Pino 的使用:速度与激情的化身
Pino 则是一个追求极致性能的日志库。它使用 JSON 格式输出日志,并且经过优化,可以达到非常高的吞吐量。
-
安装:
npm install pino
-
基本使用:
const pino = require('pino'); const logger = pino({ level: 'info' // 日志级别,可选:error, warn, info, debug, trace }); logger.info('This is an info log'); logger.warn('This is a warning log'); logger.error('This is an error log');
Pino 的配置非常简单,只需要指定日志级别即可。
-
使用 Pino Pretty 格式化输出:
虽然 Pino 默认输出 JSON 格式,但你可以使用
pino-pretty
来格式化输出,方便阅读。npm install pino-pretty
const pino = require('pino'); const pretty = require('pino-pretty'); const logger = pino({ level: 'info' }, pretty()); logger.info('Hello, Pino!');
你还可以通过配置
pino-pretty
来定制输出格式。 -
使用 Sonic Boom 提升性能:
Pino 推荐使用
sonic-boom
来提升日志写入性能。npm install sonic-boom
const pino = require('pino'); const SonicBoom = require('sonic-boom'); const dest = new SonicBoom({ dest: 'my-file.log' }); const logger = pino({ level: 'info' }, dest); logger.info('Hello, Pino!');
二、监控:给你的应用做个“体检”
光有日志还不够,你还需要监控你的应用,了解它的运行状态,例如 CPU 使用率、内存占用、请求延迟等等。 监控就像给你的应用做体检,让你及时发现潜在的问题。
1. 为什么需要监控?
- 及时发现问题: 在用户发现之前,提前发现问题并解决。
- 优化性能: 了解应用的瓶颈,优化性能。
- 容量规划: 预测未来的资源需求,做好容量规划。
- 业务决策: 通过监控数据,了解用户行为,为业务决策提供支持。
2. 监控工具的选择:Prometheus vs Grafana
Prometheus 和 Grafana 是监控领域的黄金搭档。 Prometheus 负责收集和存储监控数据,而 Grafana 负责将这些数据可视化,让你一目了然地了解应用的运行状态。
-
Prometheus:
- 特点: 开源、时序数据库、强大的查询语言 (PromQL)。
- 作用: 收集和存储监控数据。
- 类比: 就像一个辛勤的收割者,默默地收集各种指标。
-
Grafana:
- 特点: 开源、数据可视化平台、支持多种数据源。
- 作用: 将监控数据可视化,创建各种仪表盘。
- 类比: 就像一个精美的画廊,将各种数据展示出来。
3. 使用 Prometheus 监控 Node.js 应用
-
安装 Prometheus 客户端:
npm install prom-client
-
在 Node.js 应用中暴露监控指标:
const express = require('express'); const client = require('prom-client'); const app = express(); // 创建一个 Registry 来注册所有的指标 const register = new client.Registry(); // 创建一个 Gauge 指标,用来记录 HTTP 请求的数量 const httpRequestDurationMicroseconds = new client.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status_code'], buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10] // 你可以自定义 buckets }); register.registerMetric(httpRequestDurationMicroseconds); // 创建一个 Counter 指标,用来记录 HTTP 请求的总数 const totalHttpRequestCounter = new client.Counter({ name: 'http_requests_total', help: 'Total number of HTTP requests.' }); register.registerMetric(totalHttpRequestCounter); // 创建一个 Gauge 指标,用来记录 Node.js 进程的 CPU 使用率 const cpuUsageGauge = new client.Gauge({ name: 'nodejs_process_cpu_usage', help: 'CPU usage of the Node.js process.' }); register.registerMetric(cpuUsageGauge); // 创建一个 Gauge 指标,用来记录 Node.js 进程的内存使用量 const memoryUsageGauge = new client.Gauge({ name: 'nodejs_process_memory_usage_bytes', help: 'Memory usage of the Node.js process in bytes.' }); register.registerMetric(memoryUsageGauge); // 模拟一些业务逻辑 app.get('/', async (req, res) => { const start = Date.now(); // 模拟一些耗时操作 await new Promise(resolve => setTimeout(resolve, Math.random() * 200)); const duration = (Date.now() - start) / 1000; const statusCode = res.statusCode; const method = req.method; const route = req.route ? req.route.path : 'unknown'; // 尝试获取路由,如果获取不到则设置为 'unknown' // 记录 HTTP 请求的持续时间 httpRequestDurationMicroseconds.labels({ method, route, status_code: statusCode }).observe(duration); totalHttpRequestCounter.inc(); res.send('Hello World!'); }); // 创建一个 endpoint,用来暴露 Prometheus 指标 app.get('/metrics', async (req, res) => { res.setHeader('Content-Type', register.contentType); try { const metrics = await register.metrics(); res.send(metrics); } catch (ex) { res.status(500).send(ex); } }); // 定时更新 CPU 和内存使用量 setInterval(() => { const cpuUsage = process.cpuUsage(); cpuUsageGauge.set(cpuUsage.system / 1000000); // Convert to seconds const memoryUsage = process.memoryUsage(); memoryUsageGauge.set(memoryUsage.heapUsed); }, 5000); // 每 5 秒更新一次 app.listen(3000, () => { console.log('Server listening on port 3000'); });
这段代码创建了一个 HTTP 服务器,并在
/metrics
路径下暴露 Prometheus 指标。 访问/metrics
你就能看到一堆文本格式的监控数据了。 -
配置 Prometheus 抓取指标:
在
prometheus.yml
文件中配置 Prometheus 抓取 Node.js 应用暴露的指标。scrape_configs: - job_name: 'nodejs' scrape_interval: 5s # 每 5 秒抓取一次 static_configs: - targets: ['localhost:3000'] # 你的 Node.js 应用的地址 metrics_path: /metrics # 你的 Node.js 应用暴露指标的路径
然后启动 Prometheus。
-
使用 Grafana 可视化监控数据:
在 Grafana 中添加 Prometheus 数据源,并创建仪表盘,将监控数据可视化。 你可以使用 PromQL 查询语言来查询 Prometheus 中的数据,例如:
http_request_duration_seconds_sum
: HTTP 请求的总持续时间。http_request_duration_seconds_count
: HTTP 请求的总数。rate(http_request_duration_seconds_sum[5m])
: 过去 5 分钟内 HTTP 请求的平均持续时间。
4. 监控的最佳实践:
- 选择合适的指标: 监控哪些指标取决于你的应用的需求。一般来说,你需要监控 CPU 使用率、内存占用、磁盘 I/O、网络流量、请求延迟、错误率等指标。
- 设置合理的阈值: 为每个指标设置合理的阈值,当指标超过阈值时,发出告警。
- 定期审查监控配置: 定期审查监控配置,确保其仍然有效。
- 自动化监控: 使用自动化工具来管理和维护监控系统。
三、总结:让你的代码更健康
日志管理和监控是保证 Node.js 应用健康运行的关键。通过使用 Winston 或 Pino 等日志库,你可以记录详细的日志信息,方便排查问题。通过使用 Prometheus 和 Grafana 等监控工具,你可以了解应用的运行状态,及时发现潜在的问题。
记住,好的日志和监控就像一位细心的医生,能帮助你及时发现并治疗代码中的“疾病”,让你的应用更加健康、稳定!
好了,今天的讲座就到这里,希望对你有所帮助! 如果大家有什么问题,欢迎随时提问。 祝大家编程愉快!