Docker+NestJS+ELK:从零搭建全链路日志监控系统

随着 NestJS 项目容器化部署的落地,传统的 docker logs 查看日志方式已无法满足排查需求。今天花时间搭建了一套基于 Elastic Stack (ELK) 的全链路日志监控系统,实现了日志的自动采集、结构化存储和可视化分析。

1. 整体架构

使用 Docker Compose 编排整个服务栈,包含以下组件:

  • NestJS App: 业务服务,通过 TCP 直接发送日志。
  • Logstash: 接收 TCP 日志流,处理后存入 ES。
  • Elasticsearch: 存储和索引日志数据。
  • Kibana: 可视化面板,配置了中文界面和子路径访问。
  • Fleet Server: 用于管理 Elastic Agent(预留安全监控能力)。

2. 核心配置要点

2.1 Docker Compose 编排

为了在低配服务器上流畅运行,对 ES 和 Logstash 进行了严格的内存限制。

yaml 复制代码
# docker-compose.prod.yml
services:
  elasticsearch:
    image: elasticsearch:7.17.18
    environment:
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 限制内存
      - xpack.security.enabled=true      # 开启安全认证
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data

  kibana:
    image: kibana:7.17.18
    environment:
      - SERVER_BASEPATH=/log            # 配置子路径访问
      - SERVER_REWRITEBASEPATH=true
      - I18N_LOCALE=zh-CN               # 开启中文界面
    ports:
      - "5601:5601"

  logstash:
    image: logstash:7.17.18
    ports:
      - "5000:5000"                     # 暴露 TCP 端口接收日志
    volumes:
      - ./elk/logstash/pipeline/logstash.conf:/usr/share/logstash/pipeline/logstash.conf

2.2 NestJS 自定义 Logstash Transport

为了不依赖文件挂载,直接在 NestJS 中通过 TCP 发送日志。我们需要自定义一个 Winston Transport。

typescript 复制代码
// src/common/logger/logstash.transport.ts
import * as winston from 'winston';
import * as net from 'net';

export class LogstashTransport extends winston.transport {
  // ...省略连接重连逻辑
  log(info: any, callback: () => void) {
    if (this.client) {
      const logEntry = {
        '@timestamp': new Date().toISOString(),
        app: this.appName,
        ...info // 包含 message, level, req, res 等所有元数据
      };
      this.client.write(JSON.stringify(logEntry) + '\n');
    }
    callback();
  }
}

2.3 全量请求上下文捕获

这是今天的重头戏。默认的日志往往缺少入参和出参,排查问题非常痛苦。通过自定义中间件,拦截 res.send 方法,我们可以捕获完整的请求体和响应体。

typescript 复制代码
// src/common/middleware/logging.middleware.ts
@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}

  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now();
    
    // 劫持 res.send 以获取响应体
    const originalSend = res.send;
    let responseBody: any;
    res.send = function (body) {
      responseBody = body;
      return originalSend.apply(this, arguments);
    };

    res.on('finish', () => {
      // 组装结构化日志
      this.logger.info(
        `${req.method} ${req.originalUrl} ${res.statusCode}`, 
        {
          req: { body: req.body, query: req.query, ip: req.ip },
          res: { statusCode: res.statusCode, body: tryParseJSON(responseBody) },
          context: 'HTTP',
        }
      );
    });

    next();
  }
}

3. 部署与运维

编写了 deploy.sh 脚本,实现本地代码一键推送至服务器并重建容器。

解决的一个关键坑是 Kibana 的子路径代理。在 Nginx 中配置:

nginx 复制代码
location /log/ {
    proxy_pass http://localhost:5601/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    # 关键:Kibana 需要知道它运行在代理后面
}

同时在 Kibana 环境变量中必须配合 SERVER_BASEPATH=/logSERVER_REWRITEBASEPATH=true

4. 最终效果

配置完成后,在 Kibana 的 Discover 面板中,不仅能看到系统的启动日志,还能展开每一条 HTTP 请求,查看用户到底传了什么参数(req.body),以及系统到底返回了什么(res.body)。

至此,一个轻量级但功能完备的日志监控系统就搭建完成了。

相关推荐
武子康4 小时前
大数据-184 Elasticsearch Doc Values 机制详解:列式存储如何支撑排序/聚合/脚本
大数据·后端·elasticsearch
四月__4 小时前
http八股
后端
沐森4 小时前
rust并发
后端
喵个咪4 小时前
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:Casbin集成指南
后端·go
墨守城规4 小时前
FutureTask源码分析
后端
梨子同志4 小时前
Java 基础语法详解
后端
bcbnb4 小时前
详细教程:iOS应用中Swift代码混淆步骤与工具推荐
后端
expect7g4 小时前
Paimon源码解读 -- Compaction-8.专用压缩任务
大数据·后端·flink
开心就好20254 小时前
H5 混合应用加密 Web 资源暴露到 IPA 层防护的完整技术方案
后端