Winston's transport fan-out, level filtering, and the default logger's memory trap
A logger for just about everything.
At a glance
- What is it?
- winston separates the logging API from storage: one logger, many transports, and a numeric severity order that decides what each sink accepts. The rough edges are concrete. createLogger defaults to an empty transports array, the shared default logger can hold messages in memory, and exitOnError defaults to true.
- Who is it for?
- winston earns its place in services that need one message routed to several sinks with a per-sink severity cut, and the transport interface is where that flexibility actually lives. Pass on it if the database side has to come from the same dependency list as the write side, because no database driver is among its runtime dependencies.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 117 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- winstonjs/winston24,526 ★ · +7
| Repository | 2026-09-22 | 2026-11-13 |
|---|---|---|
| winstonjs/winston | 24.5k | 24.5k |
7 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
One logger fans out to many transports, and each transport filters on its own level
In winston a transport is a storage device for logs, and one logger can hold several of them, each configured at a different level. The split the project motivates is by severity: error output stored in a persistent remote location such as a database, while every message still goes to the console or a local file. Filtering rests on a numeric convention rather than on transport internals. Levels conform to RFC5424, where severity is numerically ascending from most important to least important, and the default npm set puts http at 3 between info and verbose:
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
};A transport compares numbers, not names, and that is what lets one logger feed a console and a database with different cutoffs without either knowing about the other. The cost of the design lands on whoever defines custom levels: the ordering has to stay ascending, because numbers that descend or a level you insert out of sequence invert what every transport accepts, and the failure looks like missing logs rather than an error.
createLogger defaults to level info, format json, and an empty transports array
winston.createLogger takes a fixed parameter set, and each default changes what reaches your sinks. level defaults to 'info' and logs only when info.level is less than or equal to it. levels defaults to winston.config.npm.levels. format defaults to winston.format.json, so a logger you build with nothing but transports still emits JSON lines instead of plain text. transports defaults to an empty array, meaning no targets at all. exitOnError defaults to true. silent defaults to false and suppresses every message when set. Whatever levels you pass in are also defined as convenience methods on the returned logger, so a custom set changes which method names exist on it:
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});//
// Logging
//
logger.log({
level: 'info',
message: 'Hello distributed log files!'
});
logger.info('Hello again distributed logs');Two defaults bite in practice. An omitted transports array gives every call above nowhere to go, and because format defaults to JSON, adding a console transport for local reading does not produce human-readable lines; that needs a different format passed in.
The default logger from require('winston') starts with no transports, and the memory warning is the reason to avoid it
The package also exposes a default logger through require('winston'), which the README offers as a convenient shared logger for an application that wants one everywhere. Two facts about it matter more than the convenience. It has no transports by default, so you have to add them yourself. And leaving it without any transports may produce a high memory usage issue. That pairing is the trap: a module that imports the shared logger and calls info() on a request path keeps accepting calls after every line has been written nowhere, and the cost surfaces as memory growth rather than as a missing file. The README's own recommendation is to create your own logger with winston.createLogger, where the transports array is something you write on the first line instead of forgetting on a later one.
Transports write logs, and no runtime dependency reads them back
Transports are the storage edge, and winston keeps itself out of the persistence question. The dependency list in package.json is @colors/colors, @dabh/diagnostics, async, is-stream, logform, one-time, readable-stream, safe-stable-stringify, stack-trace, triple-beam, and winston-transport. There is no database driver and no search client in that list, which is how the motivation section can talk about storing errors in a database without shipping the database code: a transport provides it. The table of contents also names Streaming Logs and Querying Logs as topics, and the repository carries a docs/ directory. For a reader that makes the read side a separate decision with its own owner, and it means connection failures, authentication, retries, and queue backpressure belong to that transport's failure modes rather than to winston's. Writing your own sink is a documented path, with sections on adding custom transports and on common transport options.
exitOnError defaults to true, so a handled exception can call process.exit
exitOnError is the one parameter that reaches outside the logging pipeline, and its default is true: a handled exception causes process.exit. Set it to false and handled exceptions no longer end the process. The README carries separate sections for exceptions and for promise rejections, including a heading on whether to exit or not, so the library does more than format a thrown error before dying.
clear, add, remove, and configure rewrite the transport set on a live logger
Transports stay mutable after construction. The logger returned by createLogger exposes clear(), add(), and remove() as chainable calls, and configure() for wholesale reconfiguration:
const files = new winston.transports.File({ filename: 'combined.log' });
const console = new winston.transports.Console();
logger
.clear() // Remove all transports
.add(console) // Add console transport
.add(files) // Add file transport
.remove(console); // Remove console transportclear() removes every transport at once, which puts the logger back into the same empty state createLogger defaults to, so a log call issued between that clear() and the next add() has no destination. Formatting is a separate axis from storage: the table of contents splits Formats from Transports, listing combining formats, string interpolation, filtering info objects, and custom formats as their own topics, and the examples directory carries both format-logger-and-transport.js and format-mutate.js. Swapping transports therefore does not reshape your messages, and changing format does not move them.
Quick Start points at examples/ instead of embedding a runnable program
winston's Quick Start does not print a runnable program. It points at the quick start example in ./examples/, notes that there are more in ./examples/*.js, and invites a pull request for a missing one. That directory carries 25 example files, 24 JavaScript and one TypeScript, and the file names are the closest thing the project has to a feature index: color-message, create-file, custom-levels, custom-pretty-print, custom-timestamp, custom-transport, delete-level, errors, exception, file-maxsize, finish-event, format-filter, interpolation, json, levels, metadata, simple-stream, and splat-message among them. The consequence is that you learn the API by reading files rather than by copying a block, and two of those names mark the operational questions people hit first: file-maxsize.js for rotation and finish-event.js for flushing. Version discipline matters, since the README states the documentation below is for winston@3, sends 2.x readers to separate documentation, and the repository root keeps UPGRADE-3.0.md.
babel produces dist, jest runs three suites, and two patch releases shipped under eight hours apart
Shipping is a babel build plus jest suites, and the package.json scripts spell out the loop:
"test": "jest",
"test:unit": "jest -c test/jest.config.unit.js",
"test:integration": "jest -c test/jest.config.integration.js",
"test:typescript": "npx --package typescript tsc --project test",
"build": "babel lib -d dist",
"prepublishOnly": "npm run build"main points at ./lib/winston.js, browser at ./dist/winston, and types at index.d.ts, so the browser bundle is a build artifact generated from lib/ and type checking runs as its own suite. The root shows the rest of the tooling: .babelrc, .eslintrc beside eslint.config.cjs, .prettierrc, jest.config.js, test/, tsconfig.json, SECURITY.md, CODE_OF_CONDUCT.md, CHANGELOG.md. Release cadence is visible too, with v3.19.0 on 2025-12-07, v3.18.2 and v3.18.3 both on 2025-09-30 less than eight hours apart, and the last push to master dated 2026-07-20.
Editorial conclusion
winston earns its place in services that need one message routed to several sinks with a per-sink severity cut, and the transport interface is where that flexibility actually lives. Pass on it if the database side has to come from the same dependency list as the write side, because no database driver is among its runtime dependencies. Before adopting, verify two things against your own tree: that every logger you construct names at least one transport, since the array defaults to empty while exitOnError still defaults to true, and that you have picked which component owns log persistence, because that component is the one that will fail on you at 3am.
Frequently asked questions
how to install winston
The package is named winston on npm, and the repository's package.json declares version 3.19.0 with main at ./lib/winston.js, browser at ./dist/winston, and types at index.d.ts. The README's badge row links to that package's npm page.
how to install winston in node js
Add the winston package to the Node.js project and require it by name, which gives you createLogger and the transports namespace. The package carries eleven runtime dependencies, including logform and winston-transport, and its own test and build steps are npm scripts around jest and babel.
how to use winston logger in node js
Build a logger with winston.createLogger and pass at least one transport, since the transports parameter defaults to an empty array. Call logger.info or logger.log with a level and a message, and remember that level defaults to 'info' and format defaults to winston.format.json.
how to use winston
The README's Usage section says the recommended way to use winston is to create your own logger with winston.createLogger. The default logger exposed by require('winston') starts with no transports, and adding them is left to you.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/winstonjs-winston)Star history chart for your README
[](https://hysenlabs.com/projects/winstonjs-winston)Was this useful?