您现在的位置是:网站首页 > 错误处理与日志记录策略文章详情
错误处理与日志记录策略
陈川
【
Node.js
】
29344人已围观
5529字
在Express框架中,错误处理和日志记录是保证应用稳定性和可维护性的关键。合理的策略能快速定位问题、优化用户体验,并为后续调试提供完整上下文。以下从错误捕获、中间件设计、日志分类等方面展开具体实践方案。
错误处理中间件设计
Express通过四参数中间件(err, req, res, next)捕获错误。典型结构应包含错误分类和响应格式化:
app.use((err, req, res, next) => {
// 识别错误类型
const isOperational = err instanceof BaseError;
const statusCode = isOperational ? err.statusCode : 500;
// 生产环境隐藏技术细节
const response = {
message: isOperational ? err.message : 'Internal Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
};
res.status(statusCode).json(response);
// 非预期错误触发告警
if (!isOperational) {
process.emit('uncaughtException', err);
}
});
自定义错误类可增强错误识别能力:
class APIError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// 使用示例
throw new APIError('用户不存在', 404);
异步错误捕获方案
Express默认不捕获异步错误,需要结合Promise链或async/await处理:
// 包装器方案
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// 路由中使用
router.get('/data', asyncHandler(async (req, res) => {
const data = await fetchData();
if (!data) throw new APIError('数据获取失败', 503);
res.json(data);
}));
对于未处理的Promise拒绝,应在进程级别监听:
process.on('unhandledRejection', (reason, promise) => {
logger.error('未处理的拒绝:', reason);
// 可在此处触发重启策略
});
多层级日志记录策略
日志系统应区分不同严重级别并结构化输出。使用winston的典型配置:
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, json } = format;
const logger = createLogger({
level: 'debug',
format: combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
json()
),
transports: [
new transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5 * 1024 * 1024 // 5MB
}),
new transports.Console({
format: format.combine(
format.colorize(),
format.printf(info =>
`${info.timestamp} [${info.level}]: ${info.message}`
)
)
})
]
});
// 中间件集成示例
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`);
next();
});
敏感信息过滤机制
日志记录需避免泄露敏感数据,可采用数据清洗中间件:
const maskFields = ['password', 'token', 'creditCard'];
function sanitizeBody(body) {
return Object.keys(body).reduce((acc, key) => {
acc[key] = maskFields.includes(key) ? '***' : body[key];
return acc;
}, {});
}
app.use((req, res, next) => {
logger.debug('请求体:', sanitizeBody(req.body));
next();
});
性能日志与追踪ID
分布式系统中需关联请求链路,推荐使用CLS(Continuation Local Storage):
const cls = require('cls-hooked');
const namespace = cls.createNamespace('app');
// 中间件生成唯一ID
app.use((req, res, next) => {
namespace.run(() => {
const traceID = crypto.randomUUID();
namespace.set('traceID', traceID);
res.setHeader('X-Trace-ID', traceID);
next();
});
});
// 日志中自动附加ID
logger.format = format.printf(info => {
const traceID = namespace.get('traceID') || 'null';
return `${info.timestamp} [${traceID}] ${info.level}: ${info.message}`;
});
错误聚合与监控集成
将关键错误上报至Sentry等监控平台:
const Sentry = require('@sentry/node');
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.5,
attachStacktrace: true
});
// 错误中间件追加
app.use((err, req, res, next) => {
Sentry.captureException(err);
next(err);
});
日志轮转与归档策略
使用logrotate或专业工具管理日志文件:
const { DailyRotateFile } = require('winston-daily-rotate-file');
logger.add(new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '30d'
}));
开发与生产环境差异化配置
通过环境变量切换日志行为:
const isProduction = process.env.NODE_ENV === 'production';
logger.configure({
level: isProduction ? 'info' : 'debug',
silent: process.env.DISABLE_LOGGING === 'true'
});
// 错误响应差异处理
app.use((err, req, res, next) => {
if (isProduction && err.isAxiosError) {
err.message = '上游服务不可用';
}
next(err);
});
HTTP请求上下文增强
扩展错误对象包含请求上下文:
class ContextualError extends Error {
constructor(req, message) {
super(message);
this.request = {
method: req.method,
path: req.path,
params: req.params,
query: req.query
};
}
}
// 使用示例
app.post('/users', (req, res, next) => {
if (!req.body.name) {
return next(new ContextualError(req, '缺少必要字段'));
}
});
客户端错误上报接口
为前端提供专用错误上报端点:
app.post('/client-error', (req, res) => {
const { message, stack, userAgent } = req.body;
logger.clientError({
message,
stack,
deviceInfo: {
userAgent,
ip: req.ip
}
});
res.sendStatus(200);
});
数据库操作错误处理
Mongoose操作的特殊处理示例:
router.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id)
.orFail(new APIError('用户不存在', 404));
if (user.isLocked) {
throw new APIError('账户已锁定', 403, {
unlockTime: user.unlockTime
});
}
}));
测试环境Mock日志
单元测试中替换真实日志:
// test/setup.js
const mockLogger = {
info: jest.fn(),
error: jest.fn()
};
jest.mock('../lib/logger', () => mockLogger);
// 测试用例示例
test('should log API errors', async () => {
await request(app).get('/error-route');
expect(mockLogger.error).toHaveBeenCalledWith(
expect.objectContaining({ message: '模拟错误' })
);
});
上一篇: 内存泄漏检测与预防
下一篇: 微前端架构中的设计模式应用