Introduction: Why Logging in TypeScript?
Logging is not just about writing messages to the console. In modern TypeScript applications (server-side on Node.js, client-side on React/Angular, or full-stack projects), logging becomes a critical tool for debugging, monitoring, and performance analysis. Without quality logging, developers spend hours reproducing errors, and DevOps engineers cannot quickly determine the cause of a failure in production.
TypeScript, as a superset of JavaScript, adds strict typing, which allows for creating more reliable and predictable logging systems. In this article, we will cover best practices that will help you organize effective logging in TypeScript projects.
Basic Principles of Logging
Log Levels
Proper use of log levels is the foundation of any monitoring system. In TypeScript, it is common to use the following levels (in increasing order of importance):
debug— detailed debugging information (development only);info— standard informational messages (server startup, requests);warn— warnings (non-critical issues);error— errors requiring attention;fatal— critical errors causing application shutdown.
Example of configuring levels using the Winston library:
import winston from 'winston';
const logger = winston.createLogger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', format: winston.format.json(), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'error.log', level: 'error' }), ],});
logger.info('Server started on port 3000');logger.warn('Memory is near the limit');logger.error('Database connection error');Structured Logs
Avoid simple string messages. Use structured logs with contextual information (JSON format). This simplifies searching and analysis in systems like ELK or Grafana.
// Badlogger.info(`User ${userId} performed action ${action}`);
// Goodlogger.info('User action', { userId: userId, action: action, timestamp: new Date().toISOString(), ip: request.ip,});This approach allows you to filter logs by fields and build charts.
Best Practices for TypeScript
Using TypeScript for Log Typing
TypeScript allows you to define interfaces for the log context, preventing accidental errors in field names:
interface LogContext { userId: string; action: string; durationMs: number; success: boolean;}
function logUserAction(context: LogContext): void { logger.info('User action', context);}
// The compiler will verify that all fields are passed correctlylogUserAction({ userId: '123', action: 'login', durationMs: 150, success: true,});Avoid Logging Sensitive Data
Never log passwords, tokens, credit card numbers, or personal data (GDPR). Use utilities for masking:
function sanitizeLogData(data: Record<string, unknown>): Record<string, unknown> { const sensitiveFields = ['password', 'token', 'ssn']; const sanitized = { ...data }; for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = '***'; } } return sanitized;}
logger.info('User registration', sanitizeLogData(userData));Asynchronous Logging and Performance
In high-load applications, synchronous logging can slow down performance. Use asynchronous transports (e.g., winston supports buffering).