+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 210 of 355

📘 Logging: Winston and Morgan

Master logging: winston and morgan in TypeScript with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻

What you'll learn

  • Understand the concept fundamentals 🎯
  • Apply the concept in real projects 🏗️
  • Debug common issues 🐛
  • Write type-safe code ✨

🎯 Introduction

Welcome to the world of professional logging in TypeScript! 🎉 In this tutorial, we’ll explore Winston and Morgan - two powerful logging libraries that will transform how you track, debug, and monitor your applications.

You’ll discover how proper logging can save you hours of debugging 🔍, help you understand user behavior 📊, and make your applications production-ready 🚀. Whether you’re building REST APIs 🌐, web servers 🖥️, or full-stack applications 📱, mastering logging is essential for maintainable code.

By the end of this tutorial, you’ll be creating beautiful, structured logs that tell the story of your application! Let’s dive in! 🏊‍♂️

📚 Understanding Logging in Node.js

🤔 What is Logging?

Logging is like keeping a detailed diary 📖 of everything your application does. Think of it as your app’s journal that records important events, errors, and activities - helping you understand what happened when things go wrong (or right!) 📝.

In TypeScript terms, logging involves capturing and storing information about your application’s execution, user interactions, and system events ✨. This means you can:

  • ✨ Track user behavior and application flow
  • 🚀 Debug issues faster with detailed error information
  • 🛡️ Monitor system health and performance
  • 📊 Analyze patterns and optimize your application

💡 Why Use Winston and Morgan?

Here’s why developers love these logging tools:

  1. Winston - Application Logging 📝: Structured, configurable logging for your business logic
  2. Morgan - HTTP Request Logging 🌐: Automatic logging of web requests and responses
  3. Type Safety 🔒: Full TypeScript support with proper type definitions
  4. Flexibility 🎨: Multiple output formats, levels, and destinations
  5. Production Ready 🏭: Battle-tested in enterprise applications

Real-world example: Imagine building a shopping cart API 🛒. With Winston and Morgan, you can track every user action, API call, and error - making debugging feel like detective work! 🕵️‍♂️

🔧 Basic Syntax and Usage

📝 Setting Up Winston

Let’s start with a friendly Winston example:

// 👋 Hello, Winston logging!
import winston from 'winston';

// 🎨 Creating a simple logger
const logger = winston.createLogger({
  level: 'info',           // 📊 Minimum log level
  format: winston.format.json(), // 📋 JSON format
  transports: [
    new winston.transports.File({ filename: 'app.log' }), // 📁 Log to file
    new winston.transports.Console()                       // 🖥️ Log to console
  ]
});

// 🎯 Using the logger
logger.info('Application started successfully! 🚀');
logger.error('Something went wrong! 😱', { error: 'Database connection failed' });

💡 Explanation: Winston creates a flexible logger that can output to multiple destinations (console, files, databases) with different formats and filtering levels!

🌐 Setting Up Morgan for HTTP Logging

Here’s how to track HTTP requests:

// 🛠️ Express server with Morgan
import express from 'express';
import morgan from 'morgan';
import winston from 'winston';

const app = express();

// 🎨 Custom Morgan format with Winston
const logger = winston.createLogger({
  transports: [new winston.transports.Console()]
});

// 🔄 Morgan stream to Winston
const stream = {
  write: (message: string) => {
    logger.info(message.trim()); // 📝 Send HTTP logs to Winston
  }
};

// 🌟 Use Morgan middleware
app.use(morgan('combined', { stream })); // 📊 Detailed HTTP logging

// 🎯 Simple API endpoint
app.get('/api/users', (req, res) => {
  logger.info('Fetching users from database 👥');
  res.json({ users: ['Alice', 'Bob', 'Charlie'] });
});

💡 Practical Examples

🛒 Example 1: E-commerce API with Comprehensive Logging

Let’s build a real e-commerce system with proper logging:

// 🏪 E-commerce API with Winston & Morgan
import express from 'express';
import winston from 'winston';
import morgan from 'morgan';

// 🎨 Configure Winston logger
const logger = winston.createLogger({
  level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',
  format: winston.format.combine(
    winston.format.timestamp(), // ⏰ Add timestamps
    winston.format.errors({ stack: true }), // 📚 Include stack traces
    winston.format.colorize(), // 🌈 Colorful console output
    winston.format.printf(({ timestamp, level, message, ...meta }) => {
      return `${timestamp} [${level}]: ${message} ${
        Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''
      }`;
    })
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ 
      filename: 'logs/error.log', 
      level: 'error' 
    }), // 🚨 Error-only file
    new winston.transports.File({ 
      filename: 'logs/combined.log' 
    }) // 📋 All logs file
  ]
});

// 🛍️ Product interface
interface Product {
  id: string;
  name: string;
  price: number;
  emoji: string;
  stock: number;
}

// 📦 Mock product database
const products: Product[] = [
  { id: '1', name: 'TypeScript Book', price: 29.99, emoji: '📘', stock: 50 },
  { id: '2', name: 'Coffee Mug', price: 12.99, emoji: '☕', stock: 100 },
  { id: '3', name: 'Laptop Sticker', price: 3.99, emoji: '💻', stock: 200 }
];

const app = express();
app.use(express.json());

// 🔍 Custom Morgan format for detailed request logging
app.use(morgan('combined', {
  stream: {
    write: (message: string) => {
      logger.http(message.trim()); // 🌐 HTTP-level logging
    }
  }
}));

// 🎯 Get all products
app.get('/api/products', (req, res) => {
  const startTime = Date.now();
  
  logger.info('📦 Fetching products list', {
    endpoint: '/api/products',
    method: 'GET',
    userId: req.headers['user-id'] || 'anonymous'
  });
  
  try {
    const responseTime = Date.now() - startTime;
    
    logger.info('✅ Products fetched successfully', {
      productCount: products.length,
      responseTime: `${responseTime}ms`,
      performanceMetric: responseTime < 100 ? 'fast' : 'slow'
    });
    
    res.json({
      success: true,
      data: products,
      meta: {
        count: products.length,
        responseTime: `${responseTime}ms`
      }
    });
  } catch (error) {
    logger.error('❌ Failed to fetch products', {
      error: error instanceof Error ? error.message : 'Unknown error',
      stack: error instanceof Error ? error.stack : undefined,
      endpoint: '/api/products'
    });
    
    res.status(500).json({
      success: false,
      error: 'Internal server error'
    });
  }
});

// 🛒 Purchase endpoint with detailed logging
app.post('/api/purchase', (req, res) => {
  const { productId, quantity, userId } = req.body;
  
  logger.info('🛒 Purchase attempt started', {
    productId,
    quantity,
    userId,
    timestamp: new Date().toISOString()
  });
  
  // 🔍 Find product
  const product = products.find(p => p.id === productId);
  
  if (!product) {
    logger.warn('⚠️ Purchase failed - Product not found', {
      productId,
      userId,
      reason: 'invalid_product_id'
    });
    
    return res.status(404).json({
      success: false,
      error: 'Product not found'
    });
  }
  
  // 📊 Check stock
  if (product.stock < quantity) {
    logger.warn('⚠️ Purchase failed - Insufficient stock', {
      productId: product.id,
      productName: product.name,
      requestedQuantity: quantity,
      availableStock: product.stock,
      userId
    });
    
    return res.status(400).json({
      success: false,
      error: 'Insufficient stock',
      available: product.stock
    });
  }
  
  // ✅ Process purchase
  product.stock -= quantity;
  const totalPrice = product.price * quantity;
  
  logger.info('🎉 Purchase completed successfully!', {
    productId: product.id,
    productName: product.name,
    productEmoji: product.emoji,
    quantity,
    totalPrice,
    remainingStock: product.stock,
    userId,
    revenue: totalPrice
  });
  
  res.json({
    success: true,
    message: `🎉 Purchase completed! ${product.emoji}`,
    data: {
      product: product.name,
      quantity,
      totalPrice,
      remainingStock: product.stock
    }
  });
});

// 🚀 Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  logger.info('🚀 E-commerce API server started!', {
    port: PORT,
    environment: process.env.NODE_ENV || 'development',
    timestamp: new Date().toISOString()
  });
});

🎯 Try it yourself: Add inventory tracking and email notification logging when stock runs low!

🎮 Example 2: Game Server with Advanced Logging

Let’s create a multiplayer game server with comprehensive logging:

// 🎮 Game server with structured logging
import winston from 'winston';
import express from 'express';
import { WebSocketServer } from 'ws';

// 🏆 Game interfaces
interface Player {
  id: string;
  name: string;
  score: number;
  level: number;
  joinedAt: Date;
}

interface GameEvent {
  type: 'join' | 'leave' | 'score' | 'level_up' | 'achievement';
  playerId: string;
  data: any;
  timestamp: Date;
}

// 📊 Advanced Winston configuration for game analytics
const gameLogger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.metadata({ fillExcept: ['message', 'level', 'timestamp'] }),
    winston.format.json()
  ),
  transports: [
    // 📈 Player analytics log
    new winston.transports.File({
      filename: 'logs/game-analytics.log',
      level: 'info',
      format: winston.format.combine(
        winston.format.label({ label: 'ANALYTICS' }),
        winston.format.json()
      )
    }),
    
    // 🚨 Game errors log
    new winston.transports.File({
      filename: 'logs/game-errors.log',
      level: 'error'
    }),
    
    // 🖥️ Console for development
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    })
  ]
});

class GameServer {
  private players: Map<string, Player> = new Map();
  private gameEvents: GameEvent[] = [];
  
  // 🎯 Player joins game
  addPlayer(player: Player): void {
    this.players.set(player.id, player);
    
    const event: GameEvent = {
      type: 'join',
      playerId: player.id,
      data: { playerName: player.name },
      timestamp: new Date()
    };
    
    this.logGameEvent(event);
    
    gameLogger.info('🎮 New player joined the game!', {
      playerId: player.id,
      playerName: player.name,
      totalPlayers: this.players.size,
      sessionMetrics: {
        averageScore: this.getAverageScore(),
        topPlayer: this.getTopPlayer()?.name || 'none'
      }
    });
  }
  
  // 🎯 Award points to player
  awardPoints(playerId: string, points: number): void {
    const player = this.players.get(playerId);
    if (!player) {
      gameLogger.warn('⚠️ Attempted to award points to non-existent player', {
        playerId,
        points,
        reason: 'player_not_found'
      });
      return;
    }
    
    const oldScore = player.score;
    const oldLevel = player.level;
    player.score += points;
    
    // 📈 Check for level up
    const newLevel = Math.floor(player.score / 100) + 1;
    if (newLevel > oldLevel) {
      player.level = newLevel;
      this.logLevelUp(player, oldLevel, newLevel);
    }
    
    const scoreEvent: GameEvent = {
      type: 'score',
      playerId,
      data: { 
        oldScore, 
        newScore: player.score, 
        pointsAwarded: points,
        achievement: points >= 50 ? 'big_score' : 'regular_score'
      },
      timestamp: new Date()
    };
    
    this.logGameEvent(scoreEvent);
    
    gameLogger.info('✨ Points awarded to player', {
      playerId,
      playerName: player.name,
      pointsAwarded: points,
      oldScore,
      newScore: player.score,
      playerRank: this.getPlayerRank(playerId)
    });
  }
  
  // 🏆 Handle level up
  private logLevelUp(player: Player, oldLevel: number, newLevel: number): void {
    const levelUpEvent: GameEvent = {
      type: 'level_up',
      playerId: player.id,
      data: { oldLevel, newLevel },
      timestamp: new Date()
    };
    
    this.logGameEvent(levelUpEvent);
    
    gameLogger.info('🎉 Player leveled up!', {
      playerId: player.id,
      playerName: player.name,
      oldLevel,
      newLevel,
      totalScore: player.score,
      sessionDuration: Date.now() - player.joinedAt.getTime(),
      milestone: newLevel % 5 === 0 ? 'major_milestone' : 'regular_level'
    });
  }
  
  // 📊 Game analytics methods
  private getAverageScore(): number {
    if (this.players.size === 0) return 0;
    const totalScore = Array.from(this.players.values())
      .reduce((sum, player) => sum + player.score, 0);
    return Math.round(totalScore / this.players.size);
  }
  
  private getTopPlayer(): Player | undefined {
    return Array.from(this.players.values())
      .sort((a, b) => b.score - a.score)[0];
  }
  
  private getPlayerRank(playerId: string): number {
    const sortedPlayers = Array.from(this.players.values())
      .sort((a, b) => b.score - a.score);
    return sortedPlayers.findIndex(p => p.id === playerId) + 1;
  }
  
  // 📝 Store game events for analytics
  private logGameEvent(event: GameEvent): void {
    this.gameEvents.push(event);
    
    // 🧹 Keep only last 1000 events in memory
    if (this.gameEvents.length > 1000) {
      this.gameEvents = this.gameEvents.slice(-1000);
    }
    
    gameLogger.debug('📋 Game event recorded', {
      eventType: event.type,
      playerId: event.playerId,
      eventData: event.data
    });
  }
  
  // 📊 Generate daily analytics report
  generateAnalyticsReport(): void {
    const report = {
      totalPlayers: this.players.size,
      averageScore: this.getAverageScore(),
      topPlayer: this.getTopPlayer(),
      eventCounts: this.getEventCounts(),
      sessionData: this.getSessionData()
    };
    
    gameLogger.info('📊 Daily analytics report generated', report);
  }
  
  private getEventCounts() {
    return this.gameEvents.reduce((counts, event) => {
      counts[event.type] = (counts[event.type] || 0) + 1;
      return counts;
    }, {} as Record<string, number>);
  }
  
  private getSessionData() {
    const now = new Date();
    return Array.from(this.players.values()).map(player => ({
      playerId: player.id,
      sessionDuration: now.getTime() - player.joinedAt.getTime(),
      score: player.score,
      level: player.level
    }));
  }
}

// 🎮 Initialize game server
const gameServer = new GameServer();

// 🚀 Start analytics reporting every hour
setInterval(() => {
  gameServer.generateAnalyticsReport();
}, 60 * 60 * 1000); // Every hour

// 🎯 Test the game server
gameServer.addPlayer({
  id: '1',
  name: 'GameMaster Alice',
  score: 0,
  level: 1,
  joinedAt: new Date()
});

gameServer.awardPoints('1', 75); // Should trigger achievement logging
gameServer.awardPoints('1', 50); // Should trigger level up!

🚀 Advanced Concepts

🧙‍♂️ Advanced Topic 1: Custom Log Formats and Transports

When you’re ready to level up, try creating custom logging solutions:

// 🎯 Advanced custom Winston configuration
import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';

// 🎨 Custom log format with emojis
const customFormat = winston.format.combine(
  winston.format.timestamp(),
  winston.format.errors({ stack: true }),
  winston.format.printf(({ timestamp, level, message, ...meta }) => {
    // 🎨 Add emojis based on log level
    const emoji = {
      error: '🚨',
      warn: '⚠️',
      info: '📝',
      debug: '🔍',
      http: '🌐'
    }[level] || '📋';
    
    return `${emoji} ${timestamp} [${level.toUpperCase()}]: ${message} ${
      Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''
    }`;
  })
);

// 🔄 Daily rotating files for production
const dailyRotateTransport = new DailyRotateFile({
  filename: 'logs/application-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  maxSize: '20m',
  maxFiles: '14d' // Keep logs for 14 days
});

// 🧙‍♂️ Advanced logger with multiple transports
const advancedLogger = winston.createLogger({
  format: customFormat,
  transports: [
    new winston.transports.Console({
      level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug'
    }),
    dailyRotateTransport,
    new winston.transports.File({
      filename: 'logs/error.log',
      level: 'error',
      handleExceptions: true,
      handleRejections: true
    })
  ]
});

// 🎯 Custom transport for external services
class SlackTransport extends winston.Transport {
  constructor(opts: any) {
    super(opts);
  }
  
  log(info: any, callback: () => void) {
    setImmediate(() => {
      this.emit('logged', info);
    });
    
    // 🚨 Send critical errors to Slack
    if (info.level === 'error') {
      this.sendToSlack(info);
    }
    
    callback();
  }
  
  private async sendToSlack(logInfo: any) {
    // 💬 Simulated Slack notification
    console.log(`🚨 SLACK ALERT: ${logInfo.message}`);
  }
}

// 📊 Add Slack notifications for errors
advancedLogger.add(new SlackTransport({}));

🏗️ Advanced Topic 2: Structured Logging with Correlation IDs

For enterprise applications, implement request correlation:

// 🔍 Request correlation and structured logging
import { v4 as uuidv4 } from 'uuid';
import { Request, Response, NextFunction } from 'express';

// 🎯 Correlation middleware
export const correlationMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const correlationId = req.headers['x-correlation-id'] as string || uuidv4();
  
  // 📝 Add correlation ID to request
  (req as any).correlationId = correlationId;
  res.setHeader('x-correlation-id', correlationId);
  
  // 🎨 Create child logger with correlation ID
  (req as any).logger = advancedLogger.child({
    correlationId,
    userAgent: req.headers['user-agent'],
    ip: req.ip
  });
  
  next();
};

// 🌐 Use in Express routes
app.use(correlationMiddleware);

app.get('/api/complex-operation', (req: any, res) => {
  const logger = req.logger;
  
  logger.info('🚀 Starting complex operation', {
    operation: 'data-processing',
    userId: req.headers['user-id']
  });
  
  // 📊 Simulate async operations with tracking
  setTimeout(() => {
    logger.info('✅ Complex operation completed', {
      duration: '2.3s',
      status: 'success'
    });
    
    res.json({ success: true, correlationId: req.correlationId });
  }, 100);
});

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: Over-logging Everything

// ❌ Wrong way - logging everything clutters your logs!
function processOrder(order: any) {
  logger.info('Starting processOrder function');
  logger.info('Order received', order);
  logger.info('Validating order...');
  logger.info('Order is valid');
  logger.info('Calculating total...');
  logger.info('Total calculated');
  logger.info('Saving to database...');
  logger.info('Order saved');
  logger.info('Ending processOrder function');
  return order;
}

// ✅ Correct way - log meaningful events!
function processOrder(order: any) {
  logger.info('🛒 Processing order', { 
    orderId: order.id, 
    customerId: order.customerId 
  });
  
  try {
    const total = calculateTotal(order);
    saveOrder(order);
    
    logger.info('✅ Order processed successfully', {
      orderId: order.id,
      total,
      processingTime: Date.now() - startTime
    });
    
    return order;
  } catch (error) {
    logger.error('❌ Order processing failed', {
      orderId: order.id,
      error: error.message
    });
    throw error;
  }
}

🤯 Pitfall 2: Logging Sensitive Information

// ❌ Dangerous - exposing sensitive data!
function loginUser(credentials: { email: string; password: string }) {
  logger.info('User login attempt', credentials); // 💥 Password exposed!
}

// ✅ Safe - sanitize sensitive data!
function loginUser(credentials: { email: string; password: string }) {
  logger.info('🔐 User login attempt', {
    email: credentials.email,
    passwordLength: credentials.password.length, // ✅ Safe metadata
    timestamp: new Date().toISOString()
  });
}

// 🛡️ Utility function for sanitizing logs
function sanitizeForLogging(obj: any): any {
  const sensitiveFields = ['password', 'token', 'secret', 'key', 'creditCard'];
  const sanitized = { ...obj };
  
  sensitiveFields.forEach(field => {
    if (sanitized[field]) {
      sanitized[field] = '[REDACTED]';
    }
  });
  
  return sanitized;
}

🛠️ Best Practices

  1. 🎯 Use Appropriate Log Levels: Info for business logic, debug for troubleshooting, error for failures
  2. 📝 Include Context: Always add relevant metadata like user IDs, correlation IDs, timestamps
  3. 🛡️ Sanitize Sensitive Data: Never log passwords, tokens, or personal information
  4. 🔄 Rotate Log Files: Use daily rotation to prevent disk space issues
  5. ⚡ Use Structured Logging: JSON format makes logs searchable and analyzable
  6. 🎨 Add Correlation IDs: Track requests across multiple services
  7. 📊 Monitor Log Volume: Too many logs can impact performance
  8. 🚨 Set Up Alerts: Critical errors should trigger immediate notifications

🧪 Hands-On Exercise

🎯 Challenge: Build a Complete Logging System

Create a type-safe logging system for a social media API:

📋 Requirements:

  • ✅ Winston logger with multiple transports (console, file, daily rotation)
  • 🌐 Morgan middleware for HTTP request logging
  • 🔍 Correlation ID middleware for request tracking
  • 📊 User activity logging (posts, likes, comments)
  • 🚨 Error logging with Slack notifications (simulated)
  • 📈 Daily analytics report generation
  • 🛡️ Sensitive data sanitization

🚀 Bonus Points:

  • Add performance monitoring
  • Implement log aggregation
  • Create custom log formats
  • Add structured error handling

💡 Solution

🔍 Click to see solution
// 🎯 Complete social media API logging system!
import express from 'express';
import winston from 'winston';
import morgan from 'morgan';
import DailyRotateFile from 'winston-daily-rotate-file';
import { v4 as uuidv4 } from 'uuid';

// 📊 Social media interfaces
interface User {
  id: string;
  username: string;
  email: string;
}

interface Post {
  id: string;
  userId: string;
  content: string;
  likes: number;
  createdAt: Date;
}

interface Activity {
  type: 'post_created' | 'post_liked' | 'user_registered';
  userId: string;
  metadata: any;
  timestamp: Date;
  correlationId?: string;
}

// 🎨 Custom Winston configuration
const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.printf(({ timestamp, level, message, ...meta }) => {
          const emoji = {
            error: '🚨',
            warn: '⚠️',
            info: '📝',
            debug: '🔍',
            http: '🌐'
          }[level] || '📋';
          
          return `${emoji} ${timestamp} [${level.toUpperCase()}]: ${message} ${
            Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''
          }`;
        })
      )
    }),
    
    // 🔄 Daily rotating files
    new DailyRotateFile({
      filename: 'logs/social-media-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '30d'
    }),
    
    // 🚨 Error-only file
    new winston.transports.File({
      filename: 'logs/errors.log',
      level: 'error'
    })
  ]
});

// 🛡️ Sensitive data sanitizer
function sanitizeForLogging(obj: any): any {
  const sensitiveFields = ['password', 'email', 'token', 'secret'];
  const sanitized = { ...obj };
  
  sensitiveFields.forEach(field => {
    if (sanitized[field]) {
      sanitized[field] = '[REDACTED]';
    }
  });
  
  return sanitized;
}

// 🔍 Correlation middleware
const correlationMiddleware = (req: any, res: any, next: any) => {
  const correlationId = req.headers['x-correlation-id'] || uuidv4();
  req.correlationId = correlationId;
  res.setHeader('x-correlation-id', correlationId);
  
  req.logger = logger.child({
    correlationId,
    userAgent: req.headers['user-agent'],
    ip: req.ip
  });
  
  next();
};

// 📊 Activity tracker
class ActivityTracker {
  private activities: Activity[] = [];
  
  logActivity(activity: Activity): void {
    this.activities.push(activity);
    
    logger.info('📈 User activity recorded', {
      activityType: activity.type,
      userId: activity.userId,
      correlationId: activity.correlationId,
      metadata: sanitizeForLogging(activity.metadata)
    });
  }
  
  generateDailyReport(): void {
    const today = new Date().toDateString();
    const todayActivities = this.activities.filter(
      a => a.timestamp.toDateString() === today
    );
    
    const report = {
      date: today,
      totalActivities: todayActivities.length,
      activityBreakdown: this.getActivityBreakdown(todayActivities),
      topUsers: this.getTopUsers(todayActivities),
      peakHours: this.getPeakHours(todayActivities)
    };
    
    logger.info('📊 Daily activity report generated', report);
  }
  
  private getActivityBreakdown(activities: Activity[]) {
    return activities.reduce((breakdown, activity) => {
      breakdown[activity.type] = (breakdown[activity.type] || 0) + 1;
      return breakdown;
    }, {} as Record<string, number>);
  }
  
  private getTopUsers(activities: Activity[]) {
    const userCounts = activities.reduce((counts, activity) => {
      counts[activity.userId] = (counts[activity.userId] || 0) + 1;
      return counts;
    }, {} as Record<string, number>);
    
    return Object.entries(userCounts)
      .sort(([, a], [, b]) => b - a)
      .slice(0, 5)
      .map(([userId, count]) => ({ userId, activityCount: count }));
  }
  
  private getPeakHours(activities: Activity[]) {
    const hourCounts = activities.reduce((counts, activity) => {
      const hour = activity.timestamp.getHours();
      counts[hour] = (counts[hour] || 0) + 1;
      return counts;
    }, {} as Record<number, number>);
    
    const peakHour = Object.entries(hourCounts)
      .sort(([, a], [, b]) => b - a)[0];
    
    return peakHour ? { hour: parseInt(peakHour[0]), count: peakHour[1] } : null;
  }
}

// 🎮 Initialize Express app
const app = express();
app.use(express.json());
app.use(correlationMiddleware);

// 🌐 Morgan middleware
app.use(morgan('combined', {
  stream: {
    write: (message: string) => {
      logger.http(message.trim());
    }
  }
}));

// 📊 Initialize activity tracker
const activityTracker = new ActivityTracker();

// 🎯 Mock data
const users: User[] = [
  { id: '1', username: 'alice_codes', email: '[email protected]' },
  { id: '2', username: 'bob_builds', email: '[email protected]' }
];

const posts: Post[] = [];

// 🏗️ API Routes
app.post('/api/users', (req: any, res) => {
  const { username, email, password } = req.body;
  
  req.logger.info('👤 New user registration attempt', {
    username,
    email: email.replace(/(.{2}).*(@.*)/, '$1***$2') // Partially mask email
  });
  
  try {
    const newUser: User = {
      id: uuidv4(),
      username,
      email
    };
    
    users.push(newUser);
    
    // 📈 Log activity
    activityTracker.logActivity({
      type: 'user_registered',
      userId: newUser.id,
      metadata: { username },
      timestamp: new Date(),
      correlationId: req.correlationId
    });
    
    req.logger.info('✅ User registered successfully', {
      userId: newUser.id,
      username
    });
    
    res.status(201).json({
      success: true,
      user: sanitizeForLogging(newUser)
    });
  } catch (error) {
    req.logger.error('❌ User registration failed', {
      username,
      error: error instanceof Error ? error.message : 'Unknown error'
    });
    
    res.status(500).json({
      success: false,
      error: 'Registration failed'
    });
  }
});

app.post('/api/posts', (req: any, res) => {
  const { userId, content } = req.body;
  
  req.logger.info('📝 New post creation attempt', {
    userId,
    contentLength: content.length
  });
  
  try {
    const newPost: Post = {
      id: uuidv4(),
      userId,
      content,
      likes: 0,
      createdAt: new Date()
    };
    
    posts.push(newPost);
    
    // 📈 Log activity
    activityTracker.logActivity({
      type: 'post_created',
      userId,
      metadata: { 
        postId: newPost.id,
        contentLength: content.length
      },
      timestamp: new Date(),
      correlationId: req.correlationId
    });
    
    req.logger.info('✅ Post created successfully', {
      postId: newPost.id,
      userId,
      contentPreview: content.substring(0, 50) + '...'
    });
    
    res.status(201).json({
      success: true,
      post: newPost
    });
  } catch (error) {
    req.logger.error('❌ Post creation failed', {
      userId,
      error: error instanceof Error ? error.message : 'Unknown error'
    });
    
    res.status(500).json({
      success: false,
      error: 'Post creation failed'
    });
  }
});

app.post('/api/posts/:postId/like', (req: any, res) => {
  const { postId } = req.params;
  const { userId } = req.body;
  
  req.logger.info('❤️ Post like attempt', { postId, userId });
  
  const post = posts.find(p => p.id === postId);
  
  if (!post) {
    req.logger.warn('⚠️ Like failed - Post not found', { postId, userId });
    return res.status(404).json({ success: false, error: 'Post not found' });
  }
  
  post.likes++;
  
  // 📈 Log activity
  activityTracker.logActivity({
    type: 'post_liked',
    userId,
    metadata: { 
      postId,
      totalLikes: post.likes
    },
    timestamp: new Date(),
    correlationId: req.correlationId
  });
  
  req.logger.info('💖 Post liked successfully', {
    postId,
    userId,
    totalLikes: post.likes
  });
  
  res.json({
    success: true,
    likes: post.likes
  });
});

// 📊 Analytics endpoint
app.get('/api/analytics/report', (req: any, res) => {
  req.logger.info('📊 Analytics report requested');
  
  activityTracker.generateDailyReport();
  
  res.json({
    success: true,
    message: 'Report generated and logged'
  });
});

// 🚀 Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  logger.info('🚀 Social media API server started!', {
    port: PORT,
    environment: process.env.NODE_ENV || 'development'
  });
});

// ⏰ Generate daily reports
setInterval(() => {
  activityTracker.generateDailyReport();
}, 24 * 60 * 60 * 1000); // Every 24 hours

🎓 Key Takeaways

You’ve learned so much about professional logging! Here’s what you can now do:

  • Set up Winston with multiple transports and custom formats 💪
  • Implement Morgan for HTTP request logging like a pro 🛡️
  • Create structured logs with correlation IDs and metadata 🎯
  • Avoid common logging mistakes that expose sensitive data 🐛
  • Build production-ready logging systems for real applications! 🚀

Remember: Good logging is like having a time machine for your application - it helps you see exactly what happened and when! 🤝

🤝 Next Steps

Congratulations! 🎉 You’ve mastered logging with Winston and Morgan!

Here’s what to do next:

  1. 💻 Practice with the social media API exercise above
  2. 🏗️ Add logging to an existing project using these patterns
  3. 📚 Move on to our next tutorial: Error Handling and Recovery Patterns
  4. 🌟 Share your logging victories with other developers!

Remember: Every debugging session is easier with great logging. Keep coding, keep logging, and most importantly, have fun building awesome applications! 🚀


Happy coding! 🎉🚀✨