Winston

repository·master·Indexed 12 days ago

https://github.com/winstonjs/winston

A universal, multi-transport logging library for Node.js (v3.19.0) designed to decouple log formatting and levels from storage implementations. It supports custom logging levels, flexible formatting via winston.format, and multiple transports such as Console, File, and Http. Features include child loggers for metadata overrides, handling of uncaught exceptions and promise rejections, and execution time profiling.

Tokens
19.8K
Snippets
66
Records
78
Agent score
92%

What's inside Winston

  1. Understand the `info` object and metadata

    master

    In winston, log messages are handled as info objects within an objectMode stream. Every info object must contain at least a level and a message property.

    Any additional properties provided are treated as meta (metadata).

    Important Note on Message Concatenation: If you provide a message property inside a metadata object, winston will automatically concatenate it to the primary message. For example: logger.info('hello', { message: 'world' }); results in a message of 'hello world'.

    Commonly added properties by formats:

    • splat: Used by splat() for string interpolation (%d, %s).
    • timestamp: Added by timestamp().
    • label: Added by label().
    • ms: Added by ms() (milliseconds since last log).
    // The structure of an info object
    const info = {
      level: 'info',                 // Level of the logging message
      message: 'Hey! Log something?', // Descriptive message
      requestId: '123'               // Custom metadata
    };
  2. Use winston.format for all formatting tasks

    master

    In winston@3, formatting is no longer configured directly on the Logger or Transport options. Instead, all formatting (JSON, colorization, timestamps, etc.) must be applied via the winston.format API.

    Commonly used formats include:

    • format.json(): Default output format.
    • format.colorize(): Adds color to log levels.
    • format.timestamp(): Adds a timestamp to the log.
    • format.printf(): Allows for custom string templates.
    • format.splat(): Enables string interpolation (e.g., logger.info('hello %s', 'world')).
  3. Migrate filters and rewriters to formats

    master

    In winston@3.x.x, the concepts of filters and rewriters from winston@2 have been unified into the formats API. Because info objects are mutable in winston@3, you can implement both filtering (returning a modified or original object) and rewriting (changing property values) using custom formats.

    Example: Implementing a Filter

    To filter or mask sensitive information, create a custom format that modifies the info.message and return the info object.

    Example: Implementing a Rewriter

    To rewrite data (e.g., masking credit card numbers), create a custom format that modifies existing properties on the info object.

    Use format.combine() to chain your custom logic with standard formats like format.json() or format.printf().

    const { createLogger, format, transports } = require('winston');
    
    // Custom filter/rewriter format
    const maskFormat = format((info) => {
      if (info.creditCard) {
        info.creditCard = maskCardNumbers(info.creditCard);
      }
      info.hasCreditCard = !!info.creditCard;
      return info;
    });
    
    const logger = createLogger({
      format: format.combine(
        maskFormat(),
        format.json()
      ),
      transports: [new transports.Console()]
    });
    
    logger.info('transaction ok', { creditCard: 123456789012345 });
  4. Use the default winston logger

    master

    You can log directly using the default logger exported by require('winston'). This is intended as a convenient shared logger for applications.

    Warning: The default logger has no transports configured by default. You must manually add transports using winston.add(). Leaving the default logger without transports may lead to high memory usage.

    const winston = require('winston');
    
    // This will not output anywhere unless you add a transport first
    winston.info('Hello world');
    
    winston.add(new winston.transports.Console());
    winston.info('Now it works!');
  5. Understand the modular architecture of winston@3

    master

    As of winston@3.0.0, the project is composed of several specialized modules:

    • winston: The main entry point.
    • winston-transport: Provides the Transport stream implementation.
    • logform: Contains all the logic for winston.format.
    • triple-beam: Exposes LEVEL and MESSAGE symbols.

    When building custom transports or complex formatting logic, you may need to interact with these underlying packages directly.

    const { createLogger, transports, format } = require('winston');
    const Transport = require('winston-transport');
    const logform = require('logform');
    const { combine, timestamp, label, printf } = logform.format;
    
    // Note: winston.format is an alias for logform
    console.log(logform.format === format); // true
  6. What are Winston Transports?

    master
    In winston, a transport is a storage device for your logs. Each logger instance can have multiple transports configured at different levels. This allows you to route different types of logs to different destinations—for example, sending error logs to a persistent remote database while simultaneously outputting all logs to the console or a local file.
  7. Filter log messages by returning falsey values in custom formats

    master

    You can create a custom format to filter out specific log entries. If a format's transform function returns a falsey value, the info object is ignored, and subsequent formats in a combine chain will not be executed for that entry.

    const { createLogger, format, transports } = require('winston');
    
    // Ignore log messages if they have { private: true }
    const ignorePrivate = format((info, opts) => {
      if (info.private) { return false; }
      return info;
    });
    
    const logger = createLogger({
      format: format.combine(
        ignorePrivate(),
        format.json()
      ),
      transports: [new transports.Console()]
    });
    
    // Outputs: {"level":"error","message":"Public error to share"}
    logger.log({
      level: 'error',
      message: 'Public error to share'
    });
    
    // Messages with { private: true } will not be written when logged.
    logger.log({
      private: true,
      level: 'error',
      message: 'This is super secret - hide it.'
    });
  8. Configure logging levels and transport thresholds

    master

    Winston uses integer priorities for levels (lower number = higher priority). By default, it uses npm levels. You can set a level on individual transports to define the maximum severity level they will handle.

    Example: A Console transport set to error will only log errors, while a File transport set to info will log everything from info upwards (including errors).

    const logger = winston.createLogger({
      levels: winston.config.syslog.levels,
      transports: [
        new winston.transports.Console({ level: 'error' }),
        new winston.transports.File({
          filename: 'combined.log',
          level: 'info'
        })
      ]
    });
  9. Manage multiple loggers using winston.loggers or winston.Container

    master

    For complex applications, you can manage multiple logger instances with different configurations (formats, transports, levels) using a Container.

    winston.loggers is a predefined instance of winston.Container that allows you to define named loggers that can be retrieved anywhere in your application by their name.

    Alternatively, you can instantiate your own new winston.Container() to manage a private set of loggers.

    const winston = require('winston');
    const { format } = winston;
    const { combine, label, json } = format;
    
    // 1. Configure named loggers in the global container
    winston.loggers.add('category1', {
      format: combine(
        label({ label: 'category one' }),
        json()
      ),
      transports: [
        new winston.transports.Console({ level: 'silly' }),
        new winston.transports.File({ filename: 'somefile.log' })
      ]
    });
    
    winston.loggers.add('category2', {
      format: combine(
        label({ label: 'category two' }),
        json()
      ),
      transports: [
        new winston.transports.Http({ host: 'localhost', port:8080 })
      ]
    });
    
    // 2. Retrieve them anywhere in your app
    const category1 = winston.loggers.get('category1');
    const category2 = winston.loggers.get('category2');
    
    category1.info('logging to file and console transports');
    category2.info('logging to http transport');
  10. Handle uncaught exceptions

    master

    Winston can catch and log uncaughtException events. You can enable this in three ways:

    1. During logger creation: Use the exceptionHandlers array.
    2. After creation: Use logger.exceptions.handle(transport).
    3. On a per-transport basis: Set handleExceptions: true on a specific transport.

    If you want to prevent the process from exiting after an exception is logged, set exitOnError: false (or provide a function to determine if the error should trigger an exit).

    const { createLogger, transports } = require('winston');
    
    // 1. Enable via exceptionHandlers
    const logger = createLogger({
      transports: [
        new transports.File({ filename: 'combined.log' })
      ],
      exceptionHandlers: [
        new transports.File({ filename: 'exceptions.log' })
      ]
    });
    
    // 2. Enable via .exceptions.handle()
    logger.exceptions.handle(
      new transports.File({ filename: 'exceptions.log' })
    );
    
    // 3. Enable via transport option
    winston.add(new winston.transports.File({
      filename: 'path/to/combined.log',
      handleExceptions: true
    }));
    
    // Control exit behavior
    const loggerWithNoExit = winston.createLogger({ exitOnError: false });
    
    // Control exit behavior with a predicate function
    function ignoreEpipe(err) {
      return err.code !== 'EPIPE';
    }
    const loggerWithCustomExit = winston.createLogger({ exitOnError: ignoreEpipe });
  11. Handle logging completion and process exit

    master

    In winston@3.0.0, winston.Logger.log and level-specific methods (like .info(), .error()) no longer accept a callback. To ensure all logs are processed before exiting a process, use the finish event and call logger.end().

    logger.log('info', 'some message');
    logger.on('finish', () => process.exit());
    logger.end();