@nest-omni/core 4.1.3-26 → 4.1.3-28

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.
@@ -13,7 +13,7 @@ const nestjs_i18n_1 = require("nestjs-i18n");
13
13
  const validation_metadata_helper_1 = require("../common/helpers/validation-metadata-helper");
14
14
  let HttpExceptionFilter = class HttpExceptionFilter {
15
15
  catch(exception, host) {
16
- var _a, _b, _c, _d, _e;
16
+ var _a;
17
17
  const i18n = nestjs_i18n_1.I18nContext.current(host);
18
18
  const ctx = host.switchToHttp();
19
19
  const response = ctx.getResponse();
@@ -26,19 +26,13 @@ let HttpExceptionFilter = class HttpExceptionFilter {
26
26
  statusCode = 404;
27
27
  error = 'Entity not found';
28
28
  }
29
- if (statusCode === 500) {
30
- console.error({
31
- message: exception.message,
32
- stack: exception.stack,
33
- connection: {
34
- localAddress: (_a = request.socket) === null || _a === void 0 ? void 0 : _a.localAddress,
35
- remoteAddress: (_b = request.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress,
36
- bytesRead: (_c = request.socket) === null || _c === void 0 ? void 0 : _c.bytesRead,
37
- bytesWritten: (_d = request.socket) === null || _d === void 0 ? void 0 : _d.bytesWritten,
38
- },
39
- headers: request.headers,
40
- });
41
- }
29
+ // 将异常信息存储到 request 对象上,供 HttpLoggingInterceptor 使用
30
+ request._exception = {
31
+ message: exception.message,
32
+ stack: exception.stack,
33
+ code: exception.code,
34
+ statusCode,
35
+ };
42
36
  const getFirstError = (error, property = '') => {
43
37
  var _a;
44
38
  if (((_a = error === null || error === void 0 ? void 0 : error.children) === null || _a === void 0 ? void 0 : _a.length) > 0) {
@@ -57,7 +51,7 @@ let HttpExceptionFilter = class HttpExceptionFilter {
57
51
  const lang = i18n.lang || 'zh';
58
52
  // 在 stringify 之前获取 target
59
53
  // firstError.target 可能是实例而不是构造函数,所以需要获取 constructor
60
- let target = ((_e = firstError.target) === null || _e === void 0 ? void 0 : _e.constructor) || firstError.target;
54
+ let target = ((_a = firstError.target) === null || _a === void 0 ? void 0 : _a.constructor) || firstError.target;
61
55
  if (!target && (firstError === null || firstError === void 0 ? void 0 : firstError.object)) {
62
56
  target = firstError.object.constructor;
63
57
  }
@@ -62,7 +62,7 @@ exports.DEFAULT_HTTP_CLIENT_CONFIG = {
62
62
  logHeaders: false, // 默认不记录头信息(安全考虑)
63
63
  logBody: true,
64
64
  maxBodyLength: 1000,
65
- sanitizeHeaders: ['authorization', 'apikey', 'password', 'token', 'cookie'],
65
+ sanitize: ['password', 'secret', 'token', 'apiKey', 'authorization', 'cookie'],
66
66
  logLevel: 'info',
67
67
  databaseLogging: {
68
68
  enabled: false, // 默认不启用数据库日志
@@ -40,7 +40,7 @@ export declare const HttpCircuitBreaker: (options?: {
40
40
  export declare const HttpLogRequest: (options?: {
41
41
  logHeaders?: boolean;
42
42
  logBody?: boolean;
43
- sanitizeHeaders?: string[];
43
+ sanitize?: string[];
44
44
  databaseLog?: boolean;
45
45
  logLevel?: "debug" | "info" | "warn" | "error";
46
46
  serviceClass?: string;
@@ -125,7 +125,7 @@ const HttpLogRequest = (options = {}) => {
125
125
  const loggingOptions = {
126
126
  logHeaders: (_a = options.logHeaders) !== null && _a !== void 0 ? _a : true,
127
127
  logBody: (_b = options.logBody) !== null && _b !== void 0 ? _b : true,
128
- sanitizeHeaders: options.sanitizeHeaders || [
128
+ sanitize: options.sanitize || [
129
129
  'authorization',
130
130
  'apikey',
131
131
  'password',
@@ -158,7 +158,7 @@ __decorate([
158
158
  logHeaders: true,
159
159
  logBody: true,
160
160
  databaseLog: true,
161
- sanitizeHeaders: ['authorization', 'x-api-key'],
161
+ sanitize: ['authorization', 'x-api-key'],
162
162
  }),
163
163
  __metadata("design:type", Function),
164
164
  __metadata("design:paramtypes", [Object, String]),
@@ -185,7 +185,7 @@ exports.GitHubHttpService = GitHubHttpService = __decorate([
185
185
  logHeaders: true,
186
186
  logBody: true,
187
187
  maxBodyLength: 1000,
188
- sanitizeHeaders: ['authorization'],
188
+ sanitize: ['authorization'],
189
189
  logLevel: 'info',
190
190
  },
191
191
  }),
@@ -315,7 +315,7 @@ exports.PaymentHttpService = PaymentHttpService = __decorate([
315
315
  logHeaders: false, // 不记录敏感头信息
316
316
  logBody: false, // 不记录支付数据
317
317
  maxBodyLength: 1000,
318
- sanitizeHeaders: ['authorization', 'x-api-key', 'x-client-secret'],
318
+ sanitize: ['authorization', 'x-api-key', 'x-client-secret'],
319
319
  logLevel: 'info',
320
320
  },
321
321
  }),
@@ -295,8 +295,8 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
295
295
  maxBodyLength: configService.get('HTTP_CLIENT_LOG_MAX_BODY_LENGTH')
296
296
  ? parseInt(configService.get('HTTP_CLIENT_LOG_MAX_BODY_LENGTH'))
297
297
  : undefined,
298
- sanitizeHeaders: (_a = configService
299
- .get('HTTP_CLIENT_LOG_SANITIZE_HEADERS')) === null || _a === void 0 ? void 0 : _a.split(','),
298
+ sanitize: (_a = configService
299
+ .get('HTTP_CLIENT_LOG_SANITIZE')) === null || _a === void 0 ? void 0 : _a.split(','),
300
300
  logLevel: configService.get('HTTP_CLIENT_LOG_LEVEL'),
301
301
  databaseLogging: {
302
302
  enabled: configService.get('HTTP_CLIENT_DB_LOGGING_ENABLED') ===
@@ -166,7 +166,12 @@ export interface LoggingConfig {
166
166
  logHeaders?: boolean;
167
167
  logBody?: boolean;
168
168
  maxBodyLength?: number;
169
- sanitizeHeaders?: string[];
169
+ /**
170
+ * 脱敏配置
171
+ * 敏感字段列表,统一适用于 headers、body、query string
172
+ * 例如:['password', 'token', 'secret', 'apiKey']
173
+ */
174
+ sanitize?: string[];
170
175
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
171
176
  databaseLogging?: {
172
177
  enabled?: boolean;
@@ -47,7 +47,7 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
47
47
  logHeaders: true,
48
48
  logBody: true,
49
49
  maxBodyLength: 10000,
50
- sanitizeHeaders: ['authorization', 'api-key'],
50
+ sanitize: ['authorization', 'api-key'],
51
51
  logLevel: 'info',
52
52
  },
53
53
  timeout: 30000,
@@ -716,7 +716,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
716
716
  logHeaders: true,
717
717
  logBody: true,
718
718
  maxBodyLength: 1000,
719
- sanitizeHeaders: ['authorization', 'apikey', 'password', 'token'],
719
+ sanitize: ['password', 'secret', 'token', 'apiKey', 'authorization', 'cookie'],
720
720
  logLevel: 'info',
721
721
  databaseLogging: {
722
722
  enabled: true,
@@ -33,7 +33,7 @@ export declare class HttpLoggingService implements OnModuleDestroy {
33
33
  private isProcessing;
34
34
  constructor();
35
35
  onModuleDestroy(): void;
36
- initRepository(dataSource: string, tableName: string): Repository<HttpLogEntity>;
36
+ initRepository(dataSource: string, tableName: string): Repository<HttpLogEntity> | null;
37
37
  /**
38
38
  * 记录请求开始
39
39
  */
@@ -119,22 +119,6 @@ export declare class HttpLoggingService implements OnModuleDestroy {
119
119
  * 同步保存日志到数据库(用于异步模式内部)
120
120
  */
121
121
  private saveLogToDatabase;
122
- /**
123
- * 转换请求体为字符串
124
- */
125
- private sanitizeBodyAsString;
126
- /**
127
- * 清理敏感头信息
128
- */
129
- private sanitizeHeaders;
130
- /**
131
- * 清理敏感请求体信息
132
- */
133
- private sanitizeBody;
134
- /**
135
- * 递归清理对象中的敏感字段
136
- */
137
- private sanitizeObject;
138
122
  /**
139
123
  * 获取完整URL
140
124
  * @param config Axios请求配置
@@ -25,6 +25,7 @@ const http_log_entity_1 = require("../entities/http-log.entity");
25
25
  const request_id_util_1 = require("../utils/request-id.util");
26
26
  const context_extractor_util_1 = require("../utils/context-extractor.util");
27
27
  const call_stack_extractor_util_1 = require("../utils/call-stack-extractor.util");
28
+ const sanitize_util_1 = require("../utils/sanitize.util");
28
29
  const transaction_1 = require("@nest-omni/transaction");
29
30
  /**
30
31
  * HTTP日志服务
@@ -67,8 +68,9 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
67
68
  return this.logRepository;
68
69
  }
69
70
  catch (error) {
70
- this.logger.error('Failed to initialize database logging', error);
71
- throw error;
71
+ this.logger.warn('Database logging not available, continuing without it');
72
+ this.logRepository = null;
73
+ return null;
72
74
  }
73
75
  }
74
76
  /**
@@ -78,16 +80,16 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
78
80
  // 从ContextProvider获取上下文信息
79
81
  const context = context_extractor_util_1.ContextExtractor.getHttpContext();
80
82
  const actualRequestId = requestId || context.requestId || (0, request_id_util_1.generateRequestId)();
81
- const { logHeaders = true, logBody = true, sanitizeHeaders = [], } = loggingOptions || {};
83
+ const { logHeaders = true, logBody = true, sanitize = [], } = loggingOptions || {};
82
84
  const logData = {
83
85
  requestId: actualRequestId,
84
86
  userId: context.userId,
85
- method: config.method ? config.method.toUpperCase() : 'GET',
86
- url: config.url,
87
- headers: logHeaders
88
- ? this.sanitizeHeaders(config.headers, sanitizeHeaders)
87
+ method: (config === null || config === void 0 ? void 0 : config.method) ? config.method.toUpperCase() : 'GET',
88
+ url: config ? sanitize_util_1.SanitizeUtil.sanitizeQueryString(config.url, sanitize) : '',
89
+ headers: logHeaders && config
90
+ ? sanitize_util_1.SanitizeUtil.sanitizeHeaders(config.headers, sanitize)
89
91
  : undefined,
90
- body: logBody ? this.sanitizeBody(config.data) : undefined,
92
+ body: logBody && config ? sanitize_util_1.SanitizeUtil.sanitizeBody(config.data, sanitize) : undefined,
91
93
  clientIp: context.clientIp,
92
94
  serviceName: context.appId,
93
95
  };
@@ -108,8 +110,8 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
108
110
  */
109
111
  logRequestSuccess(response_1, startTime_1, requestId_1, loggingOptions_1) {
110
112
  return __awaiter(this, arguments, void 0, function* (response, startTime, requestId, loggingOptions, databaseLogging = false, retryRecords, circuitBreakerState, decoratorContext, clientName, callingContext) {
111
- var _a, _b, _c, _d;
112
- const { logHeaders = true, logBody = true, sanitizeHeaders = [], } = loggingOptions || {};
113
+ var _a, _b, _c, _d, _e, _f;
114
+ const { logHeaders = true, logBody = true, sanitize = [], } = loggingOptions || {};
113
115
  const responseTime = Date.now() - startTime;
114
116
  // 获取当前上下文信息
115
117
  const context = context_extractor_util_1.ContextExtractor.getHttpContext();
@@ -119,9 +121,9 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
119
121
  statusCode: response.status,
120
122
  responseTime,
121
123
  headers: logHeaders
122
- ? this.sanitizeHeaders(response.headers, sanitizeHeaders)
124
+ ? sanitize_util_1.SanitizeUtil.sanitizeHeaders(response.headers, sanitize)
123
125
  : undefined,
124
- body: logBody ? this.sanitizeBody(response.data) : undefined,
126
+ body: logBody ? sanitize_util_1.SanitizeUtil.sanitizeBody(response.data, sanitize) : undefined,
125
127
  responseSize: JSON.stringify(response.data).length,
126
128
  circuitBreakerState,
127
129
  };
@@ -140,7 +142,7 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
140
142
  dataSource: (_a = loggingOptions.databaseLogging) === null || _a === void 0 ? void 0 : _a.dataSource,
141
143
  tableName: (_b = loggingOptions.databaseLogging) === null || _b === void 0 ? void 0 : _b.tableName,
142
144
  });
143
- this.initRepository(loggingOptions.databaseLogging.dataSource, loggingOptions.databaseLogging.tableName);
145
+ this.initRepository(((_c = loggingOptions.databaseLogging) === null || _c === void 0 ? void 0 : _c.dataSource) || 'default', ((_d = loggingOptions.databaseLogging) === null || _d === void 0 ? void 0 : _d.tableName) || 'http_log');
144
146
  }
145
147
  // 数据库日志记录
146
148
  if (databaseLogging && this.logRepository) {
@@ -157,25 +159,25 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
157
159
  this.saveToDatabase({
158
160
  requestId,
159
161
  userId: context.userId,
160
- method: ((_c = response.config.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()) || 'UNKNOWN',
161
- url: this.getFullUrl(response.config) || '',
162
- headers: this.sanitizeHeaders(response.config.headers, sanitizeHeaders),
163
- body: this.sanitizeBodyAsString(response.config.data),
164
- params: response.config.params,
162
+ method: ((_e = response.config.method) === null || _e === void 0 ? void 0 : _e.toUpperCase()) || 'UNKNOWN',
163
+ url: sanitize_util_1.SanitizeUtil.sanitizeQueryString(this.getFullUrl(response.config), sanitize),
164
+ headers: sanitize_util_1.SanitizeUtil.sanitizeHeaders(response.config.headers, sanitize),
165
+ body: sanitize_util_1.SanitizeUtil.sanitizeBodyAsString(response.config.data, sanitize),
166
+ params: sanitize_util_1.SanitizeUtil.sanitizeParams(response.config.params, sanitize),
165
167
  statusCode: response.status,
166
168
  responseTime,
167
169
  attemptCount: (retryRecords === null || retryRecords === void 0 ? void 0 : retryRecords.length)
168
170
  ? Math.max(...retryRecords.map((r) => r.attempt)) + 1
169
171
  : 1,
170
172
  success: true,
171
- responseHeaders: this.sanitizeHeaders(response.headers, sanitizeHeaders),
173
+ responseHeaders: sanitize_util_1.SanitizeUtil.sanitizeHeaders(response.headers, sanitize),
172
174
  responseBody: logBody
173
- ? this.sanitizeBodyAsString(response.data)
175
+ ? sanitize_util_1.SanitizeUtil.sanitizeBodyAsString(response.data, sanitize)
174
176
  : undefined,
175
177
  serviceName: clientName || context.appId,
176
178
  operationName: callInfo.operationName,
177
179
  clientIp: context.clientIp,
178
- source: (_d = context.metadata) === null || _d === void 0 ? void 0 : _d.source,
180
+ source: (_f = context.metadata) === null || _f === void 0 ? void 0 : _f.source,
179
181
  tags: context.tags,
180
182
  metadata: logData,
181
183
  retryRecords,
@@ -188,8 +190,8 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
188
190
  * 记录请求错误
189
191
  */
190
192
  logRequestError(error, startTime, requestId, attemptCount, loggingOptions, databaseLogging = false, retryRecords, circuitBreakerState, decoratorContext, clientName, callingContext) {
191
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
192
- const { logHeaders = true, sanitizeHeaders = [] } = loggingOptions;
193
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
194
+ const { logHeaders = true, sanitize = [] } = loggingOptions;
193
195
  const responseTime = Date.now() - startTime;
194
196
  // 获取当前上下文信息
195
197
  const context = context_extractor_util_1.ContextExtractor.getHttpContext();
@@ -203,7 +205,7 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
203
205
  errorCode: error.code,
204
206
  statusCode: (_a = error.response) === null || _a === void 0 ? void 0 : _a.status,
205
207
  headers: ((_b = error.config) === null || _b === void 0 ? void 0 : _b.headers) && logHeaders
206
- ? this.sanitizeHeaders(error.config.headers, sanitizeHeaders)
208
+ ? sanitize_util_1.SanitizeUtil.sanitizeHeaders(error.config.headers, sanitize)
207
209
  : undefined,
208
210
  circuitBreakerState,
209
211
  };
@@ -223,7 +225,7 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
223
225
  break;
224
226
  }
225
227
  if (!this.logRepository) {
226
- this.initRepository(loggingOptions.databaseLogging.dataSource, loggingOptions.databaseLogging.tableName);
228
+ this.initRepository(((_c = loggingOptions.databaseLogging) === null || _c === void 0 ? void 0 : _c.dataSource) || 'default', ((_d = loggingOptions.databaseLogging) === null || _d === void 0 ? void 0 : _d.tableName) || 'http_log');
227
229
  }
228
230
  // 数据库日志记录
229
231
  if (databaseLogging && this.logRepository) {
@@ -234,14 +236,14 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
234
236
  this.saveToDatabase({
235
237
  requestId,
236
238
  userId: context.userId,
237
- method: ((_d = (_c = error.config) === null || _c === void 0 ? void 0 : _c.method) === null || _d === void 0 ? void 0 : _d.toUpperCase()) || 'UNKNOWN',
238
- url: this.getFullUrl(error.config) || '',
239
- headers: ((_e = error.config) === null || _e === void 0 ? void 0 : _e.headers)
240
- ? this.sanitizeHeaders(error.config.headers, sanitizeHeaders)
239
+ method: ((_f = (_e = error.config) === null || _e === void 0 ? void 0 : _e.method) === null || _f === void 0 ? void 0 : _f.toUpperCase()) || 'UNKNOWN',
240
+ url: sanitize_util_1.SanitizeUtil.sanitizeQueryString(this.getFullUrl(error.config), sanitize),
241
+ headers: ((_g = error.config) === null || _g === void 0 ? void 0 : _g.headers)
242
+ ? sanitize_util_1.SanitizeUtil.sanitizeHeaders(error.config.headers, sanitize)
241
243
  : {},
242
- body: this.sanitizeBodyAsString((_f = error.config) === null || _f === void 0 ? void 0 : _f.data),
243
- params: (_g = error.config) === null || _g === void 0 ? void 0 : _g.params,
244
- statusCode: (_h = error.response) === null || _h === void 0 ? void 0 : _h.status,
244
+ body: sanitize_util_1.SanitizeUtil.sanitizeBodyAsString((_h = error.config) === null || _h === void 0 ? void 0 : _h.data, sanitize),
245
+ params: sanitize_util_1.SanitizeUtil.sanitizeParams((_j = error.config) === null || _j === void 0 ? void 0 : _j.params, sanitize),
246
+ statusCode: (_k = error.response) === null || _k === void 0 ? void 0 : _k.status,
245
247
  responseTime,
246
248
  attemptCount,
247
249
  success: false,
@@ -251,7 +253,7 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
251
253
  serviceName: clientName || context.appId,
252
254
  operationName: callInfo.operationName,
253
255
  clientIp: context.clientIp,
254
- source: (_j = context.metadata) === null || _j === void 0 ? void 0 : _j.source,
256
+ source: (_l = context.metadata) === null || _l === void 0 ? void 0 : _l.source,
255
257
  tags: context.tags,
256
258
  metadata: logData,
257
259
  retryRecords,
@@ -581,97 +583,6 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
581
583
  }
582
584
  });
583
585
  }
584
- /**
585
- * 转换请求体为字符串
586
- */
587
- sanitizeBodyAsString(data) {
588
- if (!data)
589
- return undefined;
590
- if (typeof data === 'string') {
591
- return data.length > 5000 ? data.substring(0, 5000) + '...' : data;
592
- }
593
- const jsonString = JSON.stringify(data);
594
- return jsonString.length > 5000
595
- ? jsonString.substring(0, 5000) + '...'
596
- : jsonString;
597
- }
598
- /**
599
- * 清理敏感头信息
600
- */
601
- sanitizeHeaders(headers, sanitizeHeaders) {
602
- if (!headers)
603
- return {};
604
- const sanitized = {};
605
- const defaultSensitiveHeaders = [
606
- 'authorization',
607
- 'apikey',
608
- 'password',
609
- 'token',
610
- 'secret',
611
- 'cookie',
612
- 'set-cookie',
613
- 'x-api-key',
614
- 'x-auth-token',
615
- ];
616
- const headersToSanitize = [...defaultSensitiveHeaders, ...sanitizeHeaders];
617
- Object.keys(headers).forEach((key) => {
618
- if (headersToSanitize.some((sensitive) => key.toLowerCase().includes(sensitive.toLowerCase()))) {
619
- sanitized[key] = '[FILTERED]';
620
- }
621
- else {
622
- sanitized[key] = String(headers[key]);
623
- }
624
- });
625
- return sanitized;
626
- }
627
- /**
628
- * 清理敏感请求体信息
629
- */
630
- sanitizeBody(body) {
631
- if (!body)
632
- return undefined;
633
- if (typeof body === 'string') {
634
- try {
635
- body = JSON.parse(body);
636
- }
637
- catch (_a) {
638
- return body.length > 1000 ? body.substring(0, 1000) + '...' : body;
639
- }
640
- }
641
- if (typeof body === 'object') {
642
- const sanitized = Object.assign({}, body);
643
- const sensitiveFields = [
644
- 'password',
645
- 'secret',
646
- 'token',
647
- 'key',
648
- 'authorization',
649
- 'credential',
650
- 'private',
651
- 'confidential',
652
- 'ssn',
653
- 'creditCard',
654
- ];
655
- this.sanitizeObject(sanitized, sensitiveFields);
656
- return sanitized;
657
- }
658
- return body;
659
- }
660
- /**
661
- * 递归清理对象中的敏感字段
662
- */
663
- sanitizeObject(obj, sensitiveFields) {
664
- if (typeof obj !== 'object' || obj === null)
665
- return;
666
- for (const key in obj) {
667
- if (sensitiveFields.some((field) => key.toLowerCase().includes(field.toLowerCase()))) {
668
- obj[key] = '[FILTERED]';
669
- }
670
- else if (typeof obj[key] === 'object') {
671
- this.sanitizeObject(obj[key], sensitiveFields);
672
- }
673
- }
674
- }
675
586
  /**
676
587
  * 获取完整URL
677
588
  * @param config Axios请求配置
@@ -137,13 +137,10 @@ class CurlGenerator {
137
137
  config.url = urlMatch[1];
138
138
  }
139
139
  // 提取头信息
140
- const headerMatches = curlCommand.match(/-H\s+'([^:]+):\s*([^']+)'/g) || [];
140
+ const headerMatches = Array.from(curlCommand.matchAll(/-H\s+'([^:]+):\s*([^']+)'/g));
141
141
  config.headers = {};
142
142
  headerMatches.forEach((header) => {
143
- const headerMatch = header.match(/-H\s+'([^:]+):\s*([^']+)'/);
144
- if (headerMatch) {
145
- config.headers[headerMatch[1]] = headerMatch[2];
146
- }
143
+ config.headers[header[1]] = header[2];
147
144
  });
148
145
  // 提取数据
149
146
  const dataMatch = curlCommand.match(/-d\s+'([^']+)'/);
@@ -3,3 +3,4 @@ export * from './retry-recorder.util';
3
3
  export * from './context-extractor.util';
4
4
  export * from './call-stack-extractor.util';
5
5
  export * from './proxy-environment.util';
6
+ export * from './sanitize.util';
@@ -19,3 +19,4 @@ __exportStar(require("./retry-recorder.util"), exports);
19
19
  __exportStar(require("./context-extractor.util"), exports);
20
20
  __exportStar(require("./call-stack-extractor.util"), exports);
21
21
  __exportStar(require("./proxy-environment.util"), exports);
22
+ __exportStar(require("./sanitize.util"), exports);
@@ -21,10 +21,6 @@ export declare class RetryRecorder {
21
21
  * 获取重试原因
22
22
  */
23
23
  private static getRetryReason;
24
- /**
25
- * 过滤敏感头信息
26
- */
27
- private static sanitizeHeaders;
28
24
  /**
29
25
  * 添加重试记录
30
26
  */
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RetryRecorder = void 0;
4
+ const sanitize_util_1 = require("./sanitize.util");
4
5
  /**
5
6
  * 请求重试记录器
6
7
  */
@@ -22,7 +23,7 @@ class RetryRecorder {
22
23
  requestConfig: {
23
24
  method: ((_a = config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'UNKNOWN',
24
25
  url: config.url || '',
25
- headers: this.sanitizeHeaders(config.headers || {}),
26
+ headers: sanitize_util_1.SanitizeUtil.sanitizeHeaders(config.headers || {}),
26
27
  },
27
28
  };
28
29
  }
@@ -83,32 +84,6 @@ class RetryRecorder {
83
84
  }
84
85
  return `HTTP error: ${status}`;
85
86
  }
86
- /**
87
- * 过滤敏感头信息
88
- */
89
- static sanitizeHeaders(headers) {
90
- const sanitized = {};
91
- const sensitiveKeys = [
92
- 'authorization',
93
- 'apikey',
94
- 'password',
95
- 'secret',
96
- 'token',
97
- 'x-api-key',
98
- 'x-auth-token',
99
- 'cookie',
100
- 'set-cookie',
101
- ];
102
- Object.entries(headers).forEach(([key, value]) => {
103
- if (sensitiveKeys.some((sensitive) => key.toLowerCase().includes(sensitive))) {
104
- sanitized[key] = '[FILTERED]';
105
- }
106
- else {
107
- sanitized[key] = value;
108
- }
109
- });
110
- return sanitized;
111
- }
112
87
  /**
113
88
  * 添加重试记录
114
89
  */
@@ -0,0 +1,58 @@
1
+ /**
2
+ * HTTP 请求数据脱敏工具
3
+ * 统一处理 headers、body、query string 的敏感信息过滤
4
+ */
5
+ export declare class SanitizeUtil {
6
+ /**
7
+ * 默认敏感字段列表
8
+ */
9
+ private static readonly DEFAULT_SENSITIVE_FIELDS;
10
+ /**
11
+ * 脱敏 headers
12
+ * @param headers - 原始 headers 对象
13
+ * @param sensitiveFields - 自定义敏感字段列表
14
+ * @returns 脱敏后的 headers
15
+ */
16
+ static sanitizeHeaders(headers: any, sensitiveFields?: string[]): Record<string, string>;
17
+ /**
18
+ * 脱敏 body
19
+ * @param body - 原始 body 数据
20
+ * @param sensitiveFields - 自定义敏感字段列表
21
+ * @returns 脱敏后的 body
22
+ */
23
+ static sanitizeBody(body: any, sensitiveFields?: string[]): any;
24
+ /**
25
+ * 脱敏 URL 中的 query string
26
+ * @param url - 原始 URL
27
+ * @param sensitiveFields - 自定义敏感字段列表
28
+ * @returns 脱敏后的 URL
29
+ */
30
+ static sanitizeQueryString(url: string, sensitiveFields?: string[]): string;
31
+ /**
32
+ * 脱敏 params 对象
33
+ * @param params - 原始 params 对象
34
+ * @param sensitiveFields - 自定义敏感字段列表
35
+ * @returns 脱敏后的 params
36
+ */
37
+ static sanitizeParams(params: Record<string, any>, sensitiveFields?: string[]): Record<string, any>;
38
+ /**
39
+ * 将 body 转换为字符串并脱敏
40
+ * @param data - 原始数据
41
+ * @param sensitiveFields - 自定义敏感字段列表
42
+ * @returns 脱敏后的字符串
43
+ */
44
+ static sanitizeBodyAsString(data: any, sensitiveFields?: string[]): string | undefined;
45
+ /**
46
+ * 判断字段是否为敏感字段
47
+ * @param key - 字段名
48
+ * @param sensitiveFields - 敏感字段列表
49
+ * @returns 是否为敏感字段
50
+ */
51
+ private static isSensitiveField;
52
+ /**
53
+ * 递归脱敏对象中的敏感字段
54
+ * @param obj - 目标对象
55
+ * @param sensitiveFields - 敏感字段列表
56
+ */
57
+ private static sanitizeObject;
58
+ }
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SanitizeUtil = void 0;
4
+ /**
5
+ * HTTP 请求数据脱敏工具
6
+ * 统一处理 headers、body、query string 的敏感信息过滤
7
+ */
8
+ class SanitizeUtil {
9
+ /**
10
+ * 脱敏 headers
11
+ * @param headers - 原始 headers 对象
12
+ * @param sensitiveFields - 自定义敏感字段列表
13
+ * @returns 脱敏后的 headers
14
+ */
15
+ static sanitizeHeaders(headers, sensitiveFields = []) {
16
+ if (!headers)
17
+ return {};
18
+ const sanitized = {};
19
+ const fieldsToSanitize = [
20
+ ...this.DEFAULT_SENSITIVE_FIELDS,
21
+ ...sensitiveFields,
22
+ ];
23
+ Object.keys(headers).forEach((key) => {
24
+ if (this.isSensitiveField(key, fieldsToSanitize)) {
25
+ sanitized[key] = '[FILTERED]';
26
+ }
27
+ else {
28
+ sanitized[key] = String(headers[key]);
29
+ }
30
+ });
31
+ return sanitized;
32
+ }
33
+ /**
34
+ * 脱敏 body
35
+ * @param body - 原始 body 数据
36
+ * @param sensitiveFields - 自定义敏感字段列表
37
+ * @returns 脱敏后的 body
38
+ */
39
+ static sanitizeBody(body, sensitiveFields = []) {
40
+ if (!body)
41
+ return undefined;
42
+ if (typeof body === 'string') {
43
+ try {
44
+ body = JSON.parse(body);
45
+ }
46
+ catch (_a) {
47
+ // 非 JSON 字符串,不做处理
48
+ return body.length > 1000 ? body.substring(0, 1000) + '...' : body;
49
+ }
50
+ }
51
+ if (typeof body === 'object') {
52
+ const sanitized = Object.assign({}, body);
53
+ const fieldsToSanitize = [
54
+ ...this.DEFAULT_SENSITIVE_FIELDS,
55
+ ...sensitiveFields,
56
+ ];
57
+ this.sanitizeObject(sanitized, fieldsToSanitize);
58
+ return sanitized;
59
+ }
60
+ return body;
61
+ }
62
+ /**
63
+ * 脱敏 URL 中的 query string
64
+ * @param url - 原始 URL
65
+ * @param sensitiveFields - 自定义敏感字段列表
66
+ * @returns 脱敏后的 URL
67
+ */
68
+ static sanitizeQueryString(url, sensitiveFields = []) {
69
+ if (!url)
70
+ return url;
71
+ const queryIndex = url.indexOf('?');
72
+ if (queryIndex === -1) {
73
+ return url; // 没有 query string
74
+ }
75
+ const baseUrl = url.substring(0, queryIndex);
76
+ const queryString = url.substring(queryIndex + 1);
77
+ const params = new URLSearchParams(queryString);
78
+ const fieldsToSanitize = [
79
+ ...this.DEFAULT_SENSITIVE_FIELDS,
80
+ ...sensitiveFields,
81
+ ];
82
+ params.forEach((value, key) => {
83
+ if (this.isSensitiveField(key, fieldsToSanitize)) {
84
+ params.set(key, '[FILTERED]');
85
+ }
86
+ });
87
+ const sanitizedQuery = params.toString();
88
+ return sanitizedQuery ? `${baseUrl}?${sanitizedQuery}` : baseUrl;
89
+ }
90
+ /**
91
+ * 脱敏 params 对象
92
+ * @param params - 原始 params 对象
93
+ * @param sensitiveFields - 自定义敏感字段列表
94
+ * @returns 脱敏后的 params
95
+ */
96
+ static sanitizeParams(params, sensitiveFields = []) {
97
+ if (!params)
98
+ return {};
99
+ const sanitized = {};
100
+ const fieldsToSanitize = [
101
+ ...this.DEFAULT_SENSITIVE_FIELDS,
102
+ ...sensitiveFields,
103
+ ];
104
+ Object.keys(params).forEach((key) => {
105
+ if (this.isSensitiveField(key, fieldsToSanitize)) {
106
+ sanitized[key] = '[FILTERED]';
107
+ }
108
+ else {
109
+ sanitized[key] = params[key];
110
+ }
111
+ });
112
+ return sanitized;
113
+ }
114
+ /**
115
+ * 将 body 转换为字符串并脱敏
116
+ * @param data - 原始数据
117
+ * @param sensitiveFields - 自定义敏感字段列表
118
+ * @returns 脱敏后的字符串
119
+ */
120
+ static sanitizeBodyAsString(data, sensitiveFields = []) {
121
+ const sanitized = this.sanitizeBody(data, sensitiveFields);
122
+ if (!sanitized)
123
+ return undefined;
124
+ if (typeof sanitized === 'string') {
125
+ return sanitized.length > 5000
126
+ ? sanitized.substring(0, 5000) + '...'
127
+ : sanitized;
128
+ }
129
+ const jsonString = JSON.stringify(sanitized);
130
+ return jsonString.length > 5000
131
+ ? jsonString.substring(0, 5000) + '...'
132
+ : jsonString;
133
+ }
134
+ /**
135
+ * 判断字段是否为敏感字段
136
+ * @param key - 字段名
137
+ * @param sensitiveFields - 敏感字段列表
138
+ * @returns 是否为敏感字段
139
+ */
140
+ static isSensitiveField(key, sensitiveFields) {
141
+ const lowerKey = key.toLowerCase();
142
+ return sensitiveFields.some((field) => lowerKey.includes(field.toLowerCase()));
143
+ }
144
+ /**
145
+ * 递归脱敏对象中的敏感字段
146
+ * @param obj - 目标对象
147
+ * @param sensitiveFields - 敏感字段列表
148
+ */
149
+ static sanitizeObject(obj, sensitiveFields) {
150
+ if (typeof obj !== 'object' || obj === null)
151
+ return;
152
+ for (const key in obj) {
153
+ if (this.isSensitiveField(key, sensitiveFields)) {
154
+ obj[key] = '[FILTERED]';
155
+ }
156
+ else if (typeof obj[key] === 'object') {
157
+ this.sanitizeObject(obj[key], sensitiveFields);
158
+ }
159
+ }
160
+ }
161
+ }
162
+ exports.SanitizeUtil = SanitizeUtil;
163
+ /**
164
+ * 默认敏感字段列表
165
+ */
166
+ SanitizeUtil.DEFAULT_SENSITIVE_FIELDS = [
167
+ // Headers 相关
168
+ 'authorization',
169
+ 'apikey',
170
+ 'x-api-key',
171
+ 'x-auth-token',
172
+ 'cookie',
173
+ 'set-cookie',
174
+ // Body/Query 通用字段
175
+ 'password',
176
+ 'secret',
177
+ 'token',
178
+ 'key',
179
+ 'credential',
180
+ 'private',
181
+ 'confidential',
182
+ 'ssn',
183
+ 'creditCard',
184
+ 'accessToken',
185
+ 'refreshToken',
186
+ 'apiKey',
187
+ 'apiSecret',
188
+ ];
@@ -0,0 +1,34 @@
1
+ import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { ApiConfigService } from '../shared/services/api-config.service';
4
+ /**
5
+ * HTTP 日志拦截器
6
+ * 参考 Tomcat AccessLog 的实现,每个请求只记录一条日志
7
+ * 只在 finish 事件中记录,因为异常过滤器会处理所有错误并返回响应
8
+ */
9
+ export declare class HttpLoggingInterceptor implements NestInterceptor {
10
+ private readonly configService;
11
+ private readonly logger;
12
+ constructor(configService: ApiConfigService);
13
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
14
+ /**
15
+ * 生成请求 ID
16
+ */
17
+ private generateRequestId;
18
+ /**
19
+ * 记录请求和响应(一条日志)
20
+ */
21
+ private logRequest;
22
+ /**
23
+ * 清理敏感的 header 信息
24
+ */
25
+ private sanitizeHeaders;
26
+ /**
27
+ * 清理敏感的 body 信息
28
+ */
29
+ private sanitizeBody;
30
+ /**
31
+ * 从 headers 提取用户信息
32
+ */
33
+ private extractUser;
34
+ }
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var HttpLoggingInterceptor_1;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.HttpLoggingInterceptor = void 0;
14
+ const common_1 = require("@nestjs/common");
15
+ const api_config_service_1 = require("../shared/services/api-config.service");
16
+ /**
17
+ * HTTP 日志拦截器
18
+ * 参考 Tomcat AccessLog 的实现,每个请求只记录一条日志
19
+ * 只在 finish 事件中记录,因为异常过滤器会处理所有错误并返回响应
20
+ */
21
+ let HttpLoggingInterceptor = HttpLoggingInterceptor_1 = class HttpLoggingInterceptor {
22
+ constructor(configService) {
23
+ this.configService = configService;
24
+ this.logger = new common_1.Logger(HttpLoggingInterceptor_1.name);
25
+ }
26
+ intercept(context, next) {
27
+ const ctx = context.switchToHttp();
28
+ const request = ctx.getRequest();
29
+ const response = ctx.getResponse();
30
+ const startTime = Date.now();
31
+ // 只在 finish 事件中记录日志(此时响应已完成,状态码已确定)
32
+ response.on('finish', () => {
33
+ const duration = Date.now() - startTime;
34
+ const requestId = String(request.id ||
35
+ request.headers['x-request-id'] ||
36
+ this.generateRequestId());
37
+ this.logRequest(requestId, request, response, duration);
38
+ });
39
+ return next.handle();
40
+ }
41
+ /**
42
+ * 生成请求 ID
43
+ */
44
+ generateRequestId() {
45
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
46
+ }
47
+ /**
48
+ * 记录请求和响应(一条日志)
49
+ */
50
+ logRequest(requestId, request, response, duration) {
51
+ const statusCode = response.statusCode;
52
+ // 从 request 对象中读取异常信息(由 HttpExceptionFilter 存储)
53
+ const exception = request._exception;
54
+ // 将异常信息放在 message 中,这样一定会被输出
55
+ let errorMessage = '';
56
+ if (exception) {
57
+ errorMessage = `\n Error: ${exception.message}\n Stack: ${exception.stack}`;
58
+ }
59
+ const logData = Object.assign(Object.assign({ requestId, timestamp: new Date().toISOString(), env: this.configService.nodeEnv, appName: this.configService.getString('NAME'), httpMethod: request.method, httpUrl: request.url, query: request.query, statusCode, duration: `${duration}ms`, requestHeaders: this.sanitizeHeaders(request.headers), requestBody: this.sanitizeBody(request.body), responseHeaders: this.sanitizeHeaders(response.getHeaders()) }, (exception && {
60
+ errorMessage: exception.message,
61
+ errorStack: exception.stack,
62
+ errorCode: exception.code,
63
+ originalStatusCode: exception.statusCode,
64
+ })), { user: this.extractUser(request.headers) });
65
+ const message = `HTTP ${request.method} ${request.url} ${statusCode} (${duration}ms)${errorMessage}`;
66
+ // 根据是否有异常来决定日志级别
67
+ if ((exception === null || exception === void 0 ? void 0 : exception.statusCode) >= 500) {
68
+ this.logger.error(message, logData);
69
+ }
70
+ else if ((exception === null || exception === void 0 ? void 0 : exception.statusCode) >= 400) {
71
+ this.logger.warn(message, logData);
72
+ }
73
+ else if (statusCode >= 500) {
74
+ this.logger.error(message, logData);
75
+ }
76
+ else if (statusCode >= 400) {
77
+ this.logger.warn(message, logData);
78
+ }
79
+ else {
80
+ this.logger.log(message, logData);
81
+ }
82
+ }
83
+ /**
84
+ * 清理敏感的 header 信息
85
+ */
86
+ sanitizeHeaders(headers) {
87
+ if (!headers)
88
+ return {};
89
+ const sanitized = Object.assign({}, headers);
90
+ const sensitiveKeys = [
91
+ 'authorization',
92
+ 'apikey',
93
+ 'x-api-key',
94
+ 'token',
95
+ 'cookie',
96
+ ];
97
+ for (const key of Object.keys(sanitized)) {
98
+ if (sensitiveKeys.includes(key.toLowerCase())) {
99
+ sanitized[key] = '[REDACTED]';
100
+ }
101
+ }
102
+ return sanitized;
103
+ }
104
+ /**
105
+ * 清理敏感的 body 信息
106
+ */
107
+ sanitizeBody(body) {
108
+ if (!body)
109
+ return body;
110
+ const bodyStr = JSON.stringify(body);
111
+ if (bodyStr.length > 1000) {
112
+ return { _large: `${bodyStr.length} bytes` };
113
+ }
114
+ return body;
115
+ }
116
+ /**
117
+ * 从 headers 提取用户信息
118
+ */
119
+ extractUser(headers) {
120
+ const userHeader = headers['user'] || headers['x-user'];
121
+ if (userHeader) {
122
+ try {
123
+ return typeof userHeader === 'string'
124
+ ? JSON.parse(userHeader)
125
+ : userHeader;
126
+ }
127
+ catch (_a) {
128
+ return userHeader;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ };
134
+ exports.HttpLoggingInterceptor = HttpLoggingInterceptor;
135
+ exports.HttpLoggingInterceptor = HttpLoggingInterceptor = HttpLoggingInterceptor_1 = __decorate([
136
+ (0, common_1.Injectable)(),
137
+ __metadata("design:paramtypes", [api_config_service_1.ApiConfigService])
138
+ ], HttpLoggingInterceptor);
@@ -1,2 +1,3 @@
1
1
  export * from './language-interceptor.service';
2
2
  export * from './translation-interceptor.service';
3
+ export * from './http-logging-interceptor.service';
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./language-interceptor.service"), exports);
18
18
  __exportStar(require("./translation-interceptor.service"), exports);
19
+ __exportStar(require("./http-logging-interceptor.service"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-omni/core",
3
- "version": "4.1.3-26",
3
+ "version": "4.1.3-28",
4
4
  "description": "A comprehensive NestJS framework for building enterprise-grade applications with best practices",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -166,7 +166,7 @@ function bootstrapSetup(AppModule, SetupSwagger) {
166
166
  const reflector = app.get(core_1.Reflector);
167
167
  app.useGlobalFilters(new setup_1.SentryGlobalFilter(), new __1.HttpExceptionFilter(), new __1.QueryFailedFilter(reflector));
168
168
  // 全局拦截器
169
- app.useGlobalInterceptors(new __1.LanguageInterceptor(), new __1.TranslationInterceptor(), new nestjs_pino_1.LoggerErrorInterceptor());
169
+ app.useGlobalInterceptors(new __1.HttpLoggingInterceptor(configService), new __1.LanguageInterceptor(), new __1.TranslationInterceptor());
170
170
  // 全局管道
171
171
  app.useGlobalPipes(new nestjs_i18n_1.I18nValidationPipe({
172
172
  whitelist: true,
@@ -214,25 +214,10 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
214
214
  genReqId: (req) => req.id,
215
215
  transport,
216
216
  level: this.isDev ? 'debug' : 'info',
217
- customLogLevel: function (res) {
218
- if (res.statusCode >= 500)
219
- return 'error';
220
- if (res.statusCode >= 400)
221
- return 'warn';
222
- return 'info';
223
- },
224
- wrapSerializers: true,
225
- customProps: (req, res) => {
226
- return {
227
- env: this.nodeEnv,
228
- appName: this.getString('NAME'),
229
- user: req === null || req === void 0 ? void 0 : req.user,
230
- 'req.body': req === null || req === void 0 ? void 0 : req.body,
231
- 'res.body': res === null || res === void 0 ? void 0 : res.body,
232
- };
233
- },
217
+ // 禁用 pinoHttp 的自动日志,由 HttpLoggingInterceptor 处理
218
+ autoLogging: false,
234
219
  redact: {
235
- paths: ['req.headers.authorization', 'req.headers.apikey'],
220
+ paths: ['req.headers.authorization', 'req.headers.apikey', 'req.headers.cookie'],
236
221
  remove: true,
237
222
  },
238
223
  },