npm package: @lark-apaas/nestjs-logger
Description: Nestjs Logger
Latest version: 1.0.16

# NestJS Logger

基于 Pino 的高性能 NestJS 日志库，支持请求追踪和结构化日志。

## 功能特性

- 基于 Pino 的高性能日志记录
- 自动 HTTP 请求追踪（双层日志：info 链路日志 + verbose 详细日志）
- 请求和响应体日志记录（可配置）
- 支持多日志级别
- 独立的 trace 日志文件
- 请求上下文传递
- 日志体长度截断
- **完美兼容 Exception Filter**：
  - 拦截 `response.json()` 和 `response.send()` 方法
  - 能够捕获 Exception Filter 改写的响应体和最终状态码
  - 确保异常日志的完整性和准确性

## 安装

```bash
npm install @lark-apaas/nestjs-logger
```

## 基本使用

### 1. 导入模块

```typescript
import { Module } from '@nestjs/common';
import { LoggerModule } from '@lark-apaas/nestjs-logger';

@Module({
  imports: [LoggerModule],
})
export class AppModule {}
```

### 2. 使用 Logger（完全兼容 NestJS 官方 API）

本库完全遵循 NestJS 官方 Logger API，无缝替换官方 Logger。

```typescript
import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class MyService {
  private readonly logger = new Logger(MyService.name);

  doSomething() {
    // 简单日志
    this.logger.log('This is an info log');

    // 带 context 的日志（最后一个参数）
    this.logger.log('User logged in', 'AuthModule');
    //                                ↑ context

    // 多个参数会用空格连接
    this.logger.log('User', userId, 'logged in from', ip);
    // 输出: User 123 logged in from 192.168.1.1

    // 使用模板字符串
    this.logger.log(`User ${userId} logged in from ${ip}`);

    // 多参数 + context
    this.logger.log('Processing order', orderId, 'OrderService');
    //                                             ↑ context
  }

  handleError() {
    try {
      // 业务逻辑
    } catch (error) {
      // Error 日志 + stack trace（官方推荐方式）
      this.logger.error('Failed to process', error.stack);
      //                                     ↑ 第一个参数自动识别为 stack

      // Error + stack + context
      this.logger.error('Payment failed', error.stack, 'PaymentService');
      //                                  ↑ stack      ↑ context
    }
  }
}
```

**参数规则（与 NestJS 官方一致）**：
- **最后一个字符串参数**：自动识别为 `context`
- **Error/Fatal 方法的第一个参数**：如果是 stack trace 格式，自动识别为 `stack`
- **多个参数**：会用空格连接成一个字符串
- **不支持格式化占位符**：不支持 `%s`, `%d`, `%o` 等占位符，请使用模板字符串

### 3. 使用 logStructured 记录结构化日志（扩展功能）

除了标准的 Logger API，本库还提供了 `logStructured()` 方法用于记录结构化日志：

```typescript
import { Inject, Injectable } from '@nestjs/common';
import { AppLogger } from '@lark-apaas/nestjs-logger';

@Injectable()
export class MyService {
  constructor(
    @Inject(AppLogger) private readonly logger: AppLogger,
  ) {}

  doSomething() {
    // 使用 logStructured 记录结构化日志
    this.logger.logStructured(
      'log',  // 日志级别
      'User action completed',  // 消息
      {  // 元数据对象（会合并到日志字段中）
        action: 'create_order',
        order_id: '123',
        amount: 100,
      },
      'MyService',  // context（可选）
    );
  }
}
```

**两种方式对比**：

| 特性 | 标准 Logger API | logStructured API |
|------|----------------|-------------------|
| **用途** | 常规日志，兼容 NestJS 官方 | 结构化日志，将 meta 合并到字段 |
| **Context** | 最后一个参数自动识别 | 显式传递第4个参数 |
| **消息格式** | 使用模板字符串拼接 | 纯文本消息 |
| **元数据** | 需要手动拼接到消息中 | 作为独立字段存储，便于查询 |
| **使用场景** | 日常日志、错误日志 | 需要结构化查询的业务日志 |

## 环境变量配置

### 基础配置

```bash
# 日志级别: trace, debug, info, warn, error, fatal, silent
# 注意：HTTP 请求追踪日志使用 verbose 级别（对应 Pino 的 trace）
# 生产环境默认为 info，不会打印 verbose 级别的日志
LOGGER_LEVEL=info

# 日志目录
LOG_DIR=logs

# Node 环境（默认：开发环境使用 debug，生产环境使用 info）
NODE_ENV=production
```

### 请求/响应体日志配置

```bash
# 启用请求体日志（默认：false）
LOG_REQUEST_BODY=true

# 启用响应体日志（默认：false）
LOG_RESPONSE_BODY=true

# 注意：即使启用了请求/响应体日志，也需要将日志级别设置为 verbose 才能实际输出
# 因为 HTTP 请求追踪日志使用的是 verbose 级别
LOGGER_LEVEL=verbose

# 日志体最大长度，超过会截断（默认：不限制）
# 不设置此环境变量时，不会进行截断
LOG_MAX_BODY_LENGTH=10000
```

## 请求/响应体日志

### 日志级别说明

HTTP 请求追踪日志使用 **`verbose` 级别**（对应 Pino 的 `trace` 级别），这意味着：

- **生产环境默认不打印**：生产环境默认日志级别为 `info`，不会输出 verbose 级别的日志
- **需要显式启用**：要查看 HTTP 请求追踪日志，需要将 `LOGGER_LEVEL` 设置为 `verbose`
- **细粒度控制**：可以通过日志级别控制是否打印请求追踪，而无需修改 `LOG_REQUEST_BODY` 和 `LOG_RESPONSE_BODY` 配置

### 响应体记录规则

**只针对 JSON 响应记录 body**：
- 自动检测 `Content-Type` 是否包含 `application/json`
- 只有 JSON 响应才会记录 `response_body` 字段
- 文件下载、HTML 页面等非 JSON 响应不会记录 body，避免日志污染
- 这样可以显著减少日志体积，同时保持日志的可读性

### 启用方式

通过环境变量启用：

```bash
# 方式一：仅启用 HTTP 请求追踪（不包含 body）
LOGGER_LEVEL=verbose

# 方式二：启用 HTTP 请求追踪 + 请求/响应体
LOGGER_LEVEL=verbose
LOG_REQUEST_BODY=true
LOG_RESPONSE_BODY=true
```

### 重要说明

1. **双层日志机制**：
   - **info 级别**：始终记录基础链路日志（不含 body），用于生产环境请求追踪
   - **verbose 级别**：记录详细追踪日志（包含 body），用于开发调试

2. **默认不限制长度**：如果不设置 `LOG_MAX_BODY_LENGTH` 环境变量，日志体不会被截断。建议在生产环境设置合理的限制值。

3. **响应体过滤**：
   - 只记录 JSON 响应的 body（检测 Content-Type）
   - 文件下载、HTML、图片等非 JSON 响应不会记录 body
   - 避免记录无法序列化的数据（如 Buffer、Stream）
   - 显著减少日志体积，提高日志可读性

### 日志输出示例

#### Info 级别（链路日志）

```json
{
  "level": "INFO",
  "time": 1234567890,
  "msg": "HTTP request started",
  "method": "GET",
  "path": "/api/users",
  "trace_id": "req-123-456",
  "user_id": "user-001",
  "tenant_id": 24020896,
  "app_id": "",
  "context": "HTTPTraceInterceptor"
}
```

```json
{
  "level": "INFO",
  "time": 1234567890,
  "msg": "HTTP request completed",
  "method": "GET",
  "path": "/api/users",
  "trace_id": "req-123-456",
  "status_code": 200,
  "duration_ms": 125,
  "context": "HTTPTraceInterceptor"
}
```

#### Verbose 级别（详细追踪日志）

```json
{
  "level": "TRACE",
  "time": 1234567890,
  "msg": "HTTP request started",
  "method": "POST",
  "path": "/api/users",
  "trace_id": "req-123-456",
  "request_body": {
    "username": "john_doe",
    "email": "john@example.com",
    "password": "secret123"
  },
  "query_params": {
    "filter": "active"
  },
  "context": "HTTPTraceInterceptor"
}
```

```json
{
  "level": "TRACE",
  "time": 1234567890,
  "msg": "HTTP request completed",
  "method": "POST",
  "path": "/api/users",
  "trace_id": "req-123-456",
  "status_code": 201,
  "duration_ms": 125,
  "response_body": {
    "id": "user-123",
    "username": "john_doe",
    "email": "john@example.com"
  },
  "context": "HTTPTraceInterceptor"
}
```

## 数据截断

默认情况下，不会对日志体进行截断。当设置 `LOG_MAX_BODY_LENGTH` 环境变量后，如果请求/响应体超过指定长度，会自动截断：

```json
{
  "response_body": {
    "_truncated": true,
    "_originalLength": 50000,
    "_data": "{ 前 10000 个字符... }..."
  }
}
```

**建议**：在生产环境设置合理的限制值（如 10000），避免单条日志过大。

## 日志级别映射

NestJS LoggerService 级别到 Pino 级别的映射：

- `fatal` → `fatal`
- `error` → `error`
- `warn` → `warn`
- `log` → `info`
- `debug` → `debug`
- `verbose` → `trace`

## 日志文件

日志会写入以下文件（默认在 `logs/` 目录）：

- `server.log` - 应用日志
- `trace.log` - HTTP 请求追踪日志

## 注意事项

### 架构设计

1. **Exception Filter 兼容性**：
   - 使用 `response.on('finish')` 事件记录日志，确保在响应完全发送后才记录
   - 拦截 `response.json()` 和 `response.send()` 方法，捕获最终发送的响应体
   - 能够正确捕获全局 Exception Filter 设置的最终状态码和响应体
   - 日志记录发生在 NestJS 请求生命周期的最后阶段，确保信息完整准确

2. **请求生命周期**：
   ```
   Middleware → Guards → Interceptor (before) → Pipes → Controller
       ↓
   Interceptor (after/catchError) → Exception Filters → Response Finish (✅ 日志记录)
   ```

3. **实现原理**：
   - **拦截响应方法**：在 Interceptor 中拦截 `res.json()` 和 `res.send()`，存储响应体到 `res.__finalResponseBody`
   - **类型检测**：通过 `Content-Type` header 判断是否为 JSON 响应，只记录 JSON 类型的 body
   - **延迟记录**：不在 Interceptor 的 `tap()` 或 `catchError()` 中记录日志，而是在 `finish` 事件中统一记录
   - **完整信息**：此时可以获取 Exception Filter 处理后的最终状态码和响应体，确保日志准确性

4. **Logger API 兼容性**：
   - **完全兼容 NestJS 官方**：实现与 `@nestjs/common` 的 Logger 完全一致的 API
   - **参数解析规则**：
     - 最后一个字符串参数自动识别为 `context`
     - Error/Fatal 方法的第一个参数如果是 stack trace 格式，自动识别为 `stack`
   - **无缝替换**：可以直接替换项目中的官方 Logger，无需修改业务代码
   - **扩展功能**：提供 `logStructured()` 方法用于结构化日志场景

### 安全性

1. **生产环境默认不打印 HTTP 追踪日志**：由于使用 verbose 级别，生产环境（info 级别）默认不会输出详细日志
2. **双层日志设计**：info 级别始终记录链路日志，verbose 级别记录详细信息，按需启用
3. **按需启用**：需要查看详细追踪时，将 LOGGER_LEVEL 设置为 verbose
4. **敏感数据处理**：日志库不提供脱敏功能，请在业务层或通过中间件处理敏感数据

### 性能

1. verbose 级别会输出所有 HTTP 请求详细日志，可能影响性能
2. 开启请求/响应体日志会增加日志体积和 I/O 开销
3. 大对象的序列化会影响性能
4. 建议在开发/测试环境使用，生产环境按需临时启用

### 存储

1. 开发环境默认启用日志轮转（基于 [rotating-file-stream](https://github.com/iccicci/rotating-file-stream)），活跃文件始终为 `server.log`，归档为 `server.log.1`（最新）→ `server.log.N`（最旧）
2. 默认单文件 10MB，最多保留 5 个归档，总占用上限约 60MB
3. 可通过环境变量控制轮转行为：`LOG_ROTATION_ENABLED`、`LOG_ROTATION_MAX_SIZE`、`LOG_ROTATION_MAX_FILES`
4. verbose 级别 + 请求/响应体日志会显著增加日志量
5. 建议设置 `LOG_MAX_BODY_LENGTH` 限制单条日志大小（默认不限制）

## 最佳实践

### 开发环境

```bash
NODE_ENV=development
# 使用 verbose 级别查看 HTTP 请求追踪
LOGGER_LEVEL=verbose
LOG_REQUEST_BODY=true
LOG_RESPONSE_BODY=true
# 开发环境可以不限制长度，或设置较大值
# LOG_MAX_BODY_LENGTH=50000
```

### 生产环境

```bash
NODE_ENV=production
# 生产环境使用 info 级别，不会打印 HTTP 请求追踪日志
LOGGER_LEVEL=info
# 这两个配置可以保留，只有当 LOGGER_LEVEL=verbose 时才会生效
LOG_REQUEST_BODY=false
LOG_RESPONSE_BODY=false
```

### 故障排查

临时启用详细日志：

```bash
NODE_ENV=production
# 临时开启 verbose 级别查看 HTTP 请求追踪
LOGGER_LEVEL=verbose
LOG_REQUEST_BODY=true
LOG_RESPONSE_BODY=true
LOG_MAX_BODY_LENGTH=10000  # 建议设置限制，避免日志过大
```

## License

MIT