Guides

Migrating from winston

Formats become formatters, transports become sinks.

winston's ideas — formats, transports, child loggers, default metadata — all have a place in logit. The difference is that the message is a template and the event is captured once, so formats never need to re-parse anything.

Side by side#

winstonlogit
winston.createLogger({ … })createLogger({ … }) or configure({ … }) for the global log
logger.info('hello', { user })log.info('Hello {@User}', user) — or log.info({ user }, 'hello') during migration
logger.log('info', msg)log.write('info', msg)
logger.error('failed', err) / logger.error(err)log.error(err, 'failed') / log.error(err)
format.combine(format.timestamp(), format.json())consoleSink({ format: 'json' }) — timestamp, level, message and template are always there
format.printf(info => \${info.timestamp} ${info.level}: ${info.message}`)`format: 'text', outputTemplate: '{Timestamp:HH:mm:ss} {Level:u3}: {Message:lj}'
format.colorize(), format.simple(), format.cli()format: 'pretty' (the default on a terminal)
format.splat()message templates
format.errors({ stack: true })always: errors are captured with their stack and cause chain
format.label({ label })forSource(label)
format.metadata()the properties are the event
format((info) => { info.x = 1; return info; })an enricher: (event) => event.addPropertyIfAbsent('x', 1)
transports.ConsoleconsoleSink()
transports.File({ filename, maxsize, maxFiles })fileSink({ path, maxBytes, retainedFileCount })
transports.HttphttpSink()
winston-daily-rotate-filefileSink({ rolling: 'day' })
transport.levelrestrictedToMinimumLevel on any sink
defaultMeta: { service }properties: { Service: service }
logger.child({ requestId })log.forContext({ requestId })
levels: winston.config.npm.levels (silly, verbose, http…)six levels: verbose debug info warn error fatal — silly → verbose, http → info with a forSource('http')
exceptionHandlers, rejectionHandlerscloseOnExit(log) logs both at fatal
logger.end(), logger.on('finish')await log.close()
express-winstonrequestLogger() from /express

A winston-style configuration, in logit#

import { createLogger, consoleSink, withProperties } from '@unhingged/logit';
import { fileSink, closeOnExit } from '@unhingged/logit/node';

const log = createLogger({
  minimumLevel: process.env.LOG_LEVEL ?? 'info',
  properties: { service: 'shop' },
  sinks: [
    consoleSink(),
    fileSink({ path: 'logs/error.log', restrictedToMinimumLevel: 'error' }),
    fileSink({ path: 'logs/combined.log', rolling: 'day', retainedFileCount: 14 }),
  ],
});
closeOnExit(log);

What changes in your code#

  1. The metadata object becomes template holes or forContext. logger.info('order shipped', { orderId }) → log.info('Order {OrderId} shipped', orderId).
  2. Custom formats mostly disappear: the timestamp, the level, the stack are already there, and the shape is a formatter option rather than a chain.
  3. A printf format becomes an output template — the same tokens, the same idea.
  4. level on a transport becomes restrictedToMinimumLevel; level on the logger becomes minimumLevel.