@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 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. **仅记录 JSON 响应**:响应体日志只会记录 Content-Type 为 JSON 格式的响应(如 `application/json`、`application/vnd.api+json` 等),其他类型(如文件下载、HTML、图片等)不会被记录。
114
+ 1. **双层日志机制**:
115
+ - **info 级别**:始终记录基础链路日志(不含 body),用于生产环境请求追踪
116
+ - **verbose 级别**:记录详细追踪日志(包含 body),用于开发调试
119
117
 
120
118
  2. **默认不限制长度**:如果不设置 `LOG_MAX_BODY_LENGTH` 环境变量,日志体不会被截断。建议在生产环境设置合理的限制值。
121
119
 
122
- 3. **自动脱敏**:所有敏感字段会自动被替换为 `***MASKED***`。
120
+ 3. **响应数据处理**:日志库会尝试序列化所有响应数据,对于无法序列化的数据(如 Buffer、Stream)会返回类型信息。
123
121
 
124
122
  ### 日志输出示例
125
123
 
126
- #### 请求日志(包含请求体)
124
+ #### Info 级别(链路日志)
127
125
 
128
126
  ```json
129
127
  {
130
- "level": "info",
128
+ "level": "INFO",
131
129
  "time": 1234567890,
132
130
  "msg": "HTTP request started",
133
- "method": "POST",
131
+ "method": "GET",
134
132
  "path": "/api/users",
135
133
  "trace_id": "req-123-456",
136
134
  "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
- }
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": "info",
143
+ "level": "INFO",
153
144
  "time": 1234567890,
154
145
  "msg": "HTTP request completed",
155
- "method": "POST",
146
+ "method": "GET",
156
147
  "path": "/api/users",
157
148
  "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
- }
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
- ```typescript
202
- // 原始数据
157
+ ```json
203
158
  {
204
- username: "john",
205
- password: "secret123",
206
- userToken: "abc123",
207
- nested: {
208
- apiKey: "key123"
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
- username: "john",
215
- password: "***MASKED***",
216
- userToken: "***MASKED***", // 匹配 'token'
217
- nested: {
218
- apiKey: "***MASKED***"
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
- "responseBody": {
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. **按需启用**:需要查看请求追踪时,将 LOGGER_LEVEL 设置为 verbose
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
- this.logger.level = this.levelState.getMinPinoLevel();
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" : "debug"));
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 sanitized;
14921
+ return data;
14873
14922
  }
14874
- const jsonStr = JSON.stringify(sanitized);
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 sanitized;
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: "trace",
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: "trace",
15089
+ level: config.level,
15068
15090
  filePath: `${config.logDir}/trace.log`
15069
15091
  }), requestContext), "useFactory"),
15070
15092
  inject: [