0%

告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南

告别笨重的 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/ 直接查看实时日志仪表盘,且日志包含完整的 ReqRes 结构化数据。


🛠️ 第一步:NestJS 结构化日志改造

要想日志查得爽,日志格式必须标准。我们使用 winston 来生成 JSON 格式的日志。

1. 安装依赖

1
pnpm add nest-winston winston

2. 配置 Winston (app.module.ts)

重点是使用 winston.format.json(),这样 Promtail 才能轻松解析。

1
2
3
4
5
6
7
8
9
10
11
12
// 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,把请求参数、响应体、耗时都记录下来。

1
2
3
4
5
6
7
8
9
10
// 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 中一键拉起所有服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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,它决定了如何解析你的日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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 配置:

1
2
3
4
5
location /log/ {
proxy_pass http://localhost:3001; # 👈 转发到 Grafana 端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

踩坑指南:

  1. Grafana 白屏/404: 必须配置 GF_SERVER_SERVE_FROM_SUB_PATH=true
  2. 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/

  1. 实时流: Live 模式下看着日志一条条刷出来,极度舒适。
  2. 精准检索: 输入 {app="nest-prisma-app", level="error"} 瞬间定位所有错误。
  3. 上下文详情: 点击日志展开,直接看到 req.bodyres.body,再也不用猜用户传了什么参数!

总结
这套 PLG 方案占用资源极少(几十 MB 内存),但功能却非常强大。配合 NestJS 的结构化日志,是中小型项目监控的不二之选。