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

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

相关推荐
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金3 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛4 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒4 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境4 小时前
提升 Python 代码健壮性的方法大盘点
后端·python