feat: 请求日志 + 前端埋点系统
后端: - 新增 logger 中间件,记录所有请求到 logs/ 目录 - 新增 /api/track 接口接收前端埋点数据 前端: - 新增 tracker 工具,支持页面访问/操作/错误埋点 - request.ts 添加 API 错误自动埋点 - App.vue 初始化全局错误追踪 文档: - CLAUDE.md 更新项目结构 - DEV.md 添加埋点和日志说明
This commit is contained in:
84
server/src/middleware/logger.ts
Normal file
84
server/src/middleware/logger.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// 日志目录
|
||||
const LOG_DIR = path.resolve(process.env.LOG_DIR || './logs')
|
||||
if (!fs.existsSync(LOG_DIR)) {
|
||||
fs.mkdirSync(LOG_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
/** 获取当前日期字符串 YYYY-MM-DD */
|
||||
function getDateStr(): string {
|
||||
const d = new Date()
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
/** 格式化时间 HH:mm:ss.SSS */
|
||||
function formatTime(date: Date): string {
|
||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}.${String(date.getMilliseconds()).padStart(3, '0')}`
|
||||
}
|
||||
|
||||
/** 写入日志文件 */
|
||||
function writeLog(level: string, message: string, meta?: Record<string, any>) {
|
||||
const dateStr = getDateStr()
|
||||
const logFile = path.join(LOG_DIR, `${dateStr}.log`)
|
||||
const time = formatTime(new Date())
|
||||
const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''
|
||||
const line = `[${time}] ${level} ${message}${metaStr}\n`
|
||||
|
||||
fs.appendFile(logFile, line, (err) => {
|
||||
if (err) console.error('[Logger] Write error:', err)
|
||||
})
|
||||
}
|
||||
|
||||
/** 请求日志中间件 */
|
||||
export function requestLogger(req: Request, res: Response, next: NextFunction) {
|
||||
const startTime = Date.now()
|
||||
const { method, url, ip } = req
|
||||
|
||||
// 提取用户 ID(如果有 auth 中间件设置)
|
||||
const userId = (req as any).userId
|
||||
|
||||
// 响应结束时记录日志
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - startTime
|
||||
const { statusCode } = res
|
||||
|
||||
const meta = {
|
||||
method,
|
||||
url,
|
||||
status: statusCode,
|
||||
duration: `${duration}ms`,
|
||||
ip: ip || req.headers['x-forwarded-for'] || 'unknown',
|
||||
userId: userId || null,
|
||||
userAgent: req.headers['user-agent']?.slice(0, 100) || ''
|
||||
}
|
||||
|
||||
if (statusCode >= 400) {
|
||||
writeLog('ERROR', `${method} ${url} ${statusCode} ${duration}ms`, meta)
|
||||
} else if (duration > 1000) {
|
||||
writeLog('WARN', `Slow request: ${method} ${url} ${duration}ms`, meta)
|
||||
} else {
|
||||
writeLog('INFO', `${method} ${url} ${statusCode} ${duration}ms`, meta)
|
||||
}
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
/** 错误日志 */
|
||||
export function logError(message: string, error: Error | any, meta?: Record<string, any>) {
|
||||
writeLog('ERROR', message, {
|
||||
...meta,
|
||||
error: error?.message || String(error),
|
||||
stack: error?.stack?.slice(0, 500) || ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 业务日志 */
|
||||
export function logInfo(message: string, meta?: Record<string, any>) {
|
||||
writeLog('INFO', message, meta)
|
||||
}
|
||||
|
||||
export default { requestLogger, logError, logInfo }
|
||||
Reference in New Issue
Block a user