@desolint/logger
v0.0.1
Published
Generic, structured Winston logger with pluggable transports and field redaction. No root export — import only what you need: @desolint/logger/config, /services.
Downloads
26
Readme
@desolint/logger
Generic, structured Winston logger with pluggable transports and field redaction.
No root export. import ... from '@desolint/logger' resolves to
nothing — import only what you need:
@desolint/logger/config @desolint/logger/services| Subpath | What it's for |
| ----------- | ----------------------------------------------------- |
| /config | Set up the Winston logger. Nothing else. |
| /services | info/warn/error/debug — the actual log calls. |
Requirements
- Node.js 22 or newer (declared in
engines) - npm 7 or newer — npm 7+ installs peer dependencies automatically
Install
npm install @desolint/loggerThat is all you need on npm 7+: winston is listed as a peer dependency, so npm resolves and installs it for you.
Neither installs peer dependencies automatically, so name it explicitly:
yarn add @desolint/logger winston
# or
pnpm add @desolint/logger winstonWhy winston is a peer dependency, not a regular one
winston keeps a global registry of transports and log levels. Two copies means
two registries: logs written through one would silently bypass the transports
configured on the other, so output could vanish depending on which copy a given
module happened to import.
Declaring it as a peer dependency means npm reuses the copy your application already has instead of nesting a second one under this package. You keep control of the version; this package just states the range it works with.
Quick start
// config/logger.js
import {initializeLogger} from '@desolint/logger/config';
initializeLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'silly',
redact: ['password', 'token', 'authorization'],
// omit `transports` in dev to get the default Console transport;
// pass your own in prod (built by you — this library never imports a
// transport for a specific third-party service itself)
});// anywhere else in your app
import {info, warn, error} from '@desolint/logger/services';
info({message: 'Server is listening on port 3000'});
warn({message: 'Skipping SMS send — Twilio is not configured.', meta: {to}});
try {
await doSomething();
} catch (err) {
error({message: err}); // accepts an Error directly, captures its stack
}/config
initializeLogger({level?, transports?, defaultMeta?, redact?})
Builds the shared Winston logger /services's info/warn/error/
debug all use.
| Param | Type | Required | Notes |
| ------------- | ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | string | no | Default 'info'. |
| transports | winston.transport[] | no | Replaces the default single Console transport entirely when given. You construct the transport (a SentryTransport, Datadog, ...) — this library never imports one for a specific third-party service itself. |
| defaultMeta | Record<string, unknown> | no | Merged into every logged entry (e.g. {service: 'my-app'}). |
| redact | string[] | no | Field names (case-insensitive) to mask with '***' in every logged call's meta. See Redaction below. |
Always-on formatting, not configurable: JSON output, a timestamp on
every entry, and errors({stack: true}) so passing an Error as
message captures its stack trace properly.
Returns: the Winston Logger instance itself, if you need it directly.
/services
info({message, meta?})
warn({message, meta?})
error({message, meta?}) // message: string | Error
debug({message, meta?})message accepts a string or an Error directly — passing an Error
captures its stack trace via /config's always-on errors({stack: true})
format, same as error({message: err}) should.
Redaction
Before a call reaches Winston, meta's keys are walked (case-insensitive)
against the redact list configured in initializeLogger. Any key that
matches gets its value replaced with '***':
initializeLogger({redact: ['password']});
info({
message: 'User logged in',
meta: {email: '[email protected]', password: 'hunter2'},
});
// what actually gets logged: {email: '[email protected]', password: '***'}This is a blanket safety net against accidentally logging a sensitive
field — it matches by key name only, one level deep (it catches
{password: '...'} but not a password hidden inside a differently-named
nested field). Sanitizing a specific third-party SDK's error shape
(e.g. picking only safe fields out of a Twilio error response) is a
narrower, app-specific concern this library doesn't attempt — that stays
in your own code, same as it does today.
Development
npm install # install dependencies
npm run build # type-check, then bundle each subpath into dist/
npm test # jest
npm run lint # eslintscripts/build.mjs bundles each subpath into one self-contained JS file plus a
.d.ts, then deletes everything else from dist/ — internal modules
(src/logger/*, src/shared/*) never ship, so there is nothing for an editor or a
moduleResolution: "node" consumer to resolve beyond the two public subpaths
documented above.
License
MIT © Desolint — see LICENSE.
Free to use, modify and redistribute, commercially or otherwise. Provided "as is", without warranty or liability of any kind.
