@lark-apaas/nestjs-logger 0.1.0-alpha.1 → 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -0
- package/dist/index.cjs +112 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +112 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# NestJS Logger
|
|
2
|
+
|
|
3
|
+
基于 Pino 的高性能 NestJS 日志库,支持请求追踪、结构化日志和敏感信息脱敏。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 基于 Pino 的高性能日志记录
|
|
8
|
+
- 自动 HTTP 请求追踪
|
|
9
|
+
- 请求和响应体日志记录(可配置)
|
|
10
|
+
- 敏感字段自动脱敏
|
|
11
|
+
- 支持多日志级别
|
|
12
|
+
- 独立的 trace 日志文件
|
|
13
|
+
- 请求上下文传递
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @lark-apaas/nestjs-logger
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 基本使用
|
|
22
|
+
|
|
23
|
+
### 1. 导入模块
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Module } from '@nestjs/common';
|
|
27
|
+
import { LoggerModule } from '@lark-apaas/nestjs-logger';
|
|
28
|
+
|
|
29
|
+
@Module({
|
|
30
|
+
imports: [LoggerModule],
|
|
31
|
+
})
|
|
32
|
+
export class AppModule {}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. 使用 Logger
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
39
|
+
|
|
40
|
+
@Injectable()
|
|
41
|
+
export class MyService {
|
|
42
|
+
private readonly logger = new Logger(MyService.name);
|
|
43
|
+
|
|
44
|
+
doSomething() {
|
|
45
|
+
this.logger.log('This is an info log');
|
|
46
|
+
this.logger.debug('This is a debug log');
|
|
47
|
+
this.logger.warn('This is a warning');
|
|
48
|
+
this.logger.error('This is an error', error.stack);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 环境变量配置
|
|
54
|
+
|
|
55
|
+
### 基础配置
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 日志级别: trace, debug, info, warn, error, fatal, silent
|
|
59
|
+
# 注意:HTTP 请求追踪日志使用 verbose 级别(对应 Pino 的 trace)
|
|
60
|
+
# 生产环境默认为 info,不会打印 verbose 级别的日志
|
|
61
|
+
LOGGER_LEVEL=info
|
|
62
|
+
|
|
63
|
+
# 日志目录
|
|
64
|
+
LOG_DIR=logs
|
|
65
|
+
|
|
66
|
+
# Node 环境(默认:开发环境使用 debug,生产环境使用 info)
|
|
67
|
+
NODE_ENV=production
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 请求/响应体日志配置
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 启用请求体日志(默认:false)
|
|
74
|
+
LOG_REQUEST_BODY=true
|
|
75
|
+
|
|
76
|
+
# 启用响应体日志(默认:false)
|
|
77
|
+
# 注意:仅记录 Content-Type 为 JSON 的响应
|
|
78
|
+
LOG_RESPONSE_BODY=true
|
|
79
|
+
|
|
80
|
+
# 注意:即使启用了请求/响应体日志,也需要将日志级别设置为 verbose 才能实际输出
|
|
81
|
+
# 因为 HTTP 请求追踪日志使用的是 verbose 级别
|
|
82
|
+
LOGGER_LEVEL=verbose
|
|
83
|
+
|
|
84
|
+
# 日志体最大长度,超过会截断(默认:不限制)
|
|
85
|
+
# 不设置此环境变量时,不会进行截断
|
|
86
|
+
LOG_MAX_BODY_LENGTH=10000
|
|
87
|
+
|
|
88
|
+
# 敏感字段配置(逗号分隔,默认包含常见敏感字段)
|
|
89
|
+
LOG_SENSITIVE_FIELDS=password,token,secret,authorization,cookie,apiKey,accessToken,refreshToken
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 请求/响应体日志
|
|
93
|
+
|
|
94
|
+
### 日志级别说明
|
|
95
|
+
|
|
96
|
+
HTTP 请求追踪日志使用 **`verbose` 级别**(对应 Pino 的 `trace` 级别),这意味着:
|
|
97
|
+
|
|
98
|
+
- **生产环境默认不打印**:生产环境默认日志级别为 `info`,不会输出 verbose 级别的日志
|
|
99
|
+
- **需要显式启用**:要查看 HTTP 请求追踪日志,需要将 `LOGGER_LEVEL` 设置为 `verbose`
|
|
100
|
+
- **细粒度控制**:可以通过日志级别控制是否打印请求追踪,而无需修改 `LOG_REQUEST_BODY` 和 `LOG_RESPONSE_BODY` 配置
|
|
101
|
+
|
|
102
|
+
### 启用方式
|
|
103
|
+
|
|
104
|
+
通过环境变量启用:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 方式一:仅启用 HTTP 请求追踪(不包含 body)
|
|
108
|
+
LOGGER_LEVEL=verbose
|
|
109
|
+
|
|
110
|
+
# 方式二:启用 HTTP 请求追踪 + 请求/响应体
|
|
111
|
+
LOGGER_LEVEL=verbose
|
|
112
|
+
LOG_REQUEST_BODY=true
|
|
113
|
+
LOG_RESPONSE_BODY=true
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 重要说明
|
|
117
|
+
|
|
118
|
+
1. **仅记录 JSON 响应**:响应体日志只会记录 Content-Type 为 JSON 格式的响应(如 `application/json`、`application/vnd.api+json` 等),其他类型(如文件下载、HTML、图片等)不会被记录。
|
|
119
|
+
|
|
120
|
+
2. **默认不限制长度**:如果不设置 `LOG_MAX_BODY_LENGTH` 环境变量,日志体不会被截断。建议在生产环境设置合理的限制值。
|
|
121
|
+
|
|
122
|
+
3. **自动脱敏**:所有敏感字段会自动被替换为 `***MASKED***`。
|
|
123
|
+
|
|
124
|
+
### 日志输出示例
|
|
125
|
+
|
|
126
|
+
#### 请求日志(包含请求体)
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"level": "info",
|
|
131
|
+
"time": 1234567890,
|
|
132
|
+
"msg": "HTTP request started",
|
|
133
|
+
"method": "POST",
|
|
134
|
+
"path": "/api/users",
|
|
135
|
+
"trace_id": "req-123-456",
|
|
136
|
+
"user_id": "user-001",
|
|
137
|
+
"requestBody": {
|
|
138
|
+
"username": "john_doe",
|
|
139
|
+
"email": "john@example.com",
|
|
140
|
+
"password": "***MASKED***"
|
|
141
|
+
},
|
|
142
|
+
"queryParams": {
|
|
143
|
+
"filter": "active"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### 响应日志(包含响应体)
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"level": "info",
|
|
153
|
+
"time": 1234567890,
|
|
154
|
+
"msg": "HTTP request completed",
|
|
155
|
+
"method": "POST",
|
|
156
|
+
"path": "/api/users",
|
|
157
|
+
"trace_id": "req-123-456",
|
|
158
|
+
"statusCode": 201,
|
|
159
|
+
"durationMs": 125,
|
|
160
|
+
"responseBody": {
|
|
161
|
+
"id": "user-123",
|
|
162
|
+
"username": "john_doe",
|
|
163
|
+
"email": "john@example.com",
|
|
164
|
+
"accessToken": "***MASKED***"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 敏感字段脱敏
|
|
170
|
+
|
|
171
|
+
### 默认脱敏字段
|
|
172
|
+
|
|
173
|
+
以下字段会自动脱敏(不区分大小写,支持部分匹配):
|
|
174
|
+
|
|
175
|
+
- password
|
|
176
|
+
- token
|
|
177
|
+
- secret
|
|
178
|
+
- authorization
|
|
179
|
+
- cookie
|
|
180
|
+
- apiKey
|
|
181
|
+
- accessToken
|
|
182
|
+
- refreshToken
|
|
183
|
+
|
|
184
|
+
### 自定义敏感字段
|
|
185
|
+
|
|
186
|
+
通过环境变量配置:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
LOG_SENSITIVE_FIELDS=password,token,secret,myCustomSecret,privateKey
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 脱敏规则
|
|
193
|
+
|
|
194
|
+
- 字段匹配不区分大小写
|
|
195
|
+
- 支持部分匹配(例如:`accessToken` 会匹配 `token`)
|
|
196
|
+
- 敏感字段值会被替换为 `***MASKED***`
|
|
197
|
+
- 嵌套对象中的敏感字段也会被脱敏
|
|
198
|
+
|
|
199
|
+
### 示例
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// 原始数据
|
|
203
|
+
{
|
|
204
|
+
username: "john",
|
|
205
|
+
password: "secret123",
|
|
206
|
+
userToken: "abc123",
|
|
207
|
+
nested: {
|
|
208
|
+
apiKey: "key123"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 脱敏后
|
|
213
|
+
{
|
|
214
|
+
username: "john",
|
|
215
|
+
password: "***MASKED***",
|
|
216
|
+
userToken: "***MASKED***", // 匹配 'token'
|
|
217
|
+
nested: {
|
|
218
|
+
apiKey: "***MASKED***"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## 数据截断
|
|
224
|
+
|
|
225
|
+
默认情况下,不会对日志体进行截断。当设置 `LOG_MAX_BODY_LENGTH` 环境变量后,如果请求/响应体超过指定长度,会自动截断:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"responseBody": {
|
|
230
|
+
"_truncated": true,
|
|
231
|
+
"_originalLength": 50000,
|
|
232
|
+
"_data": "{ 前 10000 个字符... }..."
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**建议**:在生产环境设置合理的限制值(如 10000),避免单条日志过大。
|
|
238
|
+
|
|
239
|
+
## 日志级别映射
|
|
240
|
+
|
|
241
|
+
NestJS LoggerService 级别到 Pino 级别的映射:
|
|
242
|
+
|
|
243
|
+
- `fatal` → `fatal`
|
|
244
|
+
- `error` → `error`
|
|
245
|
+
- `warn` → `warn`
|
|
246
|
+
- `log` → `info`
|
|
247
|
+
- `debug` → `debug`
|
|
248
|
+
- `verbose` → `trace`
|
|
249
|
+
|
|
250
|
+
## 日志文件
|
|
251
|
+
|
|
252
|
+
日志会写入以下文件(默认在 `logs/` 目录):
|
|
253
|
+
|
|
254
|
+
- `app.log` - 应用日志
|
|
255
|
+
- `trace.log` - HTTP 请求追踪日志
|
|
256
|
+
|
|
257
|
+
## 注意事项
|
|
258
|
+
|
|
259
|
+
### 安全性
|
|
260
|
+
|
|
261
|
+
1. **生产环境默认不打印 HTTP 追踪日志**:由于使用 verbose 级别,生产环境(info 级别)默认不会输出
|
|
262
|
+
2. **按需启用**:需要查看请求追踪时,将 LOGGER_LEVEL 设置为 verbose
|
|
263
|
+
3. 确保敏感字段配置完整,覆盖所有可能的敏感数据
|
|
264
|
+
4. 定期审查日志内容,确保没有遗漏的敏感信息
|
|
265
|
+
5. **仅 JSON 响应会被记录**,文件下载、HTML 等其他类型不会被记录
|
|
266
|
+
|
|
267
|
+
### 性能
|
|
268
|
+
|
|
269
|
+
1. verbose 级别会输出所有 HTTP 请求日志,可能影响性能
|
|
270
|
+
2. 开启请求/响应体日志会增加日志体积和 I/O 开销
|
|
271
|
+
3. 大对象的序列化和脱敏会影响性能
|
|
272
|
+
4. 建议在开发/测试环境使用,生产环境按需临时启用
|
|
273
|
+
|
|
274
|
+
### 存储
|
|
275
|
+
|
|
276
|
+
1. 注意日志文件大小,建议配置日志轮转
|
|
277
|
+
2. verbose 级别 + 请求/响应体日志会显著增加日志量
|
|
278
|
+
3. 建议设置 `LOG_MAX_BODY_LENGTH` 限制单条日志大小(默认不限制)
|
|
279
|
+
|
|
280
|
+
## 最佳实践
|
|
281
|
+
|
|
282
|
+
### 开发环境
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
NODE_ENV=development
|
|
286
|
+
# 使用 verbose 级别查看 HTTP 请求追踪
|
|
287
|
+
LOGGER_LEVEL=verbose
|
|
288
|
+
LOG_REQUEST_BODY=true
|
|
289
|
+
LOG_RESPONSE_BODY=true
|
|
290
|
+
# 开发环境可以不限制长度,或设置较大值
|
|
291
|
+
# LOG_MAX_BODY_LENGTH=50000
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 生产环境
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
NODE_ENV=production
|
|
298
|
+
# 生产环境使用 info 级别,不会打印 HTTP 请求追踪日志
|
|
299
|
+
LOGGER_LEVEL=info
|
|
300
|
+
# 这两个配置可以保留,只有当 LOGGER_LEVEL=verbose 时才会生效
|
|
301
|
+
LOG_REQUEST_BODY=false
|
|
302
|
+
LOG_RESPONSE_BODY=false
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 故障排查
|
|
306
|
+
|
|
307
|
+
临时启用详细日志:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
NODE_ENV=production
|
|
311
|
+
# 临时开启 verbose 级别查看 HTTP 请求追踪
|
|
312
|
+
LOGGER_LEVEL=verbose
|
|
313
|
+
LOG_REQUEST_BODY=true
|
|
314
|
+
LOG_RESPONSE_BODY=true
|
|
315
|
+
LOG_MAX_BODY_LENGTH=10000 # 建议设置限制,避免日志过大
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -14530,13 +14530,11 @@ var BasePinoLogger = class _BasePinoLogger {
|
|
|
14530
14530
|
const traceId = requestState?.requestId ?? null;
|
|
14531
14531
|
const payload = {
|
|
14532
14532
|
trace_id: traceId,
|
|
14533
|
-
requestId: traceId,
|
|
14534
14533
|
path: requestState?.path,
|
|
14535
14534
|
method: requestState?.method,
|
|
14536
14535
|
user_id: requestState?.userId ?? null,
|
|
14537
14536
|
app_id: requestState?.appId ?? null,
|
|
14538
14537
|
tenant_id: requestState?.tenantId ?? null,
|
|
14539
|
-
ip: requestState?.ip ?? null,
|
|
14540
14538
|
pid: process.pid
|
|
14541
14539
|
};
|
|
14542
14540
|
if (context) {
|
|
@@ -14683,7 +14681,7 @@ __name(sanitizeValue, "sanitizeValue");
|
|
|
14683
14681
|
// src/module.ts
|
|
14684
14682
|
var import_common5 = require("@nestjs/common");
|
|
14685
14683
|
var import_core = require("@nestjs/core");
|
|
14686
|
-
var
|
|
14684
|
+
var import_config3 = __toESM(require_config2(), 1);
|
|
14687
14685
|
|
|
14688
14686
|
// src/config/logger.config.ts
|
|
14689
14687
|
var import_config = __toESM(require_config2(), 1);
|
|
@@ -14703,49 +14701,23 @@ function normalizeLevel(level) {
|
|
|
14703
14701
|
}
|
|
14704
14702
|
}
|
|
14705
14703
|
__name(normalizeLevel, "normalizeLevel");
|
|
14706
|
-
function parseDestinations(raw) {
|
|
14707
|
-
if (!raw) {
|
|
14708
|
-
return [
|
|
14709
|
-
{
|
|
14710
|
-
type: "stdout"
|
|
14711
|
-
}
|
|
14712
|
-
];
|
|
14713
|
-
}
|
|
14714
|
-
return raw.split(",").map((token) => token.trim()).filter(Boolean).map((token) => {
|
|
14715
|
-
const [typeAndPath, level] = token.split("@");
|
|
14716
|
-
const [type, path] = typeAndPath.split(":");
|
|
14717
|
-
const normalizedType = (type ?? "stdout").toLowerCase();
|
|
14718
|
-
if (normalizedType === "file") {
|
|
14719
|
-
return {
|
|
14720
|
-
type: "file",
|
|
14721
|
-
path: path || "logs/app.log",
|
|
14722
|
-
level
|
|
14723
|
-
};
|
|
14724
|
-
}
|
|
14725
|
-
if (normalizedType === "stderr") {
|
|
14726
|
-
return {
|
|
14727
|
-
type: "stderr",
|
|
14728
|
-
level
|
|
14729
|
-
};
|
|
14730
|
-
}
|
|
14731
|
-
return {
|
|
14732
|
-
type: "stdout",
|
|
14733
|
-
level
|
|
14734
|
-
};
|
|
14735
|
-
});
|
|
14736
|
-
}
|
|
14737
|
-
__name(parseDestinations, "parseDestinations");
|
|
14738
14704
|
var logger_config_default = (0, import_config.registerAs)("logger", () => {
|
|
14739
14705
|
const level = normalizeLevel(process.env.LOGGER_LEVEL || (process.env.NODE_ENV === "production" ? "info" : "debug"));
|
|
14740
|
-
const
|
|
14706
|
+
const maxBodyLengthEnv = process.env.LOG_MAX_BODY_LENGTH;
|
|
14707
|
+
const maxBodyLength = maxBodyLengthEnv ? Number(maxBodyLengthEnv) : null;
|
|
14741
14708
|
return {
|
|
14742
14709
|
level,
|
|
14743
|
-
|
|
14710
|
+
logDir: process.env.LOG_DIR || "logs",
|
|
14711
|
+
logRequestBody: process.env.LOG_REQUEST_BODY === "true",
|
|
14712
|
+
logResponseBody: process.env.LOG_RESPONSE_BODY === "true",
|
|
14713
|
+
maxBodyLength,
|
|
14714
|
+
sensitiveFields: (process.env.LOG_SENSITIVE_FIELDS || "password,token,secret,authorization,cookie,apiKey,accessToken,refreshToken").split(",").map((f) => f.trim())
|
|
14744
14715
|
};
|
|
14745
14716
|
});
|
|
14746
14717
|
|
|
14747
14718
|
// src/interceptor/logging.interceptor.ts
|
|
14748
14719
|
var import_common3 = require("@nestjs/common");
|
|
14720
|
+
var import_config2 = __toESM(require_config2(), 1);
|
|
14749
14721
|
var import_operators = __toESM(require_operators(), 1);
|
|
14750
14722
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
14751
14723
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -14770,9 +14742,11 @@ var LoggingInterceptor = class {
|
|
|
14770
14742
|
}
|
|
14771
14743
|
traceLogger;
|
|
14772
14744
|
requestContext;
|
|
14773
|
-
|
|
14745
|
+
config;
|
|
14746
|
+
constructor(traceLogger, requestContext, config) {
|
|
14774
14747
|
this.traceLogger = traceLogger;
|
|
14775
14748
|
this.requestContext = requestContext;
|
|
14749
|
+
this.config = config;
|
|
14776
14750
|
}
|
|
14777
14751
|
intercept(context, next) {
|
|
14778
14752
|
if (context.getType() !== "http") {
|
|
@@ -14791,7 +14765,6 @@ var LoggingInterceptor = class {
|
|
|
14791
14765
|
const baseMeta = {
|
|
14792
14766
|
method,
|
|
14793
14767
|
path: url,
|
|
14794
|
-
requestId: req.requestId ?? req.id,
|
|
14795
14768
|
trace_id: req.requestId ?? req.id,
|
|
14796
14769
|
user_id: req.userContext?.userId ?? null,
|
|
14797
14770
|
tenant_id: req.userContext?.tenantId ?? null,
|
|
@@ -14799,15 +14772,32 @@ var LoggingInterceptor = class {
|
|
|
14799
14772
|
ip: req.ip ?? null,
|
|
14800
14773
|
pid: process.pid
|
|
14801
14774
|
};
|
|
14802
|
-
|
|
14803
|
-
|
|
14775
|
+
const requestMeta = {
|
|
14776
|
+
...baseMeta
|
|
14777
|
+
};
|
|
14778
|
+
if (this.config.logRequestBody && req.body) {
|
|
14779
|
+
requestMeta["requestBody"] = this.sanitizeAndTruncate(req.body);
|
|
14780
|
+
}
|
|
14781
|
+
if (this.config.logRequestBody && Object.keys(req.query || {}).length > 0) {
|
|
14782
|
+
requestMeta["queryParams"] = this.sanitizeAndTruncate(req.query);
|
|
14783
|
+
}
|
|
14784
|
+
this.traceLogger.verbose?.("HTTP request started", requestMeta, "HTTP");
|
|
14785
|
+
return next.handle().pipe((0, import_operators.tap)((responseData) => {
|
|
14804
14786
|
const durationMs = Date.now() - startedAt;
|
|
14805
14787
|
const statusCode = res.statusCode;
|
|
14806
|
-
|
|
14788
|
+
const responseMeta = {
|
|
14807
14789
|
...baseMeta,
|
|
14808
14790
|
statusCode,
|
|
14809
14791
|
durationMs
|
|
14810
|
-
}
|
|
14792
|
+
};
|
|
14793
|
+
if (this.config.logResponseBody && responseData !== void 0) {
|
|
14794
|
+
const contentType = res.getHeader("content-type");
|
|
14795
|
+
const isJsonResponse = this.isJsonContentType(contentType);
|
|
14796
|
+
if (isJsonResponse) {
|
|
14797
|
+
responseMeta["responseBody"] = this.sanitizeAndTruncate(responseData);
|
|
14798
|
+
}
|
|
14799
|
+
}
|
|
14800
|
+
this.traceLogger.verbose?.("HTTP request completed", responseMeta, "HTTP");
|
|
14811
14801
|
}), (0, import_operators.catchError)((error) => {
|
|
14812
14802
|
const durationMs = Date.now() - startedAt;
|
|
14813
14803
|
const statusCode = res.statusCode >= 400 ? res.statusCode : 500;
|
|
@@ -14824,14 +14814,78 @@ var LoggingInterceptor = class {
|
|
|
14824
14814
|
throw error;
|
|
14825
14815
|
}));
|
|
14826
14816
|
}
|
|
14817
|
+
/**
|
|
14818
|
+
* 对数据进行脱敏和截断处理
|
|
14819
|
+
*/
|
|
14820
|
+
sanitizeAndTruncate(data) {
|
|
14821
|
+
try {
|
|
14822
|
+
const sanitized = this.maskSensitiveFields(data);
|
|
14823
|
+
if (this.config.maxBodyLength === null) {
|
|
14824
|
+
return sanitized;
|
|
14825
|
+
}
|
|
14826
|
+
const jsonStr = JSON.stringify(sanitized);
|
|
14827
|
+
if (jsonStr.length > this.config.maxBodyLength) {
|
|
14828
|
+
return {
|
|
14829
|
+
_truncated: true,
|
|
14830
|
+
_originalLength: jsonStr.length,
|
|
14831
|
+
_data: jsonStr.substring(0, this.config.maxBodyLength) + "..."
|
|
14832
|
+
};
|
|
14833
|
+
}
|
|
14834
|
+
return sanitized;
|
|
14835
|
+
} catch (error) {
|
|
14836
|
+
return {
|
|
14837
|
+
_error: "Failed to serialize data",
|
|
14838
|
+
_message: error instanceof Error ? error.message : String(error)
|
|
14839
|
+
};
|
|
14840
|
+
}
|
|
14841
|
+
}
|
|
14842
|
+
/**
|
|
14843
|
+
* 判断是否是 JSON 响应类型
|
|
14844
|
+
*/
|
|
14845
|
+
isJsonContentType(contentType) {
|
|
14846
|
+
if (typeof contentType !== "string") {
|
|
14847
|
+
return false;
|
|
14848
|
+
}
|
|
14849
|
+
const contentTypeLower = contentType.toLowerCase();
|
|
14850
|
+
return contentTypeLower.includes("application/json") || contentTypeLower.includes("application/vnd.api+json") || contentTypeLower.includes("+json");
|
|
14851
|
+
}
|
|
14852
|
+
/**
|
|
14853
|
+
* 脱敏敏感字段
|
|
14854
|
+
*/
|
|
14855
|
+
maskSensitiveFields(data) {
|
|
14856
|
+
if (data === null || data === void 0) {
|
|
14857
|
+
return data;
|
|
14858
|
+
}
|
|
14859
|
+
if (Array.isArray(data)) {
|
|
14860
|
+
return data.map((item) => this.maskSensitiveFields(item));
|
|
14861
|
+
}
|
|
14862
|
+
if (typeof data === "object") {
|
|
14863
|
+
const result = {};
|
|
14864
|
+
for (const [key, value] of Object.entries(data)) {
|
|
14865
|
+
const lowerKey = key.toLowerCase();
|
|
14866
|
+
const isSensitive = this.config.sensitiveFields.some((field) => lowerKey.includes(field.toLowerCase()));
|
|
14867
|
+
if (isSensitive) {
|
|
14868
|
+
result[key] = "***MASKED***";
|
|
14869
|
+
} else if (typeof value === "object" && value !== null) {
|
|
14870
|
+
result[key] = this.maskSensitiveFields(value);
|
|
14871
|
+
} else {
|
|
14872
|
+
result[key] = value;
|
|
14873
|
+
}
|
|
14874
|
+
}
|
|
14875
|
+
return result;
|
|
14876
|
+
}
|
|
14877
|
+
return data;
|
|
14878
|
+
}
|
|
14827
14879
|
};
|
|
14828
14880
|
LoggingInterceptor = _ts_decorate3([
|
|
14829
14881
|
(0, import_common3.Injectable)(),
|
|
14830
14882
|
_ts_param2(0, (0, import_common3.Inject)(TRACE_LOGGER)),
|
|
14883
|
+
_ts_param2(2, (0, import_common3.Inject)(logger_config_default.KEY)),
|
|
14831
14884
|
_ts_metadata2("design:type", Function),
|
|
14832
14885
|
_ts_metadata2("design:paramtypes", [
|
|
14833
14886
|
typeof import_common3.LoggerService === "undefined" ? Object : import_common3.LoggerService,
|
|
14834
|
-
typeof RequestContextService === "undefined" ? Object : RequestContextService
|
|
14887
|
+
typeof RequestContextService === "undefined" ? Object : RequestContextService,
|
|
14888
|
+
typeof import_config2.ConfigType === "undefined" ? Object : import_config2.ConfigType
|
|
14835
14889
|
])
|
|
14836
14890
|
], LoggingInterceptor);
|
|
14837
14891
|
|
|
@@ -14854,12 +14908,12 @@ function createPinoLogger(config) {
|
|
|
14854
14908
|
}
|
|
14855
14909
|
}
|
|
14856
14910
|
};
|
|
14857
|
-
const
|
|
14911
|
+
const streams = [
|
|
14858
14912
|
{
|
|
14859
|
-
|
|
14913
|
+
level: config.level,
|
|
14914
|
+
stream: createFileDestination(config.filePath)
|
|
14860
14915
|
}
|
|
14861
14916
|
];
|
|
14862
|
-
const streams = buildStreams(destinations, baseLevel);
|
|
14863
14917
|
if (streams.length === 0) {
|
|
14864
14918
|
return (0, import_pino.default)(options);
|
|
14865
14919
|
}
|
|
@@ -14869,30 +14923,6 @@ function createPinoLogger(config) {
|
|
|
14869
14923
|
return (0, import_pino.default)(options, import_pino.default.multistream(streams));
|
|
14870
14924
|
}
|
|
14871
14925
|
__name(createPinoLogger, "createPinoLogger");
|
|
14872
|
-
function buildStreams(destinations, fallbackLevel) {
|
|
14873
|
-
return destinations.map((destination) => {
|
|
14874
|
-
const level = normalizeLevel(destination.level ?? fallbackLevel);
|
|
14875
|
-
switch (destination.type) {
|
|
14876
|
-
case "stderr":
|
|
14877
|
-
return {
|
|
14878
|
-
level,
|
|
14879
|
-
stream: process.stderr
|
|
14880
|
-
};
|
|
14881
|
-
case "file":
|
|
14882
|
-
return {
|
|
14883
|
-
level,
|
|
14884
|
-
stream: createFileDestination(destination.path)
|
|
14885
|
-
};
|
|
14886
|
-
case "stdout":
|
|
14887
|
-
default:
|
|
14888
|
-
return {
|
|
14889
|
-
level,
|
|
14890
|
-
stream: process.stdout
|
|
14891
|
-
};
|
|
14892
|
-
}
|
|
14893
|
-
});
|
|
14894
|
-
}
|
|
14895
|
-
__name(buildStreams, "buildStreams");
|
|
14896
14926
|
function createFileDestination(pathname) {
|
|
14897
14927
|
const target = pathname && pathname.length > 0 ? pathname : "logs/app.log";
|
|
14898
14928
|
const resolved = (0, import_path.isAbsolute)(target) ? target : (0, import_path.join)(process.cwd(), target);
|
|
@@ -14973,32 +15003,32 @@ LoggerModule = _ts_decorate5([
|
|
|
14973
15003
|
(0, import_common5.Global)(),
|
|
14974
15004
|
(0, import_common5.Module)({
|
|
14975
15005
|
imports: [
|
|
14976
|
-
|
|
15006
|
+
import_config3.ConfigModule.forFeature(logger_config_default)
|
|
14977
15007
|
],
|
|
14978
15008
|
providers: [
|
|
14979
15009
|
RequestContextService,
|
|
14980
15010
|
LoggerContextMiddleware,
|
|
14981
15011
|
{
|
|
14982
15012
|
provide: PINO_ROOT_LOGGER,
|
|
14983
|
-
useFactory: /* @__PURE__ */ __name((config) =>
|
|
15013
|
+
useFactory: /* @__PURE__ */ __name((config) => {
|
|
15014
|
+
return createPinoLogger({
|
|
15015
|
+
level: "trace",
|
|
15016
|
+
filePath: `${config.logDir}/app.log`
|
|
15017
|
+
});
|
|
15018
|
+
}, "useFactory"),
|
|
14984
15019
|
inject: [
|
|
14985
15020
|
logger_config_default.KEY
|
|
14986
15021
|
]
|
|
14987
15022
|
},
|
|
14988
15023
|
{
|
|
14989
15024
|
provide: TRACE_LOGGER,
|
|
14990
|
-
useFactory: /* @__PURE__ */ __name((requestContext) => new PinoLoggerService(createPinoLogger({
|
|
15025
|
+
useFactory: /* @__PURE__ */ __name((requestContext, config) => new PinoLoggerService(createPinoLogger({
|
|
14991
15026
|
level: "trace",
|
|
14992
|
-
|
|
14993
|
-
{
|
|
14994
|
-
type: "file",
|
|
14995
|
-
path: "logs/trace.log",
|
|
14996
|
-
level: "trace"
|
|
14997
|
-
}
|
|
14998
|
-
]
|
|
15027
|
+
filePath: `${config.logDir}/trace.log`
|
|
14999
15028
|
}), requestContext), "useFactory"),
|
|
15000
15029
|
inject: [
|
|
15001
|
-
RequestContextService
|
|
15030
|
+
RequestContextService,
|
|
15031
|
+
logger_config_default.KEY
|
|
15002
15032
|
]
|
|
15003
15033
|
},
|
|
15004
15034
|
AppLogger,
|