Node.js(Express多模块项目)的规范错误日志记录方案
Got it, let's break down a robust error logging solution tailored to your multi-module Express project. Based on your folder structure and need for actionable logs that support manual intervention, here's a step-by-step implementation:
First, create a reusable error class to standardize error metadata across all services. This ensures every error includes critical details for debugging.
// utils/ServiceError.js (create this new file) class ServiceError extends Error { constructor(errorCode, message, context = {}) { super(message); this.name = 'ServiceError'; this.errorCode = errorCode; // Unique identifier (e.g., "PAYMENT_TRANSACTION_FAILED") this.context = context; // Extra details: user ID, transaction ID, service name Error.captureStackTrace(this, this.constructor); // Preserve stack trace } } module.exports = ServiceError;
Since each service's index.js acts as the external gateway, use it to catch errors from the service's routes and wrap them in your custom error class.
For example, in payment_service/index.js:
const express = require('express'); const router = require('./routes'); const ServiceError = require('../utils/ServiceError'); // Gateway wrapper to catch route errors const paymentGateway = (req, res, next) => { router(req, res, (err) => { if (err) { // Wrap raw errors into our standardized format const standardizedError = new ServiceError( err.errorCode || 'PAYMENT_UNKNOWN_ERROR', err.message || 'Payment service encountered an unexpected error', { service: 'payment_service', requestPath: req.path, ...err.context // Merge any existing context from the route error } ); next(standardizedError); // Pass to global error handler } }); }; module.exports = paymentGateway;
This middleware will be the single point to log all errors and send consistent responses to clients. We'll use a structured logging library like winston (no external links needed—just npm install it) for machine-readable logs.
First, set up the logger:
// utils/logger.js const winston = require('winston'); const logger = winston.createLogger({ level: 'error', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.json() // Structured format for easy filtering/searching ), transports: [ new winston.transports.File({ filename: 'critical-errors.log' }), // Persist to file new winston.transports.Console() // Also log to console for development ] }); module.exports = logger;
Then add the global error middleware to app.js:
const express = require('express'); const requestId = require('express-request-id')(); // Adds unique request ID for tracing const logger = require('./utils/logger'); const paymentGateway = require('./payment_service'); const ServiceError = require('./utils/ServiceError'); const app = express(); // Add request ID to track full request lifecycle app.use(requestId); // Mount your service gateways app.use('/payment', paymentGateway); // Global error handler (must have 4 parameters) app.use((err, req, res, next) => { // Standardize any non-ServiceError errors const error = err instanceof ServiceError ? err : new ServiceError( 'GENERAL_SERVER_ERROR', 'An internal server error occurred', { service: 'unknown', requestPath: req.path } ); // Log structured error with request context logger.error({ requestId: req.id, service: error.context.service, errorCode: error.errorCode, errorMessage: error.message, stackTrace: error.stack, context: error.context }); // Send consistent response to client res.status(500).json({ success: false, message: 'We encountered an issue. Please reference request ID: ' + req.id, requestId: req.id }); // Trigger alerts for errors requiring manual intervention const criticalErrors = [ 'PAYMENT_TRANSACTION_FAILED', 'DB_CONNECTION_LOST', 'INSUFFICIENT_FUNDS' ]; if (criticalErrors.includes(error.errorCode)) { // Add your alert logic here (e.g., email to admins, Slack notification) // alertAdmin(`Critical Error: ${error.errorCode} - Request ID: ${req.id}`); } }); app.listen(3000, () => console.log('Server running on port 3000'));
In your service routes and models, throw ServiceError instances with specific context to make logs actionable.
Example in payment_service/routes.js:
const express = require('express'); const router = express.Router(); const PaymentModel = require('../models/model_1'); const ServiceError = require('../utils/ServiceError'); router.post('/charge', async (req, res, next) => { try { const { userId, amount, transactionId } = req.body; const paymentResult = await PaymentModel.chargeUser(userId, amount); if (!paymentResult.success) { throw new ServiceError( 'PAYMENT_INSUFFICIENT_FUNDS', 'User has insufficient funds for transaction', { userId, transactionId, amount } ); } res.json({ success: true, transactionId }); } catch (dbErr) { throw new ServiceError( 'PAYMENT_DB_ERROR', 'Failed to process payment in database', { userId: req.body.userId, dbErrorMessage: dbErr.message } ); } }); module.exports = router;
- Structured Logs: JSON format makes it easy to search/filter errors by service, error code, or request ID.
- Actionable Context: Every log includes details like user IDs, transaction IDs, and service names to speed up manual fixes.
- Centralized Control: All errors flow through one middleware, so you can update logging/alerts without touching every service.
- Request Tracing: The request ID lets you track a single request across all modules, perfect for debugging cross-service issues.
内容的提问来源于stack exchange,提问作者Krrish Raj

