阿里云主机折上折
  • 微信号
Current Site:Index > Error handling and logging strategy

Error handling and logging strategy

Author:Chuan Chen 阅读数:29791人阅读 分类: Node.js

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.