Error handling and logging strategy
Basic Usage of Error Handling Middleware
Error handling middleware in Express is similar to other middleware but requires four parameters: err
, req
, res
, next
. Error handling middleware should be defined after all routes and other middleware.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
When next(err)
is called in a route or middleware, Express skips all remaining non-error-handling middleware and directly executes the error-handling middleware.
Synchronous and Asynchronous Error Handling
Express can catch errors in synchronous code by default, but for asynchronous code, errors must be manually passed to the next()
function.
Synchronous error handling example:
app.get('/sync-error', (req, res) => {
throw new Error('Synchronous error example');
});
Asynchronous error handling example:
app.get('/async-error', async (req, res, next) => {
try {
await someAsyncOperation();
} catch (err) {
next(err); // Must manually pass the error
}
});
Custom Error Classes
Creating custom error classes can better organize and manage different types of errors:
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);
}
}
// Usage example
app.get('/custom-error', (req, res, next) => {
next(new AppError('Custom error message', 404));
});
Logging Strategies
Basic Logging
Use the morgan
middleware to log HTTP requests:
const morgan = require('morgan');
app.use(morgan('combined')); // Use predefined log format
Custom Log Format
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
Log File Storage
Use the winston
library for more powerful logging:
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' })
]
});
// Usage in error handling
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('Something broke!');
});
Error Classification and Handling
Operational Errors vs. Programming Errors
Operational errors are expected error conditions, such as invalid user input. Programming errors are bugs in the code, such as undefined variables.
// Operational error example
app.get('/user/:id', (req, res, next) => {
const user = getUserById(req.params.id);
if (!user) {
return next(new AppError('User not found', 404));
}
res.json(user);
});
// Programming error example
app.get('/bug', (req, res) => {
console.log(undefinedVariable); // Undefined variable
});
Error Handling in Development vs. Production Environments
Development environments need detailed error stacks, while production environments should return simplified error messages.
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
});
}
});
Global Uncaught Exception Handling
Handle uncaught exceptions and unhandled Promise rejections:
process.on('uncaughtException', err => {
console.error('Uncaught exception:', err);
process.exit(1);
});
process.on('unhandledRejection', err => {
console.error('Unhandled Promise rejection:', err);
});
Request Validation and Error Handling
Use express-validator
for request validation:
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('Validation failed', 400, { errors: errors.array() }));
}
// Process valid request
}
);
Performance Monitoring and Error Correlation
Use APM tools like elastic-apm-node
to correlate errors with performance data:
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);
});
Log Aggregation and Analysis
Configure log aggregation systems like 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]
});
Security-Related Error Handling
Handle security-related errors, such as invalid CSRF tokens:
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/process', csrfProtection, (req, res, next) => {
// Normal processing
}, (err, req, res, next) => {
if (err.code !== 'EBADCSRFTOKEN') return next(err);
res.status(403).json({ error: 'Invalid CSRF token' });
});
Database Error Handling
Handle specific errors in database operations:
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 table does not exist error code
next(new AppError('Requested resource does not exist', 404));
} else {
next(err);
}
}
});
Error Notification Systems
Integrate error notification services like Sentry:
const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'https://example@sentry.io/1234567' });
app.use(Sentry.Handlers.errorHandler());
// Custom error handling
app.use((err, req, res, next) => {
Sentry.captureException(err);
next(err);
});
Error Handling in Testing
Verify error handling when writing tests:
const request = require('supertest');
const app = require('../app');
describe('Error handling', () => {
it('should handle 404 errors', async () => {
const res = await request(app).get('/non-existent-route');
expect(res.statusCode).toEqual(404);
expect(res.body.message).toMatch(/Not found/);
});
it('should handle 500 errors', async () => {
const res = await request(app).get('/trigger-error');
expect(res.statusCode).toEqual(500);
expect(res.body.message).toMatch(/Something went wrong/);
});
});
Client-Side Error Handling
Include error details in responses to help clients handle errors properly:
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);
});
Organizing Error Handling Middleware
Organize error handling logic into separate files:
// errorController.js
exports.handleErrors = (err, req, res, next) => {
// Error handling logic
};
// app.js
const { handleErrors } = require('./controllers/errorController');
app.use(handleErrors);
Performance Considerations
Performance optimizations in error handling:
app.use((err, req, res, next) => {
// Avoid time-consuming synchronous operations in error handling
setImmediate(() => {
logger.error(err.stack);
});
// Respond quickly to the client
res.status(500).json({ error: 'Internal Server Error' });
});
Multilingual Error Messages
Support multilingual error messages:
const i18n = require('i18n');
app.use((err, req, res, next) => {
const message = i18n.__(err.message);
res.status(err.statusCode || 500).json({ error: message });
});
Error Handling and API Versioning
Handle errors in different API versions:
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// Version-specific error handling
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
}
});
});
Testing Error Handling Middleware
Test error handling middleware:
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('should handle AppError', () => {
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
const err = new AppError('Test error', 400);
testErrorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
status: 'fail',
message: 'Test error'
});
});
Log Rotation and Archiving
Configure log rotation to prevent log files from becoming too large:
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'
})
]
});
Error Handling and GraphQL
Handle errors in 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
});
Error Handling and WebSocket
Handle errors in WebSocket connections:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
ws.on('error', (err) => {
console.error('WebSocket error:', err);
ws.send(JSON.stringify({
type: 'error',
message: 'Error processing request'
}));
});
});
Error Handling and File Uploads
Handle errors in file uploads:
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
// Process file
}, (err, req, res, next) => {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({ error: 'File too large' });
}
next(err);
});
Error Handling and Authentication
Handle authentication-related errors:
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 || 'Authentication failed', 401));
}
// Successful login
})(req, res, next);
});
Error Handling and Rate Limiting
Handle rate-limiting-related errors:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
handler: (req, res, next) => {
next(new AppError('Too many requests, please try again later', 429));
}
});
app.use(limiter);
Error Handling and Caching
Handle caching-related errors:
const redis = require('redis');
const client = redis.createClient();
client.on('error', (err) => {
logger.error('Redis error:', err);
// Fallback or use alternative caching solutions
});
app.get('/cached-data', (req, res, next) => {
client.get('someKey', (err, reply) => {
if (err) {
// Fallback to database
return getDataFromDB()
.then(data => res.json(data))
.catch(next);
}
res.json(JSON.parse(reply));
});
});
Error Handling and Queue Systems
Handle errors in queue systems:
const { Worker } = require('bullmq');
const worker = new Worker('emailQueue', async job => {
// Process job
}, {
connection: { host: 'localhost', port: 6379 },
limiter: { max: 10, duration: 1000 }
});
worker.on('failed', (job, err) => {
logger.error(`Job ${job.id} failed:`, err);
// Retry or notify admin
});
Error Handling and Scheduled Tasks
Handle errors in scheduled tasks:
const cron = require('node-cron');
cron.schedule('* * * * *', () => {
try {
// Execute scheduled task
} catch (err) {
logger.error('Scheduled task failed:', err);
// Send notification or log detailed error
}
});
Error Handling and Third-Party API Calls
Handle errors when calling third-party APIs:
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) {
// Third-party API returned an error response
next(new AppError(`Third-party API error: ${err.response.statusText}`, err.response.status));
} else if (err.request) {
// Request was made but no response received
next(new AppError('Could not connect to third-party API', 502));
} else {
// Error setting up the request
next(new AppError('Error processing request', 500));
}
}
});
Error Handling and File System Operations
Handle errors in file system operations:
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('File does not exist', 404));
} else {
next(err);
}
}
});
Error Handling and Child Processes
Handle errors in child processes:
const { exec } = require('child_process');
app.get('/run-script', (req, res, next) => {
exec('some-script.sh', (error, stdout, stderr) => {
if (error) {
logger.error(`Script execution error: ${error.message}`);
return next(new AppError('Script execution failed', 500));
}
if (stderr) {
logger.warn(`Script output to stderr: ${stderr}`);
}
res.send(stdout);
});
});
Error Handling and Database Connections
Handle database connection errors:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/db', {
useNewUrlParser: true,
useUnifiedTopology: true
}).catch(err => {
logger.error('Database connection failed:', err);
process.exit(1); // If database connection fails, typically the application should terminate
});
mongoose.connection.on('error', err => {
logger.error('Database error:', err);
});
Error Handling and Memory Leaks
Monitor and handle memory leaks:
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(`Out of memory, heap dump created: ${filename}`);
process.exit(1);
});
}
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn