告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
💡 前言 : 在微服务和容器化大行其道的今天,日志监控是必不可少的一环。传统的 ELK (Elasticsearch, Logstash, Kibana) 方案虽然强大,但对于中小型项目来说,资源占用过高(尤其是 ES)。 本文将带你实战一套轻量级的日志监控方案:Promtail + Loki + Grafana (PLG) ,并结合 NestJS 实现结构化日志的采集与可视化。
🚀 为什么选择 PLG?
- Loki: 被称为 "云原生的 Prometheus",它不索引日志全文,只索引 Label,因此资源占用极低,写入速度极快。
- Promtail: 专门为 Loki 设计的日志采集器,配置简单,支持多种抓取方式。
- Grafana: 颜值即正义,统一的监控大屏,不仅能看指标(Metrics),还能看日志(Logs)。
目标效果 : 通过 Nginx 反向代理,访问 https://your-domain.com/log/ 直接查看实时日志仪表盘,且日志包含完整的 Req 和 Res 结构化数据。
🛠️ 第一步:NestJS 结构化日志改造
要想日志查得爽,日志格式必须标准。我们使用 winston 来生成 JSON 格式的日志。
1. 安装依赖
bash
pnpm add nest-winston winston
2. 配置 Winston (app.module.ts)
重点是使用 winston.format.json(),这样 Promtail 才能轻松解析。
typescript
// src/app.module.ts
WinstonModule.forRoot({
transports: [
new winston.transports.File({
filename: 'logs/combined.log',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(), // 👈 关键点
),
}),
],
})
3. 全局日志中间件
手写一个 Middleware,把请求参数、响应体、耗时都记录下来。
typescript
// src/common/middleware/logging.middleware.ts
// ...省略部分代码...
this.logger.info(
`${method} ${originalUrl} ${statusCode} ...`,
{
req: { body: req.body, query: req.query }, // 👈 结构化数据
res: { body: parsedResponseBody },
context: 'HTTP',
}
);
🐳 第二步:Docker Compose 编排 PLG
在 docker-compose.prod.yml 中一键拉起所有服务。
yaml
services:
# 你的应用服务
app:
# ...
volumes:
- app_logs:/app/logs # 👈 挂载卷,把日志暴露出来
# 日志聚合
loki:
image: grafana/loki:2.9.2
command: -config.file=/etc/loki/local-config.yaml
# 日志采集
promtail:
image: grafana/promtail:2.9.2
volumes:
- ./plg/promtail/config.yaml:/etc/promtail/config.yaml
- app_logs:/var/log/app # 👈 读取同一个卷
depends_on:
- loki
# 可视化
grafana:
image: grafana/grafana:latest
environment:
- GF_SERVER_ROOT_URL=https://your-domain.com/log/ # 👈 子路径部署关键配置
- GF_SERVER_SERVE_FROM_SUB_PATH=true
volumes:
- ./plg/grafana/provisioning:/etc/grafana/provisioning # 👈 自动化配置
⚙️ 第三步:Promtail 采集配置
Promtail 的核心在于 pipeline_stages,它决定了如何解析你的日志。
yaml
# plg/promtail/config.yaml
scrape_configs:
- job_name: nest_logs
static_configs:
- targets: ['localhost']
labels:
app: nest-prisma-app
__path__: /var/log/app/*.log
pipeline_stages:
- json: # 👈 解析 JSON,把 req, res 提取出来
expressions:
level: level
req: req
res: res
- labels: # 👈 把 level 变成标签,方便筛选错误日志
level:
🔌 第四步:Nginx 反向代理与 Grafana 子路径
为了安全和方便,我们通常把 Grafana 藏在 Nginx 后面,通过 /log/ 访问。
Nginx 配置:
nginx
location /log/ {
proxy_pass http://localhost:3001; # 👈 转发到 Grafana 端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
踩坑指南:
- Grafana 白屏/404 : 必须配置
GF_SERVER_SERVE_FROM_SUB_PATH=true。 - Proxy Pass 斜杠 : Nginx 中
proxy_pass http://localhost:3001;后面尽量别带/,让 Grafana 自己处理路径前缀。
✨ 第五步:自动化 Dashboard (Provisioning)
不要每次部署都手动配数据源!使用 Grafana Provisioning 功能。
在 plg/grafana/provisioning/datasources/datasource.yml 中定义 Loki 数据源,容器启动时自动加载。这样即使容器销毁,配置也不会丢。
🎉 最终效果
部署完成后,打开 https://your-domain.com/log/:
- 实时流 :
Live模式下看着日志一条条刷出来,极度舒适。 - 精准检索 : 输入
{app="nest-prisma-app", level="error"}瞬间定位所有错误。 - 上下文详情 : 点击日志展开,直接看到
req.body和res.body,再也不用猜用户传了什么参数!
总结 这套 PLG 方案占用资源极少(几十 MB 内存),但功能却非常强大。配合 NestJS 的结构化日志,是中小型项目监控的不二之选。