@vafast/request-logger 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +0 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -8
package/dist/index.d.mts
CHANGED
|
@@ -89,11 +89,6 @@ interface RequestLoggerConfig {
|
|
|
89
89
|
getAppId?: (req: Request) => string | undefined;
|
|
90
90
|
/** 服务标识(区分不同服务,如 auth-server、ones-server) */
|
|
91
91
|
service?: string;
|
|
92
|
-
/**
|
|
93
|
-
* API 路径前缀,用于在查询路由注册表时匹配正确的路径
|
|
94
|
-
* 例如:'/restfulApi' → 请求路径 '/restfulApi/auth/signIn' 会在注册表中查找 '/auth/signIn'
|
|
95
|
-
*/
|
|
96
|
-
pathPrefix?: string;
|
|
97
92
|
/** 错误回调 */
|
|
98
93
|
onError?: (error: Error) => void;
|
|
99
94
|
/** 是否启用 @default true */
|
|
@@ -111,7 +106,6 @@ interface RequestLoggerConfig {
|
|
|
111
106
|
*
|
|
112
107
|
* const requestLogger = createRequestLogger({
|
|
113
108
|
* storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),
|
|
114
|
-
* pathPrefix: '/restfulApi',
|
|
115
109
|
* getUserId: (req) => getLocals(req)?.userInfo?.id,
|
|
116
110
|
* })
|
|
117
111
|
*
|
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;;;;ACvHT;;;;;AAqBA;AAYA;;AAEmC,iBDkBnB,QClBmB,CAAA,CAAA,CAAA,CAAA,IAAA,EDkBD,CClBC,EAAA,MAAA,CAAA,EDkBW,cClBX,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EDkBuC,CClBvC;;;;AAKnC;;;;;;;
|
|
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;;;;;AAqBA;AAYA;;AAEmC,iBDkBnB,QClBmB,CAAA,CAAA,CAAA,CAAA,IAAA,EDkBD,CClBC,EAAA,MAAA,CAAA,EDkBW,cClBX,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EDkBuC,CClBvC;;;;AAKnC;;;;;;;AAqDgB,iBDuBA,eAAA,CCvB4B,OAAA,EDwBjC,MCxBiC,CAAA,MAAsB,EAAA,MAAU,CAAA,EAAA,MAAA,CAAA,EDyBjE,cCzBiE,CAAA,ED0BzE,MC1ByE,CAAA,MAAA,EAAA,MAAA,CAAA;;;UA7F3D,UAAA;;;EAAA,IAAA,EAAA,MAAA;EAIN,OAAA,EAAA,MAAA,CAAA,MAAA,EAAA,MAAA,CAAA;EAEF,IAAA,EAAA,OAAA;EAYI,KAAA,EAZJ,MAYI,CAAA,MAAA,EAAA,MAAA,CAAA;EAAI,QAAA,EAAA;IAGA,OAAA,CAAA,EAAW,OAAA;IAYX,OAAA,CAAA,EAAA,MAAc;IAET,IAAA,CAAA,EAAA,MAAA;EAAa,CAAA;EAEZ,MAAA,EAAA,MAAA;EAAc,QAAA,EAAA,MAAA;EAAO,MAAA,CAAA,EAAA,MAAA;EAG3B,KAAA,CAAA,EAAA,MAAA;EAEN;EAEE,OAAA,CAAA,EAAA,MAAA;EAEO,SAAA,EA5BP,IA4BO;;AAMA,UA/BH,WAAA,CA+BG;EAAK,YAAA,EAAA,MAAA;EAyCT,OAAA,CAAA,EAAA,OAAA;EA+JA,OAAA,CAAA,EAAA,MAAA;EA+BA,IAAA,CAAA,EAAA,MAAA;;aAhQH;;;;;UAMI,cAAA;;sBAEK,aAAa;;uBAEZ,cAAc;;UAGpB,mBAAA;;WAEN;;aAEE;;oBAEO;;mBAED;;;;oBAIC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCJ,mBAAA,SAA4B,sBAAsB;;;;;;;;;;;;iBA+JlD,kBAAA;;6BACiD;;;;;;8DAG9D;;;;iBA2Ba,oBAAA,CAAA,GAAwB"}
|
package/dist/index.mjs
CHANGED
|
@@ -116,7 +116,6 @@ function sanitizeHeaders(headers, config) {
|
|
|
116
116
|
*
|
|
117
117
|
* const requestLogger = createRequestLogger({
|
|
118
118
|
* storage: createMongoAdapter(mongoDb, 'logs', 'logsResponse'),
|
|
119
|
-
* pathPrefix: '/restfulApi',
|
|
120
119
|
* getUserId: (req) => getLocals(req)?.userInfo?.id,
|
|
121
120
|
* })
|
|
122
121
|
*
|
|
@@ -140,14 +139,13 @@ function sanitizeHeaders(headers, config) {
|
|
|
140
139
|
* ```
|
|
141
140
|
*/
|
|
142
141
|
function createRequestLogger(config) {
|
|
143
|
-
const { storage, sanitize: sanitizeConfig, getUserId, getAppId, service,
|
|
142
|
+
const { storage, sanitize: sanitizeConfig, getUserId, getAppId, service, onError = console.error, enabled = true } = config;
|
|
144
143
|
return async (req, next) => {
|
|
145
144
|
if (!enabled) return next();
|
|
146
145
|
const startTime = Date.now();
|
|
147
146
|
const response = await next();
|
|
148
147
|
recordLog(req, response, startTime, {
|
|
149
148
|
storage,
|
|
150
|
-
pathPrefix,
|
|
151
149
|
sanitizeConfig,
|
|
152
150
|
getUserId,
|
|
153
151
|
getAppId,
|
|
@@ -161,18 +159,18 @@ function createRequestLogger(config) {
|
|
|
161
159
|
* 检查路由是否配置了 log: false
|
|
162
160
|
* 使用 vafast RouteRegistry 查询路由配置(支持嵌套继承)
|
|
163
161
|
*/
|
|
164
|
-
function shouldSkipLog(method, path
|
|
162
|
+
function shouldSkipLog(method, path) {
|
|
165
163
|
try {
|
|
166
|
-
return getRoute(method,
|
|
164
|
+
return getRoute(method, path)?.log === false;
|
|
167
165
|
} catch {
|
|
168
166
|
return false;
|
|
169
167
|
}
|
|
170
168
|
}
|
|
171
169
|
async function recordLog(req, response, startTime, options) {
|
|
172
|
-
const { storage,
|
|
170
|
+
const { storage, sanitizeConfig, getUserId, getAppId, service } = options;
|
|
173
171
|
const url = new URL(req.url);
|
|
174
172
|
const path = url.pathname;
|
|
175
|
-
if (shouldSkipLog(req.method, path
|
|
173
|
+
if (shouldSkipLog(req.method, path)) return;
|
|
176
174
|
let body = null;
|
|
177
175
|
try {
|
|
178
176
|
const clonedReq = req.clone();
|
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 type { Middleware } 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 /** 服务标识(区分不同服务,如 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 /** 服务标识(区分不同服务,如 auth-server、ones-server) */\n service?: string\n /** \n * API 路径前缀,用于在查询路由注册表时匹配正确的路径\n * 例如:'/restfulApi' → 请求路径 '/restfulApi/auth/signIn' 会在注册表中查找 '/auth/signIn'\n */\n pathPrefix?: 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 * pathPrefix: '/restfulApi',\n * getUserId: (req) => getLocals(req)?.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): Middleware {\n const {\n storage,\n sanitize: sanitizeConfig,\n getUserId,\n getAppId,\n service,\n pathPrefix = '',\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 pathPrefix,\n sanitizeConfig,\n getUserId,\n getAppId,\n service,\n onError,\n }).catch(onError)\n\n return response\n }\n}\n\n// ============ Internal Functions ============\n\ninterface RecordLogOptions {\n storage: StorageAdapter\n pathPrefix: string\n sanitizeConfig?: SanitizeConfig\n getUserId?: (req: Request) => string | undefined\n getAppId?: (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, pathPrefix: string): boolean {\n try {\n // 移除 pathPrefix 以匹配路由注册表中的路径\n const routePath = pathPrefix ? path.replace(new RegExp(`^${pathPrefix}`), '') : path\n const route = getRoute<{ log?: boolean }>(method, routePath)\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, pathPrefix, sanitizeConfig, getUserId, getAppId, 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, pathPrefix)) {\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\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 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\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 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 type { Middleware } 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 /** 服务标识(区分不同服务,如 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 /** 服务标识(区分不同服务,如 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) => getLocals(req)?.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): Middleware {\n const {\n storage,\n sanitize: sanitizeConfig,\n getUserId,\n getAppId,\n service,\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 sanitizeConfig,\n getUserId,\n getAppId,\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 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, 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\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 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\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vafast/request-logger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "API request logging middleware for Vafast with sensitive data sanitization",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.
|
|
7
|
-
"module": "./dist/index.
|
|
8
|
-
"types": "./dist/index.d.
|
|
6
|
+
"main": "./dist/index.mjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./dist/index.d.
|
|
12
|
-
"import": "./dist/index.
|
|
13
|
-
"default": "./dist/index.
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
@@ -52,4 +52,3 @@
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
|