@logtape/adaptor-winston 1.0.0-dev.257

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.
package/mod.ts ADDED
@@ -0,0 +1,453 @@
1
+ /**
2
+ * A winston adapter for LogTape logging library.
3
+ *
4
+ * This module provides functionality to integrate LogTape with winston,
5
+ * allowing LogTape logs to be forwarded to winston loggers while maintaining
6
+ * structured logging capabilities and category information.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { configure } from "@logtape/logtape";
11
+ * import winston from "winston";
12
+ * import { getWinstonSink } from "@logtape/adaptor-winston";
13
+ *
14
+ * const winstonLogger = winston.createLogger({
15
+ * level: "info",
16
+ * format: winston.format.json(),
17
+ * transports: [new winston.transports.Console()]
18
+ * });
19
+ *
20
+ * await configure({
21
+ * sinks: {
22
+ * winston: getWinstonSink(winstonLogger)
23
+ * },
24
+ * loggers: [
25
+ * { category: "myapp", sinks: ["winston"] }
26
+ * ]
27
+ * });
28
+ * ```
29
+ *
30
+ * @module
31
+ * @since 1.0.0
32
+ */
33
+ import {
34
+ configureSync,
35
+ type LogLevel,
36
+ type LogRecord,
37
+ type Sink,
38
+ } from "@logtape/logtape";
39
+ import winston, { type LeveledLogMethod } from "winston";
40
+ import { inspect } from "node:util";
41
+
42
+ /**
43
+ * Logger interface for Winston-compatible loggers.
44
+ * @since 1.0.0
45
+ */
46
+ export interface Logger {
47
+ error: LeveledLogMethod;
48
+ warn: LeveledLogMethod;
49
+ info: LeveledLogMethod;
50
+ http: LeveledLogMethod;
51
+ verbose: LeveledLogMethod;
52
+ debug: LeveledLogMethod;
53
+ silly: LeveledLogMethod;
54
+ }
55
+
56
+ /**
57
+ * Configuration options for the winston sink.
58
+ *
59
+ * @example Basic usage with default options
60
+ * ```typescript
61
+ * const sink = getWinstonSink(winstonLogger);
62
+ * ```
63
+ *
64
+ * @example Custom level mapping
65
+ * ```typescript
66
+ * const sink = getWinstonSink(winstonLogger, {
67
+ * levelsMap: {
68
+ * "trace": "debug",
69
+ * "debug": "debug",
70
+ * "info": "info",
71
+ * "warning": "warn",
72
+ * "error": "error",
73
+ * "fatal": "error"
74
+ * }
75
+ * });
76
+ * ```
77
+ *
78
+ * @example Custom category formatting
79
+ * ```typescript
80
+ * const sink = getWinstonSink(winstonLogger, {
81
+ * category: {
82
+ * separator: ".",
83
+ * position: "start",
84
+ * decorator: "[]"
85
+ * }
86
+ * });
87
+ * ```
88
+ *
89
+ * @since 1.0.0
90
+ */
91
+ export interface WinstonSinkOptions {
92
+ /**
93
+ * Mapping between LogTape log levels and winston log levels.
94
+ *
95
+ * By default, LogTape levels are mapped as follows:
96
+ *
97
+ * - `trace` → `silly`
98
+ * - `debug` → `debug`
99
+ * - `info` → `info`
100
+ * - `warning` → `warn`
101
+ * - `error` → `error`
102
+ * - `fatal` → `error`
103
+ */
104
+ readonly levelsMap?: Readonly<Record<LogLevel, keyof Logger>>;
105
+
106
+ /**
107
+ * Configuration for how LogTape categories are handled in winston logs.
108
+ *
109
+ * - `false` or `undefined`: Categories are not included in the log message
110
+ * - `true`: Categories are included with default formatting (":" decorator at start)
111
+ * - `CategoryOptions`: Custom category formatting configuration
112
+ *
113
+ * @default undefined
114
+ */
115
+ readonly category?: boolean | CategoryOptions;
116
+
117
+ /**
118
+ * Custom formatter for interpolated values in log messages.
119
+ *
120
+ * This function is used to convert values that are interpolated into
121
+ * log messages (e.g., the `name` in
122
+ * `logger.info("Hello, {name}!", { name: "world" })`).
123
+ *
124
+ * @param value The value to format
125
+ * @returns A string representation of the value
126
+ * @default `inspect` (from `node:util` module)
127
+ */
128
+ readonly valueFormatter?: (value: unknown) => string;
129
+ }
130
+
131
+ /**
132
+ * Configuration options for formatting LogTape categories in winston log messages.
133
+ *
134
+ * Categories in LogTape represent a hierarchical namespace for loggers
135
+ * (e.g., ["myapp", "database", "connection"]). This interface controls
136
+ * how these categories are formatted when included in winston log messages.
137
+ *
138
+ * @example Default formatting
139
+ * ```typescript
140
+ * // With category ["myapp", "db"] and default options:
141
+ * // Output: "myapp·db: User logged in"
142
+ * const options: CategoryOptions = {};
143
+ * ```
144
+ *
145
+ * @example Custom separator and decorator
146
+ * ```typescript
147
+ * // With category ["myapp", "db"] and custom options:
148
+ * // Output: "[myapp.db] User logged in"
149
+ * const options: CategoryOptions = {
150
+ * separator: ".",
151
+ * decorator: "[]"
152
+ * };
153
+ * ```
154
+ *
155
+ * @example Category at end
156
+ * ```typescript
157
+ * // With category ["myapp", "db"] and position at end:
158
+ * // Output: "User logged in: myapp·db"
159
+ * const options: CategoryOptions = {
160
+ * position: "end"
161
+ * };
162
+ * ```
163
+ *
164
+ * @since 1.0.0
165
+ */
166
+ export interface CategoryOptions {
167
+ /**
168
+ * The separator used to join category parts when multiple categories exist.
169
+ * @default "·"
170
+ */
171
+ readonly separator?: string;
172
+
173
+ /**
174
+ * Where to position the category in the log message.
175
+ * - `"start"`: Category appears at the beginning of the message
176
+ * - `"end"`: Category appears at the end of the message
177
+ * @default "start"
178
+ */
179
+ readonly position?: "start" | "end";
180
+
181
+ /**
182
+ * The decorator used to format the category in the log message.
183
+ * - `"[]"`: [category] format
184
+ * - `"()"`: (category) format
185
+ * - `"<>"`: <category> format
186
+ * - `"{}"`: {category} format
187
+ * - `":"`: category: format
188
+ * - `"-"`: category - format
189
+ * - `"|"`: category | format
190
+ * - `"/"`: category / format
191
+ * - `""`: category format (no decoration)
192
+ * @default ":"
193
+ */
194
+ readonly decorator?: "[]" | "()" | "<>" | "{}" | ":" | "-" | "|" | "/" | "";
195
+ }
196
+
197
+ const DEFAULT_LEVELS_MAP: Readonly<Record<LogLevel, keyof Logger>> = {
198
+ "trace": "silly",
199
+ "debug": "debug",
200
+ "info": "info",
201
+ "warning": "warn",
202
+ "error": "error",
203
+ "fatal": "error",
204
+ };
205
+
206
+ /**
207
+ * Creates a LogTape sink that forwards log records to a winston logger.
208
+ *
209
+ * This function creates a sink function that can be used with LogTape's
210
+ * configuration system. The sink will format LogTape log records and
211
+ * forward them to the provided winston logger instance.
212
+ *
213
+ * @example Basic usage
214
+ * ```typescript
215
+ * import winston from "winston";
216
+ * import { configure } from "@logtape/logtape";
217
+ * import { getWinstonSink } from "@logtape/adaptor-winston";
218
+ *
219
+ * const winstonLogger = winston.createLogger({
220
+ * level: "info",
221
+ * format: winston.format.combine(
222
+ * winston.format.timestamp(),
223
+ * winston.format.json()
224
+ * ),
225
+ * transports: [new winston.transports.Console()]
226
+ * });
227
+ *
228
+ * await configure({
229
+ * sinks: {
230
+ * winston: getWinstonSink(winstonLogger)
231
+ * },
232
+ * loggers: [
233
+ * { category: ["myapp"], sinks: ["winston"] }
234
+ * ]
235
+ * });
236
+ * ```
237
+ *
238
+ * @example With custom options
239
+ * ```typescript
240
+ * const sink = getWinstonSink(winstonLogger, {
241
+ * category: {
242
+ * separator: ".",
243
+ * position: "start",
244
+ * decorator: "[]"
245
+ * },
246
+ * levelsMap: {
247
+ * "trace": "debug", // Map trace to debug instead of silly
248
+ * "debug": "debug",
249
+ * "info": "info",
250
+ * "warning": "warn",
251
+ * "error": "error",
252
+ * "fatal": "error"
253
+ * }
254
+ * });
255
+ * ```
256
+ *
257
+ * @param logger The winston logger instance to forward logs to. Must implement
258
+ * the Logger interface with error, warn, info, http, verbose,
259
+ * debug, and silly methods.
260
+ * @param options Configuration options for the sink behavior.
261
+ * @returns A sink function that can be used with LogTape's configure() function.
262
+ * @since 1.0.0
263
+ */
264
+ export function getWinstonSink(
265
+ logger: Logger,
266
+ options: WinstonSinkOptions = {},
267
+ ): Sink {
268
+ const { levelsMap = DEFAULT_LEVELS_MAP, valueFormatter = inspect } = options;
269
+ const categoryOptions = !options.category
270
+ ? undefined
271
+ : typeof options.category === "object"
272
+ ? options.category
273
+ : {};
274
+ const category: Required<CategoryOptions> | undefined =
275
+ categoryOptions == null ? undefined : {
276
+ separator: categoryOptions.separator ?? "·",
277
+ position: categoryOptions.position ?? "start",
278
+ decorator: categoryOptions.decorator ?? ":",
279
+ };
280
+
281
+ return (record: LogRecord) => {
282
+ const level = levelsMap[record.level];
283
+ let message = "";
284
+ if (category?.position === "start" && record.category.length > 0) {
285
+ const joinedCategory = record.category.join(category.separator);
286
+ message += category.decorator === "[]"
287
+ ? `[${joinedCategory}] `
288
+ : category.decorator === "()"
289
+ ? `(${joinedCategory}) `
290
+ : category.decorator === "<>"
291
+ ? `<${joinedCategory}> `
292
+ : category.decorator === "{}"
293
+ ? `{${joinedCategory}} `
294
+ : category.decorator === ":"
295
+ ? `${joinedCategory}: `
296
+ : category.decorator === "-"
297
+ ? `${joinedCategory} - `
298
+ : category.decorator === "|"
299
+ ? `${joinedCategory} | `
300
+ : category.decorator === "/"
301
+ ? `${joinedCategory} / `
302
+ : `${joinedCategory} `;
303
+ }
304
+ for (let i = 0; i < record.message.length; i += 2) {
305
+ message += record.message[i];
306
+ if (i + 1 < record.message.length) {
307
+ message += valueFormatter(record.message[i + 1]);
308
+ }
309
+ }
310
+ if (category?.position === "end" && record.category.length > 0) {
311
+ const joinedCategory = record.category.join(category.separator);
312
+ message += category.decorator === "[]"
313
+ ? ` [${joinedCategory}]`
314
+ : category.decorator === "()"
315
+ ? ` (${joinedCategory})`
316
+ : category.decorator === "<>"
317
+ ? ` <${joinedCategory}>`
318
+ : category.decorator === "{}"
319
+ ? ` {${joinedCategory}}`
320
+ : category.decorator === ":"
321
+ ? `: ${joinedCategory}`
322
+ : category.decorator === "-"
323
+ ? ` - ${joinedCategory}`
324
+ : category.decorator === "|"
325
+ ? ` | ${joinedCategory}`
326
+ : category.decorator === "/"
327
+ ? ` / ${joinedCategory}`
328
+ : ` ${joinedCategory}`;
329
+ }
330
+ logger[level](message, record.properties);
331
+ };
332
+ }
333
+
334
+ /**
335
+ * Automatically configures LogTape to route all logs to a winston logger.
336
+ *
337
+ * This is a convenience function that automatically sets up LogTape to forward
338
+ * all log records to a winston logger instance. By default, it uses winston's
339
+ * default logger, but you can provide a custom logger as the first parameter.
340
+ *
341
+ * @param logger The winston logger instance to use.
342
+ * @param options Configuration options for the winston sink behavior.
343
+ *
344
+ * @example Basic auto-configuration with default logger
345
+ * ```typescript
346
+ * import { install } from "@logtape/adaptor-winston";
347
+ *
348
+ * // Automatically route all LogTape logs to winston's default logger
349
+ * install();
350
+ *
351
+ * // Now any LogTape-enabled library will log through winston
352
+ * import { getLogger } from "@logtape/logtape";
353
+ * const logger = getLogger("my-app");
354
+ * logger.info("This will be logged through winston");
355
+ * ```
356
+ *
357
+ * @example Auto-configuration with custom winston logger
358
+ * ```typescript
359
+ * import winston from "winston";
360
+ * import { install } from "@logtape/adaptor-winston";
361
+ *
362
+ * const customLogger = winston.createLogger({
363
+ * level: "info",
364
+ * format: winston.format.combine(
365
+ * winston.format.timestamp(),
366
+ * winston.format.json()
367
+ * ),
368
+ * transports: [
369
+ * new winston.transports.Console(),
370
+ * new winston.transports.File({ filename: "app.log" })
371
+ * ]
372
+ * });
373
+ *
374
+ * // Install with custom logger
375
+ * install(customLogger);
376
+ * ```
377
+ *
378
+ * @example Auto-configuration with custom options
379
+ * ```typescript
380
+ * import { install } from "@logtape/adaptor-winston";
381
+ *
382
+ * install(undefined, {
383
+ * category: {
384
+ * position: "start",
385
+ * decorator: "[]",
386
+ * separator: "."
387
+ * },
388
+ * levelsMap: {
389
+ * "trace": "debug" // Map LogTape trace to winston debug
390
+ * }
391
+ * });
392
+ * ```
393
+ *
394
+ * @example Custom logger with custom options
395
+ * ```typescript
396
+ * import winston from "winston";
397
+ * import { install } from "@logtape/adaptor-winston";
398
+ *
399
+ * const customLogger = winston.createLogger({
400
+ * transports: [new winston.transports.Console()]
401
+ * });
402
+ *
403
+ * install(customLogger, {
404
+ * category: { position: "start", decorator: "[]" }
405
+ * });
406
+ * ```
407
+ *
408
+ * @since 1.0.0
409
+ */
410
+ export function install(
411
+ logger: Logger,
412
+ options?: WinstonSinkOptions,
413
+ ): void;
414
+
415
+ /**
416
+ * Configures LogTape to route all logs to winston's default logger.
417
+ *
418
+ * @param options Optional configuration for the winston sink behavior.
419
+ * @since 1.0.0
420
+ */
421
+ export function install(
422
+ options?: WinstonSinkOptions,
423
+ ): void;
424
+
425
+ export function install(
426
+ loggerOrOptions?: Logger | WinstonSinkOptions,
427
+ options: WinstonSinkOptions = {},
428
+ ): void {
429
+ let logger: Logger;
430
+ let sinkOptions: WinstonSinkOptions;
431
+
432
+ // Handle overloaded parameters
433
+ if (
434
+ loggerOrOptions && ("error" in loggerOrOptions || "info" in loggerOrOptions)
435
+ ) {
436
+ // First parameter is a Logger
437
+ logger = loggerOrOptions as Logger;
438
+ sinkOptions = options;
439
+ } else {
440
+ // First parameter is WinstonSinkOptions or undefined
441
+ logger = winston;
442
+ sinkOptions = (loggerOrOptions as WinstonSinkOptions) || {};
443
+ }
444
+
445
+ configureSync({
446
+ sinks: {
447
+ winston: getWinstonSink(logger, sinkOptions),
448
+ },
449
+ loggers: [
450
+ { category: [], sinks: ["winston"] },
451
+ ],
452
+ });
453
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@logtape/adaptor-winston",
3
+ "version": "1.0.0-dev.257+f86501ac",
4
+ "description": "winston adapter for LogTape logging library",
5
+ "keywords": [
6
+ "logging",
7
+ "log",
8
+ "logger",
9
+ "winston",
10
+ "adapter",
11
+ "logtape",
12
+ "sink"
13
+ ],
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Hong Minhee",
17
+ "email": "hong@minhee.org",
18
+ "url": "https://hongminhee.org/"
19
+ },
20
+ "homepage": "https://logtape.org/",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/dahlia/logtape.git",
24
+ "directory": "adaptor-winston/"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/dahlia/logtape/issues"
28
+ },
29
+ "funding": [
30
+ "https://github.com/sponsors/dahlia"
31
+ ],
32
+ "type": "module",
33
+ "module": "./dist/mod.js",
34
+ "main": "./dist/mod.cjs",
35
+ "types": "./dist/mod.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": {
39
+ "import": "./dist/mod.d.ts",
40
+ "require": "./dist/mod.d.cts"
41
+ },
42
+ "import": "./dist/mod.js",
43
+ "require": "./dist/mod.cjs"
44
+ },
45
+ "./install": {
46
+ "types": {
47
+ "import": "./dist/install.d.ts",
48
+ "require": "./dist/install.d.cts"
49
+ },
50
+ "import": "./dist/install.js",
51
+ "require": "./dist/install.cjs"
52
+ },
53
+ "./package.json": "./package.json"
54
+ },
55
+ "sideEffects": [
56
+ "./dist/install.*"
57
+ ],
58
+ "peerDependencies": {
59
+ "winston": "^3.17.0",
60
+ "@logtape/logtape": "1.0.0-dev.257+f86501ac"
61
+ },
62
+ "devDependencies": {
63
+ "@alinea/suite": "^0.6.3",
64
+ "@std/assert": "npm:@jsr/std__assert@^1.0.13",
65
+ "@std/async": "npm:@jsr/std__async@^1.0.13",
66
+ "tsdown": "^0.12.7",
67
+ "typescript": "^5.8.3",
68
+ "winston": "^3.17.0",
69
+ "winston-transport": "^4.9.0"
70
+ },
71
+ "scripts": {
72
+ "build": "tsdown",
73
+ "prepublish": "tsdown",
74
+ "test": "tsdown && node --experimental-transform-types --test",
75
+ "test:bun": "tsdown && bun test",
76
+ "test:deno": "deno test --allow-env",
77
+ "test-all": "tsdown && node --experimental-transform-types --test && bun test && deno test"
78
+ }
79
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: ["mod.ts", "install.ts"],
5
+ dts: {
6
+ sourcemap: true,
7
+ },
8
+ format: ["esm", "cjs"],
9
+ platform: "node",
10
+ unbundle: true,
11
+ });