express-enhanced-logger
v2.9.0
Published
An enhanced Express.js logger with performance monitoring, SQL query formatting, and customizable features
Maintainers
Readme
express-enhanced-logger
A Rails-inspired Express.js logger with clean output, performance monitoring, SQL query formatting, and Prisma integration. Built on Winston with full TypeScript support.
✨ Features
- 🚂 Rails-Style Output - Clean, minimal logging format inspired by Ruby on Rails (no emojis, clear formatting)
- ⏱️ Timing Breakdown - Rails-style timing breakdown showing Logic, DB, and Total durations for each request
- 🚀 Performance Monitoring - Track slow requests, memory usage, and response times
- 🔍 Smart SQL Formatting - Intelligent truncation for large IN clauses with parameter substitution
- 🗄️ Prisma Integration - Plug-and-play Prisma logging with one line of code + automatic DB time tracking
- 📏 measure() Helper - Wrap any code block to measure and log its execution time
- 📁 File Logging - Automatic log rotation with configurable retention (JSON format)
- ⚙️ Highly Configurable - Extensive customization options for any use case
- 🔧 TypeScript First - Full type definitions and interfaces included
- ✅ Well Tested - 89% test coverage with 229 passing tests
- 🪶 Lightweight - Minimal dependencies (winston, chalk, winston-daily-rotate-file)
📦 Installation
npm install express-enhanced-logger
# or
yarn add express-enhanced-logger
# or
pnpm add express-enhanced-loggerRequirements:
- Node.js >= 18.0.0
- Express.js >= 4.0.0 or >= 5.0.0 (peer dependency)
Module System Support:
This library supports both ES modules and CommonJS:
// ES modules (import)
import { EnhancedLogger } from 'express-enhanced-logger';
// CommonJS (require)
const { EnhancedLogger } = require('express-enhanced-logger');🚀 Quick Start
Basic Usage
import express from 'express';
import { createLogger, requestLogger } from 'express-enhanced-logger';
const app = express();
// Create logger with default configuration
const logger = createLogger();
// Add request logging middleware
app.use(requestLogger());
// Use logger in your routes
app.get('/api/users', (req, res) => {
logger.info('Fetching users');
res.json({ users: [] });
});
app.listen(3000, () => {
logger.info('Server started on port 3000');
});Prisma Integration (One-Line Setup!)
import { PrismaClient } from '@prisma/client';
import { setupPrismaLogging } from 'express-enhanced-logger';
const prismaClient = new PrismaClient({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'event', level: 'info' },
{ emit: 'event', level: 'warn' },
{ emit: 'event', level: 'error' }
]
});
// That's it! Automatic logging for all Prisma events
setupPrismaLogging(prismaClient);
export default prismaClient;🎯 See the Prisma Integration section below for detailed usage and examples.
🚂 Rails-Style Logging
The logger produces clean, Rails-inspired output that's minimal and easy to read:
Key Characteristics
- ✨ No emojis or fancy symbols - Just clean, professional text
- 🕐 Timestamps only at request start - Not cluttering every line
- 📏 Proper indentation - 2 spaces for SQL queries and parameters
- 🎯 Clear request flow - Started → Processing → Queries → Completed
Example Output
Started GET "/api/users/123" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by UsersController#show as JSON
SELECT * FROM users WHERE id = $1 (1.5ms) ['123']
Completed 200 OK in 15msAdding Controller Metadata
For even cleaner logs that show controller actions, add metadata to your routes:
app.get('/users/:id', (req, res) => {
// Add controller metadata
req.route.controller = 'UsersController';
req.route.action = 'show';
// Your route logic
res.json({ id: req.params.id, name: 'John Doe' });
});This produces:
Processing by UsersController#show as JSONInstead of:
Processing by /users/:id as JSONSQL Query Format
Queries are logged with proper indentation and timing:
SELECT * FROM users WHERE id = $1 (1.5ms) ['123']
INSERT INTO orders (user_id, total) VALUES ($1, $2) (2.3ms) [123, 99.99]Format: QUERY (duration) [params]
With Custom Configuration
import { createLogger, requestLogger } from 'express-enhanced-logger';
const logger = createLogger({
level: 'debug',
enableFileLogging: true,
logsDirectory: 'my-logs',
slowRequestThreshold: 500, // Log requests slower than 500ms
slowQueryThreshold: 1000, // Log slow Prisma queries
enableColors: true,
maxArrayLength: 10, // Truncate arrays longer than 10 items
});
// Use request logger middleware
app.use(requestLogger());⚙️ Configuration Options
LoggerConfig Interface
interface LoggerConfig {
/** Log level (default: 'info') */
level?: 'error' | 'warn' | 'info' | 'debug' | 'query';
/** Enable file logging (default: true) */
enableFileLogging?: boolean;
/** Directory for log files (default: 'logs') */
logsDirectory?: string;
/** Maximum file size before rotation (default: '20m') */
maxFileSize?: string;
/** Number of days to keep log files (default: '7d') */
maxFiles?: string;
/** Enable gzip compression of rotated logs (default: true) */
zippedArchive?: boolean;
/** Slow request threshold in milliseconds (default: 1000) */
slowRequestThreshold?: number;
/** Slow query threshold in milliseconds for Prisma queries (default: 1000) */
slowQueryThreshold?: number;
/** Memory warning threshold in bytes (default: 100MB) */
memoryWarningThreshold?: number;
/** Maximum array length to show in logs before truncating (default: 5) */
maxArrayLength?: number;
/** Maximum string length to show in logs before truncating (default: 100) */
maxStringLength?: number;
/** Maximum object keys to show in logs before truncating (default: 20) */
maxObjectKeys?: number;
/** Enable colored console output (default: true in development, false in production) */
enableColors?: boolean;
/** Enable simple logging mode - shows only the message without level or formatting (default: false) */
simpleLogging?: boolean;
/** Enable Rails-style timing breakdown in request logs (default: true) */
enableTimingBreakdown?: boolean;
/** Custom query formatter function for SQL queries */
customQueryFormatter?: (query: string, params: string) => string;
/** Function to extract user information from request */
getUserFromRequest?: (
req: Request
) => { email?: string; id?: string | number; [key: string]: unknown } | undefined;
/** Function to extract request ID from request */
getRequestId?: (req: Request) => string | undefined;
/** Custom log format function (replaces default formatting) */
customLogFormat?: (info: WinstonLogInfo) => string;
/** Additional metadata to include in request logs */
additionalMetadata?: (req: Request, res: Response) => Record<string, unknown>;
}📝 Usage Examples
Basic Logging
import { createLogger } from 'express-enhanced-logger';
const logger = createLogger();
// String messages
logger.info('Application started');
logger.warn('This is a warning');
logger.error('An error occurred');
logger.debug('Debug information');
// With metadata
logger.info('User login', { userId: 123, ip: '192.168.1.1' });
logger.error('Database error', { error: 'Connection timeout', query: 'SELECT * FROM users' });
// Object messages
logger.info({ event: 'user_login', userId: 456, success: true });Using Convenience Functions
import { info, warn, error, debug } from 'express-enhanced-logger';
// Use exported functions directly (uses default logger)
info('Server starting...');
warn('Low memory warning');
error('Failed to connect to database');
debug('Request payload', { body: req.body });Simple Logging Mode
For even cleaner output without any formatting (just raw messages):
import { createLogger } from 'express-enhanced-logger';
// Enable simple logging mode - shows only the raw message
const logger = createLogger({ simpleLogging: true });
logger.info('Just the message'); // Output: Just the message
logger.warn('A warning message'); // Output: A warning message
logger.error({ code: 500, msg: 'Error' }); // Output: { code: 500, msg: 'Error' }
// Compare with normal Rails-style logging:
const normalLogger = createLogger({ simpleLogging: false });
normalLogger.info('Started GET "/" for 127.0.0.1 at 11/21/2025, 10:30:45 CST');
// Output: Started GET "/" for 127.0.0.1 at 11/21/2025, 10:30:45 CSTNote: Simple logging mode disables Rails-style request formatting and SQL query formatting.
Request Logging Middleware
import express from 'express';
import { createLogger, requestLogger } from 'express-enhanced-logger';
const app = express();
// Basic request logging (using default logger)
app.use(requestLogger());
// With custom configuration
const logger = createLogger({
slowRequestThreshold: 500,
getUserFromRequest: (req) => req.currentUser,
additionalMetadata: (req, res) => ({
tenantId: req.headers['x-tenant-id'],
apiVersion: req.headers['api-version'],
}),
});
// Use the request logger
app.use(logger.requestLogger());Prisma Integration (SQL Query Logging)
The logger includes smart SQL formatting that efficiently truncates large queries, particularly IN clauses with many parameters.
⚠️ IMPORTANT: Prisma integration is incompatible with simpleLogging: true. Make sure to set simpleLogging: false (or omit it, as false is the default) when using logger.query().
🎯 Caller Location Tracking (NEW!)
Track exactly where in your code each Prisma query originates with Rails-style ↳ indicators:
import { PrismaClient } from '@prisma/client';
import { createPrismaExtension, setupPrismaLogging, createLogger } from 'express-enhanced-logger';
const prisma = new PrismaClient({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'event', level: 'error' },
{ emit: 'event', level: 'info' },
{ emit: 'event', level: 'warn' },
],
});
// Apply caller location extension
const extendedPrisma = prisma.$extends(createPrismaExtension());
// Setup logging
const logger = createLogger({ level: 'query' });
setupPrismaLogging(prisma);
export default extendedPrisma;Output with caller location:
User Load (12.3ms) SELECT "User"."id", "User"."email" FROM "User" WHERE "User"."id" = $1 ["123"]
↳ src/controllers/users.ts:42The caller location feature uses AsyncLocalStorage to track where queries originate from, helping you debug and optimize database calls.
Plug-and-Play Setup (Recommended)
The easiest way to integrate Prisma logging is using the setupPrismaLogging() function:
import { PrismaClient } from '@prisma/client';
import { createLogger, setupPrismaLogging } from 'express-enhanced-logger';
// Create logger with Prisma integration
const logger = createLogger({
level: 'query', // Enable query-level logging
slowQueryThreshold: 1000, // Log queries slower than 1 second as warnings
});
// Create Prisma client with event logging
const prismaClient = new PrismaClient({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'event', level: 'info' },
{ emit: 'event', level: 'warn' },
{ emit: 'event', level: 'error' }
]
});
// Setup Prisma logging - automatically configures all event handlers
logger.setupPrismaLogging(prismaClient);
// That's it! All Prisma events are now logged through the enhanced logger
export default prismaClient;The setupPrismaLogging() function automatically:
- Enables Prisma integration
- Sets up event handlers for
query,info,warn, anderrorevents - Formats and logs queries with smart truncation
- Highlights slow queries based on
slowQueryThreshold - Only activates in non-test environments
Using with Custom Logger Instance
If you're using a custom logger instance:
import { EnhancedLogger } from 'express-enhanced-logger';
import { PrismaClient } from '@prisma/client';
const logger = new EnhancedLogger({
level: 'query',
slowQueryThreshold: 500, // Custom threshold
});
const prismaClient = new PrismaClient({
log: [
{ emit: 'event', level: 'query' },
{ emit: 'event', level: 'info' },
{ emit: 'event', level: 'warn' },
{ emit: 'event', level: 'error' }
]
});
// Setup logging on the custom instance
logger.setupPrismaLogging(prismaClient);Manual Setup (Advanced)
If you need more control, you can manually setup the event handlers:
import { createLogger } from 'express-enhanced-logger';
import { PrismaClient } from '@prisma/client';
const logger = createLogger({
level: 'query', // Enable query-level logging
simpleLogging: false,
});
const prisma = new PrismaClient({
log: [
{
emit: 'event',
level: 'query',
},
],
});
// Subscribe to Prisma query events
prisma.$on('query', (e) => {
// Use logger.query() with proper data structure
logger.query({
type: e.query.split(' ')[0].toUpperCase(), // Extract query type (SELECT, INSERT, etc.)
query: e.query,
params: JSON.stringify(e.params),
duration: `${e.duration}ms`,
});
});Smart Query Truncation:
The logger intelligently truncates only queries with large parameter lists (10+ parameters in IN clauses):
// Short queries - displayed in full
SELECT * FROM users WHERE id IN (1, 2, 3)
// Large IN clauses - smartly truncated
SELECT * FROM users WHERE id IN (1,2,3,...12 more...,18,19,20)
// Works even without parameter values - truncates placeholders
SELECT * FROM lots WHERE id IN (@P1,@P2,@P3,...530 more...,@P534,@P535,@P536)Common Issues:
- "undefined" in logs: You have
simpleLogging: trueenabled. Set it tofalse. - Queries not truncated: Make sure you're calling
logger.query()(notlogger.info()) with the correct data structure{type, query, params, duration}. - Configuration warning on startup: The logger will warn you if you have incompatible settings enabled.
Custom User Extraction
import jwt from 'jsonwebtoken';
// Extract user from JWT token
// Configure logger with custom user extraction
const logger = createLogger({
getUserFromRequest: (req) => {
if (req.headers.authorization) {
try {
const token = req.headers.authorization.replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET) as any;
return { email: decoded.email, id: decoded.userId };
} catch {
return undefined;
}
}
return undefined;
},
getRequestId: (req) => {
// Extract from header or generate
return req.headers['x-request-id'] as string || crypto.randomUUID();
},
});
app.use(requestLogger());Multiple Logger Instances
import { EnhancedLogger } from 'express-enhanced-logger';
// Create specific loggers for different modules
const authLogger = new EnhancedLogger({
level: 'debug',
logsDirectory: 'logs/auth',
additionalMetadata: (req, res) => ({ module: 'auth' }),
});
const apiLogger = new EnhancedLogger({
level: 'info',
logsDirectory: 'logs/api',
slowRequestThreshold: 2000,
});
// Use different loggers for different routes
app.use('/auth', authLogger.requestLogger);
app.use('/api', apiLogger.requestLogger);Custom Log Format
const logger = createLogger({
customLogFormat: (info) => {
const { timestamp, level, message } = info;
return `[${timestamp}] ${level.toUpperCase()}: ${JSON.stringify(message)}`;
},
});Performance Monitoring
The logger automatically tracks request performance and memory usage:
const logger = createLogger({
slowRequestThreshold: 1000, // Warn about requests slower than 1 second
memoryWarningThreshold: 50 * 1024 * 1024, // Warn when heap exceeds 50MB
});
app.use(requestLogger());What's tracked automatically:
- ⏱️ Request Duration - Logs slow requests above threshold with warning emoji
- 💾 Memory Usage - Shows heap memory delta per request
- 📊 HTTP Status Codes - Color-coded by status ranges (2xx green, 4xx yellow, 5xx red)
- 📦 Response Sizes - Tracks Content-Length headers
- 👤 User Context - Includes user email/ID when available
- 🔗 Request IDs - Tracks request correlation IDs
📊 Sample Output
The logger produces clean, Rails-style output with no emojis or fancy formatting - just clear, readable logs:
HTTP Request Log
Started GET "/api/users/123" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by UsersController#show as JSON
Completed 200 OK in 245msRequest with Parameters
Started POST "/api/reports" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by ReportsController#create as JSON
Parameters: { type: 'monthly', year: 2025 }
Completed 200 OK in 1850msSlow Request Warning
Started POST "/api/reports" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by ReportsController#create as JSON
Completed 200 OK in 1850ms (Slow request)SQL Query Log (with Prisma)
Started GET "/api/users/123" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by UsersController#show as JSON
SELECT * FROM users WHERE id = $1 AND status = $2 (45ms) ['123', 'ACTIVE']
Completed 200 OK in 245msLarge IN Clause (Smart Truncation)
SELECT * FROM orders WHERE id IN (1,2,3,...47 more...,53,54,55) (120ms)Error Log
Started GET "/api/users/999" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by UsersController#show as JSON
User not found
Completed 500 Error in 15msComplete Request Flow Example
Started POST "/api/users" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by UsersController#create as JSON
Parameters: { name: 'John Doe', email: '[email protected]' }
INSERT INTO "users" ("name", "email", "created_at") VALUES ($1, $2, $3) RETURNING "id" (2.5ms) ['John Doe', '[email protected]', '2025-11-21T16:30:45.123Z']
Completed 201 Created in 25msRails-Style Timing Breakdown
When enableTimingBreakdown: true (default), the logger automatically tracks and displays timing breakdown:
Started GET "/api/reports" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by ReportsController#index as JSON
SELECT * FROM reports WHERE user_id = $1 (45ms) ['123']
SELECT * FROM categories (15ms)
Completed 200 OK in 204ms (Logic: 144.0ms | DB: 60.0ms)Breaking down the timing:
- Total: 204ms (end-to-end request time)
- DB: 60.0ms (cumulative time spent in database queries: 45ms + 15ms)
- Logic: 144.0ms (application logic time: Total - DB = 204ms - 60ms)
This helps identify whether performance issues are due to slow queries or application logic.
Using the measure() Helper
Track custom operations within your request handlers:
import { measure } from 'express-enhanced-logger';
app.get('/api/report.pdf', async (req, res) => {
const logger = getLogger();
// Measure PDF generation
const pdf = await measure('Rendering PDF', async () => {
return await generatePDF(reportData);
}, logger);
res.send(pdf);
});Output:
Started GET "/api/report.pdf" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by /api/report.pdf as HTML
Rendering PDF (Duration: 145.2ms)
Completed 200 OK in 152msThe measure() helper works both inside and outside request contexts, and can track:
- PDF/report generation
- External API calls
- Heavy computations
- File I/O operations
- Any async or sync operation
Complete Request Flow Example
import { createLogger, measure } from 'express-enhanced-logger';
const logger = createLogger({ enableTimingBreakdown: true });
app.get('/api/dashboard', async (req, res) => {
// DB queries are automatically tracked via Prisma integration
const users = await prisma.user.findMany(); // 25ms
const posts = await prisma.post.findMany(); // 30ms
// Custom operations can be measured
const stats = await measure('Calculating statistics', async () => {
return calculateDashboardStats(users, posts);
}, logger);
// External API call
const weather = await measure('Fetching weather data', async () => {
return await fetch('https://api.weather.com/...');
}, logger);
res.json({ users, posts, stats, weather });
});Output:
Started GET "/api/dashboard" for 127.0.0.1 at 11/21/2025, 10:30:45 CST
Processing by /api/dashboard as JSON
SELECT * FROM users (25ms)
SELECT * FROM posts (30ms)
Calculating statistics (Duration: 15.3ms)
Fetching weather data (Duration: 234.7ms)
Completed 200 OK in 320ms (Logic: 265.0ms | DB: 55.0ms)Timing breakdown:
- Total: 320ms
- DB: 55.0ms (Prisma queries: 25ms + 30ms)
- Logic: 265.0ms (includes both measure() operations and other processing)
TypeScript Types
// Query log data for Prisma integration
interface QueryLogData {
type: string; // Query type (e.g., 'query', 'SELECT', 'INSERT')
query: string; // SQL query string
params: string; // JSON stringified parameters
duration: string; // Query duration (e.g., '50' or '50ms')
}
// Request log data (internal)
interface RequestLogData {
timestamp: string;
requestId?: string;
method: string;
url: string;
status: number;
statusText: string;
duration: number;
memoryUsed: string;
userEmail?: string;
body?: unknown;
// ... additional fields
}Express Type Extensions
The package extends Express types for better TypeScript support:
declare module 'express' {
interface Request {
requestId?: string;
currentUser?: {
email?: string;
id?: string | number;
[key: string]: unknown;
};
}
}You can use these in your Express routes:
app.get('/api/profile', (req, res) => {
// TypeScript knows about these properties
console.log(req.requestId);
console.log(req.currentUser);
});📁 File Logging
Logs are automatically rotated and organized by date:
logs/
├── combined-2025-11-17.log # All logs for today
├── error-2025-11-17.log # Error logs only for today
├── combined-2025-11-16.log.gz # Compressed logs from yesterday
└── error-2025-11-16.log.gz # Compressed error logs from yesterdayConfiguration:
const logger = createLogger({
enableFileLogging: true,
logsDirectory: 'logs',
maxFileSize: '20m', // Rotate when file reaches 20MB
maxFiles: '7d', // Keep logs for 7 days
zippedArchive: true, // Compress old logs
});Log Format:
File logs use JSON format for easy parsing and analysis:
{
"timestamp": "2025-11-17T10:30:45.123Z",
"level": "info",
"message": "GET /api/users 200 OK",
"requestId": "req_abc123",
"method": "GET",
"url": "/api/users",
"status": 200,
"duration": 245,
"userEmail": "[email protected]"
}🔌 API Reference
Exported Functions
createLogger(config?: LoggerConfig): EnhancedLogger
Creates a new logger instance and sets it as the default logger.
const logger = createLogger({ level: 'debug' });getLogger(): EnhancedLogger
Gets the default logger instance (creates one with default config if none exists).
const logger = getLogger();
logger.info('Using default logger');requestLogger(config?: LoggerConfig): RequestHandler
Returns Express middleware for request logging.
app.use(requestLogger());Convenience Logging Functions
Use the default logger directly without creating an instance:
import { error, warn, info, debug, query } from 'express-enhanced-logger';
info('Server started');
warn('Low disk space');
error('Database connection failed', { code: 'ECONNREFUSED' });
debug('Processing request', { requestId: '123' });
query({ type: 'query', query: 'SELECT ...', params: '[]', duration: '50' });measure<T>(name, fn, logger?): Promise<T>
Measure the execution time of a synchronous or async function. Returns the result of the function.
import { measure, createLogger } from 'express-enhanced-logger';
const logger = createLogger();
// Measure async operations
const users = await measure('Fetching users from database', async () => {
return await prisma.user.findMany();
}, logger);
// Measure sync operations
const result = measure('Heavy calculation', () => {
return performComplexCalculation();
}, logger);
// Without logger (no logging, just execution)
const data = await measure('Silent operation', async () => {
return await fetchData();
});Parameters:
name(string) - Name of the operation being measuredfn(() => T | Promise) - Function to execute and measurelogger(optional) - Logger instance to log the operation. If omitted, the operation runs silently.
Returns: Promise - The result of executing the function
EnhancedLogger Class
Methods
error(message, meta?)- Log error messagewarn(message, meta?)- Log warning messageinfo(message, meta?)- Log info messagedebug(message, meta?)- Log debug messagequery(data: QueryLogData, meta?)- Log SQL query with optional metadatagetWinstonLogger(): winston.Logger- Get underlying Winston instanceupdateConfig(newConfig: Partial<LoggerConfig>)- Update configuration at runtime
Properties
requestLogger- Express middleware function for request logging
Example
import { EnhancedLogger } from 'express-enhanced-logger';
const logger = new EnhancedLogger({
level: 'info',
enableFileLogging: true,
});
logger.info('Application started');
logger.error('Something went wrong', { errorCode: 500 });
// Update config at runtime
logger.updateConfig({ level: 'debug' });
// Access Winston logger directly
const winston = logger.getWinstonLogger();
winston.log('custom', 'Custom log level');🔄 Migration from Winston
If you're migrating from Winston, it's straightforward:
// Before (Winston)
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
]
});
// After (Enhanced Logger)
import { createLogger } from 'express-enhanced-logger';
const logger = createLogger({
level: 'info',
enableFileLogging: true,
logsDirectory: 'logs'
});
// The logging methods remain the same
logger.info('message');
logger.error('error message', { context: 'additional data' });
// Plus you get additional features
app.use(logger.requestLogger); // Built-in request logging🎯 Best Practices
1. Environment-Based Configuration
const logger = createLogger({
level: process.env.LOG_LEVEL || 'info',
enableFileLogging: process.env.NODE_ENV === 'production',
enableColors: process.env.NODE_ENV !== 'production',
slowRequestThreshold: Number(process.env.SLOW_REQUEST_THRESHOLD) || 1000,
});2. Structured Logging
Always include context in your logs:
// Good ✅
logger.info('User login successful', {
userId: user.id,
ip: req.ip,
timestamp: new Date().toISOString(),
});
// Less useful ❌
logger.info('Login successful');3. Error Handling
Log errors with full context:
try {
await processPayment(order);
} catch (error) {
logger.error('Payment processing failed', {
orderId: order.id,
amount: order.total,
error: error.message,
stack: error.stack,
});
throw error;
}4. Request Correlation
Use request IDs to trace requests through your system:
// Configure logger with custom request ID extraction
const logger = createLogger({
getRequestId: (req) => {
// Use existing ID or generate new one
return req.headers['x-request-id'] as string || crypto.randomUUID();
},
});
app.use(requestLogger());
// Then in your route handlers
app.get('/api/users/:id', async (req, res) => {
logger.info('Fetching user', {
requestId: req.requestId,
userId: req.params.id
});
});5. Sensitive Data
Never log sensitive information:
// Bad ❌
logger.info('User login', {
email: user.email,
password: user.password // Never log passwords!
});
// Good ✅
logger.info('User login', {
userId: user.id,
email: user.email,
// password is omitted
});🤝 Contributing
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
Quick Start for Contributors:
# Clone and install
git clone https://github.com/Deetss/express-enhanced-logger.git
cd express-enhanced-logger
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Lint and format
npm run lint
npm run formatCurrent Test Coverage: 89% (229 passing tests)
🎮 Demo Application
Want to see all features in action? Check out the comprehensive demo application at examples/demo-app:
# Install dependencies
npm install
# Generate Prisma client and setup database
cd examples/demo-app
npx prisma generate
npx prisma migrate dev --name init
# Run the demo
cd ../..
npm run demoThe demo showcases:
- ✨ All logging features (info, warn, error, debug)
- 🚀 Request/response logging with performance tracking
- 🗄️ Prisma integration with AsyncLocalStorage duration tracking
- ⏱️ Custom timing with the
measure()helper - 🎯 Controller helpers for Rails-style organization
- 🔍 Error handling and context logging
- And much more!
Visit http://localhost:3000 after starting the demo for a full route listing.
📖 Read the demo documentation for detailed setup instructions and usage examples.
📄 License
MIT License - see LICENSE file for details.
🙏 Acknowledgments
Built with:
- Winston - Logging framework
- Chalk - Terminal string styling
- winston-daily-rotate-file - Log rotation
📞 Support
🔗 Related Projects
- express-winston - Alternative Express logging middleware
- morgan - HTTP request logger middleware
- pino - Fast JSON logger
Made with ❤️ by Dylan Dietz
