@lark-apaas/nestjs-logger 0.1.0-alpha.9 → 1.0.1-alpha.0
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 +24 -0
- package/dist/index.cjs +34 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +34 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
- 独立的 trace 日志文件
|
|
12
12
|
- 请求上下文传递
|
|
13
13
|
- 日志体长度截断
|
|
14
|
+
- **完美兼容 Exception Filter**:
|
|
15
|
+
- 拦截 `response.json()` 和 `response.send()` 方法
|
|
16
|
+
- 能够捕获 Exception Filter 改写的响应体和最终状态码
|
|
17
|
+
- 确保异常日志的完整性和准确性
|
|
14
18
|
|
|
15
19
|
## 安装
|
|
16
20
|
|
|
@@ -229,6 +233,26 @@ NestJS LoggerService 级别到 Pino 级别的映射:
|
|
|
229
233
|
|
|
230
234
|
## 注意事项
|
|
231
235
|
|
|
236
|
+
### 架构设计
|
|
237
|
+
|
|
238
|
+
1. **Exception Filter 兼容性**:
|
|
239
|
+
- 使用 `response.on('finish')` 事件记录日志,确保在响应完全发送后才记录
|
|
240
|
+
- 拦截 `response.json()` 和 `response.send()` 方法,捕获最终发送的响应体
|
|
241
|
+
- 能够正确捕获全局 Exception Filter 设置的最终状态码和响应体
|
|
242
|
+
- 日志记录发生在 NestJS 请求生命周期的最后阶段,确保信息完整准确
|
|
243
|
+
|
|
244
|
+
2. **请求生命周期**:
|
|
245
|
+
```
|
|
246
|
+
Middleware → Guards → Interceptor (before) → Pipes → Controller
|
|
247
|
+
↓
|
|
248
|
+
Interceptor (after/catchError) → Exception Filters → Response Finish (✅ 日志记录)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
3. **实现原理**:
|
|
252
|
+
- **拦截响应方法**:在 Interceptor 中拦截 `res.json()` 和 `res.send()`,存储响应体到 `res.__finalResponseBody`
|
|
253
|
+
- **延迟记录**:不在 Interceptor 的 `tap()` 或 `catchError()` 中记录日志,而是在 `finish` 事件中统一记录
|
|
254
|
+
- **完整信息**:此时可以获取 Exception Filter 处理后的最终状态码和响应体,确保日志准确性
|
|
255
|
+
|
|
232
256
|
### 安全性
|
|
233
257
|
|
|
234
258
|
1. **生产环境默认不打印 HTTP 追踪日志**:由于使用 verbose 级别,生产环境(info 级别)默认不会输出详细日志
|
package/dist/index.cjs
CHANGED
|
@@ -10567,7 +10567,7 @@ var require_tap = __commonJS({
|
|
|
10567
10567
|
var lift_1 = require_lift();
|
|
10568
10568
|
var OperatorSubscriber_1 = require_OperatorSubscriber();
|
|
10569
10569
|
var identity_1 = require_identity();
|
|
10570
|
-
function
|
|
10570
|
+
function tap(observerOrNext, error, complete) {
|
|
10571
10571
|
var tapObserver = isFunction_1.isFunction(observerOrNext) || error || complete ? {
|
|
10572
10572
|
next: observerOrNext,
|
|
10573
10573
|
error,
|
|
@@ -10600,8 +10600,8 @@ var require_tap = __commonJS({
|
|
|
10600
10600
|
}));
|
|
10601
10601
|
}) : identity_1.identity;
|
|
10602
10602
|
}
|
|
10603
|
-
__name(
|
|
10604
|
-
exports2.tap =
|
|
10603
|
+
__name(tap, "tap");
|
|
10604
|
+
exports2.tap = tap;
|
|
10605
10605
|
}
|
|
10606
10606
|
});
|
|
10607
10607
|
|
|
@@ -14855,7 +14855,16 @@ var LoggingInterceptor = class {
|
|
|
14855
14855
|
ip: req.ip ?? null,
|
|
14856
14856
|
pid: process.pid
|
|
14857
14857
|
};
|
|
14858
|
-
|
|
14858
|
+
const originalJson = res.json.bind(res);
|
|
14859
|
+
const originalSend = res.send.bind(res);
|
|
14860
|
+
res.json = function(body) {
|
|
14861
|
+
res.__finalResponseBody = body;
|
|
14862
|
+
return originalJson(body);
|
|
14863
|
+
};
|
|
14864
|
+
res.send = function(body) {
|
|
14865
|
+
res.__finalResponseBody = body;
|
|
14866
|
+
return originalSend(body);
|
|
14867
|
+
};
|
|
14859
14868
|
const requestMeta = {
|
|
14860
14869
|
...baseMeta
|
|
14861
14870
|
};
|
|
@@ -14865,50 +14874,36 @@ var LoggingInterceptor = class {
|
|
|
14865
14874
|
if (this.config.logRequestBody && Object.keys(req.query || {}).length > 0) {
|
|
14866
14875
|
requestMeta["query_params"] = this.sanitizeAndTruncate(req.query);
|
|
14867
14876
|
}
|
|
14877
|
+
this.appLogger.log("HTTP request started \n url=%s \n request_query=%o \n request_body=%o", req.url, requestMeta["query_params"] ?? {}, requestMeta["request_body"] ?? {});
|
|
14868
14878
|
this.traceLogger.logStructured("verbose", "HTTP request started", requestMeta, "HTTPTraceInterceptor");
|
|
14869
|
-
|
|
14879
|
+
let logged = false;
|
|
14880
|
+
res.on("finish", () => {
|
|
14881
|
+
if (logged) {
|
|
14882
|
+
return;
|
|
14883
|
+
}
|
|
14884
|
+
logged = true;
|
|
14870
14885
|
const durationMs = Date.now() - startedAt;
|
|
14871
14886
|
const statusCode = res.statusCode;
|
|
14872
|
-
this.appLogger.logStructured("log", "HTTP request completed", {
|
|
14873
|
-
...baseMeta,
|
|
14874
|
-
status_code: statusCode,
|
|
14875
|
-
duration_ms: durationMs
|
|
14876
|
-
}, "HTTPTraceInterceptor");
|
|
14877
14887
|
const responseMeta = {
|
|
14878
14888
|
...baseMeta,
|
|
14879
14889
|
status_code: statusCode,
|
|
14880
14890
|
duration_ms: durationMs
|
|
14881
14891
|
};
|
|
14882
|
-
|
|
14883
|
-
|
|
14892
|
+
const finalResponseBody = res.__finalResponseBody;
|
|
14893
|
+
if (this.config.logResponseBody && finalResponseBody !== void 0) {
|
|
14894
|
+
responseMeta["response_body"] = this.sanitizeAndTruncate(finalResponseBody);
|
|
14884
14895
|
}
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
const
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
...baseMeta,
|
|
14891
|
-
status_code: statusCode,
|
|
14892
|
-
duration_ms: durationMs
|
|
14893
|
-
};
|
|
14894
|
-
if (error instanceof Error) {
|
|
14895
|
-
linkMeta["error_message"] = error.message;
|
|
14896
|
-
}
|
|
14897
|
-
this.appLogger.logStructured("log", "HTTP request failed", linkMeta, "HTTPTraceInterceptor");
|
|
14898
|
-
const meta = {
|
|
14899
|
-
...baseMeta,
|
|
14900
|
-
status_code: statusCode,
|
|
14901
|
-
duration_ms: durationMs
|
|
14902
|
-
};
|
|
14903
|
-
if (error instanceof Error) {
|
|
14904
|
-
meta["error"] = {
|
|
14905
|
-
message: error.message,
|
|
14906
|
-
stack: error.stack
|
|
14907
|
-
};
|
|
14896
|
+
const isError = statusCode >= 400;
|
|
14897
|
+
const appLog = this.appLogger[isError ? "error" : "log"].bind(this.appLogger);
|
|
14898
|
+
const traceLevel = isError ? "error" : "verbose";
|
|
14899
|
+
if (isError) {
|
|
14900
|
+
appLog("HTTP request failed\n url=%s \n status_code=%d \n duration_ms=%d \n response_body=%o", req.url, statusCode, durationMs, finalResponseBody ?? {});
|
|
14908
14901
|
} else {
|
|
14909
|
-
|
|
14902
|
+
appLog("HTTP request completed\n url=%s \n status_code=%d \n duration_ms=%d \n response_body=%o", req.url, statusCode, durationMs, finalResponseBody ?? {});
|
|
14910
14903
|
}
|
|
14911
|
-
this.traceLogger.logStructured(
|
|
14904
|
+
this.traceLogger.logStructured(traceLevel, isError ? "HTTP request failed" : "HTTP request completed", responseMeta, "HTTPTraceInterceptor");
|
|
14905
|
+
});
|
|
14906
|
+
return next.handle().pipe((0, import_operators.catchError)((error) => {
|
|
14912
14907
|
throw error;
|
|
14913
14908
|
}));
|
|
14914
14909
|
}
|
|
@@ -14987,7 +14982,7 @@ function createPinoLogger(config) {
|
|
|
14987
14982
|
}
|
|
14988
14983
|
__name(createPinoLogger, "createPinoLogger");
|
|
14989
14984
|
function createFileDestination(pathname) {
|
|
14990
|
-
const target = pathname && pathname.length > 0 ? pathname : "logs/
|
|
14985
|
+
const target = pathname && pathname.length > 0 ? pathname : "logs/server.log";
|
|
14991
14986
|
const resolved = (0, import_path.isAbsolute)(target) ? target : (0, import_path.join)(process.cwd(), target);
|
|
14992
14987
|
const dir = (0, import_path.dirname)(resolved);
|
|
14993
14988
|
if (!(0, import_fs.existsSync)(dir)) {
|
|
@@ -15076,7 +15071,7 @@ LoggerModule = _ts_decorate5([
|
|
|
15076
15071
|
useFactory: /* @__PURE__ */ __name((config) => {
|
|
15077
15072
|
return createPinoLogger({
|
|
15078
15073
|
level: config.level,
|
|
15079
|
-
filePath: `${config.logDir}/
|
|
15074
|
+
filePath: `${config.logDir}/server.log`
|
|
15080
15075
|
});
|
|
15081
15076
|
}, "useFactory"),
|
|
15082
15077
|
inject: [
|