@logtape/adaptor-pino 1.0.0-dev.253
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/LICENSE +20 -0
- package/README.md +92 -0
- package/deno.json +37 -0
- package/dist/mod.cjs +78 -0
- package/dist/mod.d.cts +93 -0
- package/dist/mod.d.cts.map +1 -0
- package/dist/mod.d.ts +93 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +78 -0
- package/dist/mod.js.map +1 -0
- package/mod.test.ts +405 -0
- package/mod.ts +178 -0
- package/package.json +69 -0
- package/tsdown.config.ts +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2024 Hong Minhee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!-- deno-fmt-ignore-file -->
|
|
2
|
+
|
|
3
|
+
@logtape/adaptor-pino
|
|
4
|
+
=====================
|
|
5
|
+
|
|
6
|
+
[![JSR][JSR badge]][JSR]
|
|
7
|
+
[![npm][npm badge]][npm]
|
|
8
|
+
|
|
9
|
+
*@logtape/adaptor-pino* is a [LogTape] adapter that forwards log records to
|
|
10
|
+
[Pino] loggers, enabling seamless integration between LogTape-enabled libraries
|
|
11
|
+
and applications using Pino for logging infrastructure.
|
|
12
|
+
|
|
13
|
+
[JSR]: https://jsr.io/@logtape/adaptor-pino
|
|
14
|
+
[JSR badge]: https://jsr.io/badges/@logtape/adaptor-pino
|
|
15
|
+
[npm]: https://www.npmjs.com/package/@logtape/adaptor-pino
|
|
16
|
+
[npm badge]: https://img.shields.io/npm/v/@logtape/adaptor-pino?logo=npm
|
|
17
|
+
[LogTape]: https://logtape.org/
|
|
18
|
+
[Pino]: https://getpino.io/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Installation
|
|
22
|
+
------------
|
|
23
|
+
|
|
24
|
+
~~~~ sh
|
|
25
|
+
deno add jsr:@logtape/adaptor-pino # for Deno
|
|
26
|
+
npm add @logtape/adaptor-pino # for npm
|
|
27
|
+
pnpm add @logtape/adaptor-pino # for pnpm
|
|
28
|
+
yarn add @logtape/adaptor-pino # for Yarn
|
|
29
|
+
bun add @logtape/adaptor-pino # for Bun
|
|
30
|
+
~~~~
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Usage
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
Configure LogTape to use the Pino adapter in your application:
|
|
37
|
+
|
|
38
|
+
~~~~ typescript
|
|
39
|
+
import { configure } from "@logtape/logtape";
|
|
40
|
+
import { getPinoSink } from "@logtape/adaptor-pino";
|
|
41
|
+
import pino from "pino";
|
|
42
|
+
|
|
43
|
+
const pinoLogger = pino();
|
|
44
|
+
|
|
45
|
+
await configure({
|
|
46
|
+
sinks: {
|
|
47
|
+
pino: getPinoSink(pinoLogger, {
|
|
48
|
+
category: {
|
|
49
|
+
position: "start",
|
|
50
|
+
decorator: "[]",
|
|
51
|
+
separator: "."
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
loggers: [
|
|
56
|
+
{ category: "my-library", sinks: ["pino"] }
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
~~~~
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
Category formatting
|
|
63
|
+
-------------------
|
|
64
|
+
|
|
65
|
+
The adapter supports flexible category formatting options:
|
|
66
|
+
|
|
67
|
+
~~~~typescript
|
|
68
|
+
import { getPinoSink } from "@logtape/adaptor-pino";
|
|
69
|
+
|
|
70
|
+
// Hide categories completely
|
|
71
|
+
const sink1 = getPinoSink(logger, { category: false });
|
|
72
|
+
|
|
73
|
+
// Use default formatting (category with ":" separator)
|
|
74
|
+
const sink2 = getPinoSink(logger, { category: true });
|
|
75
|
+
|
|
76
|
+
// Custom formatting
|
|
77
|
+
const sink3 = getPinoSink(logger, {
|
|
78
|
+
category: {
|
|
79
|
+
position: "end", // "start" or "end"
|
|
80
|
+
decorator: "[]", // "[]", "()", "<>", "{}", ":", "-", "|", "/", ""
|
|
81
|
+
separator: "::" // custom separator for multi-part categories
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
~~~~
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Docs
|
|
88
|
+
----
|
|
89
|
+
|
|
90
|
+
See the [API reference] on JSR for further details.
|
|
91
|
+
|
|
92
|
+
[API reference]: https://jsr.io/@logtape/adaptor-pino/doc
|
package/deno.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@logtape/adaptor-pino",
|
|
3
|
+
"version": "1.0.0-dev.253+3fd9a841",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"exports": "./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/dist/mod.cjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
//#region mod.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates a LogTape sink that forwards log records to a Pino logger.
|
|
5
|
+
*
|
|
6
|
+
* This adapter allows LogTape-enabled libraries to integrate seamlessly with
|
|
7
|
+
* applications that use Pino for logging.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { configure } from "@logtape/logtape";
|
|
12
|
+
* import { getPinoSink } from "@logtape/adaptor-pino";
|
|
13
|
+
* import pino from "pino";
|
|
14
|
+
*
|
|
15
|
+
* const pinoLogger = pino();
|
|
16
|
+
*
|
|
17
|
+
* await configure({
|
|
18
|
+
* sinks: {
|
|
19
|
+
* pino: getPinoSink(pinoLogger, {
|
|
20
|
+
* category: {
|
|
21
|
+
* position: "start",
|
|
22
|
+
* decorator: "[]",
|
|
23
|
+
* separator: "."
|
|
24
|
+
* }
|
|
25
|
+
* })
|
|
26
|
+
* },
|
|
27
|
+
* loggers: [
|
|
28
|
+
* { category: "my-library", sinks: ["pino"] }
|
|
29
|
+
* ]
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @typeParam CustomLevels The custom log levels supported by the Pino logger.
|
|
34
|
+
* @typeParam UseOnlyCustomLevels Whether to use only custom levels defined
|
|
35
|
+
* in the Pino logger.
|
|
36
|
+
* @param logger The Pino logger instance to forward logs to.
|
|
37
|
+
* @param options Configuration options for the sink adapter.
|
|
38
|
+
* @returns A LogTape sink function that can be used in LogTape configuration.
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
*/
|
|
41
|
+
function getPinoSink(logger, options) {
|
|
42
|
+
const categoryOptions = !options?.category ? void 0 : typeof options.category === "object" ? options.category : {};
|
|
43
|
+
const category = categoryOptions == null ? void 0 : {
|
|
44
|
+
separator: categoryOptions.separator ?? "·",
|
|
45
|
+
position: categoryOptions.position ?? "start",
|
|
46
|
+
decorator: categoryOptions.decorator ?? ":"
|
|
47
|
+
};
|
|
48
|
+
return (record) => {
|
|
49
|
+
let message = "";
|
|
50
|
+
const interpolationValues = [];
|
|
51
|
+
if (category?.position === "start" && record.category.length > 0) {
|
|
52
|
+
message += category.decorator === "[]" ? "[%s] " : category.decorator === "()" ? "(%s) " : category.decorator === "<>" ? "<%s> " : category.decorator === "{}" ? "{%s} " : category.decorator === ":" ? "%s: " : category.decorator === "-" ? "%s - " : category.decorator === "|" ? "%s | " : category.decorator === "/" ? "%s / " : "%s ";
|
|
53
|
+
interpolationValues.push(record.category.join(category.separator));
|
|
54
|
+
}
|
|
55
|
+
for (let i = 0; i < record.message.length; i += 2) {
|
|
56
|
+
message += record.message[i];
|
|
57
|
+
if (i + 1 < record.message.length) {
|
|
58
|
+
message += "%o";
|
|
59
|
+
interpolationValues.push(record.message[i + 1]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (category?.position === "end" && record.category.length > 0) {
|
|
63
|
+
message += category.decorator === "[]" ? " [%s]" : category.decorator === "()" ? " (%s)" : category.decorator === "<>" ? " <%s>" : category.decorator === "{}" ? " {%s}" : category.decorator === ":" ? ": %s" : category.decorator === "-" ? " - %s" : category.decorator === "|" ? " | %s" : category.decorator === "/" ? " / %s" : " %s";
|
|
64
|
+
interpolationValues.push(record.category.join(category.separator));
|
|
65
|
+
}
|
|
66
|
+
switch (record.level) {
|
|
67
|
+
case "trace": return logger.trace(record.properties, message, interpolationValues);
|
|
68
|
+
case "debug": return logger.debug(record.properties, message, interpolationValues);
|
|
69
|
+
case "info": return logger.info(record.properties, message, interpolationValues);
|
|
70
|
+
case "warning": return logger.warn(record.properties, message, interpolationValues);
|
|
71
|
+
case "error": return logger.error(record.properties, message, interpolationValues);
|
|
72
|
+
case "fatal": return logger.fatal(record.properties, message, interpolationValues);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
exports.getPinoSink = getPinoSink;
|
package/dist/mod.d.cts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Logger } from "pino";
|
|
2
|
+
import { Sink } from "@logtape/logtape";
|
|
3
|
+
|
|
4
|
+
//#region mod.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for configuring the Pino sink adapter.
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
interface PinoSinkOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for how LogTape categories are handled in Pino logs.
|
|
13
|
+
* - `false` or `undefined`: Categories are not included in the log message
|
|
14
|
+
* - `true`: Categories are included with default formatting
|
|
15
|
+
* - `CategoryOptions`: Custom category formatting configuration
|
|
16
|
+
*/
|
|
17
|
+
readonly category?: boolean | CategoryOptions;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for formatting LogTape categories in Pino log messages.
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
*/
|
|
23
|
+
interface CategoryOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The separator used to join category parts when multiple categories exist.
|
|
26
|
+
* @default "·"
|
|
27
|
+
*/
|
|
28
|
+
readonly separator?: string;
|
|
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
|
+
* The decorator used to format the category in the log message.
|
|
38
|
+
* - `"[]"`: [category] format
|
|
39
|
+
* - `"()"`: (category) format
|
|
40
|
+
* - `"<>"`: <category> format
|
|
41
|
+
* - `"{}"`: {category} format
|
|
42
|
+
* - `":"`: category: format
|
|
43
|
+
* - `"-"`: category - format
|
|
44
|
+
* - `"|"`: category | format
|
|
45
|
+
* - `"/"`: category / format
|
|
46
|
+
* - `""`: category format (no decoration)
|
|
47
|
+
* @default ":"
|
|
48
|
+
*/
|
|
49
|
+
readonly decorator?: "[]" | "()" | "<>" | "{}" | ":" | "-" | "|" | "/" | "";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a LogTape sink that forwards log records to a Pino logger.
|
|
53
|
+
*
|
|
54
|
+
* This adapter allows LogTape-enabled libraries to integrate seamlessly with
|
|
55
|
+
* applications that use Pino for logging.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { configure } from "@logtape/logtape";
|
|
60
|
+
* import { getPinoSink } from "@logtape/adaptor-pino";
|
|
61
|
+
* import pino from "pino";
|
|
62
|
+
*
|
|
63
|
+
* const pinoLogger = pino();
|
|
64
|
+
*
|
|
65
|
+
* await configure({
|
|
66
|
+
* sinks: {
|
|
67
|
+
* pino: getPinoSink(pinoLogger, {
|
|
68
|
+
* category: {
|
|
69
|
+
* position: "start",
|
|
70
|
+
* decorator: "[]",
|
|
71
|
+
* separator: "."
|
|
72
|
+
* }
|
|
73
|
+
* })
|
|
74
|
+
* },
|
|
75
|
+
* loggers: [
|
|
76
|
+
* { category: "my-library", sinks: ["pino"] }
|
|
77
|
+
* ]
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @typeParam CustomLevels The custom log levels supported by the Pino logger.
|
|
82
|
+
* @typeParam UseOnlyCustomLevels Whether to use only custom levels defined
|
|
83
|
+
* in the Pino logger.
|
|
84
|
+
* @param logger The Pino logger instance to forward logs to.
|
|
85
|
+
* @param options Configuration options for the sink adapter.
|
|
86
|
+
* @returns A LogTape sink function that can be used in LogTape configuration.
|
|
87
|
+
* @since 1.0.0
|
|
88
|
+
*/
|
|
89
|
+
declare function getPinoSink<CustomLevels extends string, UseOnlyCustomLevels extends boolean>(logger: Logger<CustomLevels, UseOnlyCustomLevels>, options?: PinoSinkOptions): Sink;
|
|
90
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
91
|
+
//#endregion
|
|
92
|
+
export { CategoryOptions, PinoSinkOptions, getPinoSink };
|
|
93
|
+
//# sourceMappingURL=mod.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.cts","names":[],"sources":["../mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AAOA;AAcA;AAqEgB,UAnFC,eAAA,CAmFU;EAAA;;;;;;EAMpB,SAAA,QAAA,CAAA,EAAA,OAAA,GAlFyB,eAkFzB;;;;;;UA3EU,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqED,sFAIN,OAAO,cAAc,gCACnB,kBACT"}
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Logger } from "pino";
|
|
2
|
+
import { Sink } from "@logtape/logtape";
|
|
3
|
+
|
|
4
|
+
//#region mod.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for configuring the Pino sink adapter.
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
interface PinoSinkOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for how LogTape categories are handled in Pino logs.
|
|
13
|
+
* - `false` or `undefined`: Categories are not included in the log message
|
|
14
|
+
* - `true`: Categories are included with default formatting
|
|
15
|
+
* - `CategoryOptions`: Custom category formatting configuration
|
|
16
|
+
*/
|
|
17
|
+
readonly category?: boolean | CategoryOptions;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for formatting LogTape categories in Pino log messages.
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
*/
|
|
23
|
+
interface CategoryOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The separator used to join category parts when multiple categories exist.
|
|
26
|
+
* @default "·"
|
|
27
|
+
*/
|
|
28
|
+
readonly separator?: string;
|
|
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
|
+
* The decorator used to format the category in the log message.
|
|
38
|
+
* - `"[]"`: [category] format
|
|
39
|
+
* - `"()"`: (category) format
|
|
40
|
+
* - `"<>"`: <category> format
|
|
41
|
+
* - `"{}"`: {category} format
|
|
42
|
+
* - `":"`: category: format
|
|
43
|
+
* - `"-"`: category - format
|
|
44
|
+
* - `"|"`: category | format
|
|
45
|
+
* - `"/"`: category / format
|
|
46
|
+
* - `""`: category format (no decoration)
|
|
47
|
+
* @default ":"
|
|
48
|
+
*/
|
|
49
|
+
readonly decorator?: "[]" | "()" | "<>" | "{}" | ":" | "-" | "|" | "/" | "";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates a LogTape sink that forwards log records to a Pino logger.
|
|
53
|
+
*
|
|
54
|
+
* This adapter allows LogTape-enabled libraries to integrate seamlessly with
|
|
55
|
+
* applications that use Pino for logging.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { configure } from "@logtape/logtape";
|
|
60
|
+
* import { getPinoSink } from "@logtape/adaptor-pino";
|
|
61
|
+
* import pino from "pino";
|
|
62
|
+
*
|
|
63
|
+
* const pinoLogger = pino();
|
|
64
|
+
*
|
|
65
|
+
* await configure({
|
|
66
|
+
* sinks: {
|
|
67
|
+
* pino: getPinoSink(pinoLogger, {
|
|
68
|
+
* category: {
|
|
69
|
+
* position: "start",
|
|
70
|
+
* decorator: "[]",
|
|
71
|
+
* separator: "."
|
|
72
|
+
* }
|
|
73
|
+
* })
|
|
74
|
+
* },
|
|
75
|
+
* loggers: [
|
|
76
|
+
* { category: "my-library", sinks: ["pino"] }
|
|
77
|
+
* ]
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @typeParam CustomLevels The custom log levels supported by the Pino logger.
|
|
82
|
+
* @typeParam UseOnlyCustomLevels Whether to use only custom levels defined
|
|
83
|
+
* in the Pino logger.
|
|
84
|
+
* @param logger The Pino logger instance to forward logs to.
|
|
85
|
+
* @param options Configuration options for the sink adapter.
|
|
86
|
+
* @returns A LogTape sink function that can be used in LogTape configuration.
|
|
87
|
+
* @since 1.0.0
|
|
88
|
+
*/
|
|
89
|
+
declare function getPinoSink<CustomLevels extends string, UseOnlyCustomLevels extends boolean>(logger: Logger<CustomLevels, UseOnlyCustomLevels>, options?: PinoSinkOptions): Sink;
|
|
90
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
91
|
+
//#endregion
|
|
92
|
+
export { CategoryOptions, PinoSinkOptions, getPinoSink };
|
|
93
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","names":[],"sources":["../mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AAOA;AAcA;AAqEgB,UAnFC,eAAA,CAmFU;EAAA;;;;;;EAMpB,SAAA,QAAA,CAAA,EAAA,OAAA,GAlFyB,eAkFzB;;;;;;UA3EU,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqED,sFAIN,OAAO,cAAc,gCACnB,kBACT"}
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region mod.ts
|
|
2
|
+
/**
|
|
3
|
+
* Creates a LogTape sink that forwards log records to a Pino logger.
|
|
4
|
+
*
|
|
5
|
+
* This adapter allows LogTape-enabled libraries to integrate seamlessly with
|
|
6
|
+
* applications that use Pino for logging.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { configure } from "@logtape/logtape";
|
|
11
|
+
* import { getPinoSink } from "@logtape/adaptor-pino";
|
|
12
|
+
* import pino from "pino";
|
|
13
|
+
*
|
|
14
|
+
* const pinoLogger = pino();
|
|
15
|
+
*
|
|
16
|
+
* await configure({
|
|
17
|
+
* sinks: {
|
|
18
|
+
* pino: getPinoSink(pinoLogger, {
|
|
19
|
+
* category: {
|
|
20
|
+
* position: "start",
|
|
21
|
+
* decorator: "[]",
|
|
22
|
+
* separator: "."
|
|
23
|
+
* }
|
|
24
|
+
* })
|
|
25
|
+
* },
|
|
26
|
+
* loggers: [
|
|
27
|
+
* { category: "my-library", sinks: ["pino"] }
|
|
28
|
+
* ]
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @typeParam CustomLevels The custom log levels supported by the Pino logger.
|
|
33
|
+
* @typeParam UseOnlyCustomLevels Whether to use only custom levels defined
|
|
34
|
+
* in the Pino logger.
|
|
35
|
+
* @param logger The Pino logger instance to forward logs to.
|
|
36
|
+
* @param options Configuration options for the sink adapter.
|
|
37
|
+
* @returns A LogTape sink function that can be used in LogTape configuration.
|
|
38
|
+
* @since 1.0.0
|
|
39
|
+
*/
|
|
40
|
+
function getPinoSink(logger, options) {
|
|
41
|
+
const categoryOptions = !options?.category ? void 0 : typeof options.category === "object" ? options.category : {};
|
|
42
|
+
const category = categoryOptions == null ? void 0 : {
|
|
43
|
+
separator: categoryOptions.separator ?? "·",
|
|
44
|
+
position: categoryOptions.position ?? "start",
|
|
45
|
+
decorator: categoryOptions.decorator ?? ":"
|
|
46
|
+
};
|
|
47
|
+
return (record) => {
|
|
48
|
+
let message = "";
|
|
49
|
+
const interpolationValues = [];
|
|
50
|
+
if (category?.position === "start" && record.category.length > 0) {
|
|
51
|
+
message += category.decorator === "[]" ? "[%s] " : category.decorator === "()" ? "(%s) " : category.decorator === "<>" ? "<%s> " : category.decorator === "{}" ? "{%s} " : category.decorator === ":" ? "%s: " : category.decorator === "-" ? "%s - " : category.decorator === "|" ? "%s | " : category.decorator === "/" ? "%s / " : "%s ";
|
|
52
|
+
interpolationValues.push(record.category.join(category.separator));
|
|
53
|
+
}
|
|
54
|
+
for (let i = 0; i < record.message.length; i += 2) {
|
|
55
|
+
message += record.message[i];
|
|
56
|
+
if (i + 1 < record.message.length) {
|
|
57
|
+
message += "%o";
|
|
58
|
+
interpolationValues.push(record.message[i + 1]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (category?.position === "end" && record.category.length > 0) {
|
|
62
|
+
message += category.decorator === "[]" ? " [%s]" : category.decorator === "()" ? " (%s)" : category.decorator === "<>" ? " <%s>" : category.decorator === "{}" ? " {%s}" : category.decorator === ":" ? ": %s" : category.decorator === "-" ? " - %s" : category.decorator === "|" ? " | %s" : category.decorator === "/" ? " / %s" : " %s";
|
|
63
|
+
interpolationValues.push(record.category.join(category.separator));
|
|
64
|
+
}
|
|
65
|
+
switch (record.level) {
|
|
66
|
+
case "trace": return logger.trace(record.properties, message, interpolationValues);
|
|
67
|
+
case "debug": return logger.debug(record.properties, message, interpolationValues);
|
|
68
|
+
case "info": return logger.info(record.properties, message, interpolationValues);
|
|
69
|
+
case "warning": return logger.warn(record.properties, message, interpolationValues);
|
|
70
|
+
case "error": return logger.error(record.properties, message, interpolationValues);
|
|
71
|
+
case "fatal": return logger.fatal(record.properties, message, interpolationValues);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { getPinoSink };
|
|
78
|
+
//# sourceMappingURL=mod.js.map
|
package/dist/mod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","names":["logger: Logger<CustomLevels, UseOnlyCustomLevels>","options?: PinoSinkOptions","category: Required<CategoryOptions> | undefined","record: LogRecord","interpolationValues: unknown[]"],"sources":["../mod.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport type { LogRecord, Sink } from \"@logtape/logtape\";\n\n/**\n * Options for configuring the Pino sink adapter.\n * @since 1.0.0\n */\nexport interface PinoSinkOptions {\n /**\n * Configuration for how LogTape categories are handled in Pino logs.\n * - `false` or `undefined`: Categories are not included in the log message\n * - `true`: Categories are included with default formatting\n * - `CategoryOptions`: Custom category formatting configuration\n */\n readonly category?: boolean | CategoryOptions;\n}\n\n/**\n * Configuration options for formatting LogTape categories in Pino log messages.\n * @since 1.0.0\n */\nexport interface CategoryOptions {\n /**\n * The separator used to join category parts when multiple categories exist.\n * @default \"·\"\n */\n readonly separator?: string;\n\n /**\n * Where to position the category in the log message.\n * - `\"start\"`: Category appears at the beginning of the message\n * - `\"end\"`: Category appears at the end of the message\n * @default \"start\"\n */\n readonly position?: \"start\" | \"end\";\n\n /**\n * The decorator used to format the category in the log message.\n * - `\"[]\"`: [category] format\n * - `\"()\"`: (category) format\n * - `\"<>\"`: <category> format\n * - `\"{}\"`: {category} format\n * - `\":\"`: category: format\n * - `\"-\"`: category - format\n * - `\"|\"`: category | format\n * - `\"/\"`: category / format\n * - `\"\"`: category format (no decoration)\n * @default \":\"\n */\n readonly decorator?: \"[]\" | \"()\" | \"<>\" | \"{}\" | \":\" | \"-\" | \"|\" | \"/\" | \"\";\n}\n\n/**\n * Creates a LogTape sink that forwards log records to a Pino logger.\n *\n * This adapter allows LogTape-enabled libraries to integrate seamlessly with\n * applications that use Pino for logging.\n *\n * @example\n * ```typescript\n * import { configure } from \"@logtape/logtape\";\n * import { getPinoSink } from \"@logtape/adaptor-pino\";\n * import pino from \"pino\";\n *\n * const pinoLogger = pino();\n *\n * await configure({\n * sinks: {\n * pino: getPinoSink(pinoLogger, {\n * category: {\n * position: \"start\",\n * decorator: \"[]\",\n * separator: \".\"\n * }\n * })\n * },\n * loggers: [\n * { category: \"my-library\", sinks: [\"pino\"] }\n * ]\n * });\n * ```\n *\n * @typeParam CustomLevels The custom log levels supported by the Pino logger.\n * @typeParam UseOnlyCustomLevels Whether to use only custom levels defined\n * in the Pino logger.\n * @param logger The Pino logger instance to forward logs to.\n * @param options Configuration options for the sink adapter.\n * @returns A LogTape sink function that can be used in LogTape configuration.\n * @since 1.0.0\n */\nexport function getPinoSink<\n CustomLevels extends string,\n UseOnlyCustomLevels extends boolean,\n>(\n logger: Logger<CustomLevels, UseOnlyCustomLevels>,\n options?: PinoSinkOptions,\n): Sink {\n const categoryOptions = !options?.category\n ? undefined\n : typeof options.category === \"object\"\n ? options.category\n : {};\n const category: Required<CategoryOptions> | undefined =\n categoryOptions == null ? undefined : {\n separator: categoryOptions.separator ?? \"·\",\n position: categoryOptions.position ?? \"start\",\n decorator: categoryOptions.decorator ?? \":\",\n };\n return (record: LogRecord) => {\n let message = \"\";\n const interpolationValues: unknown[] = [];\n if (category?.position === \"start\" && record.category.length > 0) {\n message += category.decorator === \"[]\"\n ? \"[%s] \"\n : category.decorator === \"()\"\n ? \"(%s) \"\n : category.decorator === \"<>\"\n ? \"<%s> \"\n : category.decorator === \"{}\"\n ? \"{%s} \"\n : category.decorator === \":\"\n ? \"%s: \"\n : category.decorator === \"-\"\n ? \"%s - \"\n : category.decorator === \"|\"\n ? \"%s | \"\n : category.decorator === \"/\"\n ? \"%s / \"\n : \"%s \";\n interpolationValues.push(\n record.category.join(category.separator),\n );\n }\n for (let i = 0; i < record.message.length; i += 2) {\n message += record.message[i];\n if (i + 1 < record.message.length) {\n message += \"%o\";\n interpolationValues.push(record.message[i + 1]);\n }\n }\n if (category?.position === \"end\" && record.category.length > 0) {\n message += category.decorator === \"[]\"\n ? \" [%s]\"\n : category.decorator === \"()\"\n ? \" (%s)\"\n : category.decorator === \"<>\"\n ? \" <%s>\"\n : category.decorator === \"{}\"\n ? \" {%s}\"\n : category.decorator === \":\"\n ? \": %s\"\n : category.decorator === \"-\"\n ? \" - %s\"\n : category.decorator === \"|\"\n ? \" | %s\"\n : category.decorator === \"/\"\n ? \" / %s\"\n : \" %s\";\n interpolationValues.push(\n record.category.join(category.separator),\n );\n }\n switch (record.level) {\n case \"trace\":\n return logger.trace(record.properties, message, interpolationValues);\n case \"debug\":\n return logger.debug(record.properties, message, interpolationValues);\n case \"info\":\n return logger.info(record.properties, message, interpolationValues);\n case \"warning\":\n return logger.warn(record.properties, message, interpolationValues);\n case \"error\":\n return logger.error(record.properties, message, interpolationValues);\n case \"fatal\":\n return logger.fatal(record.properties, message, interpolationValues);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,SAAgB,YAIdA,QACAC,SACM;CACN,MAAM,mBAAmB,SAAS,2BAEvB,QAAQ,aAAa,WAC5B,QAAQ,WACR,CAAE;CACN,MAAMC,WACJ,mBAAmB,gBAAmB;EACpC,WAAW,gBAAgB,aAAa;EACxC,UAAU,gBAAgB,YAAY;EACtC,WAAW,gBAAgB,aAAa;CACzC;AACH,QAAO,CAACC,WAAsB;EAC5B,IAAI,UAAU;EACd,MAAMC,sBAAiC,CAAE;AACzC,MAAI,UAAU,aAAa,WAAW,OAAO,SAAS,SAAS,GAAG;AAChE,cAAW,SAAS,cAAc,OAC9B,UACA,SAAS,cAAc,OACvB,UACA,SAAS,cAAc,OACvB,UACA,SAAS,cAAc,OACvB,UACA,SAAS,cAAc,MACvB,SACA,SAAS,cAAc,MACvB,UACA,SAAS,cAAc,MACvB,UACA,SAAS,cAAc,MACvB,UACA;AACJ,uBAAoB,KAClB,OAAO,SAAS,KAAK,SAAS,UAAU,CACzC;EACF;AACD,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK,GAAG;AACjD,cAAW,OAAO,QAAQ;AAC1B,OAAI,IAAI,IAAI,OAAO,QAAQ,QAAQ;AACjC,eAAW;AACX,wBAAoB,KAAK,OAAO,QAAQ,IAAI,GAAG;GAChD;EACF;AACD,MAAI,UAAU,aAAa,SAAS,OAAO,SAAS,SAAS,GAAG;AAC9D,cAAW,SAAS,cAAc,OAC9B,UACA,SAAS,cAAc,OACvB,UACA,SAAS,cAAc,OACvB,UACA,SAAS,cAAc,OACvB,UACA,SAAS,cAAc,MACvB,SACA,SAAS,cAAc,MACvB,UACA,SAAS,cAAc,MACvB,UACA,SAAS,cAAc,MACvB,UACA;AACJ,uBAAoB,KAClB,OAAO,SAAS,KAAK,SAAS,UAAU,CACzC;EACF;AACD,UAAQ,OAAO,OAAf;GACE,KAAK,QACH,QAAO,OAAO,MAAM,OAAO,YAAY,SAAS,oBAAoB;GACtE,KAAK,QACH,QAAO,OAAO,MAAM,OAAO,YAAY,SAAS,oBAAoB;GACtE,KAAK,OACH,QAAO,OAAO,KAAK,OAAO,YAAY,SAAS,oBAAoB;GACrE,KAAK,UACH,QAAO,OAAO,KAAK,OAAO,YAAY,SAAS,oBAAoB;GACrE,KAAK,QACH,QAAO,OAAO,MAAM,OAAO,YAAY,SAAS,oBAAoB;GACtE,KAAK,QACH,QAAO,OAAO,MAAM,OAAO,YAAY,SAAS,oBAAoB;EACvE;CACF;AACF"}
|
package/mod.test.ts
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
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/mod.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { LogRecord, 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
|
+
* @typeParam CustomLevels The custom log levels supported by the Pino logger.
|
|
84
|
+
* @typeParam 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
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@logtape/adaptor-pino",
|
|
3
|
+
"version": "1.0.0-dev.253+3fd9a841",
|
|
4
|
+
"description": "Pino adapter for LogTape logging library",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"logging",
|
|
7
|
+
"log",
|
|
8
|
+
"logger",
|
|
9
|
+
"pino",
|
|
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-pino/"
|
|
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
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": false,
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"pino": "^9.7.0",
|
|
50
|
+
"@logtape/logtape": "1.0.0-dev.253+3fd9a841"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@alinea/suite": "^0.6.3",
|
|
54
|
+
"@std/assert": "npm:@jsr/std__assert@^1.0.13",
|
|
55
|
+
"@std/async": "npm:@jsr/std__async@^1.0.13",
|
|
56
|
+
"pino": "^9.7.0",
|
|
57
|
+
"pino-abstract-transport": "^2.0.0",
|
|
58
|
+
"tsdown": "^0.12.7",
|
|
59
|
+
"typescript": "^5.8.3"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsdown",
|
|
63
|
+
"prepublish": "tsdown",
|
|
64
|
+
"test": "tsdown && node --experimental-transform-types --test",
|
|
65
|
+
"test:bun": "tsdown && bun test",
|
|
66
|
+
"test:deno": "deno test --allow-env --allow-sys",
|
|
67
|
+
"test-all": "tsdown && node --experimental-transform-types --test && bun test && deno test"
|
|
68
|
+
}
|
|
69
|
+
}
|