Skip to content

Repository files navigation

express-enhanced-logger

npm version Test Coverage TypeScript License: MIT

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-logger

Requirements:

  • 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 15ms

Adding 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 JSON

Instead of:

Processing by /users/:id as JSON

SQL 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 CST

Note: 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:42

The 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, and error events
  • 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: true enabled. Set it to false.
  • Queries not truncated: Make sure you're calling logger.query() (not logger.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 245ms

Request 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 1850ms

Slow 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 245ms

Large 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 15ms

Complete 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: 'john@example.com' }
  INSERT INTO "users" ("name", "email", "created_at") VALUES ($1, $2, $3) RETURNING "id" (2.5ms)  ['John Doe', 'john@example.com', '2025-11-21T16:30:45.123Z']
Completed 201 Created in 25ms

Rails-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 152ms

The 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 yesterday

Configuration:

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": "john@example.com"
}

๐Ÿ”Œ 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 measured
  • fn (() => T | Promise) - Function to execute and measure
  • logger (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 message
  • warn(message, meta?) - Log warning message
  • info(message, meta?) - Log info message
  • debug(message, meta?) - Log debug message
  • query(data: QueryLogData, meta?) - Log SQL query with optional metadata
  • getWinstonLogger(): winston.Logger - Get underlying Winston instance
  • updateConfig(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 format

Current 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 demo

The 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:

๐Ÿ“ž Support

๐Ÿ”— Related Projects


Made with โค๏ธ by Dylan Dietz

About

An enhanced Express.js logger with performance monitoring, SQL query formatting, and highly customizable features. Built on Winston with TypeScript support.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages