Error Monitoring and Reporting in Express.js

Error monitoring and reporting are crucial components in maintaining a healthy and reliable Express.js application.

Introduction to Error Monitoring in Express.js

Error monitoring in Express.js involves tracking and recording application errors and exceptions that occur during runtime. With error monitoring in place, developers can detect and resolve issues faster, improving the overall reliability and user experience.

Error Handling Basics in Express.js

Express.js provides built-in error-handling capabilities, allowing developers to catch and manage errors efficiently.

Types of Errors

  1. Synchronous Errors: These occur immediately during execution and can be caught in try-catch blocks.
  2. Asynchronous Errors: These occur in asynchronous code, like callbacks, promises, or async/await functions, and require specialized handling.

Default Error Handler in Express.js

Express comes with a default error handler that sends a generic error response to the client. However, for production applications, it’s essential to customize this handler to ensure that sensitive information is not exposed.

Basic Error-Handling Middleware

In Express.js, error-handling middleware functions are identified by having four parameters: err, req, res, and next. Here’s a basic example:

				
					const express = require('express');
const app = express();

app.get('/', (req, res) => {
  throw new Error('Something went wrong!');
});

// Error-handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('An unexpected error occurred!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

				
			

Explanation:

  • When an error occurs, Express routes it to the error-handling middleware.
  • Here, err.stack logs the error details, while res.status(500).send() returns a 500 status code.

Output:

  • If an error occurs, it logs the error and sends a message, “An unexpected error occurred!”.

Creating a Custom Error Handler

Custom error handling helps define different behaviors for various error types, such as 404 (Not Found) errors or 500 (Internal Server) errors.

Custom Error Class

Define a custom error class for handling specific errors with additional context.

				
					class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

				
			

Custom Error Middleware

Using this class in middleware provides more structured error responses.

				
					app.get('/error', (req, res, next) => {
  const error = new AppError('This is a custom error message', 400);
  next(error);
});

app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  const message = err.isOperational ? err.message : 'Internal Server Error';
  res.status(statusCode).json({
    status: 'error',
    message: message,
  });
});

				
			

Explanation:

  • Here, AppError lets us customize error details.
  • Middleware differentiates operational errors (expected) from programming errors (unexpected), sending more user-friendly error messages.

Output:

  • The response will display the custom message if isOperational is true, else it sends “Internal Server Error”.

Logging Errors in Express.js

Error logs are essential for debugging and can be stored locally or externally.

Using the Console for Local Logs

Logging errors to the console is a quick way to monitor them during development

				
					app.use((err, req, res, next) => {
  console.error(`Error logged: ${err.message}`);
  next(err);
});

				
			

Using Winston for Structured Logging

For production applications, structured logging tools like Winston or Morgan offer more control

				
					const winston = require('winston');

const logger = winston.createLogger({
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' })
  ]
});

app.use((err, req, res, next) => {
  logger.error(err.message);
  res.status(500).send('An error occurred!');
});

				
			

Explanation:

  • This middleware uses Winston to log errors to an error.log file.

Output:

  • Any errors trigger a log entry in error.log

Using Error Monitoring Tools

Error monitoring tools provide real-time tracking and alerts. Popular tools include Sentry, LogRocket, and Datadog.

Integrating Sentry

1. Install Sentry:

				
					npm install @sentry/node

				
			

2. Initialize Sentry in the main application file:

				
					const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'your_sentry_dsn' });

app.use(Sentry.Handlers.requestHandler());

app.get('/sentry-error', (req, res) => {
  throw new Error('Sentry test error');
});

app.use(Sentry.Handlers.errorHandler());

				
			

3. Configure Error Handler to integrate with Sentry:

				
					app.use((err, req, res, next) => {
  Sentry.captureException(err);
  res.status(500).send('An error occurred!');
});

				
			

Explanation:

  • The code sends errors to Sentry, where they can be monitored and alerted.

Output:

  • If an error occurs, it gets logged in the Sentry dashboard.

Setting Up Alerts for Critical Errors

Most monitoring tools support alert configurations, such as email or SMS, for critical errors.

  1. Set Up Alerts in Sentry:

    • Access the Alerts section in the Sentry dashboard.
    • Define rules for alerts, e.g., trigger on every 500 error.
  2. Use Custom Notifications:

    • Sentry supports integration with Slack, PagerDuty, and other notification services.

Best Practices for Error Handling in Express.js

  • Use Centralized Error Handling: Maintain a single error handler for all routes.
  • Avoid Exposing Internal Details: In production, avoid sending stack traces to users.
  • Log Errors for Debugging: Structured logs help trace issues efficiently.
  • Handle Rejections and Async Errors: Use .catch() in promises and try-catch in async functions.
  • Test Error Handling: Write test cases to ensure your error-handling code functions correctly.

Error monitoring and reporting are essential for maintaining stable Express.js applications. By implementing effective error-handling middleware, structured logging, and real-time monitoring with tools like Sentry, you can detect, log, and resolve issues quickly, ensuring a reliable experience for end users. Happy Coding!❤️

Table of Contents