@logtape/adaptor-pino 1.2.2 → 1.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/adaptor-pino",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Pino adapter for LogTape logging library",
5
5
  "keywords": [
6
6
  "logging",
@@ -45,9 +45,12 @@
45
45
  "./package.json": "./package.json"
46
46
  },
47
47
  "sideEffects": false,
48
+ "files": [
49
+ "dist/"
50
+ ],
48
51
  "peerDependencies": {
49
52
  "pino": "^9.7.0",
50
- "@logtape/logtape": "^1.2.2"
53
+ "@logtape/logtape": "^1.2.3"
51
54
  },
52
55
  "devDependencies": {
53
56
  "@alinea/suite": "^0.6.3",
package/deno.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "name": "@logtape/adaptor-pino",
3
- "version": "1.2.2",
4
- "license": "MIT",
5
- "exports": "./src/mod.ts",
6
- "imports": {
7
- "pino-abstract-transport": "npm:pino-abstract-transport@^2.0.0"
8
- },
9
- "exclude": [
10
- "coverage/",
11
- "npm/",
12
- ".dnt-import-map.json"
13
- ],
14
- "tasks": {
15
- "build": "pnpm build",
16
- "test": "deno test --allow-env --allow-sys",
17
- "test:node": {
18
- "dependencies": [
19
- "build"
20
- ],
21
- "command": "node --experimental-transform-types --test"
22
- },
23
- "test:bun": {
24
- "dependencies": [
25
- "build"
26
- ],
27
- "command": "bun test"
28
- },
29
- "test-all": {
30
- "dependencies": [
31
- "test",
32
- "test:node",
33
- "test:bun"
34
- ]
35
- }
36
- }
37
- }
package/src/mod.test.ts DELETED
@@ -1,405 +0,0 @@
1
- import { suite } from "@alinea/suite";
2
- import { assertEquals } from "@std/assert/equals";
3
- import { assertGreaterOrEqual } from "@std/assert/greater-or-equal";
4
- import { assertLessOrEqual } from "@std/assert/less-or-equal";
5
- import { delay } from "@std/async/delay";
6
- import os from "node:os";
7
- import process from "node:process";
8
- import { pino } from "pino";
9
- import build from "pino-abstract-transport";
10
- import { getPinoSink } from "./mod.ts";
11
-
12
- const test = suite(import.meta);
13
-
14
- interface PinoLog {
15
- level: number;
16
- time: number;
17
- pid: number;
18
- hostname: string;
19
- value: Record<string, unknown>;
20
- msg: string;
21
- [key: string]: unknown; // Allow additional properties from LogTape properties
22
- }
23
-
24
- test("getPinoSink(): basic scenario", async () => {
25
- const buffer: PinoLog[] = [];
26
- const dest = build(async (source) => {
27
- for await (const obj of source) {
28
- buffer.push(obj);
29
- }
30
- }, {});
31
- const logger = pino({
32
- useOnlyCustomLevels: false,
33
- }, dest);
34
- const sink = getPinoSink(logger);
35
- const before = Date.now();
36
- sink({
37
- category: ["test", "category"],
38
- level: "info",
39
- message: ["Test log: ", { foo: 123 }, ""],
40
- properties: { value: { foo: 123 } },
41
- rawMessage: "Test log: {value}",
42
- timestamp: Date.now(),
43
- });
44
- const after = Date.now();
45
- logger.flush();
46
- await delay(500);
47
- assertEquals(buffer, [
48
- {
49
- level: 30,
50
- time: buffer[0]?.time,
51
- pid: process.pid,
52
- hostname: os.hostname(),
53
- value: { foo: 123 },
54
- msg: 'Test log: [{"foo":123}]',
55
- },
56
- ]);
57
- assertGreaterOrEqual(buffer[0].time, before);
58
- assertLessOrEqual(buffer[0].time, after);
59
- });
60
-
61
- test("getPinoSink(): log level mappings", async () => {
62
- const buffer: PinoLog[] = [];
63
- const dest = build(async (source) => {
64
- for await (const obj of source) {
65
- buffer.push(obj);
66
- }
67
- }, {});
68
- const logger = pino({
69
- useOnlyCustomLevels: false,
70
- }, dest);
71
- const sink = getPinoSink(logger);
72
-
73
- const logLevels = [
74
- { logTapeLevel: "info", expectedPinoLevel: 30 },
75
- { logTapeLevel: "warning", expectedPinoLevel: 40 },
76
- { logTapeLevel: "error", expectedPinoLevel: 50 },
77
- { logTapeLevel: "fatal", expectedPinoLevel: 60 },
78
- ] as const;
79
-
80
- for (const { logTapeLevel } of logLevels) {
81
- sink({
82
- category: ["test"],
83
- level: logTapeLevel,
84
- message: [`${logTapeLevel} message`],
85
- properties: {},
86
- rawMessage: `${logTapeLevel} message`,
87
- timestamp: Date.now(),
88
- });
89
- }
90
-
91
- logger.flush();
92
- await delay(100);
93
-
94
- assertEquals(buffer.length, 4);
95
- for (let i = 0; i < logLevels.length; i++) {
96
- assertEquals(
97
- buffer[i].level,
98
- logLevels[i].expectedPinoLevel,
99
- `Level mapping failed for ${logLevels[i].logTapeLevel}`,
100
- );
101
- }
102
- });
103
-
104
- test("getPinoSink(): category option - false/undefined", async () => {
105
- const buffer: PinoLog[] = [];
106
- const dest = build(async (source) => {
107
- for await (const obj of source) {
108
- buffer.push(obj);
109
- }
110
- }, {});
111
- const logger = pino({
112
- useOnlyCustomLevels: false,
113
- }, dest);
114
-
115
- // Test with category: false
116
- const sinkFalse = getPinoSink(logger, { category: false });
117
- sinkFalse({
118
- category: ["test", "category"],
119
- level: "info",
120
- message: ["Test message"],
121
- properties: {},
122
- rawMessage: "Test message",
123
- timestamp: Date.now(),
124
- });
125
-
126
- // Test with no options (undefined)
127
- const sinkUndefined = getPinoSink(logger);
128
- sinkUndefined({
129
- category: ["test", "category"],
130
- level: "info",
131
- message: ["Test message 2"],
132
- properties: {},
133
- rawMessage: "Test message 2",
134
- timestamp: Date.now(),
135
- });
136
-
137
- logger.flush();
138
- await delay(100);
139
-
140
- assertEquals(buffer.length, 2);
141
- assertEquals(buffer[0].msg, "Test message");
142
- assertEquals(buffer[1].msg, "Test message 2");
143
- // Both should NOT include category in the message
144
- });
145
-
146
- test("getPinoSink(): category option - true (default formatting)", async () => {
147
- const buffer: PinoLog[] = [];
148
- const dest = build(async (source) => {
149
- for await (const obj of source) {
150
- buffer.push(obj);
151
- }
152
- }, {});
153
- const logger = pino({
154
- useOnlyCustomLevels: false,
155
- }, dest);
156
-
157
- const sink = getPinoSink(logger, { category: true });
158
- sink({
159
- category: ["test", "category"],
160
- level: "info",
161
- message: ["Test message"],
162
- properties: {},
163
- rawMessage: "Test message",
164
- timestamp: Date.now(),
165
- });
166
-
167
- logger.flush();
168
- await delay(100);
169
-
170
- assertEquals(buffer.length, 1);
171
- assertEquals(buffer[0].msg, "test·category: Test message");
172
- });
173
-
174
- test("getPinoSink(): category decorators", async () => {
175
- const buffer: PinoLog[] = [];
176
- const dest = build(async (source) => {
177
- for await (const obj of source) {
178
- buffer.push(obj);
179
- }
180
- }, {});
181
- const logger = pino({
182
- useOnlyCustomLevels: false,
183
- }, dest);
184
-
185
- const decorators = [
186
- { decorator: "[]", expected: "[test] Message" },
187
- { decorator: "()", expected: "(test) Message" },
188
- { decorator: "<>", expected: "<test> Message" },
189
- { decorator: "{}", expected: "{test} Message" },
190
- { decorator: ":", expected: "test: Message" },
191
- { decorator: "-", expected: "test - Message" },
192
- { decorator: "|", expected: "test | Message" },
193
- { decorator: "/", expected: "test / Message" },
194
- { decorator: "", expected: "test Message" },
195
- ] as const;
196
-
197
- for (const { decorator } of decorators) {
198
- const sink = getPinoSink(logger, {
199
- category: { decorator, position: "start" },
200
- });
201
- sink({
202
- category: ["test"],
203
- level: "info",
204
- message: ["Message"],
205
- properties: {},
206
- rawMessage: "Message",
207
- timestamp: Date.now(),
208
- });
209
- }
210
-
211
- logger.flush();
212
- await delay(100);
213
-
214
- assertEquals(buffer.length, decorators.length);
215
- for (let i = 0; i < decorators.length; i++) {
216
- assertEquals(
217
- buffer[i].msg,
218
- decorators[i].expected,
219
- `Decorator '${decorators[i].decorator}' failed`,
220
- );
221
- }
222
- });
223
-
224
- test("getPinoSink(): category position (start vs end)", async () => {
225
- const buffer: PinoLog[] = [];
226
- const dest = build(async (source) => {
227
- for await (const obj of source) {
228
- buffer.push(obj);
229
- }
230
- }, {});
231
- const logger = pino({
232
- useOnlyCustomLevels: false,
233
- }, dest);
234
-
235
- // Test position: "start"
236
- const sinkStart = getPinoSink(logger, {
237
- category: { position: "start", decorator: "[]" },
238
- });
239
- sinkStart({
240
- category: ["test"],
241
- level: "info",
242
- message: ["Message"],
243
- properties: {},
244
- rawMessage: "Message",
245
- timestamp: Date.now(),
246
- });
247
-
248
- // Test position: "end"
249
- const sinkEnd = getPinoSink(logger, {
250
- category: { position: "end", decorator: "[]" },
251
- });
252
- sinkEnd({
253
- category: ["test"],
254
- level: "info",
255
- message: ["Message"],
256
- properties: {},
257
- rawMessage: "Message",
258
- timestamp: Date.now(),
259
- });
260
-
261
- logger.flush();
262
- await delay(100);
263
-
264
- assertEquals(buffer.length, 2);
265
- assertEquals(buffer[0].msg, "[test] Message");
266
- assertEquals(buffer[1].msg, "Message [test]");
267
- });
268
-
269
- test("getPinoSink(): category separator", async () => {
270
- const buffer: PinoLog[] = [];
271
- const dest = build(async (source) => {
272
- for await (const obj of source) {
273
- buffer.push(obj);
274
- }
275
- }, {});
276
- const logger = pino({
277
- useOnlyCustomLevels: false,
278
- }, dest);
279
-
280
- const separators = [
281
- { separator: ".", expected: "[app.service.logger] Message" },
282
- { separator: "/", expected: "[app/service/logger] Message" },
283
- { separator: "::", expected: "[app::service::logger] Message" },
284
- { separator: " > ", expected: "[app > service > logger] Message" },
285
- ];
286
-
287
- for (const { separator } of separators) {
288
- const sink = getPinoSink(logger, {
289
- category: { separator, decorator: "[]", position: "start" },
290
- });
291
- sink({
292
- category: ["app", "service", "logger"],
293
- level: "info",
294
- message: ["Message"],
295
- properties: {},
296
- rawMessage: "Message",
297
- timestamp: Date.now(),
298
- });
299
- }
300
-
301
- logger.flush();
302
- await delay(100);
303
-
304
- assertEquals(buffer.length, separators.length);
305
- for (let i = 0; i < separators.length; i++) {
306
- assertEquals(
307
- buffer[i].msg,
308
- separators[i].expected,
309
- `Separator '${separators[i].separator}' failed`,
310
- );
311
- }
312
- });
313
-
314
- test("getPinoSink(): empty category handling", async () => {
315
- const buffer: PinoLog[] = [];
316
- const dest = build(async (source) => {
317
- for await (const obj of source) {
318
- buffer.push(obj);
319
- }
320
- }, {});
321
- const logger = pino({
322
- useOnlyCustomLevels: false,
323
- }, dest);
324
-
325
- const sink = getPinoSink(logger, {
326
- category: { decorator: "[]", position: "start" },
327
- });
328
-
329
- // Test with empty category array
330
- sink({
331
- category: [],
332
- level: "info",
333
- message: ["Message with empty category"],
334
- properties: {},
335
- rawMessage: "Message with empty category",
336
- timestamp: Date.now(),
337
- });
338
-
339
- // Test with single empty string category
340
- sink({
341
- category: [""],
342
- level: "info",
343
- message: ["Message with empty string category"],
344
- properties: {},
345
- rawMessage: "Message with empty string category",
346
- timestamp: Date.now(),
347
- });
348
-
349
- logger.flush();
350
- await delay(100);
351
-
352
- assertEquals(buffer.length, 2);
353
- // Empty category array should not include category in message
354
- assertEquals(buffer[0].msg, "Message with empty category");
355
- // Single empty string should still show the decorator
356
- assertEquals(buffer[1].msg, "[] Message with empty string category");
357
- });
358
-
359
- test("getPinoSink(): message interpolation", async () => {
360
- const buffer: PinoLog[] = [];
361
- const dest = build(async (source) => {
362
- for await (const obj of source) {
363
- buffer.push(obj);
364
- }
365
- }, {});
366
- const logger = pino({
367
- useOnlyCustomLevels: false,
368
- }, dest);
369
-
370
- const sink = getPinoSink(logger, {
371
- category: { decorator: ":", position: "start" },
372
- });
373
-
374
- // Test message with interpolated values
375
- sink({
376
- category: ["app", "auth"],
377
- level: "info",
378
- message: [
379
- "User ",
380
- { userId: 123, username: "johndoe" },
381
- " logged in",
382
- ],
383
- properties: { sessionId: "sess_abc123", source: "web" },
384
- rawMessage: "User {user} logged in",
385
- timestamp: Date.now(),
386
- });
387
-
388
- logger.flush();
389
- await delay(100);
390
-
391
- assertEquals(buffer.length, 1);
392
-
393
- // Check that the message contains expected parts
394
- const actualMsg = buffer[0].msg;
395
- assertEquals(actualMsg.includes("app·auth"), true, "Should contain category");
396
- assertEquals(actualMsg.includes("User"), true, "Should contain 'User'");
397
- assertEquals(
398
- actualMsg.includes("logged in"),
399
- true,
400
- "Should contain 'logged in'",
401
- );
402
-
403
- assertEquals(buffer[0].sessionId, "sess_abc123");
404
- assertEquals(buffer[0].source, "web");
405
- });
package/src/mod.ts DELETED
@@ -1,250 +0,0 @@
1
- import type { Logger } from "pino";
2
- import { configureSync, type LogRecord, type Sink } from "@logtape/logtape";
3
-
4
- /**
5
- * Options for configuring the Pino sink adapter.
6
- * @since 1.0.0
7
- */
8
- export interface PinoSinkOptions {
9
- /**
10
- * Configuration for how LogTape categories are handled in Pino logs.
11
- * - `false` or `undefined`: Categories are not included in the log message
12
- * - `true`: Categories are included with default formatting
13
- * - `CategoryOptions`: Custom category formatting configuration
14
- */
15
- readonly category?: boolean | CategoryOptions;
16
- }
17
-
18
- /**
19
- * Configuration options for formatting LogTape categories in Pino log messages.
20
- * @since 1.0.0
21
- */
22
- export interface CategoryOptions {
23
- /**
24
- * The separator used to join category parts when multiple categories exist.
25
- * @default "·"
26
- */
27
- readonly separator?: string;
28
-
29
- /**
30
- * Where to position the category in the log message.
31
- * - `"start"`: Category appears at the beginning of the message
32
- * - `"end"`: Category appears at the end of the message
33
- * @default "start"
34
- */
35
- readonly position?: "start" | "end";
36
-
37
- /**
38
- * The decorator used to format the category in the log message.
39
- * - `"[]"`: [category] format
40
- * - `"()"`: (category) format
41
- * - `"<>"`: <category> format
42
- * - `"{}"`: {category} format
43
- * - `":"`: category: format
44
- * - `"-"`: category - format
45
- * - `"|"`: category | format
46
- * - `"/"`: category / format
47
- * - `""`: category format (no decoration)
48
- * @default ":"
49
- */
50
- readonly decorator?: "[]" | "()" | "<>" | "{}" | ":" | "-" | "|" | "/" | "";
51
- }
52
-
53
- /**
54
- * Creates a LogTape sink that forwards log records to a Pino logger.
55
- *
56
- * This adapter allows LogTape-enabled libraries to integrate seamlessly with
57
- * applications that use Pino for logging.
58
- *
59
- * @example
60
- * ```typescript
61
- * import { configure } from "@logtape/logtape";
62
- * import { getPinoSink } from "@logtape/adaptor-pino";
63
- * import pino from "pino";
64
- *
65
- * const pinoLogger = pino();
66
- *
67
- * await configure({
68
- * sinks: {
69
- * pino: getPinoSink(pinoLogger, {
70
- * category: {
71
- * position: "start",
72
- * decorator: "[]",
73
- * separator: "."
74
- * }
75
- * })
76
- * },
77
- * loggers: [
78
- * { category: "my-library", sinks: ["pino"] }
79
- * ]
80
- * });
81
- * ```
82
- *
83
- * @template CustomLevels The custom log levels supported by the Pino logger.
84
- * @template UseOnlyCustomLevels Whether to use only custom levels defined
85
- * in the Pino logger.
86
- * @param logger The Pino logger instance to forward logs to.
87
- * @param options Configuration options for the sink adapter.
88
- * @returns A LogTape sink function that can be used in LogTape configuration.
89
- * @since 1.0.0
90
- */
91
- export function getPinoSink<
92
- CustomLevels extends string,
93
- UseOnlyCustomLevels extends boolean,
94
- >(
95
- logger: Logger<CustomLevels, UseOnlyCustomLevels>,
96
- options: PinoSinkOptions = {},
97
- ): Sink {
98
- const categoryOptions = !options.category
99
- ? undefined
100
- : typeof options.category === "object"
101
- ? options.category
102
- : {};
103
- const category: Required<CategoryOptions> | undefined =
104
- categoryOptions == null ? undefined : {
105
- separator: categoryOptions.separator ?? "·",
106
- position: categoryOptions.position ?? "start",
107
- decorator: categoryOptions.decorator ?? ":",
108
- };
109
- return (record: LogRecord) => {
110
- let message = "";
111
- const interpolationValues: unknown[] = [];
112
- if (category?.position === "start" && record.category.length > 0) {
113
- message += category.decorator === "[]"
114
- ? "[%s] "
115
- : category.decorator === "()"
116
- ? "(%s) "
117
- : category.decorator === "<>"
118
- ? "<%s> "
119
- : category.decorator === "{}"
120
- ? "{%s} "
121
- : category.decorator === ":"
122
- ? "%s: "
123
- : category.decorator === "-"
124
- ? "%s - "
125
- : category.decorator === "|"
126
- ? "%s | "
127
- : category.decorator === "/"
128
- ? "%s / "
129
- : "%s ";
130
- interpolationValues.push(
131
- record.category.join(category.separator),
132
- );
133
- }
134
- for (let i = 0; i < record.message.length; i += 2) {
135
- message += record.message[i];
136
- if (i + 1 < record.message.length) {
137
- message += "%o";
138
- interpolationValues.push(record.message[i + 1]);
139
- }
140
- }
141
- if (category?.position === "end" && record.category.length > 0) {
142
- message += category.decorator === "[]"
143
- ? " [%s]"
144
- : category.decorator === "()"
145
- ? " (%s)"
146
- : category.decorator === "<>"
147
- ? " <%s>"
148
- : category.decorator === "{}"
149
- ? " {%s}"
150
- : category.decorator === ":"
151
- ? ": %s"
152
- : category.decorator === "-"
153
- ? " - %s"
154
- : category.decorator === "|"
155
- ? " | %s"
156
- : category.decorator === "/"
157
- ? " / %s"
158
- : " %s";
159
- interpolationValues.push(
160
- record.category.join(category.separator),
161
- );
162
- }
163
- switch (record.level) {
164
- case "trace":
165
- return logger.trace(record.properties, message, interpolationValues);
166
- case "debug":
167
- return logger.debug(record.properties, message, interpolationValues);
168
- case "info":
169
- return logger.info(record.properties, message, interpolationValues);
170
- case "warning":
171
- return logger.warn(record.properties, message, interpolationValues);
172
- case "error":
173
- return logger.error(record.properties, message, interpolationValues);
174
- case "fatal":
175
- return logger.fatal(record.properties, message, interpolationValues);
176
- }
177
- };
178
- }
179
-
180
- /**
181
- * Automatically configures LogTape to route all logs to a Pino logger.
182
- *
183
- * This is a convenience function that automatically sets up LogTape to forward
184
- * all log records to a Pino logger instance.
185
- *
186
- * @example Basic auto-configuration
187
- * ```typescript
188
- * import pino from "pino";
189
- * import { install } from "@logtape/adaptor-pino";
190
- *
191
- * const pinoLogger = pino();
192
- *
193
- * // Automatically route all LogTape logs to the Pino logger
194
- * install(pinoLogger);
195
- *
196
- * // Now any LogTape-enabled library will log through Pino
197
- * import { getLogger } from "@logtape/logtape";
198
- * const logger = getLogger("my-app");
199
- * logger.info("This will be logged through Pino");
200
- * ```
201
- *
202
- * @example Auto-configuration with custom options
203
- * ```typescript
204
- * import pino from "pino";
205
- * import { install } from "@logtape/adaptor-pino";
206
- *
207
- * const pinoLogger = pino({
208
- * level: "info",
209
- * transport: {
210
- * target: "pino-pretty"
211
- * }
212
- * });
213
- *
214
- * install(pinoLogger, {
215
- * category: {
216
- * position: "start",
217
- * decorator: "[]",
218
- * separator: "."
219
- * }
220
- * });
221
- * ```
222
- *
223
- * @template CustomLevels The custom log levels supported by the Pino logger.
224
- * @template UseOnlyCustomLevels Whether to use only custom levels defined
225
- * in the Pino logger.
226
- * @param logger The Pino logger instance to forward logs to.
227
- * @param options Configuration options for the sink adapter.
228
- * @since 1.0.0
229
- */
230
- export function install<
231
- CustomLevels extends string,
232
- UseOnlyCustomLevels extends boolean,
233
- >(
234
- logger: Logger<CustomLevels, UseOnlyCustomLevels>,
235
- options: PinoSinkOptions = {},
236
- ): void {
237
- configureSync({
238
- sinks: {
239
- pino: getPinoSink(logger, options),
240
- },
241
- loggers: [
242
- {
243
- category: ["logtape", "meta"],
244
- sinks: ["pino"],
245
- lowestLevel: "warning",
246
- },
247
- { category: [], sinks: ["pino"] },
248
- ],
249
- });
250
- }
package/tsdown.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- entry: "src/mod.ts",
5
- dts: {
6
- sourcemap: true,
7
- },
8
- format: ["esm", "cjs"],
9
- platform: "node",
10
- unbundle: true,
11
- });