Skip to main content

日志策略 - Nginx, NestJS, Pino

在 Nginx 中动态查看 IP 访问情况;

在 Pino 中动态查看用户访问情况。

Nginx

Nginx 的日志管理是运维和开发中非常重要的一环。通常分为访问日志 (access_log)错误日志 (error_log)

以下是关于 Nginx 日志记录方式及常用查看工具的详细说明:


一、 Nginx 日志记录方式

1. 记录到本地文件(最常用)

这是默认方式,Nginx 将每一条请求记录在服务器的磁盘文件中。

  • 配置指令access_loglog_format
  • 自定义格式:可以自定义日志包含的字段(如 IP、时间、请求路径、状态码、响应时间等)。
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
    access_log /var/log/nginx/access.log main;
  • 现代推荐:现在很多场景推荐使用 JSON 格式 记录日志,方便程序直接解析(如 ELK、GoAccess)。

2. 记录到 Syslog

Nginx 可以将日志直接发送到系统日志服务(Syslog),便于集中管理。

  • 配置示例
    access_log syslog:server=127.0.0.1:514,facility=local7,tag=nginx,severity=info;

3. 记录到标准输出 (stdout/stderr)

这在 Docker 或 Kubernetes 环境中非常常见。Nginx 将日志打到屏幕,由容器引擎(如 Docker Engine)捕获并存储。

  • 实现方法:通常将日志文件软链接到 /dev/stdout

二、 常用的查看与搜索工具

根据需求从简单到复杂,可以将工具分为以下几类:

1. 基础命令行工具(快、准、稳)

适用于快速排查问题,无需安装额外软件。

  • tail -f: 实时查看最新日志。
    • tail -f /var/log/nginx/access.log
  • grep: 搜索特定关键词(如某个 IP 或 404 状态码)。
    • grep "404" /var/log/nginx/access.log
  • awk: 统计与过滤。比如统计访问量前 10 的 IP。
    • awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -n 10
  • zgrep / zcat: 直接搜索压缩过的旧日志(.gz 文件)。

2. 实时交互式工具(推荐)

适用于需要可视化、但不想搭建复杂系统的场景。

  • GoAccess (强烈推荐):
    • 特点:极快,支持在 Linux 终端显示,也可以生成实时的 HTML 报告。
    • 场景:想直观看到哪个 URL 访问最多、哪些 IP 异常、响应时间分布等。
  • Angie / Nginx VTS:
    • 通过安装模块,在网页上实时查看 Nginx 的流量状态。

3. 集中式日志管理系统(企业级)

适用于多台服务器、需要长期存储和复杂检索的情况。

  • ELK Stack (Elasticsearch, Logstash, Kibana):
    • 行业标准。Logstash/Filebeat 收集日志,Elasticsearch 存储,Kibana 展示。
    • 优点:功能极其强大,支持复杂的报表和搜索。
  • Loki (Grafana Labs):
    • 被称为“日志界的 Prometheus”,比 ELK 更轻量,适合 Kubernetes 环境。
  • Graylog:
    • 比 ELK 部署更简单,自带告警功能,非常适合管理 Syslog。

4. 云原生与分析工具

  • 阿里日志服务 SLS / AWS CloudWatch: 如果在云上,直接使用云厂商提供的服务,省去运维成本。
  • SQL 分析: 如果将日志导入 ClickHouse 或 StarRocks,可以用 SQL 进行亿级数据的秒级分析。

总结:我该选哪个?

  1. 随便看看 / 查个报错:直接用 tail -fgrep
  2. 看今日流量排行 / 统计分析:安装 GoAccess(只需一个命令即可看到精美报表)。
  3. 多台服务器 / 长期排查分析:搭建 ELKLoki
  4. 容器环境:直接 kubectl logs 或配套 Loki

小建议:无论用什么工具,将 Nginx 日志格式配置为 JSON 会让你后续的所有搜索和分析工作轻松 10 倍。

NestJS

在 NestJS 开发中,日志记录通常分为三个层次:内置日志生产级日志库、以及接入外部日志平台

以下是 NestJS 常用的日志记录方式以及配套的查看/搜索工具:


一、 NestJS 常用的日志记录方式

1. 内置 Logger (适合开发环境)

NestJS 自带了一个 Logger 类,可以打印带有颜色和类名的格式化日志。

  • 优点:零配置,开箱即用。
  • 缺点:功能简单,不支持日志分级写入文件,不方便在生产环境下检索。

2. Pino (推荐,性能之选)

目前 NestJS 社区最推崇的是 Pino。它以极高的性能著称,且默认输出 JSON 格式。

  • 配合库nestjs-pino
  • 特点
    • 性能极高:比 Winston 快 5-10 倍。
    • 结构化日志:默认输出 JSON,非常方便被 Logstash 或 Fluentd 解析。
    • 自动记录请求:可以自动记录 HTTP 请求的详细信息(URL、状态码、响应时间)。

3. Winston (老牌且全能)

Winston 是 Node.js 领域最成熟的日志库。

  • 配合库nest-winston
  • 特点
    • 配置丰富:支持多种 Transport(控制台、文件、HTTP、MongoDB 等)。
    • 生态强大:几乎可以对接任何存储媒介。

4. 访问日志 (Access Logs) 的实现

对于 HTTP 访问日志,通常有两种实现方式:

  • 中间件 (Middleware):使用 morgan(Node.js 经典访问日志中间件)或 pino-http
  • 拦截器 (Interceptor):通过 NestJS 拦截器手动记录请求参数和执行时间。

二、 常见的查看与搜索工具

日志记录下来后,如何查看和搜索是关键。通常根据项目规模分为以下几类方案:

1. 轻量级方案 (中小型项目)

如果你直接将日志写到了服务器的文件中:

  • jq:命令行工具。如果你的日志是 JSON 格式,tail -f access.log | jq 可以美化输出并进行过滤。
  • PM2 Log Management:如果你用 PM2 部署,可以使用 pm2 logs 查看。
  • Dozzle:如果你使用 Docker,Dozzle 是一个非常轻量级的 Web 界面,可以实时查看所有容器日志。

2. 自建日志中心 (主流企业级方案)

  • ELK Stack (Elasticsearch, Logstash, Kibana)
    • Logstash/Filebeat:负责收集日志。
    • Elasticsearch:负责存储和索引。
    • Kibana:前端可视化界面,搜索功能极其强大,是行业标准
  • Grafana Loki
    • 被称为“日志界的 Prometheus”。
    • 比 ELK 更轻量,因为它不索引全文,只索引标签。非常适合在 Kubernetes 环境中使用,配合 Grafana 界面展示。

3. 云原生与 SaaS 方案 (省心之选)

  • AWS CloudWatch Logs / AliCloud SLS:云厂商自带的日志服务,配置简单,支持全文搜索。
  • Datadog / New Relic:全家桶式的监控方案,日志、链路追踪 (APM) 和指标集成在一起,但价格较贵。
  • Sentry:虽然 Sentry 主要是做错误追踪,但它现在也支持记录面包屑 (Breadcrumbs) 和简单的日志流,是捕获异常的首选。

三、 总结与建议方案

场景推荐方案
本地开发NestJS 内置 Logger
生产环境日志库nestjs-pino (输出 JSON 格式)
访问日志收集pino-httpmorgan
查看与搜索 (中小型)Grafana Loki + Grafana (轻量、现代)
查看与搜索 (大型/复杂)ELK Stack (Kibana 是搜索体验天花板)
查看与搜索 (云原生)直接使用 AWS CloudWatch 或 阿里云 SLS

最佳实践建议:

在 NestJS 中,始终建议将日志以 JSON 格式 输出到 Stdout (标准输出)。这样符合“云原生 12 因素应用”理念,无论是 Docker、K8s 还是日志收集插件,都能轻松捕获并处理这些日志。

为啥用 Pino 替代 console.log?

在 NestJS 中使用 pino(通常通过 nestjs-pino 库)比原生的 console.log 效率要高出 非常多,在极端情况下,性能差距可以达到 5 倍到 20 倍

以下是深度对比和分析:

1. 性能基准 (Benchmarks)

根据 Pino 官方提供的基准测试(在 Node.js 环境下),每秒处理的日志条数对比大致如下:

  • console.log: 约 10,000 - 20,000 条/秒 (受限于同步阻塞和字符串格式化)
  • Winston: 约 20,000 - 40,000 条/秒
  • Pino: 约 200,000 - 300,000 条/秒
  • Pino (Extreme mode): 甚至可以更高。

2. 为什么 Pino 效率更高?

A. 非阻塞 I/O (Asynchronous Logging)

  • console.log: 在 Node.js 中,如果输出目标是 TTY(终端),它是同步阻塞的。这意味着当你的代码调用 console.log 时,事件循环(Event Loop)会暂停,直到日志写完。在高并发的 NestJS 应用中,这会导致严重的延迟。
  • Pino: 默认支持异步写入。它通过内部缓存和批量写入技术(使用 pino.destination()),尽量减少对主线程的占用。

B. 序列化开销

  • console.log: 每次调用都会调用 Node.js 内部的 util.format,这个过程涉及复杂的对象检查和字符串拼接,非常耗 CPU。
  • Pino: 专门优化了 JSON 的序列化(使用 fast-json-stringify 思想)。由于现代日志系统(如 ELK, Loki)通常需要 JSON 格式,Pino 直接生成 JSON 字符串,避免了二次处理。

C. 延迟计算

  • Pino 尽量推迟字符串化的过程。如果日志级别不匹配(例如:当前是 info 级别,但你写了 debug 日志),Pino 会以极低的开销直接跳过,而 console.log 往往已经在参数传递阶段完成了一些字符串处理。

3. 在 NestJS 中的具体表现

NestJS 默认的 Logger 类是对 console 的一层封装。

  • 内存占用:在大流量下,console.log 因为阻塞会导致请求堆积,从而引起内存上升。Pino 的内存占用非常稳定。
  • 吞吐量:使用 nestjs-pino 后,你的 API 响应时间(Response Time)在压力测试下通常会下降 10%-30%,因为记录请求日志的时间被大幅压缩了。

4. 什么时候该选 Pino?

  1. 高并发应用:如果你的 NestJS 应用每秒处理超过几百个请求。
  2. 生产环境:生产环境需要结构化日志(JSON),以便于日志分析。
  3. 对延迟敏感:不希望因为打印一行日志而增加几毫秒的接口延迟。

5. 如何在 NestJS 中获得最高性能?

如果你决定使用 Pino,建议这样配置以榨干性能:

// app.module.ts
LoggerModule.forRoot({
pinoHttp: {
// 1. 生产环境关闭美化打印 (prettyPrint),因为美化非常耗性能
transport: process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' }
: undefined,
// 2. 使用异步 destination (Extreme mode)
// 注意:极端情况下进程崩溃可能丢失最后几行日志
dest: pino.destination({ sync: false })
},
})

总结

pino 相比 console.log 不是微小的改进,而是量级的飞跃。

  • 如果只是个人小项目本地调试console.log 的性能损失可以忽略不计。
  • 对于任何严肃的后端项目,切换到 Pino 能显著降低 CPU 负载并提升系统的最大并发能力。

Github Pino

nestjs-pino

A Complete Guide to Pino Logging in Node.js

Pino 日志文件

Pino 是 Node.js 中一个高性能的 JSON 日志库,默认将日志输出到 stdout(标准输出)。要将日志写入文件,有几种常见方式,推荐使用最新版本(v7+)的 transports 机制,尤其是内置的 pino/file transport。

1. 最简单的方式:直接写入文件(不输出到控制台)

使用 pino.destination() 指定文件路径:

const pino = require('pino');

const logger = pino({
level: 'info' // 可选,设置日志级别
}, pino.destination('./logs/app.log')); // 指定文件路径(会自动创建目录如果不存在?需确保目录存在)

logger.info('Hello, 这是一条 info 日志');
logger.error('这是一条 error 日志');
  • 日志会以 JSON 格式写入 app.log 文件。
  • 注意:确保日志目录(如 ./logs)存在,否则会报错。

2. 推荐生产方式:使用 transports 写入文件

Pino v7+ 引入 transports,支持在 worker thread 中处理日志,避免阻塞主线程。

const pino = require('pino');

const transport = pino.transport({
target: 'pino/file', // 内置文件 transport
options: { destination: './logs/app.log' } // 文件路径
});

const logger = pino(transport);

logger.info('日志写入文件');
  • 如果需要同时输出到控制台(开发常见)和文件,可以使用多目标(targets):
const pino = require('pino');

const transport = pino.transport({
targets: [
{
level: 'info',
target: 'pino-pretty', // 输出到控制台并美化(需 npm i pino-pretty)
options: {} // pino-pretty 可选配置,如 colorize: true
},
{
level: 'trace',
target: 'pino/file',
options: { destination: './logs/app.log' }
}
]
});

const logger = pino(transport);
  • pino-pretty 用于开发环境美化日志(彩色、可读),生产环境建议关闭。

3. 其他高级选项

  • 日志轮转(rotation):内置 pino/file 不支持轮转,推荐第三方如 pino-roll

    npm install pino-roll
    const transport = pino.transport({
    target: 'pino-roll',
    options: {
    file: './logs/app.log',
    frequency: 'daily', // 每天轮转
    mkdir: true
    }
    });
  • 同时写入多个文件:使用 pino-multi-stream 或自定义 transports。

注意事项

  • Pino 默认输出 JSON 格式,便于机器解析(如 ELK、Splunk)。
  • 生产环境避免使用 prettyPrintpino-pretty 到文件(会影响性能)。
  • 安装 Pino:npm install pino
  • 如果需要美化控制台输出:npm install -D pino-pretty

日志轮转

Pino 的最新版本(截至 2025 年 12 月)是 v10.1.0

在 Pino 的最新版本中,使用 transport 方式写日志文件(例如通过内置的 pino/file transport 或其他目标),本身并不内置支持日志文件的自动 roll(rotation/rolling)功能。Pino 核心设计注重极致性能,默认的 file transport 只支持简单写入文件或文件描述符,不包含按大小、时间等自动轮转日志文件的机制。

不过,Pino 的 transport 系统非常灵活,你可以通过第三方 transport 轻松实现 roll 功能。最推荐且常用的方式是使用社区维护的 pino-roll 包(由 Pino 核心维护者之一开发),它专门为 Pino transport 提供了自动日志文件轮转支持:

  • 支持按 文件大小(如 10MB)、时间频率(如每日、每小时)或两者结合进行自动 rotation。
  • 支持保留旧日志文件数量限制、压缩、符号链接等功能。

使用示例

import pino from 'pino';

const transport = pino.transport({
targets: [
{
target: 'pino-roll', // 使用 pino-roll transport
level: 'info',
options: {
file: './logs/app.log', // 主日志文件路径
mkdir: true, // 自动创建目录
size: '10m', // 达到 10MB 时轮转
frequency: 'daily', // 或按时间轮转(如 daily、hourly)
limit: { count: 7 }, // 保留最近 7 个旧文件
compress: 'gzip', // 压缩旧文件
symlink: 'current.log' // 可选:创建指向当前文件的符号链接
}
}
]
});

const logger = pino(transport);
logger.info('Hello, Pino with rotation!');

安装方式:npm install pino-roll

其他备选第三方 transport(如 pino-rotating-file-stream 等)也能实现类似功能,但 pino-roll 是性能最佳且最活跃的推荐方案。

如果在生产环境,还可以结合系统工具如 logrotate(Linux)来外部管理日志轮转,但使用 transport 内的 pino-roll 更简单且跨平台。

总结:不内置支持,但通过 transport 生态(尤其是 pino-roll)非常容易且高效地实现 roll 方式写日志文件

如何查看日志

Pino 日志文件是 NDJSON(每行一个 JSON 对象)格式的,适合机器解析,但直接查看时不太友好。下面是几种简单实用的方法来查看和搜索日志内容,从最基础到稍高级,按推荐顺序排序:

1. 最简单查看:用 cat + pino-pretty 美化输出(强烈推荐)

先安装 pino-pretty(Pino 官方美化工具):

npm install -g pino-pretty  # 全局安装,便于命令行使用

然后直接美化查看日志文件:

cat logs/app.log | pino-pretty

或更高效(避免全文件加载到内存):

tail -f logs/app.log | pino-pretty  # 实时尾随查看(生产调试常用)

常用选项:

  • -c:启用颜色
  • -i pid,hostname:忽略某些字段(如 pid、hostname)
  • -t 'yyyy-mm-dd HH:MM:ss':格式化时间戳为可读格式
  • -S:单行显示(错误栈除外)

示例:

cat logs/app.log | pino-pretty -c -i pid,hostname -t 'yyyy-mm-dd HH:MM:ss'

输出会变成彩色、可读的格式,像:

2025-12-24 10:30:45 INFO: Hello, 这是一条 info 日志

2. 简单搜索:用 grep 快速过滤

直接在原始 JSON 中搜索关键词(msg 字段常见):

grep '关键词' logs/app.log

结合美化:

grep '错误关键词' logs/app.log | pino-pretty -c

高级点:搜索特定级别(如 error):

grep '"level":50' logs/app.log | pino-pretty  # level 50 是 error

3. 结构化搜索:用 jq(JSON 命令行处理器)

如果需要精确查询 JSON 字段,安装 jqhttps://stedolan.github.io/jq/):

# macOS
brew install jq
# Linux
sudo apt install jq

示例:

  • 查看所有日志并美化:
    cat logs/app.log | jq .
  • 搜索包含特定 msg 的日志:
    cat logs/app.log | jq 'select(.msg | test("关键词"))'
  • 只提取 msg 和时间:
    cat logs/app.log | jq '{time: .time, level: .level, msg: .msg}'
  • 过滤 error 级别:
    cat logs/app.log | jq 'select(.level >= 50)'

结合 pino-pretty:

cat logs/app.log | jq 'select(.msg | test("错误"))' | pino-pretty

4. 其他简单工具

  • less 查看(支持翻页):
    less logs/app.log
    在 less 中按 /关键词 搜索。
  • tail 实时监控:
    tail -f logs/app.log | pino-pretty -c

5. 如果日志量大,推荐进阶方案

  • 上传到日志平台(如 Loki + Grafana、ELK、Better Stack、SigNoz),支持全文搜索、过滤、仪表盘。
  • 但对于本地快速查看,上面 1-3 就足够简单高效。

这些方法不需要额外代码修改,直接在命令行操作。

Pino in pd

安装:

$ npm i pino

ESM 用法:

import pino from "pino";
const logger = pino({ level: process.env.LOG_LEVEL || "info" });

logger.info("Logging from Pino!");
const child = logger.child({ origin: 'pd foo' })
child.info('Message from child!')

输出:

{"level":30,"time":1766580832931,"pid":1693724,"hostname":"HP2023","msg":"Logging from Pino!"}
{"level":30,"time":1766580832932,"pid":1693724,"hostname":"HP2023","origin":"pd foo","msg":"Message from child!"}