@lark-apaas/nestjs-logger 0.1.0-alpha.2 → 0.1.0-alpha.4

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/dist/index.d.cts CHANGED
@@ -32,6 +32,14 @@ declare abstract class BasePinoLogger implements LoggerService {
32
32
  debug?(message: unknown, ...optionalParams: unknown[]): void;
33
33
  verbose?(message: unknown, ...optionalParams: unknown[]): void;
34
34
  fatal?(message: unknown, ...optionalParams: unknown[]): void;
35
+ /**
36
+ * 记录结构化日志,将 meta 对象合并到日志字段中
37
+ * @param level 日志级别
38
+ * @param message 消息文本
39
+ * @param meta 要合并的元数据对象
40
+ * @param context 上下文标识
41
+ */
42
+ logStructured(level: LogLevel, message: string, meta: Record<string, unknown>, context?: string): void;
35
43
  protected write(level: LogLevel, message: unknown, optionalParams: unknown[], treatStack?: boolean): void;
36
44
  private extractOptionalParams;
37
45
  private buildMessagePayload;
@@ -40,6 +48,8 @@ declare abstract class BasePinoLogger implements LoggerService {
40
48
  declare class AppLogger extends BasePinoLogger {
41
49
  constructor(logger: Logger, requestContext: RequestContextService);
42
50
  }
51
+ declare class PinoLoggerService extends BasePinoLogger {
52
+ }
43
53
 
44
54
  declare class LoggerModule {
45
55
  }
@@ -59,4 +69,4 @@ declare class LoggerContextMiddleware implements NestMiddleware {
59
69
 
60
70
  declare const TRACE_LOGGER: unique symbol;
61
71
 
62
- export { AppLogger, LoggerContextMiddleware, LoggerModule, TRACE_LOGGER };
72
+ export { AppLogger, LoggerContextMiddleware, LoggerModule, PinoLoggerService, TRACE_LOGGER };
package/dist/index.d.ts CHANGED
@@ -32,6 +32,14 @@ declare abstract class BasePinoLogger implements LoggerService {
32
32
  debug?(message: unknown, ...optionalParams: unknown[]): void;
33
33
  verbose?(message: unknown, ...optionalParams: unknown[]): void;
34
34
  fatal?(message: unknown, ...optionalParams: unknown[]): void;
35
+ /**
36
+ * 记录结构化日志,将 meta 对象合并到日志字段中
37
+ * @param level 日志级别
38
+ * @param message 消息文本
39
+ * @param meta 要合并的元数据对象
40
+ * @param context 上下文标识
41
+ */
42
+ logStructured(level: LogLevel, message: string, meta: Record<string, unknown>, context?: string): void;
35
43
  protected write(level: LogLevel, message: unknown, optionalParams: unknown[], treatStack?: boolean): void;
36
44
  private extractOptionalParams;
37
45
  private buildMessagePayload;
@@ -40,6 +48,8 @@ declare abstract class BasePinoLogger implements LoggerService {
40
48
  declare class AppLogger extends BasePinoLogger {
41
49
  constructor(logger: Logger, requestContext: RequestContextService);
42
50
  }
51
+ declare class PinoLoggerService extends BasePinoLogger {
52
+ }
43
53
 
44
54
  declare class LoggerModule {
45
55
  }
@@ -59,4 +69,4 @@ declare class LoggerContextMiddleware implements NestMiddleware {
59
69
 
60
70
  declare const TRACE_LOGGER: unique symbol;
61
71
 
62
- export { AppLogger, LoggerContextMiddleware, LoggerModule, TRACE_LOGGER };
72
+ export { AppLogger, LoggerContextMiddleware, LoggerModule, PinoLoggerService, TRACE_LOGGER };
package/dist/index.js CHANGED
@@ -14511,6 +14511,36 @@ var BasePinoLogger = class _BasePinoLogger {
14511
14511
  fatal(message, ...optionalParams) {
14512
14512
  this.write("fatal", message, optionalParams, true);
14513
14513
  }
14514
+ /**
14515
+ * 记录结构化日志,将 meta 对象合并到日志字段中
14516
+ * @param level 日志级别
14517
+ * @param message 消息文本
14518
+ * @param meta 要合并的元数据对象
14519
+ * @param context 上下文标识
14520
+ */
14521
+ logStructured(level, message, meta, context) {
14522
+ if (!this.levelState.isEnabled(level)) {
14523
+ return;
14524
+ }
14525
+ const requestState = this.contextStore.getContext();
14526
+ const traceId = requestState?.requestId ?? null;
14527
+ const payload = {
14528
+ trace_id: traceId,
14529
+ path: requestState?.path,
14530
+ method: requestState?.method,
14531
+ user_id: requestState?.userId ?? null,
14532
+ app_id: requestState?.appId ?? null,
14533
+ tenant_id: requestState?.tenantId ?? null,
14534
+ pid: process.pid,
14535
+ ...sanitizeValue(meta)
14536
+ };
14537
+ if (context) {
14538
+ payload.context = context;
14539
+ }
14540
+ const pinoLevel = mapLogLevelToPino(level);
14541
+ const sanitizedPayload = sanitizeValue(payload);
14542
+ this.logger[pinoLevel](sanitizedPayload, message);
14543
+ }
14514
14544
  write(level, message, optionalParams, treatStack = false) {
14515
14545
  if (!this.levelState.isEnabled(level)) {
14516
14546
  return;
@@ -14669,7 +14699,7 @@ function sanitizeValue(value) {
14669
14699
  __name(sanitizeValue, "sanitizeValue");
14670
14700
 
14671
14701
  // src/module.ts
14672
- var import_config2 = __toESM(require_config2(), 1);
14702
+ var import_config3 = __toESM(require_config2(), 1);
14673
14703
  import { Global, Module } from "@nestjs/common";
14674
14704
  import { APP_INTERCEPTOR } from "@nestjs/core";
14675
14705
 
@@ -14693,15 +14723,22 @@ function normalizeLevel(level) {
14693
14723
  __name(normalizeLevel, "normalizeLevel");
14694
14724
  var logger_config_default = (0, import_config.registerAs)("logger", () => {
14695
14725
  const level = normalizeLevel(process.env.LOGGER_LEVEL || (process.env.NODE_ENV === "production" ? "info" : "debug"));
14726
+ const maxBodyLengthEnv = process.env.LOG_MAX_BODY_LENGTH;
14727
+ const maxBodyLength = maxBodyLengthEnv ? Number(maxBodyLengthEnv) : null;
14696
14728
  return {
14697
14729
  level,
14698
- logDir: process.env.LOG_DIR || "logs"
14730
+ logDir: process.env.LOG_DIR || "logs",
14731
+ logRequestBody: process.env.LOG_REQUEST_BODY === "true",
14732
+ logResponseBody: process.env.LOG_RESPONSE_BODY === "true",
14733
+ maxBodyLength,
14734
+ sensitiveFields: (process.env.LOG_SENSITIVE_FIELDS || "password,token,secret,authorization,cookie,apiKey,accessToken,refreshToken").split(",").map((f) => f.trim())
14699
14735
  };
14700
14736
  });
14701
14737
 
14702
14738
  // src/interceptor/logging.interceptor.ts
14739
+ var import_config2 = __toESM(require_config2(), 1);
14703
14740
  var import_operators = __toESM(require_operators(), 1);
14704
- import { Inject as Inject2, Injectable as Injectable3, LoggerService } from "@nestjs/common";
14741
+ import { Inject as Inject2, Injectable as Injectable3 } from "@nestjs/common";
14705
14742
  function _ts_decorate3(decorators, target, key, desc) {
14706
14743
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
14707
14744
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -14725,9 +14762,13 @@ var LoggingInterceptor = class {
14725
14762
  }
14726
14763
  traceLogger;
14727
14764
  requestContext;
14728
- constructor(traceLogger, requestContext) {
14765
+ appLogger;
14766
+ config;
14767
+ constructor(traceLogger, requestContext, appLogger, config) {
14729
14768
  this.traceLogger = traceLogger;
14730
14769
  this.requestContext = requestContext;
14770
+ this.appLogger = appLogger;
14771
+ this.config = config;
14731
14772
  }
14732
14773
  intercept(context, next) {
14733
14774
  if (context.getType() !== "http") {
@@ -14746,7 +14787,6 @@ var LoggingInterceptor = class {
14746
14787
  const baseMeta = {
14747
14788
  method,
14748
14789
  path: url,
14749
- requestId: req.requestId ?? req.id,
14750
14790
  trace_id: req.requestId ?? req.id,
14751
14791
  user_id: req.userContext?.userId ?? null,
14752
14792
  tenant_id: req.userContext?.tenantId ?? null,
@@ -14754,39 +14794,141 @@ var LoggingInterceptor = class {
14754
14794
  ip: req.ip ?? null,
14755
14795
  pid: process.pid
14756
14796
  };
14757
- this.traceLogger.log("HTTP request started", baseMeta, "HTTP");
14758
- return next.handle().pipe((0, import_operators.tap)(() => {
14797
+ this.appLogger.logStructured("log", "HTTP request started", baseMeta, "HTTPTraceInterceptor");
14798
+ const requestMeta = {
14799
+ ...baseMeta
14800
+ };
14801
+ if (this.config.logRequestBody && req.body) {
14802
+ requestMeta["request_body"] = this.sanitizeAndTruncate(req.body);
14803
+ }
14804
+ if (this.config.logRequestBody && Object.keys(req.query || {}).length > 0) {
14805
+ requestMeta["query_params"] = this.sanitizeAndTruncate(req.query);
14806
+ }
14807
+ this.traceLogger.logStructured("verbose", "HTTP request started", requestMeta, "HTTPTraceInterceptor");
14808
+ return next.handle().pipe((0, import_operators.tap)((responseData) => {
14759
14809
  const durationMs = Date.now() - startedAt;
14760
14810
  const statusCode = res.statusCode;
14761
- this.traceLogger.log("HTTP request completed", {
14811
+ this.appLogger.logStructured("log", "HTTP request completed", {
14812
+ ...baseMeta,
14813
+ status_code: statusCode,
14814
+ duration_ms: durationMs
14815
+ }, "HTTPTraceInterceptor");
14816
+ const responseMeta = {
14762
14817
  ...baseMeta,
14763
- statusCode,
14764
- durationMs
14765
- }, "HTTP");
14818
+ status_code: statusCode,
14819
+ duration_ms: durationMs
14820
+ };
14821
+ if (this.config.logResponseBody && responseData !== void 0) {
14822
+ const contentType = res.getHeader("content-type");
14823
+ const isJsonResponse = this.isJsonContentType(contentType);
14824
+ if (isJsonResponse) {
14825
+ responseMeta["response_body"] = this.sanitizeAndTruncate(responseData);
14826
+ }
14827
+ }
14828
+ this.traceLogger.logStructured("verbose", "HTTP request completed", responseMeta, "HTTPTraceInterceptor");
14766
14829
  }), (0, import_operators.catchError)((error) => {
14767
14830
  const durationMs = Date.now() - startedAt;
14768
14831
  const statusCode = res.statusCode >= 400 ? res.statusCode : 500;
14832
+ const linkMeta = {
14833
+ ...baseMeta,
14834
+ status_code: statusCode,
14835
+ duration_ms: durationMs
14836
+ };
14837
+ if (error instanceof Error) {
14838
+ linkMeta["error_message"] = error.message;
14839
+ }
14840
+ this.appLogger.logStructured("log", "HTTP request failed", linkMeta, "HTTPTraceInterceptor");
14769
14841
  const meta = {
14770
14842
  ...baseMeta,
14771
- statusCode,
14772
- durationMs
14843
+ status_code: statusCode,
14844
+ duration_ms: durationMs
14773
14845
  };
14774
14846
  if (error instanceof Error) {
14775
- this.traceLogger.error(error, meta, "HTTP");
14847
+ meta["error"] = {
14848
+ message: error.message,
14849
+ stack: error.stack
14850
+ };
14776
14851
  } else {
14777
- this.traceLogger.error("Unknown error thrown", meta, "HTTP");
14852
+ meta["error"] = String(error);
14778
14853
  }
14854
+ this.traceLogger.logStructured("verbose", "HTTP request failed", meta, "HTTPTraceInterceptor");
14779
14855
  throw error;
14780
14856
  }));
14781
14857
  }
14858
+ /**
14859
+ * 对数据进行脱敏和截断处理
14860
+ */
14861
+ sanitizeAndTruncate(data) {
14862
+ try {
14863
+ const sanitized = this.maskSensitiveFields(data);
14864
+ if (this.config.maxBodyLength === null) {
14865
+ return sanitized;
14866
+ }
14867
+ const jsonStr = JSON.stringify(sanitized);
14868
+ if (jsonStr.length > this.config.maxBodyLength) {
14869
+ return {
14870
+ _truncated: true,
14871
+ _originalLength: jsonStr.length,
14872
+ _data: jsonStr.substring(0, this.config.maxBodyLength) + "..."
14873
+ };
14874
+ }
14875
+ return sanitized;
14876
+ } catch (error) {
14877
+ return {
14878
+ _error: "Failed to serialize data",
14879
+ _message: error instanceof Error ? error.message : String(error)
14880
+ };
14881
+ }
14882
+ }
14883
+ /**
14884
+ * 判断是否是 JSON 响应类型
14885
+ */
14886
+ isJsonContentType(contentType) {
14887
+ if (typeof contentType !== "string") {
14888
+ return false;
14889
+ }
14890
+ const contentTypeLower = contentType.toLowerCase();
14891
+ return contentTypeLower.includes("application/json");
14892
+ }
14893
+ /**
14894
+ * 脱敏敏感字段
14895
+ */
14896
+ maskSensitiveFields(data) {
14897
+ if (data === null || data === void 0) {
14898
+ return data;
14899
+ }
14900
+ if (Array.isArray(data)) {
14901
+ return data.map((item) => this.maskSensitiveFields(item));
14902
+ }
14903
+ if (typeof data === "object") {
14904
+ const result = {};
14905
+ for (const [key, value] of Object.entries(data)) {
14906
+ const lowerKey = key.toLowerCase();
14907
+ const isSensitive = this.config.sensitiveFields.some((field) => lowerKey.includes(field.toLowerCase()));
14908
+ if (isSensitive) {
14909
+ result[key] = "***MASKED***";
14910
+ } else if (typeof value === "object" && value !== null) {
14911
+ result[key] = this.maskSensitiveFields(value);
14912
+ } else {
14913
+ result[key] = value;
14914
+ }
14915
+ }
14916
+ return result;
14917
+ }
14918
+ return data;
14919
+ }
14782
14920
  };
14783
14921
  LoggingInterceptor = _ts_decorate3([
14784
14922
  Injectable3(),
14785
14923
  _ts_param2(0, Inject2(TRACE_LOGGER)),
14924
+ _ts_param2(2, Inject2(AppLogger)),
14925
+ _ts_param2(3, Inject2(logger_config_default.KEY)),
14786
14926
  _ts_metadata2("design:type", Function),
14787
14927
  _ts_metadata2("design:paramtypes", [
14788
- typeof LoggerService === "undefined" ? Object : LoggerService,
14789
- typeof RequestContextService === "undefined" ? Object : RequestContextService
14928
+ typeof PinoLoggerService === "undefined" ? Object : PinoLoggerService,
14929
+ typeof RequestContextService === "undefined" ? Object : RequestContextService,
14930
+ typeof AppLogger === "undefined" ? Object : AppLogger,
14931
+ typeof import_config2.ConfigType === "undefined" ? Object : import_config2.ConfigType
14790
14932
  ])
14791
14933
  ], LoggingInterceptor);
14792
14934
 
@@ -14904,7 +15046,7 @@ LoggerModule = _ts_decorate5([
14904
15046
  Global(),
14905
15047
  Module({
14906
15048
  imports: [
14907
- import_config2.ConfigModule.forFeature(logger_config_default)
15049
+ import_config3.ConfigModule.forFeature(logger_config_default)
14908
15050
  ],
14909
15051
  providers: [
14910
15052
  RequestContextService,
@@ -14951,6 +15093,7 @@ export {
14951
15093
  AppLogger,
14952
15094
  LoggerContextMiddleware,
14953
15095
  LoggerModule,
15096
+ PinoLoggerService,
14954
15097
  TRACE_LOGGER
14955
15098
  };
14956
15099
  //# sourceMappingURL=index.js.map