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:
- Winston - Application Logging 📝: Structured, configurable logging for your business logic
- Morgan - HTTP Request Logging 🌐: Automatic logging of web requests and responses
- Type Safety 🔒: Full TypeScript support with proper type definitions
- Flexibility 🎨: Multiple output formats, levels, and destinations
- 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
- 🎯 Use Appropriate Log Levels: Info for business logic, debug for troubleshooting, error for failures
- 📝 Include Context: Always add relevant metadata like user IDs, correlation IDs, timestamps
- 🛡️ Sanitize Sensitive Data: Never log passwords, tokens, or personal information
- 🔄 Rotate Log Files: Use daily rotation to prevent disk space issues
- ⚡ Use Structured Logging: JSON format makes logs searchable and analyzable
- 🎨 Add Correlation IDs: Track requests across multiple services
- 📊 Monitor Log Volume: Too many logs can impact performance
- 🚨 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:
- 💻 Practice with the social media API exercise above
- 🏗️ Add logging to an existing project using these patterns
- 📚 Move on to our next tutorial: Error Handling and Recovery Patterns
- 🌟 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! 🎉🚀✨