错误处理与日志记录策略
错误处理中间件的基本用法
Express中的错误处理中间件与其他中间件类似,但需要接收四个参数:err
, req
, res
, next
。错误处理中间件应该定义在所有路由和其他中间件之后。
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
当在路由或中间件中调用next(err)
时,Express会跳过所有剩余的非错误处理中间件,直接执行错误处理中间件。
同步与异步错误处理
Express默认可以捕获同步代码中的错误,但对于异步代码,需要手动将错误传递给next()
函数。
同步错误处理示例:
app.get('/sync-error', (req, res) => {
throw new Error('同步错误示例');
});
异步错误处理示例:
app.get('/async-error', async (req, res, next) => {
try {
await someAsyncOperation();
} catch (err) {
next(err); // 必须手动传递错误
}
});
自定义错误类
创建自定义错误类可以更好地组织和管理不同类型的错误:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// 使用示例
app.get('/custom-error', (req, res, next) => {
next(new AppError('自定义错误消息', 404));
});
日志记录策略
基本日志记录
使用morgan
中间件记录HTTP请求日志:
const morgan = require('morgan');
app.use(morgan('combined')); // 使用预定义的日志格式
自定义日志格式
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
日志文件存储
使用winston
库实现更强大的日志记录:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 在错误处理中使用
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('Something broke!');
});
错误分类与处理
操作错误与编程错误
操作错误是预期中的错误情况,如无效的用户输入。编程错误是代码中的bug,如未定义的变量。
// 操作错误示例
app.get('/user/:id', (req, res, next) => {
const user = getUserById(req.params.id);
if (!user) {
return next(new AppError('用户不存在', 404));
}
res.json(user);
});
// 编程错误示例
app.get('/bug', (req, res) => {
console.log(undefinedVariable); // 未定义的变量
});
开发环境与生产环境的错误处理
开发环境需要详细的错误堆栈,生产环境则应该返回简化的错误信息。
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
});
全局未捕获异常处理
处理未捕获的异常和未处理的Promise拒绝:
process.on('uncaughtException', err => {
console.error('未捕获的异常:', err);
process.exit(1);
});
process.on('unhandledRejection', err => {
console.error('未处理的Promise拒绝:', err);
});
请求验证与错误处理
使用express-validator
进行请求验证:
const { body, validationResult } = require('express-validator');
app.post('/user',
body('email').isEmail(),
body('password').isLength({ min: 5 }),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(new AppError('验证失败', 400, { errors: errors.array() }));
}
// 处理有效请求
}
);
性能监控与错误关联
使用APM工具如elastic-apm-node
将错误与性能数据关联:
const apm = require('elastic-apm-node').start({
serviceName: 'my-express-app',
serverUrl: 'http://localhost:8200'
});
app.use((err, req, res, next) => {
apm.captureError(err);
next(err);
});
日志聚合与分析
配置日志聚合系统如ELK Stack:
const { createLogger, transports } = require('winston');
const { ElasticsearchTransport } = require('winston-elasticsearch');
const esTransport = new ElasticsearchTransport({
level: 'info',
clientOpts: { node: 'http://localhost:9200' }
});
const logger = createLogger({
transports: [esTransport]
});
安全相关的错误处理
处理安全相关的错误,如CSRF令牌无效:
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/process', csrfProtection, (req, res, next) => {
// 正常处理
}, (err, req, res, next) => {
if (err.code !== 'EBADCSRFTOKEN') return next(err);
res.status(403).json({ error: 'CSRF令牌无效' });
});
数据库错误处理
处理数据库操作中的特定错误:
app.get('/db-error', async (req, res, next) => {
try {
const result = await db.query('SELECT * FROM non_existent_table');
res.json(result);
} catch (err) {
if (err.code === '42P01') { // PostgreSQL表不存在错误代码
next(new AppError('请求的资源不存在', 404));
} else {
next(err);
}
}
});
错误通知系统
集成错误通知服务如Sentry:
const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'https://example@sentry.io/1234567' });
app.use(Sentry.Handlers.errorHandler());
// 自定义错误处理
app.use((err, req, res, next) => {
Sentry.captureException(err);
next(err);
});
测试中的错误处理
编写测试时验证错误处理:
const request = require('supertest');
const app = require('../app');
describe('错误处理', () => {
it('应该处理404错误', async () => {
const res = await request(app).get('/non-existent-route');
expect(res.statusCode).toEqual(404);
expect(res.body.message).toMatch(/找不到/);
});
it('应该处理500错误', async () => {
const res = await request(app).get('/trigger-error');
expect(res.statusCode).toEqual(500);
expect(res.body.message).toMatch(/出错了/);
});
});
客户端错误处理
在响应中包含错误详细信息,帮助客户端正确处理:
app.use((err, req, res, next) => {
const response = {
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message,
details: err.details
},
links: {
documentation: 'https://api.example.com/docs/errors'
}
};
if (process.env.NODE_ENV === 'development') {
response.error.stack = err.stack;
}
res.status(err.statusCode || 500).json(response);
});
错误处理中间件的组织
将错误处理逻辑组织到单独的文件中:
// errorController.js
exports.handleErrors = (err, req, res, next) => {
// 错误处理逻辑
};
// app.js
const { handleErrors } = require('./controllers/errorController');
app.use(handleErrors);
性能考虑
错误处理中的性能优化:
app.use((err, req, res, next) => {
// 避免在错误处理中进行耗时的同步操作
setImmediate(() => {
logger.error(err.stack);
});
// 快速响应客户端
res.status(500).json({ error: 'Internal Server Error' });
});
多语言错误消息
支持多语言错误消息:
const i18n = require('i18n');
app.use((err, req, res, next) => {
const message = i18n.__(err.message);
res.status(err.statusCode || 500).json({ error: message });
});
错误处理与API版本控制
在不同API版本中处理错误:
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// 版本特定的错误处理
app.use('/api/v1', (err, req, res, next) => {
res.status(err.statusCode || 500).json({
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
});
app.use('/api/v2', (err, req, res, next) => {
res.status(err.statusCode || 500).json({
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message
}
});
});
错误处理中间件的测试
测试错误处理中间件:
const testErrorHandler = require('./middleware/errorHandler');
const mockRequest = (params = {}) => ({ params });
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
test('应该处理AppError', () => {
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
const err = new AppError('测试错误', 400);
testErrorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
status: 'fail',
message: '测试错误'
});
});
日志轮转与归档
配置日志轮转避免日志文件过大:
const { createLogger, transports, format } = require('winston');
const { combine, timestamp, printf } = format;
const DailyRotateFile = require('winston-daily-rotate-file');
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} [${level}]: ${message}`;
});
const logger = createLogger({
format: combine(timestamp(), logFormat),
transports: [
new DailyRotateFile({
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
})
]
});
错误处理与GraphQL
在GraphQL中处理错误:
const { ApolloServer } = require('apollo-server-express');
const formatError = (err) => {
if (err.originalError instanceof AppError) {
return {
message: err.message,
code: err.originalError.code,
locations: err.locations,
path: err.path
};
}
return {
message: 'Internal server error',
code: 'INTERNAL_SERVER_ERROR'
};
};
const server = new ApolloServer({
typeDefs,
resolvers,
formatError
});
错误处理与WebSocket
处理WebSocket连接中的错误:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
ws.on('error', (err) => {
console.error('WebSocket错误:', err);
ws.send(JSON.stringify({
type: 'error',
message: '处理请求时出错'
}));
});
});
错误处理与文件上传
处理文件上传中的错误:
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
// 处理文件
}, (err, req, res, next) => {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({ error: '文件太大' });
}
next(err);
});
错误处理与身份验证
处理身份验证相关的错误:
const passport = require('passport');
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
if (!user) {
return next(new AppError(info.message || '认证失败', 401));
}
// 登录成功
})(req, res, next);
});
错误处理与限流
处理限流相关的错误:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
handler: (req, res, next) => {
next(new AppError('请求过多,请稍后再试', 429));
}
});
app.use(limiter);
错误处理与缓存
处理缓存相关的错误:
const redis = require('redis');
const client = redis.createClient();
client.on('error', (err) => {
logger.error('Redis错误:', err);
// 可以降级处理或使用其他缓存方案
});
app.get('/cached-data', (req, res, next) => {
client.get('someKey', (err, reply) => {
if (err) {
// 从数据库获取数据作为回退
return getDataFromDB()
.then(data => res.json(data))
.catch(next);
}
res.json(JSON.parse(reply));
});
});
错误处理与队列系统
处理队列系统中的错误:
const { Worker } = require('bullmq');
const worker = new Worker('emailQueue', async job => {
// 处理任务
}, {
connection: { host: 'localhost', port: 6379 },
limiter: { max: 10, duration: 1000 }
});
worker.on('failed', (job, err) => {
logger.error(`任务 ${job.id} 失败:`, err);
// 可以重试或通知管理员
});
错误处理与定时任务
处理定时任务中的错误:
const cron = require('node-cron');
cron.schedule('* * * * *', () => {
try {
// 执行定时任务
} catch (err) {
logger.error('定时任务执行失败:', err);
// 可以发送通知或记录详细错误
}
});
错误处理与第三方API调用
处理调用第三方API时的错误:
const axios = require('axios');
app.get('/external-api', async (req, res, next) => {
try {
const response = await axios.get('https://api.example.com/data');
res.json(response.data);
} catch (err) {
if (err.response) {
// 第三方API返回了错误响应
next(new AppError(`第三方API错误: ${err.response.statusText}`, err.response.status));
} else if (err.request) {
// 请求已发出但没有收到响应
next(new AppError('无法连接到第三方API', 502));
} else {
// 设置请求时出错
next(new AppError('处理请求时出错', 500));
}
}
});
错误处理与文件系统操作
处理文件系统操作中的错误:
const fs = require('fs').promises;
app.get('/read-file', async (req, res, next) => {
try {
const data = await fs.readFile('somefile.txt', 'utf8');
res.send(data);
} catch (err) {
if (err.code === 'ENOENT') {
next(new AppError('文件不存在', 404));
} else {
next(err);
}
}
});
错误处理与子进程
处理子进程中的错误:
const { exec } = require('child_process');
app.get('/run-script', (req, res, next) => {
exec('some-script.sh', (error, stdout, stderr) => {
if (error) {
logger.error(`脚本执行错误: ${error.message}`);
return next(new AppError('脚本执行失败', 500));
}
if (stderr) {
logger.warn(`脚本输出到stderr: ${stderr}`);
}
res.send(stdout);
});
});
错误处理与数据库连接
处理数据库连接错误:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/db', {
useNewUrlParser: true,
useUnifiedTopology: true
}).catch(err => {
logger.error('数据库连接失败:', err);
process.exit(1); // 如果数据库连接失败,通常应该终止应用
});
mongoose.connection.on('error', err => {
logger.error('数据库错误:', err);
});
错误处理与内存泄漏
监控和处理内存泄漏:
const heapdump = require('heapdump');
process.on('uncaughtException', err => {
if (err.message.includes('JavaScript heap out of memory')) {
const filename = `heapdump-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename, () => {
logger.error(`内存不足,已创建堆转储文件: ${filename}`);
process.exit(1);
});
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn