@ddd-ts-kit/winston 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +282 -2
  2. package/dist/create-winston-logger.d.ts +33 -0
  3. package/dist/create-winston-logger.d.ts.map +1 -0
  4. package/dist/create-winston-logger.js +34 -0
  5. package/dist/create-winston-logger.js.map +1 -0
  6. package/dist/end-of.d.ts +4 -0
  7. package/dist/end-of.d.ts.map +1 -0
  8. package/dist/end-of.js +15 -0
  9. package/dist/end-of.js.map +1 -0
  10. package/dist/formats/correlation-fields.d.ts +9 -0
  11. package/dist/formats/correlation-fields.d.ts.map +1 -0
  12. package/dist/formats/correlation-fields.js +25 -0
  13. package/dist/formats/correlation-fields.js.map +1 -0
  14. package/dist/formats/iso-time.d.ts +4 -0
  15. package/dist/formats/iso-time.d.ts.map +1 -0
  16. package/dist/formats/iso-time.js +9 -0
  17. package/dist/formats/iso-time.js.map +1 -0
  18. package/dist/formats/readable-line.d.ts +4 -0
  19. package/dist/formats/readable-line.d.ts.map +1 -0
  20. package/dist/formats/readable-line.js +18 -0
  21. package/dist/formats/readable-line.js.map +1 -0
  22. package/dist/formats/redact-credentials.d.ts +7 -0
  23. package/dist/formats/redact-credentials.d.ts.map +1 -0
  24. package/dist/formats/redact-credentials.js +32 -0
  25. package/dist/formats/redact-credentials.js.map +1 -0
  26. package/dist/formats/unbreakable.d.ts +8 -0
  27. package/dist/formats/unbreakable.d.ts.map +1 -0
  28. package/dist/formats/unbreakable.js +24 -0
  29. package/dist/formats/unbreakable.js.map +1 -0
  30. package/dist/index.d.ts +5 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +5 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/winston-logger.d.ts +49 -0
  35. package/dist/winston-logger.d.ts.map +1 -0
  36. package/dist/winston-logger.js +83 -0
  37. package/dist/winston-logger.js.map +1 -0
  38. package/package.json +36 -4
package/README.md CHANGED
@@ -1,3 +1,283 @@
1
- # Temporary Holding Version
1
+ # @ddd-ts-kit/winston
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The kernel `Logger` port on [winston](https://github.com/winstonjs/winston): JSON lines on the
4
+ standard output, the correlation fields of the work in progress on every line, credentials
5
+ redacted, a flush for the shutdown. Nothing in `domain/` or `application/` imports this
6
+ package, or logs anything: the composition root binds the adapter, and infrastructure,
7
+ presentation and the aspects write through the port.
8
+
9
+ ```
10
+ npm i @ddd-ts-kit/winston winston
11
+ ```
12
+
13
+ Written against winston 3.19.
14
+
15
+ ## Composition root
16
+
17
+ ```ts
18
+ // platform/observability/create-logger.ts
19
+ import type { ExecutionScope, LogLevel } from '@ddd-ts-kit/kernel';
20
+ import { traceLogFields } from '@ddd-ts-kit/tracing';
21
+ import { createWinstonLogger, type WinstonLogger } from '@ddd-ts-kit/winston';
22
+
23
+ export interface LogSettings {
24
+ readonly level: LogLevel; // LOG_LEVEL
25
+ readonly format: 'json' | 'pretty'; // LOG_FORMAT
26
+ readonly service: string;
27
+ readonly environment: string;
28
+ readonly scrubMessages: boolean; // LOG_SCRUB_MESSAGES
29
+ }
30
+
31
+ export function createLogger(scope: ExecutionScope, settings: LogSettings): WinstonLogger {
32
+ return createWinstonLogger({ ...settings, fields: () => traceLogFields(scope) });
33
+ }
34
+ ```
35
+
36
+ ```ts
37
+ // main.ts
38
+ import { tracing } from '@/platform/tracing.js'; // first
39
+ import { AsyncLocalStorageExecutionScope } from '@ddd-ts-kit/kernel';
40
+ import { createApplication, kernelModule } from '@ddd-ts-kit/di';
41
+
42
+ const scope = new AsyncLocalStorageExecutionScope();
43
+ const logger = createLogger(scope, config.log);
44
+
45
+ const app = createApplication({
46
+ modules: [kernelModule({ executionScope: scope, logger }), ...contexts],
47
+ });
48
+
49
+ process.once('SIGTERM', () => void shutdown());
50
+
51
+ async function shutdown(): Promise<void> {
52
+ await app.stop(); // flushes the error reporter, then the logger, last: that ends the winston logger
53
+ await tracing.shutdown();
54
+ }
55
+ ```
56
+
57
+ `init --logger winston` writes this `create-logger.ts` (it answers a `NoopLogger` when given no
58
+ settings, which is how the tools and the tests stay silent); `platform/lay-foundations.ts`
59
+ calls it once and `composeApplication` hands the logger to `kernelModule({ logger })`.
60
+
61
+ One line of the application, as it is written:
62
+
63
+ ```json
64
+ {"actorId":"user-1","context":"shipping","correlationId":"9f2c","environment":"production","level":"info","message":"order shipped","origin":"http","service":"shop-api","span_id":"00f067aa0ba902b7","tenantId":"acme","time":"2026-03-01T10:00:00.000Z","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}
65
+ ```
66
+
67
+ **Correlation.** `fields` is read at each line that is written, by a winston format: no call
68
+ site passes an id. `traceLogFields(scope)` answers the trace and span ids inside a span, and
69
+ inside the execution scope the correlation id, the causation id, the origin, the actor and the
70
+ tenant. This package does not depend on the tracing package: any `() => LogFields` fits,
71
+ `() => logFieldsOf(scope.current())` included. A function that throws gives no field, never a
72
+ lost line. The fields of the line win over those of a child, which win over `fields`: the
73
+ format only fills the keys nobody gave. `StdoutLogger` and `@ddd-ts-kit/pino` differ on the
74
+ second step (there `fields` win over a child), so do not bind in a child a key that `fields`
75
+ answers if the logger may be swapped.
76
+
77
+ **An instance of your own.** The project that already configures winston keeps doing so, and
78
+ takes the formats it wants:
79
+
80
+ ```ts
81
+ import { CREDENTIAL_KEYS } from '@ddd-ts-kit/kernel';
82
+ import winston from 'winston';
83
+ import { WinstonLogger, correlationFields, redactCredentials } from '@ddd-ts-kit/winston';
84
+
85
+ const instance = winston.createLogger({
86
+ level: 'info',
87
+ format: winston.format.combine(
88
+ correlationFields(() => traceLogFields(scope)),
89
+ redactCredentials([...CREDENTIAL_KEYS, 'iban']), // after the formats that add fields
90
+ winston.format.timestamp(),
91
+ winston.format.json(),
92
+ ),
93
+ transports: [new winston.transports.Console()],
94
+ });
95
+
96
+ const logger = new WinstonLogger(instance);
97
+ ```
98
+
99
+ `createWinstonLogger` writes the instant under `time`, the level by its name and the message
100
+ under `message`, like the `StdoutLogger` of the aspects package and like `@ddd-ts-kit/pino`:
101
+ one dashboard reads the three. The keys of a line come out sorted: winston's JSON format
102
+ serializes with a stable stringifier.
103
+
104
+ ## Development mode
105
+
106
+ ```ts
107
+ createWinstonLogger({ level: 'debug', format: 'pretty', fields: () => traceLogFields(scope) });
108
+ ```
109
+
110
+ ```
111
+ 10:00:00.000 info order shipped { context: 'shipping', service: 'shop-api', correlationId: '9f2c' }
112
+ ```
113
+
114
+ The `pretty` format is made of winston's own combinators (`colorize`, `printf`), in the
115
+ process: no other package, the local time of day, the level in colour, the fields as Node
116
+ prints an object, a stack over several lines. Choose the format from configuration
117
+ (`LOG_FORMAT=pretty` in `.env`, `json` everywhere else).
118
+
119
+ ## Privacy
120
+
121
+ Log lines are where personal data leaks: they are written everywhere, read by everyone and kept
122
+ for long. Three things are done in the adapter, so that no call site has to remember them.
123
+
124
+ **Credentials are redacted** by a winston format, in the fields of a line, of a child and of
125
+ `fields` alike. The value of these keys is replaced by `[redacted]`, at the top of a line and
126
+ one level below (`headers.authorization`, `body.password`):
127
+
128
+ `password`, `authorization`, `cookie`, `set-cookie`, `token`, `secret`, `apiKey`, `api_key`,
129
+ `access_token`, `refresh_token`, `accessToken`, `refreshToken`, `client_secret`
130
+
131
+ `redact: ['iban', 'ssn']` adds to the list. Names are matched exactly, case included, and
132
+ nothing is looked for deeper than one level: log the part you mean, not a whole request. The
133
+ objects you pass are never written to: a nested one is copied when a key has to be hidden.
134
+
135
+ **An error passed as a field** (`logger.error('payment failed', { error })`: the `LogValue` of
136
+ the port admits an `Error`) is written as `describeError` of the kernel describes it, the same
137
+ from every logger of the port: its class as `type`, its `code` when
138
+ it is a `DomainError`, its `message`, its own `stack`, then its `cause` the same way, up to
139
+ eight deep. Each error of a chain is written once. Errors are looked for among the fields of
140
+ the line, not inside nested objects. `CREDENTIAL_KEYS`, `REDACTED` and `SCRUBBED_MESSAGE` are
141
+ those of the kernel too.
142
+
143
+ **Messages of unexpected errors can be dropped.** The message of an error is the first place
144
+ where personal data shows up: a driver quotes the row it refused. The default keeps it, because
145
+ a log line that says `[message removed]` costs every investigation. With one option only the
146
+ messages the project wrote itself survive, those of its `DomainError`s; class, code, stack and
147
+ cause remain for the others, and the stack loses its first line, which repeats the message:
148
+
149
+ ```ts
150
+ createWinstonLogger({ scrubMessages: true }); // or new WinstonLogger(instance, { scrubMessages: true })
151
+ ```
152
+
153
+ This is the `scrubMessages` every logger of the port takes (`StdoutLogger`, pino, winston,
154
+ `InMemoryLogger`) and the one of `@ddd-ts-kit/sentry`, with the same rule, the same
155
+ `SCRUBBED_MESSAGE` and the same trade-off: turn it on when the logs leave your hands or when no
156
+ operator should read that data there. What the message of a log line itself says stays yours:
157
+ write `order shipped` and put the identifiers in fields.
158
+
159
+ The kit itself never hands an error to a logger: the `logged` aspect, `loggingMiddleware` and
160
+ the job scheduler write `errorType` and `errorCode` only (`identifyError` of the kernel), never
161
+ a message, whatever `scrubMessages` says, and the error goes to the `ErrorReporter`. The
162
+ description above is for the error your own code chooses to log:
163
+ `logger.error('payment failed', { error })`.
164
+
165
+ ## Levels
166
+
167
+ | Port | winston (`npm` levels) | |
168
+ |---|---|---|
169
+ | `debug` | `debug` | below it, winston also has `silly` |
170
+ | `info` | `info` | the default threshold; `http` and `verbose` stand between `info` and `debug` |
171
+ | `warn` | `warn` | |
172
+ | `error` | `error` | |
173
+
174
+ The names are the same in the `cli` levels. An instance on the `syslog` levels has no `warn`:
175
+ say where it goes, or the line is dropped, since winston does not write at a level it does
176
+ not know.
177
+
178
+ ```ts
179
+ new WinstonLogger(instance, { levels: { warn: 'warning' } });
180
+ ```
181
+
182
+ A line below the threshold costs one comparison: the adapter asks `isLevelEnabled` first,
183
+ because winston alone would run every format before its transports drop the line. The
184
+ threshold of an instance changed while running (`instance.level = 'debug'`) is followed by the
185
+ adapter and by its children. `isLevelEnabled(level)` of the port answers the same question for
186
+ the mapped level: ask it before building fields that cost. Once the logger was flushed it
187
+ answers `false` for every level.
188
+
189
+ `child(fields)` is a winston child: the fields are bound once, and winston merges them under
190
+ the fields of each line. Three things differ from what winston does when called directly, all
191
+ on purpose: a field named `level` or `message` never replaces the level or the message of the
192
+ line; the message is never read as a format string (`%s`); and with `createWinstonLogger` a
193
+ field named `time` is overwritten by the instant of the line. One thing is winston's:
194
+ `service`, `environment` and `base` are its `defaultMeta`, which wins over a field of the
195
+ same name on a line or in a child, where the other two loggers of the kit let the line and
196
+ the child win.
197
+
198
+ ## Flush
199
+
200
+ `flush(): Promise<void>` ends the winston logger and resolves once each of its transports has
201
+ written what it was handed (their `finish` event). It never rejects, and a second call answers
202
+ the same promise. `app.stop()` calls it, last, after the modules stopped and the error reporter
203
+ was flushed: stopping logs too. Without the application of the di package, call it yourself at
204
+ that place.
205
+
206
+ The port calls a flush once, at shutdown, and leaves what is logged afterwards unspecified.
207
+ Here the flush is final: a winston logger does not reopen, so what is logged afterwards,
208
+ through this logger or one of its children, is dropped without an error, and `isLevelEnabled`
209
+ answers `false`. Whatever must be logged about the shutdown is logged before `app.stop()`
210
+ returns. A transport over a stream you opened yourself
211
+ (a socket, a file stream given to the `Stream` transport) has handed its lines to that stream
212
+ when `flush()` resolves: ending that stream is yours.
213
+
214
+ ## Performance
215
+
216
+ winston writes each line through a Node stream, a chain of formats and its transports, all in
217
+ the process and in the call: about 7 microseconds a line where `@ddd-ts-kit/pino` takes 3
218
+ (Node 24, a child, four correlation fields, three fields, a sink that discards), and more
219
+ with each format you add. Each line costs here one copy of its fields, one merge per level of
220
+ child, the format reading `fields`, the redaction pass over the top-level values, the instant,
221
+ and `JSON.stringify` through a safe serializer. That is nothing at a few hundred lines per
222
+ second and visible at tens of thousands: there, lower the level or take `@ddd-ts-kit/pino`.
223
+
224
+ Nothing is buffered by the adapter, so nothing is lost on a crash beyond what the transport
225
+ itself had not written. Log calls never throw: fields that cannot be read or serialized (a
226
+ getter that throws) give the line without them, marked `logError: "unserializable fields"`,
227
+ and `createWinstonLogger` holds its formats to the same rule inside the stream.
228
+
229
+ ## Which one
230
+
231
+ Take `StdoutLogger` of the aspects package when JSON lines on the standard output are all you
232
+ need: no dependency, the same line shape. Take `@ddd-ts-kit/pino` when logging volume matters
233
+ or when the framework already speaks pino (Fastify). Take winston when the team already runs
234
+ its transports and formats (files with rotation, syslog, a vendor transport) and wants to keep
235
+ them. The three implement one port and are held by one contract suite: swapping is one line of
236
+ the composition root.
237
+
238
+ ## NestJS
239
+
240
+ Nest has its own `LoggerService`, used by the framework for its own messages and by
241
+ `new Logger(context)` in Nest classes. Application code of the kit never needs it: handlers and
242
+ adapters receive the kernel `Logger`. To send what Nest itself logs through the same instance,
243
+ hand it a bridge of a few lines; the kit ships none, since it would bind this package to Nest:
244
+
245
+ ```ts
246
+ const nest = logger.child({ context: 'nest' });
247
+ const bridge: LoggerService = {
248
+ log: (message: unknown) => nest.info(String(message)),
249
+ warn: (message: unknown) => nest.warn(String(message)),
250
+ error: (message: unknown) => nest.error(String(message)),
251
+ debug: (message: unknown) => nest.debug(String(message)),
252
+ };
253
+
254
+ const app = await NestFactory.create(AppModule, { logger: bridge });
255
+ ```
256
+
257
+ `nest-winston` is the alternative when Nest classes log a lot: give it the winston instance
258
+ you built, and wrap the same instance in `new WinstonLogger(instance)` for the kit.
259
+
260
+ ## In a test
261
+
262
+ Nothing of this package: the test application binds `InMemoryLogger` of the kernel, and its
263
+ `entries` are data. To hold another adapter of the port to the same behaviour, run the contract:
264
+
265
+ ```ts
266
+ import { loggerContract } from '@ddd-ts-kit/testing/contracts';
267
+
268
+ loggerContract({ describe, it, expect, beforeEach }, {
269
+ name: 'WinstonLogger',
270
+ createLogger: (setup) => { // { level, fields, scrubMessages }
271
+ const written: string[] = [];
272
+ const stream = new Writable({
273
+ write: (chunk, _encoding, done) => {
274
+ written.push(String(chunk));
275
+ done();
276
+ },
277
+ });
278
+ const logger = createWinstonLogger({ ...setup, transports: [new winston.transports.Stream({ stream })] });
279
+
280
+ return { logger, lines: () => written.map((line) => lineOf(JSON.parse(line))) }; // { level, message, fields }
281
+ },
282
+ });
283
+ ```
@@ -0,0 +1,33 @@
1
+ import { type LogFields, type LogLevel } from '@ddd-ts-kit/kernel';
2
+ import winston from 'winston';
3
+ import { WinstonLogger, type WinstonLoggerOptions } from './winston-logger.js';
4
+ export interface CreateWinstonLoggerOptions extends Pick<WinstonLoggerOptions, 'scrubMessages'> {
5
+ /** Entries below it are not written. Default `info`. */
6
+ readonly level?: LogLevel;
7
+ /**
8
+ * `json`: one JSON object per line, what a container platform collects. `pretty`: one
9
+ * coloured line for a human, with winston's own formats. Default `json`.
10
+ */
11
+ readonly format?: 'json' | 'pretty';
12
+ /** Written on every line as `service`. */
13
+ readonly service?: string;
14
+ /** Written on every line as `environment`. */
15
+ readonly environment?: string;
16
+ /** Added to every line next to `service` and `environment`: version, region. */
17
+ readonly base?: LogFields;
18
+ /**
19
+ * Read at each line that is written: correlation fields of the work in progress, such as
20
+ * `traceLogFields` of the tracing package or `logFieldsOf` of the current execution context.
21
+ */
22
+ readonly fields?: () => LogFields;
23
+ /** Field names hidden in addition to `CREDENTIAL_KEYS`, at the top of a line and one level below. */
24
+ readonly redact?: readonly string[];
25
+ /** Where the lines go. Default: the console transport, on the standard output. */
26
+ readonly transports?: NonNullable<winston.LoggerOptions['transports']>;
27
+ }
28
+ /**
29
+ * A `WinstonLogger` on a winston logger with the defaults of the kit: `time` in ISO 8601,
30
+ * `level` by its name, `message`, then the fields; credentials redacted; errors described once.
31
+ */
32
+ export declare function createWinstonLogger(options?: CreateWinstonLoggerOptions): WinstonLogger;
33
+ //# sourceMappingURL=create-winston-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-winston-logger.d.ts","sourceRoot":"","sources":["../src/create-winston-logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,OAAO,MAAM,SAAS,CAAC;AAM9B,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE/E,MAAM,WAAW,0BAA2B,SAAQ,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC;IAC7F,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,SAAS,CAAC;IAClC,qGAAqG;IACrG,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;CACxE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,aAAa,CAW3F"}
@@ -0,0 +1,34 @@
1
+ import { CREDENTIAL_KEYS } from '@ddd-ts-kit/kernel';
2
+ import winston from 'winston';
3
+ import { correlationFields } from './formats/correlation-fields.js';
4
+ import { isoTime } from './formats/iso-time.js';
5
+ import { readableLine } from './formats/readable-line.js';
6
+ import { redactCredentials } from './formats/redact-credentials.js';
7
+ import { unbreakable } from './formats/unbreakable.js';
8
+ import { WinstonLogger } from './winston-logger.js';
9
+ /**
10
+ * A `WinstonLogger` on a winston logger with the defaults of the kit: `time` in ISO 8601,
11
+ * `level` by its name, `message`, then the fields; credentials redacted; errors described once.
12
+ */
13
+ export function createWinstonLogger(options = {}) {
14
+ const logger = winston.createLogger({
15
+ level: options.level ?? 'info',
16
+ levels: winston.config.npm.levels,
17
+ defaultMeta: baseFields(options),
18
+ format: unbreakable(lineFormat(options)),
19
+ transports: options.transports ?? [new winston.transports.Console()],
20
+ exitOnError: false,
21
+ });
22
+ return new WinstonLogger(logger, options.scrubMessages === undefined ? {} : { scrubMessages: options.scrubMessages });
23
+ }
24
+ function baseFields(options) {
25
+ return {
26
+ ...(options.service === undefined ? {} : { service: options.service }),
27
+ ...(options.environment === undefined ? {} : { environment: options.environment }),
28
+ ...options.base,
29
+ };
30
+ }
31
+ function lineFormat(options) {
32
+ return winston.format.combine(...(options.fields === undefined ? [] : [correlationFields(options.fields)]), redactCredentials([...CREDENTIAL_KEYS, ...(options.redact ?? [])]), isoTime(), options.format === 'pretty' ? readableLine() : winston.format.json());
33
+ }
34
+ //# sourceMappingURL=create-winston-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-winston-logger.js","sourceRoot":"","sources":["../src/create-winston-logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAiC,MAAM,oBAAoB,CAAC;AACpF,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AA2B/E;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAO,GAA+B,EAAE;IAC1E,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,MAAM;QAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM;QACjC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;QAChC,MAAM,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACxC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACpE,WAAW,EAAE,KAAK;KACnB,CAAC,CAAC;IAEH,OAAO,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;AACxH,CAAC;AAED,SAAS,UAAU,CAAC,OAAmC;IACrD,OAAO;QACL,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACtE,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;QAClF,GAAG,OAAO,CAAC,IAAI;KAChB,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,OAAmC;IACrD,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAC3B,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAC5E,iBAAiB,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAClE,OAAO,EAAE,EACT,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CACrE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type winston from 'winston';
2
+ /** Ends the logger and resolves once each of its transports has written what it was handed. */
3
+ export declare function endOf(logger: winston.Logger): Promise<void>;
4
+ //# sourceMappingURL=end-of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"end-of.d.ts","sourceRoot":"","sources":["../src/end-of.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,+FAA+F;AAC/F,wBAAsB,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKjE"}
package/dist/end-of.js ADDED
@@ -0,0 +1,15 @@
1
+ /** Ends the logger and resolves once each of its transports has written what it was handed. */
2
+ export async function endOf(logger) {
3
+ const finished = logger.transports.map(finishOf);
4
+ logger.end();
5
+ await Promise.all(finished);
6
+ }
7
+ function finishOf(transport) {
8
+ return new Promise((resolve) => {
9
+ transport.once('finish', resolve);
10
+ transport.once('error', () => {
11
+ resolve();
12
+ });
13
+ });
14
+ }
15
+ //# sourceMappingURL=end-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"end-of.js","sourceRoot":"","sources":["../src/end-of.ts"],"names":[],"mappings":"AAIA,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,MAAsB;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEjD,MAAM,CAAC,GAAG,EAAE,CAAC;IACb,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,QAAQ,CAAC,SAAoB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAElC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { LogFields } from '@ddd-ts-kit/kernel';
2
+ import winston from 'winston';
3
+ /**
4
+ * A winston format reading the fields of the work in progress at each line that is written,
5
+ * under the fields of the line and of its children. A source that throws (no active span, no
6
+ * execution context) gives no field, never a lost line.
7
+ */
8
+ export declare function correlationFields(fields: () => LogFields): winston.Logform.Format;
9
+ //# sourceMappingURL=correlation-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation-fields.d.ts","sourceRoot":"","sources":["../../src/formats/correlation-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAUjF"}
@@ -0,0 +1,25 @@
1
+ import winston from 'winston';
2
+ /**
3
+ * A winston format reading the fields of the work in progress at each line that is written,
4
+ * under the fields of the line and of its children. A source that throws (no active span, no
5
+ * execution context) gives no field, never a lost line.
6
+ */
7
+ export function correlationFields(fields) {
8
+ return winston.format((info) => {
9
+ for (const [key, value] of Object.entries(fieldsOrNone(fields))) {
10
+ if (info[key] === undefined) {
11
+ info[key] = value;
12
+ }
13
+ }
14
+ return info;
15
+ })();
16
+ }
17
+ function fieldsOrNone(fields) {
18
+ try {
19
+ return fields();
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ //# sourceMappingURL=correlation-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation-fields.js","sourceRoot":"","sources":["../../src/formats/correlation-fields.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAuB;IACvD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YAChE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACpB,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED,SAAS,YAAY,CAAC,MAAuB;IAC3C,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import winston from 'winston';
2
+ /** Stamps the line with `time` in ISO 8601, over a field of that name. */
3
+ export declare function isoTime(): winston.Logform.Format;
4
+ //# sourceMappingURL=iso-time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iso-time.d.ts","sourceRoot":"","sources":["../../src/formats/iso-time.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,0EAA0E;AAC1E,wBAAgB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAMhD"}
@@ -0,0 +1,9 @@
1
+ import winston from 'winston';
2
+ /** Stamps the line with `time` in ISO 8601, over a field of that name. */
3
+ export function isoTime() {
4
+ return winston.format((info) => {
5
+ info.time = new Date().toISOString();
6
+ return info;
7
+ })();
8
+ }
9
+ //# sourceMappingURL=iso-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iso-time.js","sourceRoot":"","sources":["../../src/formats/iso-time.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,0EAA0E;AAC1E,MAAM,UAAU,OAAO;IACrB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAErC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
@@ -0,0 +1,4 @@
1
+ import winston from 'winston';
2
+ /** One line for a human: the local time of day, the level in colour, the message, then the fields. */
3
+ export declare function readableLine(): winston.Logform.Format;
4
+ //# sourceMappingURL=readable-line.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readable-line.d.ts","sourceRoot":"","sources":["../../src/formats/readable-line.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAS9B,sGAAsG;AACtG,wBAAgB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAWrD"}
@@ -0,0 +1,18 @@
1
+ import { inspect } from 'node:util';
2
+ import winston from 'winston';
3
+ const TIME_OF_DAY = new Intl.DateTimeFormat('en-GB', {
4
+ hour: '2-digit',
5
+ minute: '2-digit',
6
+ second: '2-digit',
7
+ fractionalSecondDigits: 3,
8
+ });
9
+ /** One line for a human: the local time of day, the level in colour, the message, then the fields. */
10
+ export function readableLine() {
11
+ return winston.format.combine(winston.format.colorize(), winston.format.printf(({ level, message, time, ...rest }) => {
12
+ // By its string keys: winston keeps the level and the rendered line under symbols.
13
+ const fields = Object.fromEntries(Object.entries(rest));
14
+ const shown = Object.keys(fields).length === 0 ? '' : ` ${inspect(fields, { depth: 6, breakLength: 120 })}`;
15
+ return `${TIME_OF_DAY.format(new Date(String(time)))} ${level} ${String(message)}${shown}`;
16
+ }));
17
+ }
18
+ //# sourceMappingURL=readable-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readable-line.js","sourceRoot":"","sources":["../../src/formats/readable-line.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;IACnD,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,sBAAsB,EAAE,CAAC;CAC1B,CAAC,CAAC;AAEH,sGAAsG;AACtG,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,EACzB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;QAC1D,mFAAmF;QACnF,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;QAE5G,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,EAAE,CAAC;IAC7F,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import winston from 'winston';
2
+ /**
3
+ * A winston format hiding the value of the given keys at the top of a line and one level
4
+ * below. Names are matched exactly, case included. Place it after the formats that add fields.
5
+ */
6
+ export declare function redactCredentials(keys?: readonly string[]): winston.Logform.Format;
7
+ //# sourceMappingURL=redact-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-credentials.d.ts","sourceRoot":"","sources":["../../src/formats/redact-credentials.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,SAAS,MAAM,EAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAcnG"}
@@ -0,0 +1,32 @@
1
+ import { CREDENTIAL_KEYS, REDACTED } from '@ddd-ts-kit/kernel';
2
+ import winston from 'winston';
3
+ /**
4
+ * A winston format hiding the value of the given keys at the top of a line and one level
5
+ * below. Names are matched exactly, case included. Place it after the formats that add fields.
6
+ */
7
+ export function redactCredentials(keys = CREDENTIAL_KEYS) {
8
+ const hidden = new Set(keys);
9
+ return winston.format((info) => {
10
+ for (const [key, value] of Object.entries(info)) {
11
+ if (hidden.has(key)) {
12
+ info[key] = REDACTED;
13
+ }
14
+ else if (isRecord(value)) {
15
+ info[key] = withoutCredentials(value, hidden);
16
+ }
17
+ }
18
+ return info;
19
+ })();
20
+ }
21
+ function isRecord(value) {
22
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
23
+ }
24
+ /** A nested object belongs to the caller: it is copied when a key has to be hidden, never written to. */
25
+ function withoutCredentials(nested, hidden) {
26
+ const credentials = Object.keys(nested).filter((key) => hidden.has(key));
27
+ if (credentials.length === 0) {
28
+ return nested;
29
+ }
30
+ return { ...nested, ...Object.fromEntries(credentials.map((key) => [key, REDACTED])) };
31
+ }
32
+ //# sourceMappingURL=redact-credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-credentials.js","sourceRoot":"","sources":["../../src/formats/redact-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAI,GAAsB,eAAe;IACzE,MAAM,MAAM,GAAwB,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAElD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;YACvB,CAAC;iBAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC,CAAC;AAC1G,CAAC;AAED,yGAAyG;AACzG,SAAS,kBAAkB,CACzB,MAAyC,EACzC,MAA2B;IAE3B,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAEzE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;AACzF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import winston from 'winston';
2
+ /**
3
+ * The format, and when it throws on a line (a getter that throws, a `toJSON` that fails), that
4
+ * line as JSON without its fields. winston formats a line inside its stream: an exception
5
+ * there reaches the caller, or the process when the stream was holding the line back.
6
+ */
7
+ export declare function unbreakable(format: winston.Logform.Format): winston.Logform.Format;
8
+ //# sourceMappingURL=unbreakable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbreakable.d.ts","sourceRoot":"","sources":["../../src/formats/unbreakable.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAM9B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAYlF"}
@@ -0,0 +1,24 @@
1
+ import { UNWRITABLE_FIELDS } from '@ddd-ts-kit/kernel';
2
+ import winston from 'winston';
3
+ import { isoTime } from './iso-time.js';
4
+ /** The key under which winston and its transports read the level of a line (package `triple-beam`). */
5
+ const LEVEL = Symbol.for('level');
6
+ /**
7
+ * The format, and when it throws on a line (a getter that throws, a `toJSON` that fails), that
8
+ * line as JSON without its fields. winston formats a line inside its stream: an exception
9
+ * there reaches the caller, or the process when the stream was holding the line back.
10
+ */
11
+ export function unbreakable(format) {
12
+ const bare = winston.format.combine(isoTime(), winston.format.json());
13
+ return {
14
+ transform: (info, options) => {
15
+ try {
16
+ return format.transform(info, options);
17
+ }
18
+ catch {
19
+ return bare.transform({ level: info.level, message: info.message, ...UNWRITABLE_FIELDS, [LEVEL]: info[LEVEL] });
20
+ }
21
+ },
22
+ };
23
+ }
24
+ //# sourceMappingURL=unbreakable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbreakable.js","sourceRoot":"","sources":["../../src/formats/unbreakable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,uGAAuG;AACvG,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAElC;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAA8B;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAEtE,OAAO;QACL,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;YAC3B,IAAI,CAAC;gBACH,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,iBAAiB,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { createWinstonLogger, type CreateWinstonLoggerOptions } from './create-winston-logger.js';
2
+ export { correlationFields } from './formats/correlation-fields.js';
3
+ export { redactCredentials } from './formats/redact-credentials.js';
4
+ export { WinstonLogger, type WinstonLoggerOptions } from './winston-logger.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { createWinstonLogger } from './create-winston-logger.js';
2
+ export { correlationFields } from './formats/correlation-fields.js';
3
+ export { redactCredentials } from './formats/redact-credentials.js';
4
+ export { WinstonLogger } from './winston-logger.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAmC,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { type LogFields, type LogLevel, type Logger } from '@ddd-ts-kit/kernel';
2
+ import type winston from 'winston';
3
+ export interface WinstonLoggerOptions {
4
+ /**
5
+ * Writes `SCRUBBED_MESSAGE` for the message of every logged error that is not a
6
+ * `DomainError`. Default `false`: see "Privacy" in the README for the trade-off.
7
+ */
8
+ readonly scrubMessages?: boolean;
9
+ /**
10
+ * The winston level each level of the port is written at. Default: the level of the same
11
+ * name, which the `npm` and `cli` levels of winston have. An instance on the `syslog`
12
+ * levels needs `{ warn: 'warning' }`: a level the instance does not know is dropped.
13
+ */
14
+ readonly levels?: Partial<Readonly<Record<LogLevel, string>>>;
15
+ }
16
+ /**
17
+ * The `Logger` port on a winston logger: a child is a winston child, and the formats and the
18
+ * transports the instance was configured with apply to every line. `createWinstonLogger`
19
+ * builds one with the defaults of the kit; build it yourself around an instance you configured.
20
+ */
21
+ export declare class WinstonLogger implements Logger {
22
+ private readonly winston;
23
+ private readonly options;
24
+ private root;
25
+ private ended;
26
+ constructor(winston: winston.Logger, options?: WinstonLoggerOptions);
27
+ debug(message: string, fields?: LogFields): void;
28
+ info(message: string, fields?: LogFields): void;
29
+ warn(message: string, fields?: LogFields): void;
30
+ error(message: string, fields?: LogFields): void;
31
+ child(fields: LogFields): WinstonLogger;
32
+ /**
33
+ * Whether winston would hand an entry of that level to a transport. winston filters by level
34
+ * in its transports, after the formats: asked here, a dropped entry costs nothing. `false`
35
+ * for every level once the logger was flushed.
36
+ */
37
+ isLevelEnabled(level: LogLevel): boolean;
38
+ /**
39
+ * Ends the winston logger and resolves once its transports have written everything. Final:
40
+ * what is logged afterwards, through this logger or a child, is dropped without a throw. A
41
+ * second call answers the same promise. Never rejects.
42
+ */
43
+ flush(): Promise<void>;
44
+ private nativeChild;
45
+ private write;
46
+ private nativeLevel;
47
+ private writeWithoutFields;
48
+ }
49
+ //# sourceMappingURL=winston-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"winston-logger.d.ts","sourceRoot":"","sources":["../src/winston-logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0C,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAGnC,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAC/D;AAED;;;;GAIG;AACH,qBAAa,aAAc,YAAW,MAAM;IAKxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAA4B;IAEzC,YACmB,OAAO,EAAE,OAAO,CAAC,MAAM,EACvB,OAAO,GAAE,oBAAyB,EACjD;IAEJ,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE/C;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE9C;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE9C;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAE/C;IAED,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAMtC;IAED;;;;OAIG;IACH,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAEvC;IAED;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAIrB;IAED,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,KAAK;IAcb,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,kBAAkB;CAO3B"}
@@ -0,0 +1,83 @@
1
+ import { UNWRITABLE_FIELDS, describeErrorFields } from '@ddd-ts-kit/kernel';
2
+ import { endOf } from './end-of.js';
3
+ /**
4
+ * The `Logger` port on a winston logger: a child is a winston child, and the formats and the
5
+ * transports the instance was configured with apply to every line. `createWinstonLogger`
6
+ * builds one with the defaults of the kit; build it yourself around an instance you configured.
7
+ */
8
+ export class WinstonLogger {
9
+ winston;
10
+ options;
11
+ root = this;
12
+ ended;
13
+ constructor(winston, options = {}) {
14
+ this.winston = winston;
15
+ this.options = options;
16
+ }
17
+ debug(message, fields) {
18
+ this.write('debug', message, fields);
19
+ }
20
+ info(message, fields) {
21
+ this.write('info', message, fields);
22
+ }
23
+ warn(message, fields) {
24
+ this.write('warn', message, fields);
25
+ }
26
+ error(message, fields) {
27
+ this.write('error', message, fields);
28
+ }
29
+ child(fields) {
30
+ const child = new WinstonLogger(this.nativeChild(fields), this.options);
31
+ child.root = this.root;
32
+ return child;
33
+ }
34
+ /**
35
+ * Whether winston would hand an entry of that level to a transport. winston filters by level
36
+ * in its transports, after the formats: asked here, a dropped entry costs nothing. `false`
37
+ * for every level once the logger was flushed.
38
+ */
39
+ isLevelEnabled(level) {
40
+ return this.root.ended === undefined && this.winston.isLevelEnabled(this.nativeLevel(level));
41
+ }
42
+ /**
43
+ * Ends the winston logger and resolves once its transports have written everything. Final:
44
+ * what is logged afterwards, through this logger or a child, is dropped without a throw. A
45
+ * second call answers the same promise. Never rejects.
46
+ */
47
+ flush() {
48
+ this.root.ended ??= endOf(this.root.winston).catch(() => undefined);
49
+ return this.root.ended;
50
+ }
51
+ nativeChild(fields) {
52
+ try {
53
+ return this.winston.child(describeErrorFields(fields, this.options));
54
+ }
55
+ catch {
56
+ return this.winston.child(UNWRITABLE_FIELDS);
57
+ }
58
+ }
59
+ write(level, message, fields = {}) {
60
+ if (!this.isLevelEnabled(level)) {
61
+ return;
62
+ }
63
+ const native = this.nativeLevel(level);
64
+ try {
65
+ this.winston.log({ ...describeErrorFields(fields, this.options), level: native, message });
66
+ }
67
+ catch {
68
+ this.writeWithoutFields(native, message);
69
+ }
70
+ }
71
+ nativeLevel(level) {
72
+ return this.options.levels?.[level] ?? level;
73
+ }
74
+ writeWithoutFields(level, message) {
75
+ try {
76
+ this.winston.log({ ...UNWRITABLE_FIELDS, level, message });
77
+ }
78
+ catch {
79
+ return;
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=winston-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"winston-logger.js","sourceRoot":"","sources":["../src/winston-logger.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@ddd-ts-kit/winston",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Adapter of the kernel Logger port on winston, with trace and execution correlation on every line.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/gbourgeat/ddd-ts-kit.git",
9
+ "directory": "packages/winston"
10
+ },
11
+ "homepage": "https://github.com/gbourgeat/ddd-ts-kit/tree/main/packages/winston#readme",
12
+ "bugs": "https://github.com/gbourgeat/ddd-ts-kit/issues",
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "provenance": true
16
+ },
17
+ "type": "module",
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "dependencies": {
29
+ "@ddd-ts-kit/kernel": "0.1.1"
30
+ },
31
+ "devDependencies": {
32
+ "@ddd-ts-kit/testing": "0.1.1",
33
+ "winston": "^3.19.0"
34
+ },
35
+ "peerDependencies": {
36
+ "winston": "^3.19.0"
37
+ }
38
+ }