mrkeyoor.com_
Sun 20 Sept 11:45 UTC
npmInfraupdated 20 Sept 2026

winston review

Winston 3.19.0 is a Node logger built around mutable info objects, ordered format transforms, and output transports. One logger can write JSON to stdout, route errors to a file, and send selected records to a remote backend with different levels and formats. It also supports child metadata, custom levels, process exception handlers, timers, and third-party transports. Version 3.19.0 makes File transport emit finish only after buffered data drains and preserves Error.cause through child loggers. Our browser bundle failed, matching the library's Node-specific files, streams, and process handling.

Verdict

Winston 3.19.0 installed in 1.4 seconds and left 29 packages using 4 MB on our box, with 0 audit findings, and its browser build failed. Install it when in-process multi-transport routing is a requirement; choose Pino for stdout-first JSON services and write a tested redaction format before any sensitive metadata reaches Winston.

We installed it

Lab card: what happened when we installed winstonScreenshot of winston documentation
Install✓ · 1.4s29 packages on disk · 4 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does winston install cleanly?

Yes. In a fresh container with an empty cache, npm install winston finished in 1 seconds, leaving 29 packages and 4 MB on disk. npm audit reported no known vulnerabilities.

Can winston run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does winston work with both ESM and CommonJS?

Yes. Both import 'winston' and require('winston') worked in Node 22 in our run. The package is published as CommonJS.

Does winston include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

winston or pino: which should you use?

pino: Use it when the core job is low-overhead JSON to stdout and processing can move outside the request path. Winston 3.19.0 installed in 1.4 seconds and left 29 packages using 4 MB on our box, with 0 audit findings, and its browser build failed.

When should you not use winston?

The service only emits newline-delimited JSON to stdout; Winston installed 29 packages for routing and formatting that such a process may never use

API stability5/5createLogger, transport arrays, format.combine, generated level methods, child metadata, and exception configuration have retained the Winston 3 shape for years. Version 3.19.0 repairs File completion and child Error causes without changing normal call sites. The practical documentation hazard is Winston 2 material still appearing in searches, plus transport plugins that each carry their own compatibility range and release quality.
Docs4/5The README covers logger creation, object-mode info records, built-in and custom formats, levels, transport routing, exceptions, rejections, timers, queries, streams, containers, default logger behavior, and waiting for finish. It directly warns about a transportless default logger and child loggers on subclasses. The page is very long, and detailed format internals live in logform while third-party transport behavior lives in separate repositories.
Maintenance4/5npm published Winston 3.19.0 on December 7, 2025, and GitHub recorded a push on July 20, 2026. The repository is unarchived, with 529 issues and pull requests in GitHub's combined open count. The latest release fixed real loss-prone behavior in File shutdown and preserved child Error causes. That large queue still warrants checking unresolved reports for the exact transports a service plans to run.
Ecosystem5/5npm counted 28,363,048 downloads between August 19 and August 25, 2026, while GitHub showed 24,507 stars. winston-transport defines an extension base used by rotation, database, cloud, observability, messaging, and framework integrations. Bundled TypeScript declarations and working CommonJS-to-ESM interop keep the core accessible, though each community transport adds another maintainer and failure path to review.

Use it if

  • A Node process needs several log destinations with independent thresholds or representations
  • Existing code and vendor integrations already implement the winston-transport contract
  • Uncaught exceptions and unhandled rejections need dedicated transports plus an explicit exit policy
  • Log records must be transformed, filtered, colorized, or serialized before each destination receives them
Skip it if

Setup reality

We installed Winston 3.19.0 in our fresh Node 22 sandbox in 1.4 seconds. It left 29 packages using 4 MB, while npm audit found 0 known vulnerabilities. Winston's package is 396 KB unpacked with 11 direct dependencies, 0 peers, bundled TypeScript declarations, and an MIT license. The engines field accepts Node 12 or newer. The disk footprint is modest, but the 29-package tree is material for a service that only needs console JSON.

Winston is CommonJS with no exports map. require() and ESM import both worked on our box. The browser-targeted esbuild run failed, so keep logger modules out of client entry points. Create an application logger with at least 1 transport. The shared default logger begins with none, and the README warns that logging to it without transports can increase memory usage. Rotation is not built into the File transport; install and configure a separate transport such as winston-daily-rotate-file.

Formats execute in order and can mutate or reject the same info object. splat must precede the formatter that renders placeholders. colorize must run before the output it should color. json, simple, or printf normally finalizes the record. Returning false drops it. Winston has no built-in nested path redactor, so write a format before JSON serialization and test representative metadata, Errors, arrays, and nested request objects. Redacting only top-level password and token fields is rarely enough.

Transports buffer, especially files and network sinks. During shutdown, call logger.end() and wait for finish rather than calling process.exit immediately. Version 3.19.0 fixes File so finish follows drained data, which makes that event usable for process shutdown. exceptionHandlers exit by default unless exitOnError is false or a predicate says otherwise. A process corrupted by an uncaught exception is usually safer to flush and restart; logging it does not make continued execution correct.

Patterns

Write structured logs to stdout create-json-logger

const winston = require('winston');
const logger = winston.createLogger({
  level: 'info', defaultMeta: { service: 'orders-api' },
  format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
  transports: [new winston.transports.Console()]
});

Create 1 application logger with a transport; Winston's shared default logger starts with 0 transports.

Give a file its own threshold route-error-file

const logger = winston.createLogger({ transports: [
  new winston.transports.Console({ level: 'info' }),
  new winston.transports.File({ filename: 'errors.log', level: 'error', format: winston.format.json() })
] });

Both logger and transport thresholds apply; the stricter effective level decides whether the file receives a record.

Build a readable console line format-console

const consoleFormat = winston.format.combine(
  winston.format.colorize(), winston.format.timestamp(),
  winston.format.printf(({ timestamp, level, message }) => `${timestamp} ${level} ${message}`)
);

Formats mutate 1 info object in order, so colorize must precede the formatter whose text should contain colors.

Enable printf placeholders interpolate-message

const logger = winston.createLogger({
  format: winston.format.combine(winston.format.splat(), winston.format.simple()),
  transports: [new winston.transports.Console()]
});
logger.info('imported %d rows from %s', 4210, 'orders.csv');

Without format.splat before simple, the 2 placeholders remain literal text.

Create a request child logger bind-request-meta

function requestLogger(requestId) { return logger.child({ requestId }); }
requestLogger('req-451').warn('slow query', { elapsedMs: 812 });

Version 3.19.0 preserves Error.cause through child loggers; Logger subclasses still carry a documented child() warning.

Drop records marked private filter-record

const dropPrivate = winston.format(info => info.private === true ? false : info);
const format = winston.format.combine(dropPrivate(), winston.format.json());

Returning false prevents every later format and transport using this pipeline from seeing the record.

Redact before JSON serialization redact-meta

const redact = winston.format(info => {
  const copy = { ...info };
  if ('token' in copy) copy.token = '[redacted]';
  return copy;
});
const format = winston.format.combine(redact(), winston.format.json());

This removes 1 top-level key only; nested credentials require a deliberate recursive or path-based policy.

Define domain-specific levels custom-levels

const levels = { fatal: 0, error: 1, warn: 2, info: 3, audit: 4 };
const logger = winston.createLogger({ levels, level: 'audit', transports: [new winston.transports.Console()] });
logger.audit('role changed', { userId: 42 });

Lower numbers mean greater severity, and Winston creates a method for each configured level.

Route uncaught process failures handle-process-errors

const logger = winston.createLogger({
  transports: [new winston.transports.Console()],
  exceptionHandlers: [new winston.transports.File({ filename: 'exceptions.log' })],
  rejectionHandlers: [new winston.transports.File({ filename: 'rejections.log' })],
  exitOnError: true
});

Handled uncaught exceptions exit by default; choose the exit policy instead of inheriting it accidentally.

Wait for buffered transports flush-shutdown

await new Promise((resolve, reject) => {
  logger.once('finish', resolve);
  logger.once('error', reject);
  logger.end();
});

Version 3.19.0 makes File finish wait for drained data; process.exit before this promise can lose records.

Add a separate rotation transport rotate-files

const DailyRotateFile = require('winston-daily-rotate-file');
logger.add(new DailyRotateFile({
  filename: 'application-%DATE%.log', datePattern: 'YYYY-MM-DD', maxFiles: '14d', zippedArchive: true
}));

winston-daily-rotate-file is another install; test its error event and 14-day retention on the target filesystem.

Record elapsed operation time time-operation

const timer = logger.startTimer();
try { await rebuildIndex(); timer.done({ message: 'index rebuilt' }); }
catch (error) { timer.done({ message: 'index failed', level: 'error', error }); throw error; }

done emits a normal log record with duration metadata, so its configured level and transports still apply.

Alternatives

PackageRegistryPick it when
pinonpmUse it when the core job is low-overhead JSON to stdout and processing can move outside the request path
consolanpmUse it for readable CLI and development logs with a smaller reporter model
loglevelnpmUse it for a compact leveled logger that can also run in browser code

More infra guides

boto3 · opentelemetry-api · psutil · distro · @opentelemetry/api · google-cloud-storage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.