@lark-apaas/nestjs-logger 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +59 -87
- package/dist/index.cjs +67 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +67 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# NestJS Logger
|
|
2
2
|
|
|
3
|
-
基于 Pino 的高性能 NestJS
|
|
3
|
+
基于 Pino 的高性能 NestJS 日志库,支持请求追踪和结构化日志。
|
|
4
4
|
|
|
5
5
|
## 功能特性
|
|
6
6
|
|
|
7
7
|
- 基于 Pino 的高性能日志记录
|
|
8
|
-
- 自动 HTTP
|
|
8
|
+
- 自动 HTTP 请求追踪(双层日志:info 链路日志 + verbose 详细日志)
|
|
9
9
|
- 请求和响应体日志记录(可配置)
|
|
10
|
-
- 敏感字段自动脱敏
|
|
11
10
|
- 支持多日志级别
|
|
12
11
|
- 独立的 trace 日志文件
|
|
13
12
|
- 请求上下文传递
|
|
13
|
+
- 日志体长度截断
|
|
14
14
|
|
|
15
15
|
## 安装
|
|
16
16
|
|
|
@@ -74,7 +74,6 @@ NODE_ENV=production
|
|
|
74
74
|
LOG_REQUEST_BODY=true
|
|
75
75
|
|
|
76
76
|
# 启用响应体日志(默认:false)
|
|
77
|
-
# 注意:仅记录 Content-Type 为 JSON 的响应
|
|
78
77
|
LOG_RESPONSE_BODY=true
|
|
79
78
|
|
|
80
79
|
# 注意:即使启用了请求/响应体日志,也需要将日志级别设置为 verbose 才能实际输出
|
|
@@ -84,9 +83,6 @@ LOGGER_LEVEL=verbose
|
|
|
84
83
|
# 日志体最大长度,超过会截断(默认:不限制)
|
|
85
84
|
# 不设置此环境变量时,不会进行截断
|
|
86
85
|
LOG_MAX_BODY_LENGTH=10000
|
|
87
|
-
|
|
88
|
-
# 敏感字段配置(逗号分隔,默认包含常见敏感字段)
|
|
89
|
-
LOG_SENSITIVE_FIELDS=password,token,secret,authorization,cookie,apiKey,accessToken,refreshToken
|
|
90
86
|
```
|
|
91
87
|
|
|
92
88
|
## 请求/响应体日志
|
|
@@ -115,108 +111,85 @@ LOG_RESPONSE_BODY=true
|
|
|
115
111
|
|
|
116
112
|
### 重要说明
|
|
117
113
|
|
|
118
|
-
1.
|
|
114
|
+
1. **双层日志机制**:
|
|
115
|
+
- **info 级别**:始终记录基础链路日志(不含 body),用于生产环境请求追踪
|
|
116
|
+
- **verbose 级别**:记录详细追踪日志(包含 body),用于开发调试
|
|
119
117
|
|
|
120
118
|
2. **默认不限制长度**:如果不设置 `LOG_MAX_BODY_LENGTH` 环境变量,日志体不会被截断。建议在生产环境设置合理的限制值。
|
|
121
119
|
|
|
122
|
-
3.
|
|
120
|
+
3. **响应数据处理**:日志库会尝试序列化所有响应数据,对于无法序列化的数据(如 Buffer、Stream)会返回类型信息。
|
|
123
121
|
|
|
124
122
|
### 日志输出示例
|
|
125
123
|
|
|
126
|
-
####
|
|
124
|
+
#### Info 级别(链路日志)
|
|
127
125
|
|
|
128
126
|
```json
|
|
129
127
|
{
|
|
130
|
-
"level": "
|
|
128
|
+
"level": "INFO",
|
|
131
129
|
"time": 1234567890,
|
|
132
130
|
"msg": "HTTP request started",
|
|
133
|
-
"method": "
|
|
131
|
+
"method": "GET",
|
|
134
132
|
"path": "/api/users",
|
|
135
133
|
"trace_id": "req-123-456",
|
|
136
134
|
"user_id": "user-001",
|
|
137
|
-
"
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"password": "***MASKED***"
|
|
141
|
-
},
|
|
142
|
-
"queryParams": {
|
|
143
|
-
"filter": "active"
|
|
144
|
-
}
|
|
135
|
+
"tenant_id": 24020896,
|
|
136
|
+
"app_id": "",
|
|
137
|
+
"context": "HTTPTraceInterceptor"
|
|
145
138
|
}
|
|
146
139
|
```
|
|
147
140
|
|
|
148
|
-
#### 响应日志(包含响应体)
|
|
149
|
-
|
|
150
141
|
```json
|
|
151
142
|
{
|
|
152
|
-
"level": "
|
|
143
|
+
"level": "INFO",
|
|
153
144
|
"time": 1234567890,
|
|
154
145
|
"msg": "HTTP request completed",
|
|
155
|
-
"method": "
|
|
146
|
+
"method": "GET",
|
|
156
147
|
"path": "/api/users",
|
|
157
148
|
"trace_id": "req-123-456",
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"id": "user-123",
|
|
162
|
-
"username": "john_doe",
|
|
163
|
-
"email": "john@example.com",
|
|
164
|
-
"accessToken": "***MASKED***"
|
|
165
|
-
}
|
|
149
|
+
"status_code": 200,
|
|
150
|
+
"duration_ms": 125,
|
|
151
|
+
"context": "HTTPTraceInterceptor"
|
|
166
152
|
}
|
|
167
153
|
```
|
|
168
154
|
|
|
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
|
-
### 示例
|
|
155
|
+
#### Verbose 级别(详细追踪日志)
|
|
200
156
|
|
|
201
|
-
```
|
|
202
|
-
// 原始数据
|
|
157
|
+
```json
|
|
203
158
|
{
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
159
|
+
"level": "TRACE",
|
|
160
|
+
"time": 1234567890,
|
|
161
|
+
"msg": "HTTP request started",
|
|
162
|
+
"method": "POST",
|
|
163
|
+
"path": "/api/users",
|
|
164
|
+
"trace_id": "req-123-456",
|
|
165
|
+
"request_body": {
|
|
166
|
+
"username": "john_doe",
|
|
167
|
+
"email": "john@example.com",
|
|
168
|
+
"password": "secret123"
|
|
169
|
+
},
|
|
170
|
+
"query_params": {
|
|
171
|
+
"filter": "active"
|
|
172
|
+
},
|
|
173
|
+
"context": "HTTPTraceInterceptor"
|
|
210
174
|
}
|
|
175
|
+
```
|
|
211
176
|
|
|
212
|
-
|
|
177
|
+
```json
|
|
213
178
|
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
179
|
+
"level": "TRACE",
|
|
180
|
+
"time": 1234567890,
|
|
181
|
+
"msg": "HTTP request completed",
|
|
182
|
+
"method": "POST",
|
|
183
|
+
"path": "/api/users",
|
|
184
|
+
"trace_id": "req-123-456",
|
|
185
|
+
"status_code": 201,
|
|
186
|
+
"duration_ms": 125,
|
|
187
|
+
"response_body": {
|
|
188
|
+
"id": "user-123",
|
|
189
|
+
"username": "john_doe",
|
|
190
|
+
"email": "john@example.com"
|
|
191
|
+
},
|
|
192
|
+
"context": "HTTPTraceInterceptor"
|
|
220
193
|
}
|
|
221
194
|
```
|
|
222
195
|
|
|
@@ -226,7 +199,7 @@ LOG_SENSITIVE_FIELDS=password,token,secret,myCustomSecret,privateKey
|
|
|
226
199
|
|
|
227
200
|
```json
|
|
228
201
|
{
|
|
229
|
-
"
|
|
202
|
+
"response_body": {
|
|
230
203
|
"_truncated": true,
|
|
231
204
|
"_originalLength": 50000,
|
|
232
205
|
"_data": "{ 前 10000 个字符... }..."
|
|
@@ -258,22 +231,21 @@ NestJS LoggerService 级别到 Pino 级别的映射:
|
|
|
258
231
|
|
|
259
232
|
### 安全性
|
|
260
233
|
|
|
261
|
-
1. **生产环境默认不打印 HTTP 追踪日志**:由于使用 verbose 级别,生产环境(info
|
|
262
|
-
2.
|
|
263
|
-
3.
|
|
264
|
-
4.
|
|
265
|
-
5. **仅 JSON 响应会被记录**,文件下载、HTML 等其他类型不会被记录
|
|
234
|
+
1. **生产环境默认不打印 HTTP 追踪日志**:由于使用 verbose 级别,生产环境(info 级别)默认不会输出详细日志
|
|
235
|
+
2. **双层日志设计**:info 级别始终记录链路日志,verbose 级别记录详细信息,按需启用
|
|
236
|
+
3. **按需启用**:需要查看详细追踪时,将 LOGGER_LEVEL 设置为 verbose
|
|
237
|
+
4. **敏感数据处理**:日志库不提供脱敏功能,请在业务层或通过中间件处理敏感数据
|
|
266
238
|
|
|
267
239
|
### 性能
|
|
268
240
|
|
|
269
|
-
1. verbose 级别会输出所有 HTTP
|
|
241
|
+
1. verbose 级别会输出所有 HTTP 请求详细日志,可能影响性能
|
|
270
242
|
2. 开启请求/响应体日志会增加日志体积和 I/O 开销
|
|
271
|
-
3.
|
|
243
|
+
3. 大对象的序列化会影响性能
|
|
272
244
|
4. 建议在开发/测试环境使用,生产环境按需临时启用
|
|
273
245
|
|
|
274
246
|
### 存储
|
|
275
247
|
|
|
276
|
-
1.
|
|
248
|
+
1. 日志直接写入文件,不支持自动轮转,建议通过外部工具(如 logrotate)管理日志文件
|
|
277
249
|
2. verbose 级别 + 请求/响应体日志会显著增加日志量
|
|
278
250
|
3. 建议设置 `LOG_MAX_BODY_LENGTH` 限制单条日志大小(默认不限制)
|
|
279
251
|
|
package/dist/index.cjs
CHANGED
|
@@ -14450,13 +14450,7 @@ var LogLevelState = class LogLevelState2 {
|
|
|
14450
14450
|
static {
|
|
14451
14451
|
__name(this, "LogLevelState");
|
|
14452
14452
|
}
|
|
14453
|
-
levels = /* @__PURE__ */ new Set(
|
|
14454
|
-
"log",
|
|
14455
|
-
"error",
|
|
14456
|
-
"warn",
|
|
14457
|
-
"debug",
|
|
14458
|
-
"verbose"
|
|
14459
|
-
]);
|
|
14453
|
+
levels = /* @__PURE__ */ new Set();
|
|
14460
14454
|
set(levels) {
|
|
14461
14455
|
this.levels = new Set(levels);
|
|
14462
14456
|
}
|
|
@@ -14487,6 +14481,61 @@ var LogLevelState = class LogLevelState2 {
|
|
|
14487
14481
|
}
|
|
14488
14482
|
return "fatal";
|
|
14489
14483
|
}
|
|
14484
|
+
/**
|
|
14485
|
+
* 根据 Pino 级别转换为 NestJS LogLevel 数组
|
|
14486
|
+
*/
|
|
14487
|
+
static fromPinoLevel(pinoLevel) {
|
|
14488
|
+
switch (pinoLevel) {
|
|
14489
|
+
case "trace":
|
|
14490
|
+
return [
|
|
14491
|
+
"verbose",
|
|
14492
|
+
"debug",
|
|
14493
|
+
"log",
|
|
14494
|
+
"warn",
|
|
14495
|
+
"error",
|
|
14496
|
+
"fatal"
|
|
14497
|
+
];
|
|
14498
|
+
case "debug":
|
|
14499
|
+
return [
|
|
14500
|
+
"debug",
|
|
14501
|
+
"log",
|
|
14502
|
+
"warn",
|
|
14503
|
+
"error",
|
|
14504
|
+
"fatal"
|
|
14505
|
+
];
|
|
14506
|
+
case "info":
|
|
14507
|
+
return [
|
|
14508
|
+
"log",
|
|
14509
|
+
"warn",
|
|
14510
|
+
"error",
|
|
14511
|
+
"fatal"
|
|
14512
|
+
];
|
|
14513
|
+
case "warn":
|
|
14514
|
+
return [
|
|
14515
|
+
"warn",
|
|
14516
|
+
"error",
|
|
14517
|
+
"fatal"
|
|
14518
|
+
];
|
|
14519
|
+
case "error":
|
|
14520
|
+
return [
|
|
14521
|
+
"error",
|
|
14522
|
+
"fatal"
|
|
14523
|
+
];
|
|
14524
|
+
case "fatal":
|
|
14525
|
+
return [
|
|
14526
|
+
"fatal"
|
|
14527
|
+
];
|
|
14528
|
+
case "silent":
|
|
14529
|
+
return [];
|
|
14530
|
+
default:
|
|
14531
|
+
return [
|
|
14532
|
+
"log",
|
|
14533
|
+
"warn",
|
|
14534
|
+
"error",
|
|
14535
|
+
"fatal"
|
|
14536
|
+
];
|
|
14537
|
+
}
|
|
14538
|
+
}
|
|
14490
14539
|
};
|
|
14491
14540
|
var BasePinoLogger = class _BasePinoLogger {
|
|
14492
14541
|
static {
|
|
@@ -14498,7 +14547,9 @@ var BasePinoLogger = class _BasePinoLogger {
|
|
|
14498
14547
|
constructor(logger, contextStore) {
|
|
14499
14548
|
this.logger = logger;
|
|
14500
14549
|
this.contextStore = contextStore;
|
|
14501
|
-
|
|
14550
|
+
const pinoLevel = this.logger.level;
|
|
14551
|
+
const nestLevels = LogLevelState.fromPinoLevel(pinoLevel);
|
|
14552
|
+
this.levelState.set(nestLevels);
|
|
14502
14553
|
}
|
|
14503
14554
|
setLogLevels(levels) {
|
|
14504
14555
|
this.levelState.set(levels);
|
|
@@ -14733,7 +14784,7 @@ function normalizeLevel(level) {
|
|
|
14733
14784
|
}
|
|
14734
14785
|
__name(normalizeLevel, "normalizeLevel");
|
|
14735
14786
|
var logger_config_default = (0, import_config.registerAs)("logger", () => {
|
|
14736
|
-
const level = normalizeLevel(process.env.LOGGER_LEVEL || (process.env.NODE_ENV === "production" ? "info" : "
|
|
14787
|
+
const level = normalizeLevel(process.env.LOGGER_LEVEL || (process.env.NODE_ENV === "production" ? "info" : "trace"));
|
|
14737
14788
|
const maxBodyLengthEnv = process.env.LOG_MAX_BODY_LENGTH;
|
|
14738
14789
|
const maxBodyLength = maxBodyLengthEnv ? Number(maxBodyLengthEnv) : null;
|
|
14739
14790
|
return {
|
|
@@ -14741,8 +14792,7 @@ var logger_config_default = (0, import_config.registerAs)("logger", () => {
|
|
|
14741
14792
|
logDir: process.env.LOG_DIR || "logs",
|
|
14742
14793
|
logRequestBody: process.env.LOG_REQUEST_BODY === "true",
|
|
14743
14794
|
logResponseBody: process.env.LOG_RESPONSE_BODY === "true",
|
|
14744
|
-
maxBodyLength
|
|
14745
|
-
sensitiveFields: (process.env.LOG_SENSITIVE_FIELDS || "password,token,secret,authorization,cookie,apiKey,accessToken,refreshToken").split(",").map((f) => f.trim())
|
|
14795
|
+
maxBodyLength
|
|
14746
14796
|
};
|
|
14747
14797
|
});
|
|
14748
14798
|
|
|
@@ -14863,15 +14913,14 @@ var LoggingInterceptor = class {
|
|
|
14863
14913
|
}));
|
|
14864
14914
|
}
|
|
14865
14915
|
/**
|
|
14866
|
-
*
|
|
14916
|
+
* 对数据进行截断处理
|
|
14867
14917
|
*/
|
|
14868
14918
|
sanitizeAndTruncate(data) {
|
|
14869
14919
|
try {
|
|
14870
|
-
const sanitized = this.maskSensitiveFields(data);
|
|
14871
14920
|
if (this.config.maxBodyLength === null) {
|
|
14872
|
-
return
|
|
14921
|
+
return data;
|
|
14873
14922
|
}
|
|
14874
|
-
const jsonStr = JSON.stringify(
|
|
14923
|
+
const jsonStr = JSON.stringify(data);
|
|
14875
14924
|
if (jsonStr.length > this.config.maxBodyLength) {
|
|
14876
14925
|
return {
|
|
14877
14926
|
_truncated: true,
|
|
@@ -14879,7 +14928,7 @@ var LoggingInterceptor = class {
|
|
|
14879
14928
|
_data: jsonStr.substring(0, this.config.maxBodyLength) + "..."
|
|
14880
14929
|
};
|
|
14881
14930
|
}
|
|
14882
|
-
return
|
|
14931
|
+
return data;
|
|
14883
14932
|
} catch (error) {
|
|
14884
14933
|
return {
|
|
14885
14934
|
_error: "Failed to serialize data",
|
|
@@ -14888,33 +14937,6 @@ var LoggingInterceptor = class {
|
|
|
14888
14937
|
};
|
|
14889
14938
|
}
|
|
14890
14939
|
}
|
|
14891
|
-
/**
|
|
14892
|
-
* 脱敏敏感字段
|
|
14893
|
-
*/
|
|
14894
|
-
maskSensitiveFields(data) {
|
|
14895
|
-
if (data === null || data === void 0) {
|
|
14896
|
-
return data;
|
|
14897
|
-
}
|
|
14898
|
-
if (Array.isArray(data)) {
|
|
14899
|
-
return data.map((item) => this.maskSensitiveFields(item));
|
|
14900
|
-
}
|
|
14901
|
-
if (typeof data === "object") {
|
|
14902
|
-
const result = {};
|
|
14903
|
-
for (const [key, value] of Object.entries(data)) {
|
|
14904
|
-
const lowerKey = key.toLowerCase();
|
|
14905
|
-
const isSensitive = this.config.sensitiveFields.some((field) => lowerKey.includes(field.toLowerCase()));
|
|
14906
|
-
if (isSensitive) {
|
|
14907
|
-
result[key] = "***MASKED***";
|
|
14908
|
-
} else if (typeof value === "object" && value !== null) {
|
|
14909
|
-
result[key] = this.maskSensitiveFields(value);
|
|
14910
|
-
} else {
|
|
14911
|
-
result[key] = value;
|
|
14912
|
-
}
|
|
14913
|
-
}
|
|
14914
|
-
return result;
|
|
14915
|
-
}
|
|
14916
|
-
return data;
|
|
14917
|
-
}
|
|
14918
14940
|
};
|
|
14919
14941
|
LoggingInterceptor = _ts_decorate3([
|
|
14920
14942
|
(0, import_common3.Injectable)(),
|
|
@@ -15053,7 +15075,7 @@ LoggerModule = _ts_decorate5([
|
|
|
15053
15075
|
provide: PINO_ROOT_LOGGER,
|
|
15054
15076
|
useFactory: /* @__PURE__ */ __name((config) => {
|
|
15055
15077
|
return createPinoLogger({
|
|
15056
|
-
level:
|
|
15078
|
+
level: config.level,
|
|
15057
15079
|
filePath: `${config.logDir}/app.log`
|
|
15058
15080
|
});
|
|
15059
15081
|
}, "useFactory"),
|
|
@@ -15064,7 +15086,7 @@ LoggerModule = _ts_decorate5([
|
|
|
15064
15086
|
{
|
|
15065
15087
|
provide: TRACE_LOGGER,
|
|
15066
15088
|
useFactory: /* @__PURE__ */ __name((requestContext, config) => new PinoLoggerService(createPinoLogger({
|
|
15067
|
-
level:
|
|
15089
|
+
level: config.level,
|
|
15068
15090
|
filePath: `${config.logDir}/trace.log`
|
|
15069
15091
|
}), requestContext), "useFactory"),
|
|
15070
15092
|
inject: [
|