feat: 增加日志打印和异常处理
This commit is contained in:
+21
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user