feat: 增加日志打印和异常处理

This commit is contained in:
mazxd
2025-05-11 17:55:59 +08:00
parent e43ffe011f
commit fd8b4ec01b
21 changed files with 939 additions and 119 deletions
+21
View File
@@ -0,0 +1,21 @@
import express from 'express';
import { errorHandler } from './middleware/errorHandler';
import { AppError } from './utils/AppError';
const app = express();
// 中间件
app.use(express.json());
// 路由
// ... 你的路由配置 ...
// 处理未找到的路由
app.all('*', (req, res, next) => {
next(new AppError(`Can't find ${req.originalUrl} on this server!`, 404));
});
// 错误处理中间件 (必须放在所有路由之后)
app.use(errorHandler);
export default app;
+48
View File
@@ -0,0 +1,48 @@
import { AppError } from '../utils/AppError';
import logger from '../lib/logger';
export function handleError(errorMessage?: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
// 记录错误日志
logger.error(`${errorMessage || '操作执行失败'}:`, {
method: propertyKey,
args,
error: error instanceof Error ? error.message : '未知错误',
stack: error instanceof Error ? error.stack : undefined
});
// 如果是 AppError,直接抛出
if (error instanceof AppError) {
throw error;
}
// 如果是 Axios 错误,转换为 AppError
if (error.isAxiosError) {
const statusCode = error.response?.status || 500;
throw new AppError(
error.response?.data?.message || '请求外部服务失败',
statusCode
);
}
// 其他错误转换为 AppError
throw new AppError(
error instanceof Error ? error.message : '操作执行失败',
500
);
}
};
return descriptor;
};
}
+106
View File
@@ -0,0 +1,106 @@
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/AppError';
import winston from 'winston';
// 创建日志记录器
const logger = winston.createLogger({
level: 'error',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log' }),
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
// 开发环境错误处理
const sendErrorDev = (err: any, res: Response) => {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
};
// 生产环境错误处理
const sendErrorProd = (err: any, res: Response) => {
// 可操作的错误:发送详细信息给客户端
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
// 编程或其他未知错误:不泄露错误详情
else {
// 记录错误
logger.error('ERROR 💥', err);
// 发送通用错误消息
res.status(500).json({
status: 'error',
message: 'Something went wrong!'
});
}
};
// 处理特定类型的错误
const handleCastErrorDB = (err: any) => {
const message = `Invalid ${err.path}: ${err.value}.`;
return new AppError(message, 400);
};
const handleDuplicateFieldsDB = (err: any) => {
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
const message = `Duplicate field value: ${value}. Please use another value!`;
return new AppError(message, 400);
};
const handleValidationErrorDB = (err: any) => {
const errors = Object.values(err.errors).map((el: any) => el.message);
const message = `Invalid input data. ${errors.join('. ')}`;
return new AppError(message, 400);
};
// 全局错误处理中间件
export const errorHandler = (
err: any,
req: Request,
res: Response,
next: NextFunction
) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
// 记录错误日志
logger.error({
message: err.message,
stack: err.stack,
path: req.path,
method: req.method,
body: req.body,
params: req.params,
query: req.query
});
if (process.env.NODE_ENV === 'development') {
sendErrorDev(err, res);
} else {
let error = { ...err };
error.message = err.message;
if (error.name === 'CastError') error = handleCastErrorDB(error);
if (error.code === 11000) error = handleDuplicateFieldsDB(error);
if (error.name === 'ValidationError') error = handleValidationErrorDB(error);
sendErrorProd(error, res);
}
};
+14
View File
@@ -0,0 +1,14 @@
export class AppError extends Error {
statusCode: number;
status: string;
isOperational: boolean;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}