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)。

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

相关推荐
盐水冰5 小时前
【烘焙坊项目】后端搭建(14) - 工作台&导出数据报表
java·后端·学习
小杍随笔6 小时前
【Rust 语言编程知识与应用:闭包详解】
开发语言·后端·rust
小璐资源网6 小时前
从源码看ArrayList与LinkedList的性能差异
后端
紫丁香6 小时前
Dify源码深度剖析3
后端·python·ai·flask·fastapi
IT_陈寒6 小时前
JavaScript开发者必看:3个让代码效率翻倍的隐藏技巧
前端·人工智能·后端
希望永不加班6 小时前
如何在 SpringBoot 里自定义 Spring MVC 配置
java·spring boot·后端·spring·mvc
Welcome_Back6 小时前
SpringBoot后端开发测试全指南
spring boot·后端·log4j
大傻^6 小时前
Spring AI Alibaba MCP协议实战:模型上下文协议集成与工具调用
java·人工智能·后端·spring·elasticsearch·springaialibaba
稻草猫.7 小时前
MyBatis进阶:动态SQL与MyBatis Generator插件使用
java·数据库·后端·spring·mvc·mybatis
qq_256247057 小时前
Docker 部署 OpenClaw 踩坑实录:Web UI 访问、飞书配对及自定义模型配置
后端