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#
| winston | logit |
|---|---|
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.Console | consoleSink() |
transports.File({ filename, maxsize, maxFiles }) | fileSink({ path, maxBytes, retainedFileCount }) |
transports.Http | httpSink() |
winston-daily-rotate-file | fileSink({ rolling: 'day' }) |
transport.level | restrictedToMinimumLevel 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, rejectionHandlers | closeOnExit(log) logs both at fatal |
logger.end(), logger.on('finish') | await log.close() |
express-winston | requestLogger() 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#
- The metadata object becomes template holes or
forContext.logger.info('order shipped', { orderId })→log.info('Order {OrderId} shipped', orderId). - Custom formats mostly disappear: the timestamp, the level, the stack are already there, and the shape is a formatter option rather than a chain.
- A
printfformat becomes an output template — the same tokens, the same idea. levelon a transport becomesrestrictedToMinimumLevel;levelon the logger becomesminimumLevel.