Logging in TypeScript Applications: Best Practices for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Logging in TypeScript Applications

\n\n

Logging is the process of recording events that occur during program execution. For TypeScript applications, especially those running in a Node.js environment, properly configured logging is not just good practice, but a critically important tool for debugging, monitoring, and error analysis.

\n\n

Many beginner developers limit themselves to using console.log(). However, in a production environment, this approach is ineffective: it does not allow you to flexibly manage severity levels, filter messages, or save them to files. In this article, we will look at how to create a professional logging system in TypeScript from scratch.

\n\n

Why You Shouldn't Use console.log in Production?

\n\n

Let's look at a typical example:

\n\n
// Bad approach — using console.log directly\nfunction processOrder(orderId: string): void {\n  console.log(`Processing order ${orderId}`);\n  try {\n    // business logic\n  } catch (error) {\n    console.error(`Error processing order: ${error}`);\n  }\n}\n
\n\n

Problems with this approach:

\n
    \n
  • Lack of logging levels: you cannot separate debug messages from critical errors.
  • \n
  • No formatting: difficult to read large amounts of data.
  • \n
  • Cannot redirect: all messages go only to stdout/stderr.
  • \n
  • Performance issues: synchronous console writing can slow down the application.
  • \n
\n\n

Best Practices for Logging in TypeScript

\n\n

1. Choosing a Logging Library

\n\n

For TypeScript, it is recommended to use one of two popular libraries:

\n
    \n
  • Winston — a powerful and flexible logger with support for transports (file, console, databases).
  • \n
  • Pino — an ultra-fast logger, ideal for high-load applications.
  • \n
\n\n

Let's install Winston:

\n
npm install winston\nnpm install @types/winston --save-dev\n
\n\n

2. Creating a Configurable Logger

\n\n

Let's create a logger.ts module:

\n\n
import winston from 'winston';\n\nconst logger = winston.createLogger({\n  level: process.env.LOG_LEVEL || 'info',\n  format: winston.format.combine(\n    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n    winston.format.errors({ stack: true }),\n    winston.format.json()\n  ),\n  transports: [\n    new winston.transports.Console({\n      format: winston.format.combine(\n        winston.format.colorize(),\n        winston.format.simple()\n      )\n    }),\n    new winston.transports.File({ \n      filename: 'logs/error.log', \n      level: 'error' \n    }),\n    new winston.transports.File({ \n      filename: 'logs/combined.log' \n    })\n  ]\n});\n\nexport default logger;\n
\n\n

Now you can use the logger in any module:

\n
import logger from './logger';\n\nfunction getUser(id: string) {\n  logger.info(`Fetching user with ID: ${id}`);\n  try {\n    // user retrieval logic\n    logger.debug('User data retrieved successfully');\n  } catch (error) {\n    logger.error('Failed to fetch user', { userId: id, error });\n  }\n}\n
\n\n

3. Using Logging Levels

\n\n

Standard levels (from least to most important):

\n
    \n
  • debug — detailed debugging information (development only).
  • \n
  • info — standard informational messages.
  • \n
  • warn — warnings about potential issues.
  • \n
  • error — errors requiring attention.
  • \n
\n\n

Configure the logging level via environment variables:

\n
// In .env file\nLOG_LEVEL=debug  # for development\nLOG_LEVEL=warn   # for production\n
\n\n

4. Structured Logging

\n\n

Pass additional

Blogs

Book Recommendations