@vafast/request-logger 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @vafast/request-logger
|
|
2
|
+
|
|
3
|
+
API request logging middleware for Vafast with automatic sensitive data sanitization.
|
|
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
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @vafast/request-logger
|
|
18
|
+
# or
|
|
19
|
+
bun add @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
|
+
```
|
|
45
|
+
|
|
46
|
+
### Development (Console)
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { createRequestLogger, createConsoleAdapter } from '@vafast/request-logger'
|
|
50
|
+
|
|
51
|
+
const requestLogger = createRequestLogger({
|
|
52
|
+
storage: createConsoleAdapter(),
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Sensitive Data Sanitization
|
|
57
|
+
|
|
58
|
+
The middleware automatically sanitizes sensitive data before logging:
|
|
59
|
+
|
|
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" }` |
|
|
66
|
+
|
|
67
|
+
### Default Removed Fields
|
|
68
|
+
|
|
69
|
+
- `password`, `newPassword`, `oldPassword`
|
|
70
|
+
- `secret`, `secretKey`, `privateKey`
|
|
71
|
+
- `apiSecret`, `clientSecret`
|
|
72
|
+
|
|
73
|
+
### Default Masked Fields (partial)
|
|
74
|
+
|
|
75
|
+
- `token`, `accessToken`, `refreshToken`
|
|
76
|
+
- `authorization`, `apiKey`, `bearer`
|
|
77
|
+
|
|
78
|
+
### Custom Sanitization
|
|
79
|
+
|
|
80
|
+
```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
|
+
```
|
|
90
|
+
|
|
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
|
+
},
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Log Structure
|
|
130
|
+
|
|
131
|
+
### Request Log
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
{
|
|
135
|
+
method: 'POST',
|
|
136
|
+
url: 'http://localhost:3000/api/users',
|
|
137
|
+
path: '/api/users',
|
|
138
|
+
headers: { /* sanitized */ },
|
|
139
|
+
body: { /* sanitized */ },
|
|
140
|
+
query: {},
|
|
141
|
+
response: {
|
|
142
|
+
success: true,
|
|
143
|
+
message: 'OK',
|
|
144
|
+
code: 0,
|
|
145
|
+
},
|
|
146
|
+
status: 200,
|
|
147
|
+
duration: 15,
|
|
148
|
+
userId: '123',
|
|
149
|
+
createdAt: Date,
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Response Log
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
{
|
|
157
|
+
requestLogId: 'abc123',
|
|
158
|
+
success: true,
|
|
159
|
+
message: 'OK',
|
|
160
|
+
code: 0,
|
|
161
|
+
data: { /* sanitized response data */ },
|
|
162
|
+
createdAt: Date,
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
169
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Middleware } from 'vafast';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 敏感数据清洗工具
|
|
5
|
+
*
|
|
6
|
+
* 用于在记录日志前移除或脱敏敏感信息
|
|
7
|
+
*/
|
|
8
|
+
interface SanitizeConfig {
|
|
9
|
+
/** 需要完全移除的字段(小写) */
|
|
10
|
+
removeFields?: string[];
|
|
11
|
+
/** 需要脱敏的字段(小写,部分匹配) */
|
|
12
|
+
maskFields?: string[];
|
|
13
|
+
/** 脱敏占位符 @default '[REDACTED]' */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
/** 最大递归深度 @default 10 */
|
|
16
|
+
maxDepth?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 深度清洗对象中的敏感数据
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const data = { password: '123456', token: 'eyJhbG...' }
|
|
24
|
+
* const sanitized = sanitize(data)
|
|
25
|
+
* // { password: '[REDACTED]', token: 'eyJh****...' }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function sanitize<T>(data: T, config?: SanitizeConfig, depth?: number): T;
|
|
29
|
+
/**
|
|
30
|
+
* 清洗 HTTP 请求头
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const headers = { Authorization: 'Bearer eyJhbG...', Cookie: 'session=xxx' }
|
|
35
|
+
* const sanitized = sanitizeHeaders(headers)
|
|
36
|
+
* // { Authorization: 'Bearer eyJh****...', Cookie: '[REDACTED]' }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function sanitizeHeaders(headers: Record<string, string>, config?: SanitizeConfig): Record<string, string>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @vafast/request-logger - API request logging middleware for Vafast
|
|
43
|
+
*
|
|
44
|
+
* Features:
|
|
45
|
+
* - Automatic sensitive data sanitization
|
|
46
|
+
* - Pluggable storage adapters (MongoDB, custom)
|
|
47
|
+
* - Async logging (non-blocking)
|
|
48
|
+
* - Path exclusion support
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
interface RequestLog {
|
|
52
|
+
method: string;
|
|
53
|
+
url: string;
|
|
54
|
+
path: string;
|
|
55
|
+
headers: Record<string, string>;
|
|
56
|
+
body: unknown;
|
|
57
|
+
query: Record<string, string>;
|
|
58
|
+
response: {
|
|
59
|
+
success?: boolean;
|
|
60
|
+
message?: string;
|
|
61
|
+
code?: number;
|
|
62
|
+
};
|
|
63
|
+
status: number;
|
|
64
|
+
duration: number;
|
|
65
|
+
userId?: string;
|
|
66
|
+
createdAt: Date;
|
|
67
|
+
}
|
|
68
|
+
interface ResponseLog {
|
|
69
|
+
requestLogId: string;
|
|
70
|
+
success?: boolean;
|
|
71
|
+
message?: string;
|
|
72
|
+
code?: number;
|
|
73
|
+
data?: unknown;
|
|
74
|
+
createdAt: Date;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 存储适配器接口
|
|
78
|
+
*/
|
|
79
|
+
interface StorageAdapter {
|
|
80
|
+
/** 存储请求日志 */
|
|
81
|
+
saveRequestLog(log: RequestLog): Promise<string>;
|
|
82
|
+
/** 存储响应详情 */
|
|
83
|
+
saveResponseLog(log: ResponseLog): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
interface RequestLoggerConfig {
|
|
86
|
+
/** 存储适配器 */
|
|
87
|
+
storage: StorageAdapter;
|
|
88
|
+
/** 排除的路径(支持字符串或正则) */
|
|
89
|
+
excludePaths?: (string | RegExp)[];
|
|
90
|
+
/** 敏感数据清洗配置 */
|
|
91
|
+
sanitize?: SanitizeConfig;
|
|
92
|
+
/** 获取用户 ID 的函数 */
|
|
93
|
+
getUserId?: (req: Request) => string | undefined;
|
|
94
|
+
/** 错误回调 */
|
|
95
|
+
onError?: (error: Error) => void;
|
|
96
|
+
/** 是否启用 @default true */
|
|
97
|
+
enabled?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 创建请求日志中间件
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* import { createRequestLogger, createMongoAdapter } from '@vafast/request-logger'
|
|
105
|
+
* import { mongoDb } from './mongodb'
|
|
106
|
+
*
|
|
107
|
+
* const requestLogger = createRequestLogger({
|
|
108
|
+
* storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),
|
|
109
|
+
* excludePaths: ['/health', '/metrics'],
|
|
110
|
+
* getUserId: (req) => getLocals(req)?.userInfo?.id,
|
|
111
|
+
* })
|
|
112
|
+
*
|
|
113
|
+
* server.use(requestLogger)
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createRequestLogger(config: RequestLoggerConfig): Middleware;
|
|
117
|
+
/**
|
|
118
|
+
* 创建 MongoDB 存储适配器
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* import { Db } from 'mongodb'
|
|
123
|
+
* import { createMongoAdapter } from '@vafast/request-logger'
|
|
124
|
+
*
|
|
125
|
+
* const adapter = createMongoAdapter(db, 'logs', 'logsResponse')
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function createMongoAdapter(db: {
|
|
129
|
+
collection: (name: string) => {
|
|
130
|
+
insertOne: (doc: any) => Promise<{
|
|
131
|
+
insertedId: {
|
|
132
|
+
toHexString: () => string;
|
|
133
|
+
};
|
|
134
|
+
}>;
|
|
135
|
+
};
|
|
136
|
+
}, logsCollection?: string, logsResponseCollection?: string): StorageAdapter;
|
|
137
|
+
/**
|
|
138
|
+
* 创建控制台存储适配器(用于开发调试)
|
|
139
|
+
*/
|
|
140
|
+
declare function createConsoleAdapter(): StorageAdapter;
|
|
141
|
+
|
|
142
|
+
export { type RequestLog, type RequestLoggerConfig, type ResponseLog, type SanitizeConfig, type StorageAdapter, createConsoleAdapter, createMongoAdapter, createRequestLogger, createRequestLogger as default, sanitize, sanitizeHeaders };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/sanitize.ts
|
|
2
|
+
var DEFAULT_REMOVE_FIELDS = [
|
|
3
|
+
"password",
|
|
4
|
+
"newpassword",
|
|
5
|
+
"oldpassword",
|
|
6
|
+
"confirmpassword",
|
|
7
|
+
"secret",
|
|
8
|
+
"secretkey",
|
|
9
|
+
"privatekey",
|
|
10
|
+
"apisecret",
|
|
11
|
+
"clientsecret"
|
|
12
|
+
];
|
|
13
|
+
var DEFAULT_MASK_FIELDS = [
|
|
14
|
+
"token",
|
|
15
|
+
"accesstoken",
|
|
16
|
+
"refreshtoken",
|
|
17
|
+
"authorization",
|
|
18
|
+
"apikey",
|
|
19
|
+
"api_key",
|
|
20
|
+
"x-api-key",
|
|
21
|
+
"idtoken",
|
|
22
|
+
"sessiontoken",
|
|
23
|
+
"bearer"
|
|
24
|
+
];
|
|
25
|
+
var DEFAULT_PLACEHOLDER = "[REDACTED]";
|
|
26
|
+
var DEFAULT_MAX_DEPTH = 10;
|
|
27
|
+
function partialMask(value, placeholder) {
|
|
28
|
+
if (value.length <= 8) return placeholder;
|
|
29
|
+
return value.slice(0, 4) + "****" + value.slice(-4);
|
|
30
|
+
}
|
|
31
|
+
function sanitize(data, config, depth = 0) {
|
|
32
|
+
const {
|
|
33
|
+
removeFields = DEFAULT_REMOVE_FIELDS,
|
|
34
|
+
maskFields = DEFAULT_MASK_FIELDS,
|
|
35
|
+
placeholder = DEFAULT_PLACEHOLDER,
|
|
36
|
+
maxDepth = DEFAULT_MAX_DEPTH
|
|
37
|
+
} = config ?? {};
|
|
38
|
+
if (depth > maxDepth) return data;
|
|
39
|
+
if (data === null || data === void 0) {
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(data)) {
|
|
43
|
+
return data.map((item) => sanitize(item, config, depth + 1));
|
|
44
|
+
}
|
|
45
|
+
if (typeof data === "object") {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [key, value] of Object.entries(data)) {
|
|
48
|
+
const lowerKey = key.toLowerCase();
|
|
49
|
+
if (removeFields.some((field) => lowerKey === field)) {
|
|
50
|
+
result[key] = placeholder;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (maskFields.some((field) => lowerKey.includes(field))) {
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
result[key] = partialMask(value, placeholder);
|
|
56
|
+
} else {
|
|
57
|
+
result[key] = placeholder;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
result[key] = sanitize(value, config, depth + 1);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
function sanitizeHeaders(headers, config) {
|
|
68
|
+
const {
|
|
69
|
+
maskFields = DEFAULT_MASK_FIELDS,
|
|
70
|
+
placeholder = DEFAULT_PLACEHOLDER
|
|
71
|
+
} = config ?? {};
|
|
72
|
+
const result = {};
|
|
73
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
74
|
+
const lowerKey = key.toLowerCase();
|
|
75
|
+
if (lowerKey === "authorization") {
|
|
76
|
+
if (value.startsWith("Bearer ")) {
|
|
77
|
+
result[key] = "Bearer " + partialMask(value.slice(7), placeholder);
|
|
78
|
+
} else {
|
|
79
|
+
result[key] = partialMask(value, placeholder);
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (lowerKey === "cookie" || lowerKey === "set-cookie") {
|
|
84
|
+
result[key] = placeholder;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (maskFields.some((field) => lowerKey.includes(field))) {
|
|
88
|
+
result[key] = partialMask(value, placeholder);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
result[key] = value;
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/index.ts
|
|
97
|
+
function createRequestLogger(config) {
|
|
98
|
+
const {
|
|
99
|
+
storage,
|
|
100
|
+
excludePaths = [],
|
|
101
|
+
sanitize: sanitizeConfig,
|
|
102
|
+
getUserId,
|
|
103
|
+
onError = console.error,
|
|
104
|
+
enabled = true
|
|
105
|
+
} = config;
|
|
106
|
+
return async (req, next) => {
|
|
107
|
+
if (!enabled) {
|
|
108
|
+
return next();
|
|
109
|
+
}
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
const response = await next();
|
|
112
|
+
recordLog(req, response, startTime, {
|
|
113
|
+
storage,
|
|
114
|
+
excludePaths,
|
|
115
|
+
sanitizeConfig,
|
|
116
|
+
getUserId,
|
|
117
|
+
onError
|
|
118
|
+
}).catch(onError);
|
|
119
|
+
return response;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function recordLog(req, response, startTime, options) {
|
|
123
|
+
const { storage, excludePaths, sanitizeConfig, getUserId } = options;
|
|
124
|
+
const url = new URL(req.url);
|
|
125
|
+
const path = url.pathname;
|
|
126
|
+
const shouldExclude = excludePaths.some((pattern) => {
|
|
127
|
+
if (typeof pattern === "string") {
|
|
128
|
+
return path.includes(pattern);
|
|
129
|
+
}
|
|
130
|
+
return pattern.test(path);
|
|
131
|
+
});
|
|
132
|
+
if (shouldExclude) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
let body = null;
|
|
136
|
+
try {
|
|
137
|
+
const clonedReq = req.clone();
|
|
138
|
+
const contentType = req.headers.get("content-type") || "";
|
|
139
|
+
if (contentType.includes("application/json")) {
|
|
140
|
+
body = await clonedReq.json();
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
let responseData = {};
|
|
145
|
+
try {
|
|
146
|
+
const clonedRes = response.clone();
|
|
147
|
+
responseData = await clonedRes.json();
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
const headers = {};
|
|
151
|
+
req.headers.forEach((value, key) => {
|
|
152
|
+
headers[key] = value;
|
|
153
|
+
});
|
|
154
|
+
const sanitizedHeaders = sanitizeHeaders(headers, sanitizeConfig);
|
|
155
|
+
const sanitizedBody = sanitize(body, sanitizeConfig);
|
|
156
|
+
const sanitizedResponseData = sanitize(responseData.data, sanitizeConfig);
|
|
157
|
+
const now = /* @__PURE__ */ new Date();
|
|
158
|
+
const duration = Date.now() - startTime;
|
|
159
|
+
const userId = getUserId?.(req);
|
|
160
|
+
const requestLogId = await storage.saveRequestLog({
|
|
161
|
+
method: req.method,
|
|
162
|
+
url: req.url,
|
|
163
|
+
path,
|
|
164
|
+
headers: sanitizedHeaders,
|
|
165
|
+
body: sanitizedBody,
|
|
166
|
+
query: Object.fromEntries(url.searchParams),
|
|
167
|
+
response: {
|
|
168
|
+
success: responseData.success,
|
|
169
|
+
message: responseData.message,
|
|
170
|
+
code: responseData.code
|
|
171
|
+
},
|
|
172
|
+
status: response.status,
|
|
173
|
+
duration,
|
|
174
|
+
userId,
|
|
175
|
+
createdAt: now
|
|
176
|
+
});
|
|
177
|
+
await storage.saveResponseLog({
|
|
178
|
+
requestLogId,
|
|
179
|
+
success: responseData.success,
|
|
180
|
+
message: responseData.message,
|
|
181
|
+
code: responseData.code,
|
|
182
|
+
data: sanitizedResponseData,
|
|
183
|
+
createdAt: now
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function createMongoAdapter(db, logsCollection = "logs", logsResponseCollection = "logsResponse") {
|
|
187
|
+
return {
|
|
188
|
+
async saveRequestLog(log) {
|
|
189
|
+
const result = await db.collection(logsCollection).insertOne({
|
|
190
|
+
...log,
|
|
191
|
+
createAt: log.createdAt,
|
|
192
|
+
updateAt: log.createdAt
|
|
193
|
+
});
|
|
194
|
+
return result.insertedId.toHexString();
|
|
195
|
+
},
|
|
196
|
+
async saveResponseLog(log) {
|
|
197
|
+
await db.collection(logsResponseCollection).insertOne({
|
|
198
|
+
logsId: log.requestLogId,
|
|
199
|
+
...log,
|
|
200
|
+
createAt: log.createdAt,
|
|
201
|
+
updateAt: log.createdAt
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function createConsoleAdapter() {
|
|
207
|
+
let idCounter = 0;
|
|
208
|
+
return {
|
|
209
|
+
async saveRequestLog(log) {
|
|
210
|
+
const id = `log_${++idCounter}`;
|
|
211
|
+
console.log(`[REQUEST] ${log.method} ${log.path} ${log.status} ${log.duration}ms`);
|
|
212
|
+
return id;
|
|
213
|
+
},
|
|
214
|
+
async saveResponseLog(log) {
|
|
215
|
+
if (!log.success) {
|
|
216
|
+
console.log(`[RESPONSE ERROR] ${log.message}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
var index_default = createRequestLogger;
|
|
222
|
+
export {
|
|
223
|
+
createConsoleAdapter,
|
|
224
|
+
createMongoAdapter,
|
|
225
|
+
createRequestLogger,
|
|
226
|
+
index_default as default,
|
|
227
|
+
sanitize,
|
|
228
|
+
sanitizeHeaders
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"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 * - Path exclusion support\n */\nimport type { Middleware } 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 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 excludePaths?: (string | RegExp)[]\n /** 敏感数据清洗配置 */\n sanitize?: SanitizeConfig\n /** 获取用户 ID 的函数 */\n getUserId?: (req: Request) => string | undefined\n /** 错误回调 */\n onError?: (error: Error) => void\n /** 是否启用 @default true */\n enabled?: boolean\n}\n\n// ============ Middleware Factory ============\n\n/**\n * 创建请求日志中间件\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 * excludePaths: ['/health', '/metrics'],\n * getUserId: (req) => getLocals(req)?.userInfo?.id,\n * })\n * \n * server.use(requestLogger)\n * ```\n */\nexport function createRequestLogger(config: RequestLoggerConfig): Middleware {\n const {\n storage,\n excludePaths = [],\n sanitize: sanitizeConfig,\n getUserId,\n onError = console.error,\n enabled = true,\n } = config\n\n return async (req: Request, next: () => Promise<Response>) => {\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 excludePaths,\n sanitizeConfig,\n getUserId,\n onError,\n }).catch(onError)\n\n return response\n }\n}\n\n// ============ Internal Functions ============\n\ninterface RecordLogOptions {\n storage: StorageAdapter\n excludePaths: (string | RegExp)[]\n sanitizeConfig?: SanitizeConfig\n getUserId?: (req: Request) => string | undefined\n onError: (error: Error) => void\n}\n\nasync function recordLog(\n req: Request,\n response: Response,\n startTime: number,\n options: RecordLogOptions\n) {\n const { storage, excludePaths, sanitizeConfig, getUserId } = options\n\n const url = new URL(req.url)\n const path = url.pathname\n\n // 检查是否需要排除\n const shouldExclude = excludePaths.some(pattern => {\n if (typeof pattern === 'string') {\n return path.includes(pattern)\n }\n return pattern.test(path)\n })\n\n if (shouldExclude) {\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\n const userId = getUserId?.(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 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\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vafast/request-logger",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "API request logging middleware for Vafast with sensitive data sanitization",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"vafast",
|
|
18
|
+
"request-logger",
|
|
19
|
+
"api-logs",
|
|
20
|
+
"middleware",
|
|
21
|
+
"mongodb",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"clean": "rimraf dist",
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "bun run --hot example/index.ts",
|
|
28
|
+
"test": "bun test",
|
|
29
|
+
"test:types": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
31
|
+
"release": "npm run build && npm publish --access=public"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.15.30",
|
|
42
|
+
"mongodb": "^6.16.0",
|
|
43
|
+
"rimraf": "^6.0.1",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.4.5",
|
|
46
|
+
"vafast": "^0.3.9"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"vafast": ">= 0.3.0",
|
|
50
|
+
"mongodb": ">= 6.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"mongodb": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|