@lark-apaas/nestjs-logger 1.0.1-alpha.0 → 1.0.1-alpha.2
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 +101 -5
- package/dist/index.cjs +36 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,9 @@ import { LoggerModule } from '@lark-apaas/nestjs-logger';
|
|
|
36
36
|
export class AppModule {}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
### 2. 使用 Logger
|
|
39
|
+
### 2. 使用 Logger(完全兼容 NestJS 官方 API)
|
|
40
|
+
|
|
41
|
+
本库完全遵循 NestJS 官方 Logger API,无缝替换官方 Logger。
|
|
40
42
|
|
|
41
43
|
```typescript
|
|
42
44
|
import { Injectable, Logger } from '@nestjs/common';
|
|
@@ -46,14 +48,87 @@ export class MyService {
|
|
|
46
48
|
private readonly logger = new Logger(MyService.name);
|
|
47
49
|
|
|
48
50
|
doSomething() {
|
|
51
|
+
// 简单日志
|
|
49
52
|
this.logger.log('This is an info log');
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.logger.
|
|
53
|
+
|
|
54
|
+
// 带 context 的日志(最后一个参数)
|
|
55
|
+
this.logger.log('User logged in', 'AuthModule');
|
|
56
|
+
// ↑ context
|
|
57
|
+
|
|
58
|
+
// 多个参数会用空格连接
|
|
59
|
+
this.logger.log('User', userId, 'logged in from', ip);
|
|
60
|
+
// 输出: User 123 logged in from 192.168.1.1
|
|
61
|
+
|
|
62
|
+
// 使用模板字符串
|
|
63
|
+
this.logger.log(`User ${userId} logged in from ${ip}`);
|
|
64
|
+
|
|
65
|
+
// 多参数 + context
|
|
66
|
+
this.logger.log('Processing order', orderId, 'OrderService');
|
|
67
|
+
// ↑ context
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
handleError() {
|
|
71
|
+
try {
|
|
72
|
+
// 业务逻辑
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Error 日志 + stack trace(官方推荐方式)
|
|
75
|
+
this.logger.error('Failed to process', error.stack);
|
|
76
|
+
// ↑ 第一个参数自动识别为 stack
|
|
77
|
+
|
|
78
|
+
// Error + stack + context
|
|
79
|
+
this.logger.error('Payment failed', error.stack, 'PaymentService');
|
|
80
|
+
// ↑ stack ↑ context
|
|
81
|
+
}
|
|
53
82
|
}
|
|
54
83
|
}
|
|
55
84
|
```
|
|
56
85
|
|
|
86
|
+
**参数规则(与 NestJS 官方一致)**:
|
|
87
|
+
- **最后一个字符串参数**:自动识别为 `context`
|
|
88
|
+
- **Error/Fatal 方法的第一个参数**:如果是 stack trace 格式,自动识别为 `stack`
|
|
89
|
+
- **多个参数**:会用空格连接成一个字符串
|
|
90
|
+
- **不支持格式化占位符**:不支持 `%s`, `%d`, `%o` 等占位符,请使用模板字符串
|
|
91
|
+
|
|
92
|
+
### 3. 使用 logStructured 记录结构化日志(扩展功能)
|
|
93
|
+
|
|
94
|
+
除了标准的 Logger API,本库还提供了 `logStructured()` 方法用于记录结构化日志:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
98
|
+
import { AppLogger } from '@lark-apaas/nestjs-logger';
|
|
99
|
+
|
|
100
|
+
@Injectable()
|
|
101
|
+
export class MyService {
|
|
102
|
+
constructor(
|
|
103
|
+
@Inject(AppLogger) private readonly logger: AppLogger,
|
|
104
|
+
) {}
|
|
105
|
+
|
|
106
|
+
doSomething() {
|
|
107
|
+
// 使用 logStructured 记录结构化日志
|
|
108
|
+
this.logger.logStructured(
|
|
109
|
+
'log', // 日志级别
|
|
110
|
+
'User action completed', // 消息
|
|
111
|
+
{ // 元数据对象(会合并到日志字段中)
|
|
112
|
+
action: 'create_order',
|
|
113
|
+
order_id: '123',
|
|
114
|
+
amount: 100,
|
|
115
|
+
},
|
|
116
|
+
'MyService', // context(可选)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**两种方式对比**:
|
|
123
|
+
|
|
124
|
+
| 特性 | 标准 Logger API | logStructured API |
|
|
125
|
+
|------|----------------|-------------------|
|
|
126
|
+
| **用途** | 常规日志,兼容 NestJS 官方 | 结构化日志,将 meta 合并到字段 |
|
|
127
|
+
| **Context** | 最后一个参数自动识别 | 显式传递第4个参数 |
|
|
128
|
+
| **消息格式** | 支持格式化占位符 | 纯文本消息 |
|
|
129
|
+
| **元数据** | 需要手动拼接到消息中 | 作为独立字段存储,便于查询 |
|
|
130
|
+
| **使用场景** | 日常日志、错误日志 | 需要结构化查询的业务日志 |
|
|
131
|
+
|
|
57
132
|
## 环境变量配置
|
|
58
133
|
|
|
59
134
|
### 基础配置
|
|
@@ -99,6 +174,14 @@ HTTP 请求追踪日志使用 **`verbose` 级别**(对应 Pino 的 `trace` 级
|
|
|
99
174
|
- **需要显式启用**:要查看 HTTP 请求追踪日志,需要将 `LOGGER_LEVEL` 设置为 `verbose`
|
|
100
175
|
- **细粒度控制**:可以通过日志级别控制是否打印请求追踪,而无需修改 `LOG_REQUEST_BODY` 和 `LOG_RESPONSE_BODY` 配置
|
|
101
176
|
|
|
177
|
+
### 响应体记录规则
|
|
178
|
+
|
|
179
|
+
**只针对 JSON 响应记录 body**:
|
|
180
|
+
- 自动检测 `Content-Type` 是否包含 `application/json`
|
|
181
|
+
- 只有 JSON 响应才会记录 `response_body` 字段
|
|
182
|
+
- 文件下载、HTML 页面等非 JSON 响应不会记录 body,避免日志污染
|
|
183
|
+
- 这样可以显著减少日志体积,同时保持日志的可读性
|
|
184
|
+
|
|
102
185
|
### 启用方式
|
|
103
186
|
|
|
104
187
|
通过环境变量启用:
|
|
@@ -121,7 +204,11 @@ LOG_RESPONSE_BODY=true
|
|
|
121
204
|
|
|
122
205
|
2. **默认不限制长度**:如果不设置 `LOG_MAX_BODY_LENGTH` 环境变量,日志体不会被截断。建议在生产环境设置合理的限制值。
|
|
123
206
|
|
|
124
|
-
3.
|
|
207
|
+
3. **响应体过滤**:
|
|
208
|
+
- 只记录 JSON 响应的 body(检测 Content-Type)
|
|
209
|
+
- 文件下载、HTML、图片等非 JSON 响应不会记录 body
|
|
210
|
+
- 避免记录无法序列化的数据(如 Buffer、Stream)
|
|
211
|
+
- 显著减少日志体积,提高日志可读性
|
|
125
212
|
|
|
126
213
|
### 日志输出示例
|
|
127
214
|
|
|
@@ -250,9 +337,18 @@ NestJS LoggerService 级别到 Pino 级别的映射:
|
|
|
250
337
|
|
|
251
338
|
3. **实现原理**:
|
|
252
339
|
- **拦截响应方法**:在 Interceptor 中拦截 `res.json()` 和 `res.send()`,存储响应体到 `res.__finalResponseBody`
|
|
340
|
+
- **类型检测**:通过 `Content-Type` header 判断是否为 JSON 响应,只记录 JSON 类型的 body
|
|
253
341
|
- **延迟记录**:不在 Interceptor 的 `tap()` 或 `catchError()` 中记录日志,而是在 `finish` 事件中统一记录
|
|
254
342
|
- **完整信息**:此时可以获取 Exception Filter 处理后的最终状态码和响应体,确保日志准确性
|
|
255
343
|
|
|
344
|
+
4. **Logger API 兼容性**:
|
|
345
|
+
- **完全兼容 NestJS 官方**:实现与 `@nestjs/common` 的 Logger 完全一致的 API
|
|
346
|
+
- **参数解析规则**:
|
|
347
|
+
- 最后一个字符串参数自动识别为 `context`
|
|
348
|
+
- Error/Fatal 方法的第一个参数如果是 stack trace 格式,自动识别为 `stack`
|
|
349
|
+
- **无缝替换**:可以直接替换项目中的官方 Logger,无需修改业务代码
|
|
350
|
+
- **扩展功能**:提供 `logStructured()` 方法用于结构化日志场景
|
|
351
|
+
|
|
256
352
|
### 安全性
|
|
257
353
|
|
|
258
354
|
1. **生产环境默认不打印 HTTP 追踪日志**:由于使用 verbose 级别,生产环境(info 级别)默认不会输出详细日志
|
package/dist/index.cjs
CHANGED
|
@@ -14382,7 +14382,6 @@ module.exports = __toCommonJS(index_exports);
|
|
|
14382
14382
|
|
|
14383
14383
|
// src/service/app-logger.service.ts
|
|
14384
14384
|
var import_common2 = require("@nestjs/common");
|
|
14385
|
-
var import_util = require("util");
|
|
14386
14385
|
|
|
14387
14386
|
// src/helper/constants.ts
|
|
14388
14387
|
var PINO_ROOT_LOGGER = Symbol("PINO_ROOT_LOGGER");
|
|
@@ -14678,8 +14677,12 @@ var BasePinoLogger = class _BasePinoLogger {
|
|
|
14678
14677
|
messageText: message
|
|
14679
14678
|
};
|
|
14680
14679
|
}
|
|
14680
|
+
const allMessages = [
|
|
14681
|
+
message,
|
|
14682
|
+
...extras.map((e) => String(e))
|
|
14683
|
+
].join(" ");
|
|
14681
14684
|
return {
|
|
14682
|
-
messageText:
|
|
14685
|
+
messageText: allMessages
|
|
14683
14686
|
};
|
|
14684
14687
|
}
|
|
14685
14688
|
if (typeof message === "object" && message !== null) {
|
|
@@ -14784,7 +14787,7 @@ function normalizeLevel(level) {
|
|
|
14784
14787
|
}
|
|
14785
14788
|
__name(normalizeLevel, "normalizeLevel");
|
|
14786
14789
|
var logger_config_default = (0, import_config.registerAs)("logger", () => {
|
|
14787
|
-
const level = normalizeLevel(process.env.LOGGER_LEVEL || (process.env.NODE_ENV === "production" ? "
|
|
14790
|
+
const level = normalizeLevel(process.env.LOGGER_LEVEL || (process.env.NODE_ENV === "production" ? "trace" : "trace"));
|
|
14788
14791
|
const maxBodyLengthEnv = process.env.LOG_MAX_BODY_LENGTH;
|
|
14789
14792
|
const maxBodyLength = maxBodyLengthEnv ? Number(maxBodyLengthEnv) : null;
|
|
14790
14793
|
return {
|
|
@@ -14874,7 +14877,8 @@ var LoggingInterceptor = class {
|
|
|
14874
14877
|
if (this.config.logRequestBody && Object.keys(req.query || {}).length > 0) {
|
|
14875
14878
|
requestMeta["query_params"] = this.sanitizeAndTruncate(req.query);
|
|
14876
14879
|
}
|
|
14877
|
-
this.appLogger.log(
|
|
14880
|
+
this.appLogger.log(`HTTP request started
|
|
14881
|
+
url=${req.url}`, "HTTPTraceInterceptor");
|
|
14878
14882
|
this.traceLogger.logStructured("verbose", "HTTP request started", requestMeta, "HTTPTraceInterceptor");
|
|
14879
14883
|
let logged = false;
|
|
14880
14884
|
res.on("finish", () => {
|
|
@@ -14890,16 +14894,41 @@ var LoggingInterceptor = class {
|
|
|
14890
14894
|
duration_ms: durationMs
|
|
14891
14895
|
};
|
|
14892
14896
|
const finalResponseBody = res.__finalResponseBody;
|
|
14893
|
-
|
|
14897
|
+
const contentType = res.getHeader("content-type");
|
|
14898
|
+
const isJsonResponse = typeof contentType === "string" && contentType.includes("application/json");
|
|
14899
|
+
if (this.config.logResponseBody && finalResponseBody !== void 0 && isJsonResponse) {
|
|
14894
14900
|
responseMeta["response_body"] = this.sanitizeAndTruncate(finalResponseBody);
|
|
14895
14901
|
}
|
|
14896
14902
|
const isError = statusCode >= 400;
|
|
14897
14903
|
const appLog = this.appLogger[isError ? "error" : "log"].bind(this.appLogger);
|
|
14898
14904
|
const traceLevel = isError ? "error" : "verbose";
|
|
14905
|
+
const shouldLogBody = this.config.logResponseBody && isJsonResponse && finalResponseBody !== void 0;
|
|
14899
14906
|
if (isError) {
|
|
14900
|
-
|
|
14907
|
+
if (shouldLogBody) {
|
|
14908
|
+
appLog(`HTTP request failed
|
|
14909
|
+
url=${req.url}
|
|
14910
|
+
status_code=${statusCode}
|
|
14911
|
+
duration_ms=${durationMs}
|
|
14912
|
+
response_body=${JSON.stringify(finalResponseBody ?? {})}`, "HTTPTraceInterceptor");
|
|
14913
|
+
} else {
|
|
14914
|
+
appLog(`HTTP request failed
|
|
14915
|
+
url=${req.url}
|
|
14916
|
+
status_code=${statusCode}
|
|
14917
|
+
duration_ms=${durationMs}`, "HTTPTraceInterceptor");
|
|
14918
|
+
}
|
|
14901
14919
|
} else {
|
|
14902
|
-
|
|
14920
|
+
if (shouldLogBody) {
|
|
14921
|
+
appLog(`HTTP request completed
|
|
14922
|
+
url=${req.url}
|
|
14923
|
+
status_code=${statusCode}
|
|
14924
|
+
duration_ms=${durationMs}
|
|
14925
|
+
response_body=${JSON.stringify(finalResponseBody ?? {})}`, "HTTPTraceInterceptor");
|
|
14926
|
+
} else {
|
|
14927
|
+
appLog(`HTTP request completed
|
|
14928
|
+
url=${req.url}
|
|
14929
|
+
status_code=${statusCode}
|
|
14930
|
+
duration_ms=${durationMs}`, "HTTPTraceInterceptor");
|
|
14931
|
+
}
|
|
14903
14932
|
}
|
|
14904
14933
|
this.traceLogger.logStructured(traceLevel, isError ? "HTTP request failed" : "HTTP request completed", responseMeta, "HTTPTraceInterceptor");
|
|
14905
14934
|
});
|