@vafast/request-logger 0.2.3 → 0.3.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 CHANGED
@@ -1,169 +1,116 @@
1
1
  # @vafast/request-logger
2
2
 
3
- API request logging middleware for Vafast with automatic sensitive data sanitization.
3
+ API 请求日志中间件,将日志提交到远程日志服务。
4
4
 
5
- ## Features
6
-
7
- - 📝 Automatic API request/response logging
8
- - 🔒 Built-in sensitive data sanitization (passwords, tokens, etc.)
9
- - 🔌 Pluggable storage adapters (MongoDB, Console, Custom)
10
- - ⚡ Non-blocking async logging
11
- - 🚫 Path exclusion support
12
- - 📊 Request duration tracking
13
-
14
- ## Installation
5
+ ## 安装
15
6
 
16
7
  ```bash
17
8
  npm install @vafast/request-logger
18
- # or
19
- npm install @vafast/request-logger
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ### With MongoDB
25
-
26
- ```typescript
27
- import { Server } from 'vafast'
28
- import { createRequestLogger, createMongoAdapter } from '@vafast/request-logger'
29
- import { mongoDb } from './mongodb'
30
-
31
- const server = new Server(routes)
32
-
33
- // Create request logger middleware
34
- const requestLogger = createRequestLogger({
35
- storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),
36
- excludePaths: ['/health', '/metrics', '/performance/add'],
37
- getUserId: (req) => {
38
- const locals = getLocals(req)
39
- return locals?.userInfo?.id
40
- },
41
- })
42
-
43
- server.use(requestLogger)
44
9
  ```
45
10
 
46
- ### Development (Console)
11
+ ## 使用
47
12
 
48
13
  ```typescript
49
- import { createRequestLogger, createConsoleAdapter } from '@vafast/request-logger'
50
-
51
- const requestLogger = createRequestLogger({
52
- storage: createConsoleAdapter(),
53
- })
14
+ import { requestLogger } from '@vafast/request-logger'
15
+
16
+ server.use(requestLogger({
17
+ url: 'http://log-server:9005/api/logs/ingest',
18
+ service: 'my-service',
19
+ headers: { Authorization: 'Bearer apiKeyId:apiKeySecret' },
20
+ onError: (err) => console.error('日志记录失败', err),
21
+ }))
54
22
  ```
55
23
 
56
- ## Sensitive Data Sanitization
57
-
58
- The middleware automatically sanitizes sensitive data before logging:
24
+ 业务字段(appId、authType、ip、traceId 等)由日志服务端从 headers 自动解析。
59
25
 
60
- | Original | Sanitized |
61
- |----------|-----------|
62
- | `{ password: "abc123" }` | `{ password: "[REDACTED]" }` |
63
- | `Authorization: "Bearer eyJhbG..."` | `Authorization: "Bearer eyJh****bG..."` |
64
- | `Cookie: "session=xxx"` | `Cookie: "[REDACTED]"` |
65
- | `{ apiKey: "sk-1234567890abcdef" }` | `{ apiKey: "sk-1****cdef" }` |
26
+ ## 配置
66
27
 
67
- ### Default Removed Fields
28
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
29
+ |------|------|------|--------|------|
30
+ | `url` | `string` | 是 | - | 日志服务 URL |
31
+ | `service` | `string` | 是 | - | 服务标识 |
32
+ | `headers` | `Record<string, string>` | 否 | `{}` | 自定义请求头(如认证) |
33
+ | `timeout` | `number` | 否 | `5000` | 超时时间(毫秒) |
34
+ | `sanitize` | `SanitizeConfig` | 否 | - | 敏感数据清洗配置 |
35
+ | `onError` | `(err) => void` | 否 | `console.error` | 错误回调 |
36
+ | `enabled` | `boolean` | 否 | `true` | 是否启用 |
68
37
 
69
- - `password`, `newPassword`, `oldPassword`
70
- - `secret`, `secretKey`, `privateKey`
71
- - `apiSecret`, `clientSecret`
38
+ ## 路由级别控制
72
39
 
73
- ### Default Masked Fields (partial)
74
-
75
- - `token`, `accessToken`, `refreshToken`
76
- - `authorization`, `apiKey`, `bearer`
77
-
78
- ### Custom Sanitization
40
+ 在路由定义中设置 `log: false` 跳过日志记录:
79
41
 
80
42
  ```typescript
81
- const requestLogger = createRequestLogger({
82
- storage: adapter,
83
- sanitize: {
84
- removeFields: ['password', 'ssn', 'creditCard'],
85
- maskFields: ['token', 'apiKey', 'phoneNumber'],
86
- placeholder: '***HIDDEN***',
87
- },
88
- })
89
- ```
43
+ // 单个路由
44
+ { method: 'GET', path: '/health', log: false, handler: ... }
90
45
 
91
- ## Configuration
92
-
93
- ```typescript
94
- interface RequestLoggerConfig {
95
- /** Storage adapter (required) */
96
- storage: StorageAdapter
97
- /** Paths to exclude from logging */
98
- excludePaths?: (string | RegExp)[]
99
- /** Sanitization config */
100
- sanitize?: SanitizeConfig
101
- /** Function to get user ID from request */
102
- getUserId?: (req: Request) => string | undefined
103
- /** Error callback */
104
- onError?: (error: Error) => void
105
- /** Enable/disable logging @default true */
106
- enabled?: boolean
107
- }
108
- ```
109
-
110
- ## Custom Storage Adapter
111
-
112
- ```typescript
113
- import type { StorageAdapter } from '@vafast/request-logger'
114
-
115
- const myAdapter: StorageAdapter = {
116
- async saveRequestLog(log) {
117
- // Save to your database
118
- const id = await myDb.insert('request_logs', log)
119
- return id
120
- },
121
-
122
- async saveResponseLog(log) {
123
- // Save response details
124
- await myDb.insert('response_logs', log)
125
- },
46
+ // 父路由设置,子路由继承
47
+ {
48
+ path: '/internal',
49
+ log: false,
50
+ children: [
51
+ { method: 'GET', path: '/metrics', handler: ... },
52
+ { method: 'GET', path: '/status', handler: ... },
53
+ ]
126
54
  }
127
55
  ```
128
56
 
129
- ## Log Structure
57
+ ## 日志数据格式
130
58
 
131
- ### Request Log
59
+ 发送到日志服务的数据结构:
132
60
 
133
61
  ```typescript
134
62
  {
135
63
  method: 'POST',
136
- url: 'http://localhost:3000/api/users',
64
+ url: 'http://example.com/api/users?page=1',
137
65
  path: '/api/users',
138
- headers: { /* sanitized */ },
139
- body: { /* sanitized */ },
140
- query: {},
66
+ headers: { ... },
67
+ body: { ... },
68
+ query: { page: '1' },
69
+ status: 200,
70
+ duration: 50,
71
+ service: 'my-service',
72
+ userId: 'user123',
73
+ appId: 'app456',
74
+ authType: 'jwt',
75
+ ip: '192.168.1.1',
76
+ userAgent: 'Mozilla/5.0...',
77
+ traceId: 'trace789',
78
+ createdAt: '2024-01-01T00:00:00.000Z',
141
79
  response: {
142
80
  success: true,
143
81
  message: 'OK',
144
82
  code: 0,
145
83
  },
146
- status: 200,
147
- duration: 15,
148
- userId: '123',
149
- createdAt: Date,
84
+ responseData: { ... },
150
85
  }
151
86
  ```
152
87
 
153
- ### Response Log
88
+ ## 敏感数据脱敏
89
+
90
+ 默认自动脱敏以下字段:
91
+
92
+ - `password`, `pwd`, `secret`, `token`
93
+ - `authorization`, `cookie`, `x-api-key`
94
+ - `accessToken`, `refreshToken`, `apiKey`
95
+
96
+ 自定义脱敏配置:
154
97
 
155
98
  ```typescript
156
- {
157
- requestLogId: 'abc123',
158
- success: true,
159
- message: 'OK',
160
- code: 0,
161
- data: { /* sanitized response data */ },
162
- createdAt: Date,
163
- }
99
+ requestLogger({
100
+ url: '...',
101
+ service: '...',
102
+ sanitize: {
103
+ fields: ['password', 'creditCard', 'ssn'],
104
+ mask: '******',
105
+ deep: true,
106
+ },
107
+ })
164
108
  ```
165
109
 
166
- ## License
167
-
168
- MIT
110
+ ## 特性
169
111
 
112
+ - 异步非阻塞(不影响响应速度)
113
+ - 自动敏感数据脱敏
114
+ - 路由级别日志控制
115
+ - 支持多租户(appId)
116
+ - 支持分布式追踪(traceId)
package/dist/index.d.mts CHANGED
@@ -41,122 +41,73 @@ declare function sanitize<T>(data: T, config?: SanitizeConfig, depth?: number):
41
41
  declare function sanitizeHeaders(headers: Record<string, string>, config?: SanitizeConfig): Record<string, string>;
42
42
  //#endregion
43
43
  //#region src/index.d.ts
44
- interface RequestLog {
44
+ /** 请求信息 */
45
+ interface RequestData {
45
46
  method: string;
46
47
  url: string;
47
48
  path: string;
48
49
  headers: Record<string, string>;
49
50
  body: unknown;
50
51
  query: Record<string, string>;
51
- response: {
52
- success?: boolean;
53
- message?: string;
54
- code?: number;
55
- };
56
52
  status: number;
57
53
  duration: number;
58
54
  userId?: string;
59
55
  appId?: string;
60
- /** 认证类型(由调用方定义,如 apiKey、jwt 等) */
61
56
  authType?: string;
62
- /** 服务标识(区分不同服务,如 auth-server、ones-server) */
63
57
  service?: string;
58
+ ip?: string;
59
+ userAgent?: string;
60
+ traceId?: string;
64
61
  createdAt: Date;
65
62
  }
66
- interface ResponseLog {
67
- requestLogId: string;
63
+ /** 响应信息 */
64
+ interface ResponseData {
68
65
  success?: boolean;
69
66
  message?: string;
70
67
  code?: number;
71
68
  data?: unknown;
72
- createdAt: Date;
73
69
  }
74
- /**
75
- * 存储适配器接口
76
- */
77
- interface StorageAdapter {
78
- /** 存储请求日志 */
79
- saveRequestLog(log: RequestLog): Promise<string>;
80
- /** 存储响应详情 */
81
- saveResponseLog(log: ResponseLog): Promise<void>;
70
+ /** 完整日志数据 */
71
+ interface LogData {
72
+ request: RequestData;
73
+ response: ResponseData;
82
74
  }
83
- interface RequestLoggerConfig {
84
- /** 存储适配器 */
85
- storage: StorageAdapter;
75
+ /** 请求日志配置 */
76
+ interface RequestLoggerOptions {
77
+ /** 日志服务 URL */
78
+ url: string;
79
+ /** 服务标识(如 auth-server、ones-server) */
80
+ service: string;
81
+ /** 自定义请求头(如认证信息) */
82
+ headers?: Record<string, string>;
83
+ /** 超时时间(毫秒),默认 5000 */
84
+ timeout?: number;
86
85
  /** 敏感数据清洗配置 */
87
86
  sanitize?: SanitizeConfig;
88
- /** 获取用户 ID 的函数 */
89
- getUserId?: (req: Request) => string | undefined;
90
- /** 获取应用 ID 的函数(用于多租户) */
91
- getAppId?: (req: Request) => string | undefined;
92
- /** 获取认证类型的函数(如 apiKey、jwt 等) */
93
- getAuthType?: (req: Request) => string | undefined;
94
- /** 服务标识(区分不同服务,如 auth-server、ones-server) */
95
- service?: string;
96
87
  /** 错误回调 */
97
88
  onError?: (error: Error) => void;
98
89
  /** 是否启用 @default true */
99
90
  enabled?: boolean;
91
+ /** 排除的路径列表(精确匹配或正则),这些路径不记录日志 */
92
+ excludePaths?: (string | RegExp)[];
100
93
  }
101
94
  /**
102
- * 创建请求日志中间件
103
- *
104
- * 日志排除:在路由定义中设置 log: false,支持嵌套继承(父路由设置会自动继承给子路由)
105
- *
106
- * @example
107
- * ```typescript
108
- * import { createRequestLogger, createMongoAdapter } from '@vafast/request-logger'
109
- * import { mongoDb } from './mongodb'
110
- *
111
- * const requestLogger = createRequestLogger({
112
- * storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),
113
- * getUserId: (req) => (req as any).__locals?.userInfo?.id,
114
- * })
115
- *
116
- * server.use(requestLogger)
117
- * ```
118
- *
119
- * 在路由定义中使用 log: false(支持嵌套继承):
120
- * ```typescript
121
- * // 单个路由
122
- * { method: 'GET', path: '/health', log: false, handler: ... }
123
- *
124
- * // 父路由设置,所有子路由继承
125
- * {
126
- * path: '/logs',
127
- * log: false, // 所有子路由都不记录日志
128
- * children: [
129
- * { method: 'POST', path: '/find', handler: ... },
130
- * { method: 'POST', path: '/search', handler: ... },
131
- * ]
132
- * }
133
- * ```
134
- */
135
- declare function createRequestLogger(config: RequestLoggerConfig): vafast0.TypedMiddleware<object>;
136
- /**
137
- * 创建 MongoDB 存储适配器
95
+ * 请求日志中间件
138
96
  *
139
97
  * @example
140
98
  * ```typescript
141
- * import { Db } from 'mongodb'
142
- * import { createMongoAdapter } from '@vafast/request-logger'
99
+ * import { requestLogger } from '@vafast/request-logger'
143
100
  *
144
- * const adapter = createMongoAdapter(db, 'logs', 'logsResponse')
101
+ * server.use(requestLogger({
102
+ * url: 'http://log-server:9005/api/logs/ingest',
103
+ * service: 'auth-server',
104
+ * auth: { apiKeyId: 'xxx', apiKeySecret: 'yyy' },
105
+ * }))
145
106
  * ```
146
107
  */
147
- declare function createMongoAdapter(db: {
148
- collection: (name: string) => {
149
- insertOne: (doc: any) => Promise<{
150
- insertedId: {
151
- toHexString: () => string;
152
- };
153
- }>;
154
- };
155
- }, logsCollection?: string, logsResponseCollection?: string): StorageAdapter;
156
- /**
157
- * 创建控制台存储适配器(用于开发调试)
158
- */
159
- declare function createConsoleAdapter(): StorageAdapter;
108
+ declare function requestLogger(options: RequestLoggerOptions): vafast0.TypedMiddleware<object>;
109
+ /** @deprecated 使用 requestLogger 代替 */
110
+ declare const createRequestLogger: typeof requestLogger;
160
111
  //#endregion
161
- export { RequestLog, RequestLoggerConfig, ResponseLog, type SanitizeConfig, StorageAdapter, createConsoleAdapter, createMongoAdapter, createRequestLogger, createRequestLogger as default, sanitize, sanitizeHeaders };
112
+ export { LogData, RequestData, RequestLoggerOptions, ResponseData, type SanitizeConfig, createRequestLogger, requestLogger as default, requestLogger, sanitize, sanitizeHeaders };
162
113
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/sanitize.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AAQA;AA+DA;AAAkC,UA/DjB,cAAA,CA+DiB;EAAY;EAA4B,YAAA,CAAA,EAAA,MAAA,EAAA;EAAC;EA+D3D,UAAA,CAAA,EAAA,MAAe,EAAA;EACpB;EACA,WAAA,CAAA,EAAA,MAAA;EACR;EAAM,QAAA,CAAA,EAAA,MAAA;;;;ACvHT;;;;;AAuBA;AAYA;;AAEmC,iBDgBnB,QChBmB,CAAA,CAAA,CAAA,CAAA,IAAA,EDgBD,CChBC,EAAA,MAAA,CAAA,EDgBW,cChBX,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EDgBuC,CChBvC;;;;AAKnC;;;;;;;AAcyB,iBD4DT,eAAA,CC5DS,OAAA,ED6Dd,MC7Dc,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,MAAA,CAAA,ED8Dd,cC9Dc,CAAA,ED+DtB,MC/DsB,CAAA,MAAA,EAAA,MAAA,CAAA;;;UAxDR,UAAA;;EDVA,GAAA,EAAA,MAAA;EA+DD,IAAA,EAAA,MAAQ;EAAU,OAAA,ECjDvB,MDiDuB,CAAA,MAAA,EAAA,MAAA,CAAA;EAAY,IAAA,EAAA,OAAA;EAA4B,KAAA,EC/CjE,MD+CiE,CAAA,MAAA,EAAA,MAAA,CAAA;EAAC,QAAA,EAAA;IA+D3D,OAAA,CAAA,EAAA,OAAe;IACpB,OAAA,CAAA,EAAA,MAAA;IACA,IAAA,CAAA,EAAA,MAAA;EACR,CAAA;EAAM,MAAA,EAAA,MAAA;;;;ECvHQ;EAIN,QAAA,CAAA,EAAA,MAAA;EAEF;EAcI,OAAA,CAAA,EAAA,MAAA;EAAI,SAAA,EAAJ,IAAI;AAGjB;AAYiB,UAZA,WAAA,CAYc;EAET,YAAA,EAAA,MAAA;EAAa,OAAA,CAAA,EAAA,OAAA;EAEZ,OAAA,CAAA,EAAA,MAAA;EAAc,IAAA,CAAA,EAAA,MAAA;EAAO,IAAA,CAAA,EAAA,OAAA;EAG3B,SAAA,EAbJ,IAaI;;;;;AAUK,UAjBL,cAAA,CAiBK;EAIF;EAAK,cAAA,CAAA,GAAA,EAnBH,UAmBG,CAAA,EAnBU,OAmBV,CAAA,MAAA,CAAA;EAyCT;EAoKA,eAAA,CAAA,GAAA,EA9NO,WA+N0C,CAAA,EA/N5B,OAkOlC,CAAA,IAAA,CAAA;AA2BH;UA1PiB,mBAAA;;WAEN;;aAEE;;oBAEO;;mBAED;;sBAEG;;;;oBAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCJ,mBAAA,SAA4B,sBAAmB,OAAA,CAAA;;;;;;;;;;;;iBAoK/C,kBAAA;;6BACiD;;;;;;8DAG9D;;;;iBA2Ba,oBAAA,CAAA,GAAwB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/sanitize.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AAQA;AA+DA;AAAkC,UA/DjB,cAAA,CA+DiB;EAAY;EAA4B,YAAA,CAAA,EAAA,MAAA,EAAA;EAAC;EA+D3D,UAAA,CAAA,EAAA,MAAe,EAAA;EACpB;EACA,WAAA,CAAA,EAAA,MAAA;EACR;EAAM,QAAA,CAAA,EAAA,MAAA;;;;AC/GT;;;;;AAoBA;AAQA;AAMA;AAMY,iBDKI,QCLJ,CAAA,CAAA,CAAA,CAAA,IAAA,EDKsB,CCLtB,EAAA,MAAA,CAAA,EDKkC,cCLlC,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EDK8D,CCL9D;;;;;AA8BZ;AAkCA;;;;;iBDIgB,eAAA,UACL,iCACA,iBACR;;;;UC/Gc,WAAA;EDlBA,MAAA,EAAA,MAAA;EA+DD,GAAA,EAAA,MAAQ;EAAU,IAAA,EAAA,MAAA;EAAY,OAAA,ECzCnC,MDyCmC,CAAA,MAAA,EAAA,MAAA,CAAA;EAA4B,IAAA,EAAA,OAAA;EAAC,KAAA,ECvClE,MDuCkE,CAAA,MAAA,EAAA,MAAA,CAAA;EA+D3D,MAAA,EAAA,MAAA;EACL,QAAA,EAAA,MAAA;EACA,MAAA,CAAA,EAAA,MAAA;EACR,KAAA,CAAA,EAAA,MAAA;EAAM,QAAA,CAAA,EAAA,MAAA;;;;EC/GQ,OAAA,CAAA,EAAA,MAAW;EAIjB,SAAA,EAYE,IAZF;;;AAYM,UAIA,YAAA,CAJA;EAIA,OAAA,CAAA,EAAA,OAAY;EAQZ,OAAA,CAAA,EAAO,MAAA;EAMP,IAAA,CAAA,EAAA,MAAA;EAML,IAAA,CAAA,EAAA,OAAA;;;AAUe,UAtBV,OAAA,CAsBU;EAAM,OAAA,EArBtB,WAqBsB;EAoBjB,QAAA,EAxCJ,YAwCiB;AAkC7B;;UAtEiB,oBAAA;;;;;;YAML;;;;aAIC;;oBAEO;;;;2BAIO;;;;;;;;;;;;;;;;iBAoBX,aAAA,UAAuB,uBAAoB,OAAA,CAAA;;cAkC9C,4BAAmB"}
package/dist/index.mjs CHANGED
@@ -105,73 +105,60 @@ function sanitizeHeaders(headers, config) {
105
105
  //#endregion
106
106
  //#region src/index.ts
107
107
  /**
108
- * @vafast/request-logger - API request logging middleware for Vafast
108
+ * @vafast/request-logger - API 请求日志中间件
109
109
  *
110
- * Features:
111
- * - Automatic sensitive data sanitization
112
- * - Pluggable storage adapters (MongoDB, custom)
113
- * - Async logging (non-blocking)
114
- * - Route-level log control (log: false in route definition, supports inheritance)
115
- *
116
- * Uses vafast RouteRegistry to query route configurations at runtime,
117
- * similar to @vafast/webhook implementation.
118
- */
119
- /**
120
- * 创建请求日志中间件
121
- *
122
- * 日志排除:在路由定义中设置 log: false,支持嵌套继承(父路由设置会自动继承给子路由)
110
+ * 特性:
111
+ * - 自动敏感数据脱敏
112
+ * - HTTP 远程日志服务
113
+ * - 异步非阻塞记录
114
+ * - 路由级别日志控制(路由定义中设置 log: false
123
115
  *
124
116
  * @example
125
117
  * ```typescript
126
- * import { createRequestLogger, createMongoAdapter } from '@vafast/request-logger'
127
- * import { mongoDb } from './mongodb'
128
- *
129
- * const requestLogger = createRequestLogger({
130
- * storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),
131
- * getUserId: (req) => (req as any).__locals?.userInfo?.id,
132
- * })
118
+ * import { requestLogger } from '@vafast/request-logger'
133
119
  *
134
- * server.use(requestLogger)
120
+ * server.use(requestLogger({
121
+ * url: 'http://log-server:9005/api/logs/ingest',
122
+ * service: 'auth-server',
123
+ * getUserId: (req) => req.__locals?.userInfo?.id,
124
+ * }))
135
125
  * ```
126
+ */
127
+ /**
128
+ * 请求日志中间件
136
129
  *
137
- * 在路由定义中使用 log: false(支持嵌套继承):
130
+ * @example
138
131
  * ```typescript
139
- * // 单个路由
140
- * { method: 'GET', path: '/health', log: false, handler: ... }
132
+ * import { requestLogger } from '@vafast/request-logger'
141
133
  *
142
- * // 父路由设置,所有子路由继承
143
- * {
144
- * path: '/logs',
145
- * log: false, // 所有子路由都不记录日志
146
- * children: [
147
- * { method: 'POST', path: '/find', handler: ... },
148
- * { method: 'POST', path: '/search', handler: ... },
149
- * ]
150
- * }
134
+ * server.use(requestLogger({
135
+ * url: 'http://log-server:9005/api/logs/ingest',
136
+ * service: 'auth-server',
137
+ * auth: { apiKeyId: 'xxx', apiKeySecret: 'yyy' },
138
+ * }))
151
139
  * ```
152
140
  */
153
- function createRequestLogger(config) {
154
- const { storage, sanitize: sanitizeConfig, getUserId, getAppId, getAuthType, service, onError = console.error, enabled = true } = config;
141
+ function requestLogger(options) {
142
+ const { url, service, headers = {}, timeout = 5e3, sanitize: sanitizeConfig, onError = console.error, enabled = true, excludePaths = [] } = options;
155
143
  return defineMiddleware(async (req, next) => {
156
144
  if (!enabled) return next();
157
145
  const startTime = Date.now();
158
146
  const response = await next();
159
147
  recordLog(req, response, startTime, {
160
- storage,
161
- sanitizeConfig,
162
- getUserId,
163
- getAppId,
164
- getAuthType,
148
+ url,
165
149
  service,
166
- onError
150
+ headers,
151
+ timeout,
152
+ sanitizeConfig,
153
+ onError,
154
+ excludePaths
167
155
  }).catch(onError);
168
156
  return response;
169
157
  });
170
158
  }
171
- /**
172
- * 检查路由是否配置了 log: false
173
- * 使用 vafast RouteRegistry 查询路由配置(支持嵌套继承)
174
- */
159
+ /** @deprecated 使用 requestLogger 代替 */
160
+ const createRequestLogger = requestLogger;
161
+ /** 检查路由是否配置了 log: false */
175
162
  function shouldSkipLog(method, path) {
176
163
  try {
177
164
  return getRoute(method, path)?.log === false;
@@ -179,15 +166,35 @@ function shouldSkipLog(method, path) {
179
166
  return false;
180
167
  }
181
168
  }
169
+ /** 检查路径是否在排除列表中 */
170
+ function isPathExcluded(path, excludePaths) {
171
+ return excludePaths.some((pattern) => {
172
+ if (typeof pattern === "string") return path === pattern || path.startsWith(pattern + "/");
173
+ return pattern.test(path);
174
+ });
175
+ }
176
+ /** 带超时的 fetch */
177
+ async function fetchWithTimeout(targetUrl, options, timeout) {
178
+ const controller = new AbortController();
179
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
180
+ try {
181
+ return await fetch(targetUrl, {
182
+ ...options,
183
+ signal: controller.signal
184
+ });
185
+ } finally {
186
+ clearTimeout(timeoutId);
187
+ }
188
+ }
182
189
  async function recordLog(req, response, startTime, options) {
183
- const { storage, sanitizeConfig, getUserId, getAppId, getAuthType, service } = options;
184
- const url = new URL(req.url);
185
- const path = url.pathname;
190
+ const { url: logUrl, service, headers: customHeaders, timeout, sanitizeConfig, onError, excludePaths } = options;
191
+ const reqUrl = new URL(req.url);
192
+ const path = reqUrl.pathname;
193
+ if (isPathExcluded(path, excludePaths)) return;
186
194
  if (shouldSkipLog(req.method, path)) return;
187
195
  let body = null;
188
196
  try {
189
- const clonedReq = req.clone();
190
- if ((req.headers.get("content-type") || "").includes("application/json")) body = await clonedReq.json();
197
+ if ((req.headers.get("content-type") || "").includes("application/json")) body = await req.clone().json();
191
198
  } catch {}
192
199
  let responseData = {};
193
200
  try {
@@ -200,88 +207,40 @@ async function recordLog(req, response, startTime, options) {
200
207
  const sanitizedHeaders = sanitizeHeaders(headers, sanitizeConfig);
201
208
  const sanitizedBody = sanitize(body, sanitizeConfig);
202
209
  const sanitizedResponseData = sanitize(responseData.data, sanitizeConfig);
203
- const now = /* @__PURE__ */ new Date();
204
- const duration = Date.now() - startTime;
205
- const userId = getUserId?.(req);
206
- const appId = getAppId?.(req);
207
- const authType = getAuthType?.(req);
208
- const requestLogId = await storage.saveRequestLog({
210
+ const logBody = {
209
211
  method: req.method,
210
212
  url: req.url,
211
213
  path,
212
214
  headers: sanitizedHeaders,
213
215
  body: sanitizedBody,
214
- query: Object.fromEntries(url.searchParams),
216
+ query: Object.fromEntries(reqUrl.searchParams),
217
+ status: response.status,
218
+ duration: Date.now() - startTime,
219
+ service,
220
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
215
221
  response: {
216
222
  success: responseData.success,
217
223
  message: responseData.message,
218
224
  code: responseData.code
219
225
  },
220
- status: response.status,
221
- duration,
222
- userId,
223
- appId,
224
- authType,
225
- service,
226
- createdAt: now
227
- });
228
- await storage.saveResponseLog({
229
- requestLogId,
230
- success: responseData.success,
231
- message: responseData.message,
232
- code: responseData.code,
233
- data: sanitizedResponseData,
234
- createdAt: now
235
- });
236
- }
237
- /**
238
- * 创建 MongoDB 存储适配器
239
- *
240
- * @example
241
- * ```typescript
242
- * import { Db } from 'mongodb'
243
- * import { createMongoAdapter } from '@vafast/request-logger'
244
- *
245
- * const adapter = createMongoAdapter(db, 'logs', 'logsResponse')
246
- * ```
247
- */
248
- function createMongoAdapter(db, logsCollection = "logs", logsResponseCollection = "logsResponse") {
249
- return {
250
- async saveRequestLog(log) {
251
- return (await db.collection(logsCollection).insertOne({
252
- ...log,
253
- createAt: log.createdAt,
254
- updateAt: log.createdAt
255
- })).insertedId.toHexString();
256
- },
257
- async saveResponseLog(log) {
258
- await db.collection(logsResponseCollection).insertOne({
259
- logsId: log.requestLogId,
260
- ...log,
261
- createAt: log.createdAt,
262
- updateAt: log.createdAt
263
- });
264
- }
265
- };
266
- }
267
- /**
268
- * 创建控制台存储适配器(用于开发调试)
269
- */
270
- function createConsoleAdapter() {
271
- let idCounter = 0;
272
- return {
273
- async saveRequestLog(log) {
274
- const id = `log_${++idCounter}`;
275
- console.log(`[REQUEST] ${log.method} ${log.path} ${log.status} ${log.duration}ms`);
276
- return id;
277
- },
278
- async saveResponseLog(log) {
279
- if (!log.success) console.log(`[RESPONSE ERROR] ${log.message}`);
280
- }
226
+ responseData: sanitizedResponseData
281
227
  };
228
+ try {
229
+ const res = await fetchWithTimeout(logUrl, {
230
+ method: "POST",
231
+ headers: {
232
+ "Content-Type": "application/json",
233
+ ...customHeaders
234
+ },
235
+ body: JSON.stringify(logBody)
236
+ }, timeout);
237
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
238
+ } catch (error) {
239
+ onError(error);
240
+ }
282
241
  }
283
- var src_default = createRequestLogger;
242
+ var src_default = requestLogger;
284
243
 
285
244
  //#endregion
286
- export { createConsoleAdapter, createMongoAdapter, createRequestLogger, src_default as default, sanitize, sanitizeHeaders };
245
+ export { createRequestLogger, src_default as default, requestLogger, sanitize, sanitizeHeaders };
287
246
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/sanitize.ts","../src/index.ts"],"sourcesContent":["/**\n * 敏感数据清洗工具\n * \n * 用于在记录日志前移除或脱敏敏感信息\n */\n\n// ============ Types ============\n\nexport interface SanitizeConfig {\n /** 需要完全移除的字段(小写) */\n removeFields?: string[]\n /** 需要脱敏的字段(小写,部分匹配) */\n maskFields?: string[]\n /** 脱敏占位符 @default '[REDACTED]' */\n placeholder?: string\n /** 最大递归深度 @default 10 */\n maxDepth?: number\n}\n\n// ============ Default Config ============\n\n/** 默认需要完全移除的敏感字段 */\nconst DEFAULT_REMOVE_FIELDS = [\n 'password',\n 'newpassword',\n 'oldpassword',\n 'confirmpassword',\n 'secret',\n 'secretkey',\n 'privatekey',\n 'apisecret',\n 'clientsecret',\n]\n\n/** 默认需要脱敏的字段(保留部分信息) */\nconst DEFAULT_MASK_FIELDS = [\n 'token',\n 'accesstoken',\n 'refreshtoken',\n 'authorization',\n 'apikey',\n 'api_key',\n 'x-api-key',\n 'idtoken',\n 'sessiontoken',\n 'bearer',\n]\n\nconst DEFAULT_PLACEHOLDER = '[REDACTED]'\nconst DEFAULT_MAX_DEPTH = 10\n\n// ============ Sanitize Functions ============\n\n/**\n * 部分脱敏(保留前4后4位)\n */\nfunction partialMask(value: string, placeholder: string): string {\n if (value.length <= 8) return placeholder\n return value.slice(0, 4) + '****' + value.slice(-4)\n}\n\n/**\n * 深度清洗对象中的敏感数据\n * \n * @example\n * ```typescript\n * const data = { password: '123456', token: 'eyJhbG...' }\n * const sanitized = sanitize(data)\n * // { password: '[REDACTED]', token: 'eyJh****...' }\n * ```\n */\nexport function sanitize<T>(data: T, config?: SanitizeConfig, depth = 0): T {\n const {\n removeFields = DEFAULT_REMOVE_FIELDS,\n maskFields = DEFAULT_MASK_FIELDS,\n placeholder = DEFAULT_PLACEHOLDER,\n maxDepth = DEFAULT_MAX_DEPTH,\n } = config ?? {}\n\n // 防止无限递归\n if (depth > maxDepth) return data\n \n if (data === null || data === undefined) {\n return data\n }\n\n // 处理数组\n if (Array.isArray(data)) {\n return data.map(item => sanitize(item, config, depth + 1)) as T\n }\n\n // 处理对象\n if (typeof data === 'object') {\n const result: Record<string, unknown> = {}\n \n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase()\n \n // 完全移除的字段\n if (removeFields.some(field => lowerKey === field)) {\n result[key] = placeholder\n continue\n }\n \n // 部分脱敏的字段\n if (maskFields.some(field => lowerKey.includes(field))) {\n if (typeof value === 'string') {\n result[key] = partialMask(value, placeholder)\n } else {\n result[key] = placeholder\n }\n continue\n }\n \n // 递归处理嵌套对象\n result[key] = sanitize(value, config, depth + 1)\n }\n \n return result as T\n }\n\n return data\n}\n\n/**\n * 清洗 HTTP 请求头\n * \n * @example\n * ```typescript\n * const headers = { Authorization: 'Bearer eyJhbG...', Cookie: 'session=xxx' }\n * const sanitized = sanitizeHeaders(headers)\n * // { Authorization: 'Bearer eyJh****...', Cookie: '[REDACTED]' }\n * ```\n */\nexport function sanitizeHeaders(\n headers: Record<string, string>,\n config?: SanitizeConfig\n): Record<string, string> {\n const {\n maskFields = DEFAULT_MASK_FIELDS,\n placeholder = DEFAULT_PLACEHOLDER,\n } = config ?? {}\n\n const result: Record<string, string> = {}\n \n for (const [key, value] of Object.entries(headers)) {\n const lowerKey = key.toLowerCase()\n \n // Authorization 头部分脱敏\n if (lowerKey === 'authorization') {\n if (value.startsWith('Bearer ')) {\n result[key] = 'Bearer ' + partialMask(value.slice(7), placeholder)\n } else {\n result[key] = partialMask(value, placeholder)\n }\n continue\n }\n \n // Cookie 完全脱敏\n if (lowerKey === 'cookie' || lowerKey === 'set-cookie') {\n result[key] = placeholder\n continue\n }\n \n // API Key 相关头脱敏\n if (maskFields.some(field => lowerKey.includes(field))) {\n result[key] = partialMask(value, placeholder)\n continue\n }\n \n result[key] = value\n }\n \n return result\n}\n\n/**\n * 检查值是否为敏感字段\n */\nexport function isSensitiveField(fieldName: string, config?: SanitizeConfig): boolean {\n const {\n removeFields = DEFAULT_REMOVE_FIELDS,\n maskFields = DEFAULT_MASK_FIELDS,\n } = config ?? {}\n\n const lowerName = fieldName.toLowerCase()\n \n return (\n removeFields.some(field => lowerName === field) ||\n maskFields.some(field => lowerName.includes(field))\n )\n}\n\n","/**\n * @vafast/request-logger - API request logging middleware for Vafast\n * \n * Features:\n * - Automatic sensitive data sanitization\n * - Pluggable storage adapters (MongoDB, custom)\n * - Async logging (non-blocking)\n * - Route-level log control (log: false in route definition, supports inheritance)\n * \n * Uses vafast RouteRegistry to query route configurations at runtime,\n * similar to @vafast/webhook implementation.\n */\nimport { defineMiddleware } from 'vafast'\nimport { getRoute } from 'vafast'\nimport { sanitize, sanitizeHeaders, type SanitizeConfig } from './sanitize'\n\n// ============ Types ============\n\nexport interface RequestLog {\n method: string\n url: string\n path: string\n headers: Record<string, string>\n body: unknown\n query: Record<string, string>\n response: {\n success?: boolean\n message?: string\n code?: number\n }\n status: number\n duration: number\n userId?: string\n appId?: string\n /** 认证类型(由调用方定义,如 apiKey、jwt 等) */\n authType?: string\n /** 服务标识(区分不同服务,如 auth-server、ones-server) */\n service?: string\n createdAt: Date\n}\n\nexport interface ResponseLog {\n requestLogId: string\n success?: boolean\n message?: string\n code?: number\n data?: unknown\n createdAt: Date\n}\n\n/**\n * 存储适配器接口\n */\nexport interface StorageAdapter {\n /** 存储请求日志 */\n saveRequestLog(log: RequestLog): Promise<string>\n /** 存储响应详情 */\n saveResponseLog(log: ResponseLog): Promise<void>\n}\n\nexport interface RequestLoggerConfig {\n /** 存储适配器 */\n storage: StorageAdapter\n /** 敏感数据清洗配置 */\n sanitize?: SanitizeConfig\n /** 获取用户 ID 的函数 */\n getUserId?: (req: Request) => string | undefined\n /** 获取应用 ID 的函数(用于多租户) */\n getAppId?: (req: Request) => string | undefined\n /** 获取认证类型的函数(如 apiKey、jwt 等) */\n getAuthType?: (req: Request) => string | undefined\n /** 服务标识(区分不同服务,如 auth-server、ones-server) */\n service?: string\n /** 错误回调 */\n onError?: (error: Error) => void\n /** 是否启用 @default true */\n enabled?: boolean\n}\n\n// ============ Middleware Factory ============\n\n/**\n * 创建请求日志中间件\n * \n * 日志排除:在路由定义中设置 log: false,支持嵌套继承(父路由设置会自动继承给子路由)\n * \n * @example\n * ```typescript\n * import { createRequestLogger, createMongoAdapter } from '@vafast/request-logger'\n * import { mongoDb } from './mongodb'\n * \n * const requestLogger = createRequestLogger({\n * storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),\n * getUserId: (req) => (req as any).__locals?.userInfo?.id,\n * })\n * \n * server.use(requestLogger)\n * ```\n * \n * 在路由定义中使用 log: false(支持嵌套继承):\n * ```typescript\n * // 单个路由\n * { method: 'GET', path: '/health', log: false, handler: ... }\n * \n * // 父路由设置,所有子路由继承\n * {\n * path: '/logs',\n * log: false, // 所有子路由都不记录日志\n * children: [\n * { method: 'POST', path: '/find', handler: ... },\n * { method: 'POST', path: '/search', handler: ... },\n * ]\n * }\n * ```\n */\nexport function createRequestLogger(config: RequestLoggerConfig) {\n const {\n storage,\n sanitize: sanitizeConfig,\n getUserId,\n getAppId,\n getAuthType,\n service,\n onError = console.error,\n enabled = true,\n } = config\n\n return defineMiddleware(async (req, next) => {\n if (!enabled) {\n return next()\n }\n\n const startTime = Date.now()\n const response = await next()\n\n // 异步记录日志,不阻塞响应\n recordLog(req, response, startTime, {\n storage,\n sanitizeConfig,\n getUserId,\n getAppId,\n getAuthType,\n service,\n onError,\n }).catch(onError)\n\n return response\n })\n}\n\n// ============ Internal Functions ============\n\ninterface RecordLogOptions {\n storage: StorageAdapter\n sanitizeConfig?: SanitizeConfig\n getUserId?: (req: Request) => string | undefined\n getAppId?: (req: Request) => string | undefined\n getAuthType?: (req: Request) => string | undefined\n service?: string\n onError: (error: Error) => void\n}\n\n/**\n * 检查路由是否配置了 log: false\n * 使用 vafast RouteRegistry 查询路由配置(支持嵌套继承)\n */\nfunction shouldSkipLog(method: string, path: string): boolean {\n try {\n // RouteRegistry 使用完整路径 (fullPath) 作为 key,直接查询\n const route = getRoute<{ log?: boolean }>(method, path)\n return route?.log === false\n } catch {\n // RouteRegistry 未初始化时忽略错误\n return false\n }\n}\n\nasync function recordLog(\n req: Request,\n response: Response,\n startTime: number,\n options: RecordLogOptions\n) {\n const { storage, sanitizeConfig, getUserId, getAppId, getAuthType, service } = options\n\n const url = new URL(req.url)\n const path = url.pathname\n\n // 检查路由定义中的 log: false(通过 RouteRegistry 查询,支持嵌套继承)\n if (shouldSkipLog(req.method, path)) {\n return\n }\n\n // 解析请求体\n let body: unknown = null\n try {\n const clonedReq = req.clone()\n const contentType = req.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n body = await clonedReq.json()\n }\n } catch {\n // 忽略解析错误\n }\n\n // 解析响应体\n let responseData: { success?: boolean; message?: string; code?: number; data?: unknown } = {}\n try {\n const clonedRes = response.clone()\n responseData = await clonedRes.json()\n } catch {\n // 忽略解析错误\n }\n\n // 提取请求头\n const headers: Record<string, string> = {}\n req.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n // 清洗敏感数据\n const sanitizedHeaders = sanitizeHeaders(headers, sanitizeConfig)\n const sanitizedBody = sanitize(body, sanitizeConfig)\n const sanitizedResponseData = sanitize(responseData.data, sanitizeConfig)\n\n const now = new Date()\n const duration = Date.now() - startTime\n\n // 获取用户 ID、应用 ID 和认证类型\n const userId = getUserId?.(req)\n const appId = getAppId?.(req)\n const authType = getAuthType?.(req)\n\n // 存储请求日志\n const requestLogId = await storage.saveRequestLog({\n method: req.method,\n url: req.url,\n path,\n headers: sanitizedHeaders,\n body: sanitizedBody,\n query: Object.fromEntries(url.searchParams),\n response: {\n success: responseData.success,\n message: responseData.message,\n code: responseData.code,\n },\n status: response.status,\n duration,\n userId,\n appId,\n authType,\n service,\n createdAt: now,\n })\n\n // 存储响应详情\n await storage.saveResponseLog({\n requestLogId,\n success: responseData.success,\n message: responseData.message,\n code: responseData.code,\n data: sanitizedResponseData,\n createdAt: now,\n })\n}\n\n// ============ MongoDB Adapter ============\n\n/**\n * 创建 MongoDB 存储适配器\n * \n * @example\n * ```typescript\n * import { Db } from 'mongodb'\n * import { createMongoAdapter } from '@vafast/request-logger'\n * \n * const adapter = createMongoAdapter(db, 'logs', 'logsResponse')\n * ```\n */\nexport function createMongoAdapter(\n db: { collection: (name: string) => { insertOne: (doc: any) => Promise<{ insertedId: { toHexString: () => string } }> } },\n logsCollection: string = 'logs',\n logsResponseCollection: string = 'logsResponse'\n): StorageAdapter {\n return {\n async saveRequestLog(log: RequestLog): Promise<string> {\n const result = await db.collection(logsCollection).insertOne({\n ...log,\n createAt: log.createdAt,\n updateAt: log.createdAt,\n })\n return result.insertedId.toHexString()\n },\n\n async saveResponseLog(log: ResponseLog): Promise<void> {\n await db.collection(logsResponseCollection).insertOne({\n logsId: log.requestLogId,\n ...log,\n createAt: log.createdAt,\n updateAt: log.createdAt,\n })\n },\n }\n}\n\n// ============ Console Adapter (for development) ============\n\n/**\n * 创建控制台存储适配器(用于开发调试)\n */\nexport function createConsoleAdapter(): StorageAdapter {\n let idCounter = 0\n\n return {\n async saveRequestLog(log: RequestLog): Promise<string> {\n const id = `log_${++idCounter}`\n console.log(`[REQUEST] ${log.method} ${log.path} ${log.status} ${log.duration}ms`)\n return id\n },\n\n async saveResponseLog(log: ResponseLog): Promise<void> {\n if (!log.success) {\n console.log(`[RESPONSE ERROR] ${log.message}`)\n }\n },\n }\n}\n\n// ============ Re-exports ============\n\nexport { sanitize, sanitizeHeaders, type SanitizeConfig } from './sanitize'\nexport default createRequestLogger\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/sanitize.ts","../src/index.ts"],"sourcesContent":["/**\n * 敏感数据清洗工具\n * \n * 用于在记录日志前移除或脱敏敏感信息\n */\n\n// ============ Types ============\n\nexport interface SanitizeConfig {\n /** 需要完全移除的字段(小写) */\n removeFields?: string[]\n /** 需要脱敏的字段(小写,部分匹配) */\n maskFields?: string[]\n /** 脱敏占位符 @default '[REDACTED]' */\n placeholder?: string\n /** 最大递归深度 @default 10 */\n maxDepth?: number\n}\n\n// ============ Default Config ============\n\n/** 默认需要完全移除的敏感字段 */\nconst DEFAULT_REMOVE_FIELDS = [\n 'password',\n 'newpassword',\n 'oldpassword',\n 'confirmpassword',\n 'secret',\n 'secretkey',\n 'privatekey',\n 'apisecret',\n 'clientsecret',\n]\n\n/** 默认需要脱敏的字段(保留部分信息) */\nconst DEFAULT_MASK_FIELDS = [\n 'token',\n 'accesstoken',\n 'refreshtoken',\n 'authorization',\n 'apikey',\n 'api_key',\n 'x-api-key',\n 'idtoken',\n 'sessiontoken',\n 'bearer',\n]\n\nconst DEFAULT_PLACEHOLDER = '[REDACTED]'\nconst DEFAULT_MAX_DEPTH = 10\n\n// ============ Sanitize Functions ============\n\n/**\n * 部分脱敏(保留前4后4位)\n */\nfunction partialMask(value: string, placeholder: string): string {\n if (value.length <= 8) return placeholder\n return value.slice(0, 4) + '****' + value.slice(-4)\n}\n\n/**\n * 深度清洗对象中的敏感数据\n * \n * @example\n * ```typescript\n * const data = { password: '123456', token: 'eyJhbG...' }\n * const sanitized = sanitize(data)\n * // { password: '[REDACTED]', token: 'eyJh****...' }\n * ```\n */\nexport function sanitize<T>(data: T, config?: SanitizeConfig, depth = 0): T {\n const {\n removeFields = DEFAULT_REMOVE_FIELDS,\n maskFields = DEFAULT_MASK_FIELDS,\n placeholder = DEFAULT_PLACEHOLDER,\n maxDepth = DEFAULT_MAX_DEPTH,\n } = config ?? {}\n\n // 防止无限递归\n if (depth > maxDepth) return data\n \n if (data === null || data === undefined) {\n return data\n }\n\n // 处理数组\n if (Array.isArray(data)) {\n return data.map(item => sanitize(item, config, depth + 1)) as T\n }\n\n // 处理对象\n if (typeof data === 'object') {\n const result: Record<string, unknown> = {}\n \n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase()\n \n // 完全移除的字段\n if (removeFields.some(field => lowerKey === field)) {\n result[key] = placeholder\n continue\n }\n \n // 部分脱敏的字段\n if (maskFields.some(field => lowerKey.includes(field))) {\n if (typeof value === 'string') {\n result[key] = partialMask(value, placeholder)\n } else {\n result[key] = placeholder\n }\n continue\n }\n \n // 递归处理嵌套对象\n result[key] = sanitize(value, config, depth + 1)\n }\n \n return result as T\n }\n\n return data\n}\n\n/**\n * 清洗 HTTP 请求头\n * \n * @example\n * ```typescript\n * const headers = { Authorization: 'Bearer eyJhbG...', Cookie: 'session=xxx' }\n * const sanitized = sanitizeHeaders(headers)\n * // { Authorization: 'Bearer eyJh****...', Cookie: '[REDACTED]' }\n * ```\n */\nexport function sanitizeHeaders(\n headers: Record<string, string>,\n config?: SanitizeConfig\n): Record<string, string> {\n const {\n maskFields = DEFAULT_MASK_FIELDS,\n placeholder = DEFAULT_PLACEHOLDER,\n } = config ?? {}\n\n const result: Record<string, string> = {}\n \n for (const [key, value] of Object.entries(headers)) {\n const lowerKey = key.toLowerCase()\n \n // Authorization 头部分脱敏\n if (lowerKey === 'authorization') {\n if (value.startsWith('Bearer ')) {\n result[key] = 'Bearer ' + partialMask(value.slice(7), placeholder)\n } else {\n result[key] = partialMask(value, placeholder)\n }\n continue\n }\n \n // Cookie 完全脱敏\n if (lowerKey === 'cookie' || lowerKey === 'set-cookie') {\n result[key] = placeholder\n continue\n }\n \n // API Key 相关头脱敏\n if (maskFields.some(field => lowerKey.includes(field))) {\n result[key] = partialMask(value, placeholder)\n continue\n }\n \n result[key] = value\n }\n \n return result\n}\n\n/**\n * 检查值是否为敏感字段\n */\nexport function isSensitiveField(fieldName: string, config?: SanitizeConfig): boolean {\n const {\n removeFields = DEFAULT_REMOVE_FIELDS,\n maskFields = DEFAULT_MASK_FIELDS,\n } = config ?? {}\n\n const lowerName = fieldName.toLowerCase()\n \n return (\n removeFields.some(field => lowerName === field) ||\n maskFields.some(field => lowerName.includes(field))\n )\n}\n\n","/**\n * @vafast/request-logger - API 请求日志中间件\n * \n * 特性:\n * - 自动敏感数据脱敏\n * - HTTP 远程日志服务\n * - 异步非阻塞记录\n * - 路由级别日志控制(路由定义中设置 log: false)\n * \n * @example\n * ```typescript\n * import { requestLogger } from '@vafast/request-logger'\n * \n * server.use(requestLogger({\n * url: 'http://log-server:9005/api/logs/ingest',\n * service: 'auth-server',\n * getUserId: (req) => req.__locals?.userInfo?.id,\n * }))\n * ```\n */\nimport { defineMiddleware, getRoute } from 'vafast'\nimport { sanitize, sanitizeHeaders, type SanitizeConfig } from './sanitize'\n\n// ============ Types ============\n\n/** 请求信息 */\nexport interface RequestData {\n method: string\n url: string\n path: string\n headers: Record<string, string>\n body: unknown\n query: Record<string, string>\n status: number\n duration: number\n userId?: string\n appId?: string\n authType?: string\n service?: string\n ip?: string\n userAgent?: string\n traceId?: string\n createdAt: Date\n}\n\n/** 响应信息 */\nexport interface ResponseData {\n success?: boolean\n message?: string\n code?: number\n data?: unknown\n}\n\n/** 完整日志数据 */\nexport interface LogData {\n request: RequestData\n response: ResponseData\n}\n\n/** 请求日志配置 */\nexport interface RequestLoggerOptions {\n /** 日志服务 URL */\n url: string\n /** 服务标识(如 auth-server、ones-server) */\n service: string\n /** 自定义请求头(如认证信息) */\n headers?: Record<string, string>\n /** 超时时间(毫秒),默认 5000 */\n timeout?: number\n /** 敏感数据清洗配置 */\n sanitize?: SanitizeConfig\n /** 错误回调 */\n onError?: (error: Error) => void\n /** 是否启用 @default true */\n enabled?: boolean\n /** 排除的路径列表(精确匹配或正则),这些路径不记录日志 */\n excludePaths?: (string | RegExp)[]\n}\n\n\n// ============ Middleware ============\n\n/**\n * 请求日志中间件\n * \n * @example\n * ```typescript\n * import { requestLogger } from '@vafast/request-logger'\n * \n * server.use(requestLogger({\n * url: 'http://log-server:9005/api/logs/ingest',\n * service: 'auth-server',\n * auth: { apiKeyId: 'xxx', apiKeySecret: 'yyy' },\n * }))\n * ```\n */\nexport function requestLogger(options: RequestLoggerOptions) {\n const {\n url,\n service,\n headers = {},\n timeout = 5000,\n sanitize: sanitizeConfig,\n onError = console.error,\n enabled = true,\n excludePaths = [],\n } = options\n\n return defineMiddleware(async (req, next) => {\n if (!enabled) return next()\n\n const startTime = Date.now()\n const response = await next()\n\n // 异步记录日志,不阻塞响应\n recordLog(req, response, startTime, {\n url,\n service,\n headers,\n timeout,\n sanitizeConfig,\n onError,\n excludePaths,\n }).catch(onError)\n\n return response\n })\n}\n\n/** @deprecated 使用 requestLogger 代替 */\nexport const createRequestLogger = requestLogger\n\n// ============ Internal ============\n\ninterface RecordLogOptions {\n url: string\n service: string\n headers: Record<string, string>\n timeout: number\n sanitizeConfig?: SanitizeConfig\n onError: (error: Error) => void\n excludePaths: (string | RegExp)[]\n}\n\n/** 检查路由是否配置了 log: false */\nfunction shouldSkipLog(method: string, path: string): boolean {\n try {\n const route = getRoute<{ log?: boolean }>(method, path)\n return route?.log === false\n } catch {\n return false\n }\n}\n\n/** 检查路径是否在排除列表中 */\nfunction isPathExcluded(path: string, excludePaths: (string | RegExp)[]): boolean {\n return excludePaths.some(pattern => {\n if (typeof pattern === 'string') {\n return path === pattern || path.startsWith(pattern + '/')\n }\n return pattern.test(path)\n })\n}\n\n/** 带超时的 fetch */\nasync function fetchWithTimeout(\n targetUrl: string,\n options: RequestInit,\n timeout: number\n): Promise<Response> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n return await fetch(targetUrl, { ...options, signal: controller.signal })\n } finally {\n clearTimeout(timeoutId)\n }\n}\n\nasync function recordLog(\n req: Request,\n response: Response,\n startTime: number,\n options: RecordLogOptions\n) {\n const { url: logUrl, service, headers: customHeaders, timeout, sanitizeConfig, onError, excludePaths } = options\n\n const reqUrl = new URL(req.url)\n const path = reqUrl.pathname\n\n // 检查路径是否在排除列表中\n if (isPathExcluded(path, excludePaths)) return\n\n // 检查路由是否禁用日志\n if (shouldSkipLog(req.method, path)) return\n\n // 解析请求体\n let body: unknown = null\n try {\n const contentType = req.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n body = await req.clone().json()\n }\n } catch {\n // 忽略\n }\n\n // 解析响应体\n let responseData: ResponseData = {}\n try {\n responseData = await response.clone().json()\n } catch {\n // 忽略\n }\n\n // 提取请求头\n const headers: Record<string, string> = {}\n req.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n // 清洗敏感数据\n const sanitizedHeaders = sanitizeHeaders(headers, sanitizeConfig)\n const sanitizedBody = sanitize(body, sanitizeConfig)\n const sanitizedResponseData = sanitize(responseData.data, sanitizeConfig)\n\n // 构建日志数据(业务字段由 log-server 从 headers 解析)\n const logBody = {\n method: req.method,\n url: req.url,\n path,\n headers: sanitizedHeaders,\n body: sanitizedBody,\n query: Object.fromEntries(reqUrl.searchParams),\n status: response.status,\n duration: Date.now() - startTime,\n service,\n createdAt: new Date().toISOString(),\n response: {\n success: responseData.success,\n message: responseData.message,\n code: responseData.code,\n },\n responseData: sanitizedResponseData,\n }\n\n // 发送到日志服务\n try {\n const res = await fetchWithTimeout(\n logUrl,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(logBody),\n },\n timeout\n )\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}: ${res.statusText}`)\n }\n } catch (error) {\n onError(error as Error)\n }\n}\n\n// ============ Re-exports ============\n\nexport { sanitize, sanitizeHeaders, type SanitizeConfig } from './sanitize'\nexport default requestLogger\n"],"mappings":";;;;AAsBA,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;AAGD,MAAM,sBAAsB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;;;;AAO1B,SAAS,YAAY,OAAe,aAA6B;AAC/D,KAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,QAAO,MAAM,MAAM,GAAG,EAAE,GAAG,SAAS,MAAM,MAAM,GAAG;;;;;;;;;;;;AAarD,SAAgB,SAAY,MAAS,QAAyB,QAAQ,GAAM;CAC1E,MAAM,EACJ,eAAe,uBACf,aAAa,qBACb,cAAc,qBACd,WAAW,sBACT,UAAU,EAAE;AAGhB,KAAI,QAAQ,SAAU,QAAO;AAE7B,KAAI,SAAS,QAAQ,SAAS,OAC5B,QAAO;AAIT,KAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,KAAK,KAAI,SAAQ,SAAS,MAAM,QAAQ,QAAQ,EAAE,CAAC;AAI5D,KAAI,OAAO,SAAS,UAAU;EAC5B,MAAM,SAAkC,EAAE;AAE1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;GAC/C,MAAM,WAAW,IAAI,aAAa;AAGlC,OAAI,aAAa,MAAK,UAAS,aAAa,MAAM,EAAE;AAClD,WAAO,OAAO;AACd;;AAIF,OAAI,WAAW,MAAK,UAAS,SAAS,SAAS,MAAM,CAAC,EAAE;AACtD,QAAI,OAAO,UAAU,SACnB,QAAO,OAAO,YAAY,OAAO,YAAY;QAE7C,QAAO,OAAO;AAEhB;;AAIF,UAAO,OAAO,SAAS,OAAO,QAAQ,QAAQ,EAAE;;AAGlD,SAAO;;AAGT,QAAO;;;;;;;;;;;;AAaT,SAAgB,gBACd,SACA,QACwB;CACxB,MAAM,EACJ,aAAa,qBACb,cAAc,wBACZ,UAAU,EAAE;CAEhB,MAAM,SAAiC,EAAE;AAEzC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;EAClD,MAAM,WAAW,IAAI,aAAa;AAGlC,MAAI,aAAa,iBAAiB;AAChC,OAAI,MAAM,WAAW,UAAU,CAC7B,QAAO,OAAO,YAAY,YAAY,MAAM,MAAM,EAAE,EAAE,YAAY;OAElE,QAAO,OAAO,YAAY,OAAO,YAAY;AAE/C;;AAIF,MAAI,aAAa,YAAY,aAAa,cAAc;AACtD,UAAO,OAAO;AACd;;AAIF,MAAI,WAAW,MAAK,UAAS,SAAS,SAAS,MAAM,CAAC,EAAE;AACtD,UAAO,OAAO,YAAY,OAAO,YAAY;AAC7C;;AAGF,SAAO,OAAO;;AAGhB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7ET,SAAgB,cAAc,SAA+B;CAC3D,MAAM,EACJ,KACA,SACA,UAAU,EAAE,EACZ,UAAU,KACV,UAAU,gBACV,UAAU,QAAQ,OAClB,UAAU,MACV,eAAe,EAAE,KACf;AAEJ,QAAO,iBAAiB,OAAO,KAAK,SAAS;AAC3C,MAAI,CAAC,QAAS,QAAO,MAAM;EAE3B,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,WAAW,MAAM,MAAM;AAG7B,YAAU,KAAK,UAAU,WAAW;GAClC;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,CAAC,MAAM,QAAQ;AAEjB,SAAO;GACP;;;AAIJ,MAAa,sBAAsB;;AAenC,SAAS,cAAc,QAAgB,MAAuB;AAC5D,KAAI;AAEF,SADc,SAA4B,QAAQ,KAAK,EACzC,QAAQ;SAChB;AACN,SAAO;;;;AAKX,SAAS,eAAe,MAAc,cAA4C;AAChF,QAAO,aAAa,MAAK,YAAW;AAClC,MAAI,OAAO,YAAY,SACrB,QAAO,SAAS,WAAW,KAAK,WAAW,UAAU,IAAI;AAE3D,SAAO,QAAQ,KAAK,KAAK;GACzB;;;AAIJ,eAAe,iBACb,WACA,SACA,SACmB;CACnB,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,KAAI;AACF,SAAO,MAAM,MAAM,WAAW;GAAE,GAAG;GAAS,QAAQ,WAAW;GAAQ,CAAC;WAChE;AACR,eAAa,UAAU;;;AAI3B,eAAe,UACb,KACA,UACA,WACA,SACA;CACA,MAAM,EAAE,KAAK,QAAQ,SAAS,SAAS,eAAe,SAAS,gBAAgB,SAAS,iBAAiB;CAEzG,MAAM,SAAS,IAAI,IAAI,IAAI,IAAI;CAC/B,MAAM,OAAO,OAAO;AAGpB,KAAI,eAAe,MAAM,aAAa,CAAE;AAGxC,KAAI,cAAc,IAAI,QAAQ,KAAK,CAAE;CAGrC,IAAI,OAAgB;AACpB,KAAI;AAEF,OADoB,IAAI,QAAQ,IAAI,eAAe,IAAI,IACvC,SAAS,mBAAmB,CAC1C,QAAO,MAAM,IAAI,OAAO,CAAC,MAAM;SAE3B;CAKR,IAAI,eAA6B,EAAE;AACnC,KAAI;AACF,iBAAe,MAAM,SAAS,OAAO,CAAC,MAAM;SACtC;CAKR,MAAM,UAAkC,EAAE;AAC1C,KAAI,QAAQ,SAAS,OAAO,QAAQ;AAClC,UAAQ,OAAO;GACf;CAGF,MAAM,mBAAmB,gBAAgB,SAAS,eAAe;CACjE,MAAM,gBAAgB,SAAS,MAAM,eAAe;CACpD,MAAM,wBAAwB,SAAS,aAAa,MAAM,eAAe;CAGzE,MAAM,UAAU;EACd,QAAQ,IAAI;EACZ,KAAK,IAAI;EACT;EACA,SAAS;EACT,MAAM;EACN,OAAO,OAAO,YAAY,OAAO,aAAa;EAC9C,QAAQ,SAAS;EACjB,UAAU,KAAK,KAAK,GAAG;EACvB;EACA,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,UAAU;GACR,SAAS,aAAa;GACtB,SAAS,aAAa;GACtB,MAAM,aAAa;GACpB;EACD,cAAc;EACf;AAGD,KAAI;EACF,MAAM,MAAM,MAAM,iBAChB,QACA;GACE,QAAQ;GACR,SAAS;IAAE,gBAAgB;IAAoB,GAAG;IAAe;GACjE,MAAM,KAAK,UAAU,QAAQ;GAC9B,EACD,QACD;AAED,MAAI,CAAC,IAAI,GACP,OAAM,IAAI,MAAM,QAAQ,IAAI,OAAO,IAAI,IAAI,aAAa;UAEnD,OAAO;AACd,UAAQ,MAAe;;;AAO3B,kBAAe"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vafast/request-logger",
3
- "version": "0.2.3",
4
- "description": "API request logging middleware for Vafast with sensitive data sanitization",
3
+ "version": "0.3.2",
4
+ "description": "API request logging middleware for Vafast",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
7
7
  "module": "./dist/index.mjs",
@@ -18,7 +18,6 @@
18
18
  "request-logger",
19
19
  "api-logs",
20
20
  "middleware",
21
- "mongodb",
22
21
  "typescript"
23
22
  ],
24
23
  "scripts": {
@@ -36,19 +35,12 @@
36
35
  "dependencies": {},
37
36
  "devDependencies": {
38
37
  "@types/node": "^22.15.30",
39
- "mongodb": "^6.16.0",
40
38
  "rimraf": "^6.0.1",
41
39
  "tsdown": "^0.19.0-beta.4",
42
40
  "typescript": "^5.4.5",
43
41
  "vafast": "^0.5.1"
44
42
  },
45
43
  "peerDependencies": {
46
- "vafast": "^0.5.1",
47
- "mongodb": ">= 6.0.0"
48
- },
49
- "peerDependenciesMeta": {
50
- "mongodb": {
51
- "optional": true
52
- }
44
+ "vafast": "^0.5.1"
53
45
  }
54
- }
46
+ }