@vafast/request-logger 0.2.3 → 0.3.1
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 +75 -128
- package/dist/index.d.mts +32 -83
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +70 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -12
package/README.md
CHANGED
|
@@ -1,169 +1,116 @@
|
|
|
1
1
|
# @vafast/request-logger
|
|
2
2
|
|
|
3
|
-
API
|
|
3
|
+
API 请求日志中间件,将日志提交到远程日志服务。
|
|
4
4
|
|
|
5
|
-
##
|
|
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
|
-
|
|
11
|
+
## 使用
|
|
47
12
|
|
|
48
13
|
```typescript
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
The middleware automatically sanitizes sensitive data before logging:
|
|
24
|
+
业务字段(appId、authType、ip、traceId 等)由日志服务端从 headers 自动解析。
|
|
59
25
|
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
- `secret`, `secretKey`, `privateKey`
|
|
71
|
-
- `apiSecret`, `clientSecret`
|
|
38
|
+
## 路由级别控制
|
|
72
39
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- `token`, `accessToken`, `refreshToken`
|
|
76
|
-
- `authorization`, `apiKey`, `bearer`
|
|
77
|
-
|
|
78
|
-
### Custom Sanitization
|
|
40
|
+
在路由定义中设置 `log: false` 跳过日志记录:
|
|
79
41
|
|
|
80
42
|
```typescript
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
##
|
|
57
|
+
## 日志数据格式
|
|
130
58
|
|
|
131
|
-
|
|
59
|
+
发送到日志服务的数据结构:
|
|
132
60
|
|
|
133
61
|
```typescript
|
|
134
62
|
{
|
|
135
63
|
method: 'POST',
|
|
136
|
-
url: 'http://
|
|
64
|
+
url: 'http://example.com/api/users?page=1',
|
|
137
65
|
path: '/api/users',
|
|
138
|
-
headers: {
|
|
139
|
-
body: {
|
|
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
|
-
|
|
147
|
-
duration: 15,
|
|
148
|
-
userId: '123',
|
|
149
|
-
createdAt: Date,
|
|
84
|
+
responseData: { ... },
|
|
150
85
|
}
|
|
151
86
|
```
|
|
152
87
|
|
|
153
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
##
|
|
167
|
-
|
|
168
|
-
MIT
|
|
110
|
+
## 特性
|
|
169
111
|
|
|
112
|
+
- 异步非阻塞(不影响响应速度)
|
|
113
|
+
- 自动敏感数据脱敏
|
|
114
|
+
- 路由级别日志控制
|
|
115
|
+
- 支持多租户(appId)
|
|
116
|
+
- 支持分布式追踪(traceId)
|
package/dist/index.d.mts
CHANGED
|
@@ -41,122 +41,71 @@ 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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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;
|
|
100
91
|
}
|
|
101
92
|
/**
|
|
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 存储适配器
|
|
93
|
+
* 请求日志中间件
|
|
138
94
|
*
|
|
139
95
|
* @example
|
|
140
96
|
* ```typescript
|
|
141
|
-
* import {
|
|
142
|
-
* import { createMongoAdapter } from '@vafast/request-logger'
|
|
97
|
+
* import { requestLogger } from '@vafast/request-logger'
|
|
143
98
|
*
|
|
144
|
-
*
|
|
99
|
+
* server.use(requestLogger({
|
|
100
|
+
* url: 'http://log-server:9005/api/logs/ingest',
|
|
101
|
+
* service: 'auth-server',
|
|
102
|
+
* auth: { apiKeyId: 'xxx', apiKeySecret: 'yyy' },
|
|
103
|
+
* }))
|
|
145
104
|
* ```
|
|
146
105
|
*/
|
|
147
|
-
declare function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
insertedId: {
|
|
151
|
-
toHexString: () => string;
|
|
152
|
-
};
|
|
153
|
-
}>;
|
|
154
|
-
};
|
|
155
|
-
}, logsCollection?: string, logsResponseCollection?: string): StorageAdapter;
|
|
156
|
-
/**
|
|
157
|
-
* 创建控制台存储适配器(用于开发调试)
|
|
158
|
-
*/
|
|
159
|
-
declare function createConsoleAdapter(): StorageAdapter;
|
|
106
|
+
declare function requestLogger(options: RequestLoggerOptions): vafast0.TypedMiddleware<object>;
|
|
107
|
+
/** @deprecated 使用 requestLogger 代替 */
|
|
108
|
+
declare const createRequestLogger: typeof requestLogger;
|
|
160
109
|
//#endregion
|
|
161
|
-
export {
|
|
110
|
+
export { LogData, RequestData, RequestLoggerOptions, ResponseData, type SanitizeConfig, createRequestLogger, requestLogger as default, requestLogger, sanitize, sanitizeHeaders };
|
|
162
111
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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;;;;
|
|
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;;;;AA4BZ;AAgCA;;;;;;iBDQgB,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;;;AAMa,UAlBR,OAAA,CAkBQ;EAsBT,OAAA,EAvCL,WAuCkB;EAgChB,QAAA,EAtED,YAsEoC;;;UAlE/B,oBAAA;;;;;;YAML;;;;aAIC;;oBAEO;;;;;;;;;;;;;;;;;;iBAsBJ,aAAA,UAAuB,uBAAoB,OAAA,CAAA;;cAgC9C,4BAAmB"}
|
package/dist/index.mjs
CHANGED
|
@@ -105,73 +105,59 @@ function sanitizeHeaders(headers, config) {
|
|
|
105
105
|
//#endregion
|
|
106
106
|
//#region src/index.ts
|
|
107
107
|
/**
|
|
108
|
-
* @vafast/request-logger - API
|
|
108
|
+
* @vafast/request-logger - API 请求日志中间件
|
|
109
109
|
*
|
|
110
|
-
*
|
|
111
|
-
* -
|
|
112
|
-
* -
|
|
113
|
-
* -
|
|
114
|
-
* -
|
|
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 {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
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
|
|
154
|
-
const {
|
|
141
|
+
function requestLogger(options) {
|
|
142
|
+
const { url, service, headers = {}, timeout = 5e3, sanitize: sanitizeConfig, onError = console.error, enabled = true } = 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
|
-
|
|
161
|
-
sanitizeConfig,
|
|
162
|
-
getUserId,
|
|
163
|
-
getAppId,
|
|
164
|
-
getAuthType,
|
|
148
|
+
url,
|
|
165
149
|
service,
|
|
150
|
+
headers,
|
|
151
|
+
timeout,
|
|
152
|
+
sanitizeConfig,
|
|
166
153
|
onError
|
|
167
154
|
}).catch(onError);
|
|
168
155
|
return response;
|
|
169
156
|
});
|
|
170
157
|
}
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
*/
|
|
158
|
+
/** @deprecated 使用 requestLogger 代替 */
|
|
159
|
+
const createRequestLogger = requestLogger;
|
|
160
|
+
/** 检查路由是否配置了 log: false */
|
|
175
161
|
function shouldSkipLog(method, path) {
|
|
176
162
|
try {
|
|
177
163
|
return getRoute(method, path)?.log === false;
|
|
@@ -179,15 +165,27 @@ function shouldSkipLog(method, path) {
|
|
|
179
165
|
return false;
|
|
180
166
|
}
|
|
181
167
|
}
|
|
168
|
+
/** 带超时的 fetch */
|
|
169
|
+
async function fetchWithTimeout(targetUrl, options, timeout) {
|
|
170
|
+
const controller = new AbortController();
|
|
171
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
172
|
+
try {
|
|
173
|
+
return await fetch(targetUrl, {
|
|
174
|
+
...options,
|
|
175
|
+
signal: controller.signal
|
|
176
|
+
});
|
|
177
|
+
} finally {
|
|
178
|
+
clearTimeout(timeoutId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
182
181
|
async function recordLog(req, response, startTime, options) {
|
|
183
|
-
const {
|
|
184
|
-
const
|
|
185
|
-
const path =
|
|
182
|
+
const { url: logUrl, service, headers: customHeaders, timeout, sanitizeConfig, onError } = options;
|
|
183
|
+
const reqUrl = new URL(req.url);
|
|
184
|
+
const path = reqUrl.pathname;
|
|
186
185
|
if (shouldSkipLog(req.method, path)) return;
|
|
187
186
|
let body = null;
|
|
188
187
|
try {
|
|
189
|
-
|
|
190
|
-
if ((req.headers.get("content-type") || "").includes("application/json")) body = await clonedReq.json();
|
|
188
|
+
if ((req.headers.get("content-type") || "").includes("application/json")) body = await req.clone().json();
|
|
191
189
|
} catch {}
|
|
192
190
|
let responseData = {};
|
|
193
191
|
try {
|
|
@@ -200,88 +198,40 @@ async function recordLog(req, response, startTime, options) {
|
|
|
200
198
|
const sanitizedHeaders = sanitizeHeaders(headers, sanitizeConfig);
|
|
201
199
|
const sanitizedBody = sanitize(body, sanitizeConfig);
|
|
202
200
|
const sanitizedResponseData = sanitize(responseData.data, sanitizeConfig);
|
|
203
|
-
const
|
|
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({
|
|
201
|
+
const logBody = {
|
|
209
202
|
method: req.method,
|
|
210
203
|
url: req.url,
|
|
211
204
|
path,
|
|
212
205
|
headers: sanitizedHeaders,
|
|
213
206
|
body: sanitizedBody,
|
|
214
|
-
query: Object.fromEntries(
|
|
207
|
+
query: Object.fromEntries(reqUrl.searchParams),
|
|
208
|
+
status: response.status,
|
|
209
|
+
duration: Date.now() - startTime,
|
|
210
|
+
service,
|
|
211
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
215
212
|
response: {
|
|
216
213
|
success: responseData.success,
|
|
217
214
|
message: responseData.message,
|
|
218
215
|
code: responseData.code
|
|
219
216
|
},
|
|
220
|
-
|
|
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
|
-
}
|
|
217
|
+
responseData: sanitizedResponseData
|
|
281
218
|
};
|
|
219
|
+
try {
|
|
220
|
+
const res = await fetchWithTimeout(logUrl, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: {
|
|
223
|
+
"Content-Type": "application/json",
|
|
224
|
+
...customHeaders
|
|
225
|
+
},
|
|
226
|
+
body: JSON.stringify(logBody)
|
|
227
|
+
}, timeout);
|
|
228
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
onError(error);
|
|
231
|
+
}
|
|
282
232
|
}
|
|
283
|
-
var src_default =
|
|
233
|
+
var src_default = requestLogger;
|
|
284
234
|
|
|
285
235
|
//#endregion
|
|
286
|
-
export {
|
|
236
|
+
export { createRequestLogger, src_default as default, requestLogger, sanitize, sanitizeHeaders };
|
|
287
237
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -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\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 } = 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 }).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}\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/** 带超时的 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 } = options\n\n const reqUrl = new URL(req.url)\n const path = reqUrl.pathname\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":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vafast/request-logger",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "API request logging middleware for Vafast
|
|
3
|
+
"version": "0.3.1",
|
|
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
|
+
}
|