@plandek-utils/logging 0.6.0

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 ADDED
@@ -0,0 +1,16 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Plandek Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @plandek-utils/logging
2
+
3
+ [![npm version](https://badge.fury.io/js/%40plandek-utils%2Flogging.svg)](https://badge.fury.io/js/%40plandek-utils%2Flogging)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/4d6e32a1b993723d7c4f/maintainability)](https://codeclimate.com/github/plandek-utils/logging/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/4d6e32a1b993723d7c4f/test_coverage)](https://codeclimate.com/github/plandek-utils/logging/test_coverage)
6
+
7
+ TypeScript utils for Logging. Includes prettifying of JSON, logging utils, and colour utils.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @plandek-utils/logging
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Pretty JSON: `prettyJSON` and `colorPrettyJSON` or `colourPrettyJSON`
18
+
19
+ ```ts
20
+ import { prettyJSON } from "@plandek-utils/logging";
21
+
22
+ const obj = { name: "John", age: 30 };
23
+ console.log(prettyJSON(obj)); // OUTPUT: {\n "name": "John",\n "age": 30\n}
24
+ ```
25
+
26
+ ```ts
27
+ import { colorPrettyJSON } from "@plandek-utils/logging";
28
+
29
+ const obj = { name: "John", age: 30 };
30
+ console.log(colorPrettyJSON(obj));
31
+ ```
32
+
33
+ output:
34
+
35
+ ```
36
+ "\x1b[90m{\x1b[39m\x1b[90m\n" +
37
+ ' \x1b[39m\x1b[35m"name"\x1b[39m\x1b[90m:\x1b[39m\x1b[90m \x1b[39m\x1b[33m"John"\x1b[39m\x1b[90m,\x1b[39m\x1b[90m\n' +
38
+ ' \x1b[39m\x1b[35m"age"\x1b[39m\x1b[90m:\x1b[39m\x1b[90m \x1b[39m\x1b[32m30\x1b[39m\x1b[90m\n' +
39
+ "\x1b[39m\x1b[90m}\x1b[39m"
40
+ ```
41
+
42
+ You can use `colorPrettyJSON` or `colourPrettyJSON` (alias).
43
+
44
+ ### `makeColourUtils(mode: "with-colour" | "plain"): LogColourUtils`
45
+
46
+ Creates a utility object for colouring log messages using CHALK.
47
+
48
+ ```ts
49
+ import { makeColourUtils } from "@plandek-utils/logging";
50
+
51
+ const colorUtils = makeColourUtils("with-colour");
52
+ console.log(colorUtils.blue("Hello"));
53
+
54
+ const plainColorUtils = makeColourUtils("plain");
55
+ console.log(plainColorUtils.blue("Hello")); // Output: Hello as is.
56
+ ```
57
+
58
+ ### Loggers
59
+
60
+ - for real: `buildPinoLogger(level: LevelWithSilent, redactPaths?: string[]): PreparedLogger`
61
+ - for test: `buildSinkLogger(level: LevelWithSilent, bindings?: PlainObject): PreparedLogger`
62
+ - parse log level: `parseLogLevelOrDefault(x: string): LogLevel`
63
+
64
+ Creates a Pino logger with common configuration.
65
+
66
+ ```ts
67
+ import { buildPinoLogger, buildSinkLogger, parseLogLevelOrDefault } from "@plandek-utils/logging";
68
+
69
+ const level = parseLogLevelOrDefault(process.env.LOG_LEVEL, "info"); // gets the log level if present, otherwise info. If the LOG_LEVEL is not a valid one it will throw.
70
+ const logger = buildPinoLogger(level, ["req.headers.authorization"]);
71
+
72
+ const testLogger = buildSinkLogger(level); // does not send any log -> useful for tests.
73
+
74
+ logger.info({ req: { headers: { authorization: "Bearer token" } } }, "User logged in");
75
+ // {"level":30,"time":"2024-10-28T11:10:58.250Z","pid":18166,"hostname":"044ce1509ebe","req":{"headers":{"authorization":"[REDACTED]"}},"msg":"User logged in"}
76
+
77
+ logger.info("Hi!");
78
+ // {"level":30,"time":"2024-10-28T11:12:48.732Z","pid":18166,"hostname":"044ce1509ebe","msg":"Hi!"}
79
+ ```
80
+
81
+ ### `makeLogging`
82
+
83
+ ```ts
84
+ import { makeLogging } from "@plandek-utils/logging";
85
+
86
+ const logger = buildPinoLogger("info");
87
+ const logging = makeLogging({ logger, section: "api" });
88
+
89
+ logging.info("User logged in", { userId: 123 });
90
+ // {"level":30,"time":"2024-10-28T11:14:13.519Z","pid":18166,"hostname":"044ce1509ebe","logSections":["api"],"userId":123,"msg":"User logged in"}
91
+
92
+ logging.withSection("database").debug("Query executed");
93
+ // no output since `debug` is not enabled in the logger
94
+
95
+ logging.withSection("database").error("Query failed");
96
+ // {"level":50,"time":"2024-10-28T11:14:59.509Z","pid":18166,"hostname":"044ce1509ebe","logSections":["api"],"logSections":["api","database"],"msg":"Query failed"}
97
+ ```
98
+
99
+ ### `makeLoggingWithRecord`
100
+
101
+ Same as `makeLogging`, but keeping an in-memory record of all messages (and contexts) sent. This is meant for testing
102
+ purposes.
103
+
104
+ ```ts
105
+ import { makeLoggingWithRecord, type PreparedLogger } from "@plandek-utils/logging";
106
+
107
+ const logger: PreparedLogger = buildPinoLogger("info");
108
+ const loggingWithRecords = makeLoggingWithRecord({ logger, section: "api" });
109
+
110
+ loggingWithRecords.info("User logged in", { userId: 123 });
111
+ // {"level":30,"time":"2024-10-28T11:16:49.607Z","pid":18166,"hostname":"044ce1509ebe","logSections":["api"],"userId":123,"msg":"User logged in"}
112
+
113
+ console.log(loggingWithRecords.messages.info);
114
+ // [ [ "User logged in", { userId: 123 } ] ]
115
+ ```
116
+
117
+ ## Development
118
+
119
+ This package is developed with TypeScript and uses Vitest for testing.
120
+
121
+ - `npm run build`: build the package
122
+ - `npm test`: run tests
123
+ - `npm run test:watch`: run tests in watch mode
124
+ - `npm run test:coverage`: run tests with coverage
125
+ - `npm run lint`: lint files
126
+ - `npm run format`: format files
127
+
128
+ ## License
129
+
130
+ MIT License - see LICENSE file
package/dist/mod.d.ts ADDED
@@ -0,0 +1,118 @@
1
+ import type { Logger as PinoLogger } from "pino";
2
+ import type { DeepReadonly } from "simplytyped";
3
+ import type { PlainObject } from "@plandek-utils/plain-object";
4
+ /**
5
+ * Allowed log levels for the logger.
6
+ */
7
+ export declare const LOG_LEVELS: readonly ["fatal", "error", "warn", "info", "debug", "trace", "silent"];
8
+ /**
9
+ * Possible log levels for preparing the pino logger.
10
+ */
11
+ export type LogLevel = (typeof LOG_LEVELS)[number];
12
+ /**
13
+ * Parses the given log level, or returns the default level if it's not blank. If it is an invalid level it will throw an error.
14
+ */
15
+ export declare function parseLogLevelOrDefault(level: string | null | undefined, defaultLevel: LogLevel): LogLevel;
16
+ /**
17
+ * Prepared pino logger, returned by `buildPinoLogger` or `buildSinkLogger`.
18
+ *
19
+ * @see buildPinoLogger
20
+ * @see buildSinkLogger
21
+ */
22
+ export type PreparedLogger = Pick<PinoLogger, "level" | "info" | "debug" | "warn" | "error" | "bindings"> & {
23
+ child: (bindings: PlainObject) => PreparedLogger;
24
+ };
25
+ /**
26
+ * Prepares a Pino logger with the common configuration.
27
+ *
28
+ * @param level - The log level to use.
29
+ * @param redactPaths - Paths to redact from the logs. Defaults to `["req.headers.authorization", "req.headers.cookie"]`.
30
+ * @returns PinoLogger
31
+ */
32
+ export declare function buildPinoLogger(level: LogLevel, redactPaths?: string[]): PreparedLogger;
33
+ /**
34
+ * A mock logger that does nothing, holds no binding (sections).
35
+ */
36
+ export declare function buildSinkLogger(level: LogLevel, givenBindings?: PlainObject): PreparedLogger;
37
+ /**
38
+ * Util to serialise as JSON the given value, pretty-printed (2 spaces).
39
+ */
40
+ export declare function prettyJSON(obj: unknown): string;
41
+ /**
42
+ * Calls `prettyJSON` and then colourises the output.
43
+ */
44
+ export declare function colourPrettyJSON(obj: unknown): string;
45
+ /**
46
+ * Alias for `colourPrettyJSON`.
47
+ * @see colourPrettyJSON
48
+ */
49
+ export declare const colorPrettyJSON: typeof colourPrettyJSON;
50
+ /**
51
+ * Interface to provide colouring for logs.
52
+ */
53
+ export type LogColourUtils = {
54
+ blue: (x: string) => string;
55
+ cyan: (x: string) => string;
56
+ gray: (x: string) => string;
57
+ magenta: (x: string) => string;
58
+ red: (x: string) => string;
59
+ yellow: (x: string) => string;
60
+ };
61
+ /**
62
+ * Creates a LogColourUtils object, with actual colours or not depending on the given mode.
63
+ *
64
+ * @param mode: if "with-colour", it will return a set of functions that will colour the given string. If "plain", it will return a set of functions that will return the string as is.
65
+ */
66
+ export declare function makeColourUtils(mode: "with-colour" | "plain"): LogColourUtils;
67
+ /**
68
+ * Logging interface. Requires a message (string) and optionally an object to log, which is required to be a Plain Object to ensure serialisation.
69
+ */
70
+ type LogFn = (msg: string, obj?: DeepReadonly<PlainObject>) => void;
71
+ /**
72
+ * Logging interface that provides a way to log messages with different levels. It should be configured with sections. It can return a new instance with a new section.
73
+ */
74
+ export type Logging = {
75
+ logger: PreparedLogger;
76
+ info: LogFn;
77
+ warn: LogFn;
78
+ debug: LogFn;
79
+ error: LogFn;
80
+ getSections(this: Logging): string[];
81
+ withSection(this: Logging, section: string, context?: PlainObject): Logging;
82
+ };
83
+ /**
84
+ * Variant of the Logging interface that stores the messages that have been logged.
85
+ */
86
+ export type LoggingWithRecords = Omit<Logging, "withSection"> & {
87
+ withSection(this: Logging, section: string, context?: PlainObject): LoggingWithRecords;
88
+ messages: {
89
+ info: Array<[string, PlainObject | null]>;
90
+ warn: Array<[string, PlainObject | null]>;
91
+ debug: Array<[string, PlainObject | null]>;
92
+ error: Array<[string, PlainObject | null]>;
93
+ };
94
+ };
95
+ /**
96
+ * Checks if the given Logging object is a LoggingWithRecords.
97
+ * @param obj
98
+ * @returns
99
+ */
100
+ export declare function isLoggingWithRecords(obj: Logging): obj is LoggingWithRecords;
101
+ /**
102
+ * Creates a Logging object. It requires an actual pino logger to be sent, and optionally a section and a context.
103
+ */
104
+ export declare function makeLogging(opts: {
105
+ section?: string;
106
+ context?: PlainObject;
107
+ logger: PreparedLogger;
108
+ }): Logging;
109
+ /**
110
+ * Creates a LoggingWithRecords object. It requires an actual pino logger to be sent, and optionally a section and a context. You can pass it a "messages" record object to use or a new one will be created.
111
+ */
112
+ export declare function makeLoggingWithRecord(opts: {
113
+ logger: PreparedLogger;
114
+ section?: string;
115
+ context?: PlainObject;
116
+ messages?: LoggingWithRecords["messages"];
117
+ }): LoggingWithRecords;
118
+ export {};
package/dist/mod.js ADDED
@@ -0,0 +1,155 @@
1
+ // src/mod.ts
2
+ import chalk from "chalk";
3
+ import { colorize } from "json-colorizer";
4
+ import { pino } from "pino";
5
+ var LOG_LEVELS = ["fatal", "error", "warn", "info", "debug", "trace", "silent"];
6
+ function parseLogLevelOrDefault(level, defaultLevel) {
7
+ if (!level)
8
+ return defaultLevel;
9
+ const lowerLevel = level.toLowerCase();
10
+ if (LOG_LEVELS.includes(lowerLevel)) {
11
+ return lowerLevel;
12
+ }
13
+ throw new Error(`Invalid log level: ${level}`);
14
+ }
15
+ function buildPinoLogger(level, redactPaths) {
16
+ const paths = redactPaths ?? ["req.headers.authorization", "req.headers.cookie"];
17
+ return pino({
18
+ level,
19
+ timestamp: pino.stdTimeFunctions.isoTime,
20
+ redact: { paths, censor: "[REDACTED]" }
21
+ });
22
+ }
23
+ function buildSinkLogger(level, givenBindings) {
24
+ const bindings = givenBindings ?? {};
25
+ return {
26
+ level,
27
+ info: () => {
28
+ },
29
+ debug: () => {
30
+ },
31
+ warn: () => {
32
+ },
33
+ error: () => {
34
+ },
35
+ child: (childBindings) => buildSinkLogger(level, { ...bindings, ...childBindings }),
36
+ bindings: () => bindings
37
+ };
38
+ }
39
+ function prettyJSON(obj) {
40
+ return JSON.stringify(obj, null, 2);
41
+ }
42
+ function colourPrettyJSON(obj) {
43
+ return colorize(prettyJSON(obj));
44
+ }
45
+ var colorPrettyJSON = colourPrettyJSON;
46
+ function makeColourUtils(mode) {
47
+ if (mode === "with-colour") {
48
+ return {
49
+ blue: (x) => chalk.blue(x),
50
+ cyan: (x) => chalk.cyan(x),
51
+ gray: (x) => chalk.gray(x),
52
+ magenta: (x) => chalk.magenta(x),
53
+ red: (x) => chalk.red(x),
54
+ yellow: (x) => chalk.yellow(x)
55
+ };
56
+ }
57
+ if (mode === "plain") {
58
+ return {
59
+ blue: (x) => x,
60
+ cyan: (x) => x,
61
+ gray: (x) => x,
62
+ magenta: (x) => x,
63
+ red: (x) => x,
64
+ yellow: (x) => x
65
+ };
66
+ }
67
+ const _never = mode;
68
+ throw new Error(`Unknown mode: ${_never}`);
69
+ }
70
+ function isLoggingWithRecords(obj) {
71
+ return "messages" in obj;
72
+ }
73
+ function makeLogging(opts) {
74
+ const logger = loggerFor(opts.logger, opts.section ?? null, opts.context ?? null);
75
+ return {
76
+ logger,
77
+ info: (msg, obj) => logger.info(obj, msg),
78
+ debug: (msg, obj) => logger.debug(obj, msg),
79
+ warn: (msg, obj) => logger.warn(obj, msg),
80
+ error: (msg, obj) => logger.error(obj, msg),
81
+ getSections() {
82
+ return getSectionsFromLogger(logger);
83
+ },
84
+ withSection(section, context) {
85
+ if (this.getSections().includes(section))
86
+ return this;
87
+ return makeLogging({ logger: this.logger, section, context });
88
+ }
89
+ };
90
+ }
91
+ function makeLoggingWithRecord(opts) {
92
+ const logging = makeLogging(opts);
93
+ const messages = opts.messages ?? {
94
+ info: [],
95
+ warn: [],
96
+ debug: [],
97
+ error: []
98
+ };
99
+ function makeLogFn(logFn, key) {
100
+ return (msg, obj) => {
101
+ messages[key].push([msg, obj ?? null]);
102
+ return logFn(msg, obj);
103
+ };
104
+ }
105
+ return {
106
+ messages,
107
+ logger: logging.logger,
108
+ info: makeLogFn(logging.info, "info"),
109
+ debug: makeLogFn(logging.debug, "debug"),
110
+ warn: makeLogFn(logging.warn, "warn"),
111
+ error: makeLogFn(logging.error, "error"),
112
+ getSections() {
113
+ return logging.getSections();
114
+ },
115
+ withSection(section, context) {
116
+ return makeLoggingWithRecord({
117
+ logger: this.logger,
118
+ section,
119
+ messages,
120
+ context
121
+ });
122
+ }
123
+ };
124
+ }
125
+ function loggerFor(givenLogger, section, context) {
126
+ if (!context && (!section || loggerHasSection(givenLogger, section))) {
127
+ return givenLogger;
128
+ }
129
+ const childBindings = { ...context };
130
+ if (section) {
131
+ childBindings.logSections = [...getSectionsFromLogger(givenLogger), section];
132
+ }
133
+ return givenLogger.child(childBindings);
134
+ }
135
+ function getSectionsFromLogger(logger) {
136
+ return logger.bindings().logSections ?? [];
137
+ }
138
+ function loggerHasSection(logger, section) {
139
+ const sections = getSectionsFromLogger(logger);
140
+ return sections.includes(section);
141
+ }
142
+ export {
143
+ LOG_LEVELS,
144
+ buildPinoLogger,
145
+ buildSinkLogger,
146
+ colorPrettyJSON,
147
+ colourPrettyJSON,
148
+ isLoggingWithRecords,
149
+ makeColourUtils,
150
+ makeLogging,
151
+ makeLoggingWithRecord,
152
+ parseLogLevelOrDefault,
153
+ prettyJSON
154
+ };
155
+ //# sourceMappingURL=mod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mod.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { colorize } from \"json-colorizer\";\nimport type { Logger as PinoLogger } from \"pino\";\nimport { pino } from \"pino\";\nimport type { DeepReadonly } from \"simplytyped\";\n\nimport type { PlainObject, PlainObjectValue } from \"@plandek-utils/plain-object\";\n\n/**\n * Allowed log levels for the logger.\n */\nexport const LOG_LEVELS = [\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\", \"silent\"] as const;\n\n/**\n * Possible log levels for preparing the pino logger.\n */\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * Parses the given log level, or returns the default level if it's not blank. If it is an invalid level it will throw an error.\n */\nexport function parseLogLevelOrDefault(level: string | null | undefined, defaultLevel: LogLevel): LogLevel {\n if (!level) return defaultLevel;\n\n const lowerLevel = level.toLowerCase() as LogLevel;\n if (LOG_LEVELS.includes(lowerLevel)) {\n return lowerLevel;\n }\n\n throw new Error(`Invalid log level: ${level}`);\n}\n\n/**\n * Prepared pino logger, returned by `buildPinoLogger` or `buildSinkLogger`.\n *\n * @see buildPinoLogger\n * @see buildSinkLogger\n */\nexport type PreparedLogger = Pick<PinoLogger, \"level\" | \"info\" | \"debug\" | \"warn\" | \"error\" | \"bindings\"> & {\n child: (bindings: PlainObject) => PreparedLogger;\n};\n\n/**\n * Prepares a Pino logger with the common configuration.\n *\n * @param level - The log level to use.\n * @param redactPaths - Paths to redact from the logs. Defaults to `[\"req.headers.authorization\", \"req.headers.cookie\"]`.\n * @returns PinoLogger\n */\nexport function buildPinoLogger(level: LogLevel, redactPaths?: string[]): PreparedLogger {\n const paths = redactPaths ?? [\"req.headers.authorization\", \"req.headers.cookie\"];\n return pino({\n level,\n timestamp: pino.stdTimeFunctions.isoTime,\n redact: { paths, censor: \"[REDACTED]\" },\n });\n}\n\n/**\n * A mock logger that does nothing, holds no binding (sections).\n */\nexport function buildSinkLogger(level: LogLevel, givenBindings?: PlainObject): PreparedLogger {\n const bindings = givenBindings ?? {};\n return {\n level,\n info: () => {},\n debug: () => {},\n warn: () => {},\n error: () => {},\n child: (childBindings?: PlainObject) => buildSinkLogger(level, { ...bindings, ...childBindings }),\n bindings: () => bindings,\n };\n}\n\n/**\n * Util to serialise as JSON the given value, pretty-printed (2 spaces).\n */\nexport function prettyJSON(obj: unknown): string {\n return JSON.stringify(obj, null, 2);\n}\n\n/**\n * Calls `prettyJSON` and then colourises the output.\n */\nexport function colourPrettyJSON(obj: unknown): string {\n return colorize(prettyJSON(obj));\n}\n\n/**\n * Alias for `colourPrettyJSON`.\n * @see colourPrettyJSON\n */\nexport const colorPrettyJSON = colourPrettyJSON;\n\n/**\n * Interface to provide colouring for logs.\n */\nexport type LogColourUtils = {\n blue: (x: string) => string;\n cyan: (x: string) => string;\n gray: (x: string) => string;\n magenta: (x: string) => string;\n red: (x: string) => string;\n yellow: (x: string) => string;\n};\n\n/**\n * Creates a LogColourUtils object, with actual colours or not depending on the given mode.\n *\n * @param mode: if \"with-colour\", it will return a set of functions that will colour the given string. If \"plain\", it will return a set of functions that will return the string as is.\n */\nexport function makeColourUtils(mode: \"with-colour\" | \"plain\"): LogColourUtils {\n if (mode === \"with-colour\") {\n return {\n blue: (x: string) => chalk.blue(x),\n cyan: (x: string) => chalk.cyan(x),\n gray: (x: string) => chalk.gray(x),\n magenta: (x: string) => chalk.magenta(x),\n red: (x: string) => chalk.red(x),\n yellow: (x: string) => chalk.yellow(x),\n };\n }\n\n if (mode === \"plain\") {\n return {\n blue: (x: string) => x,\n cyan: (x: string) => x,\n gray: (x: string) => x,\n magenta: (x: string) => x,\n red: (x: string) => x,\n yellow: (x: string) => x,\n };\n }\n\n const _never: never = mode;\n throw new Error(`Unknown mode: ${_never}`);\n}\n\n/**\n * Logging interface. Requires a message (string) and optionally an object to log, which is required to be a Plain Object to ensure serialisation.\n */\ntype LogFn = (msg: string, obj?: DeepReadonly<PlainObject>) => void;\n\n/**\n * Logging interface that provides a way to log messages with different levels. It should be configured with sections. It can return a new instance with a new section.\n */\nexport type Logging = {\n logger: PreparedLogger;\n info: LogFn;\n warn: LogFn;\n debug: LogFn;\n error: LogFn;\n getSections(this: Logging): string[];\n withSection(this: Logging, section: string, context?: PlainObject): Logging;\n};\n\n/**\n * Variant of the Logging interface that stores the messages that have been logged.\n */\nexport type LoggingWithRecords = Omit<Logging, \"withSection\"> & {\n withSection(this: Logging, section: string, context?: PlainObject): LoggingWithRecords;\n messages: {\n info: Array<[string, PlainObject | null]>;\n warn: Array<[string, PlainObject | null]>;\n debug: Array<[string, PlainObject | null]>;\n error: Array<[string, PlainObject | null]>;\n };\n};\n\n/**\n * Checks if the given Logging object is a LoggingWithRecords.\n * @param obj\n * @returns\n */\nexport function isLoggingWithRecords(obj: Logging): obj is LoggingWithRecords {\n return \"messages\" in obj;\n}\n\n/**\n * Creates a Logging object. It requires an actual pino logger to be sent, and optionally a section and a context.\n */\nexport function makeLogging(opts: {\n section?: string;\n context?: PlainObject;\n logger: PreparedLogger;\n}): Logging {\n const logger = loggerFor(opts.logger, opts.section ?? null, opts.context ?? null);\n\n return {\n logger,\n info: (msg, obj) => logger.info(obj, msg),\n debug: (msg, obj) => logger.debug(obj, msg),\n warn: (msg, obj) => logger.warn(obj, msg),\n error: (msg, obj) => logger.error(obj, msg),\n getSections(): string[] {\n return getSectionsFromLogger(logger);\n },\n withSection(section, context) {\n if (this.getSections().includes(section)) return this;\n\n return makeLogging({ logger: this.logger, section, context });\n },\n };\n}\n\n/**\n * Creates a LoggingWithRecords object. It requires an actual pino logger to be sent, and optionally a section and a context. You can pass it a \"messages\" record object to use or a new one will be created.\n */\nexport function makeLoggingWithRecord(opts: {\n logger: PreparedLogger;\n section?: string;\n context?: PlainObject;\n messages?: LoggingWithRecords[\"messages\"];\n}): LoggingWithRecords {\n const logging = makeLogging(opts);\n const messages = opts.messages ?? {\n info: [],\n warn: [],\n debug: [],\n error: [],\n };\n\n function makeLogFn(logFn: LogFn, key: keyof typeof messages): LogFn {\n return (msg, obj) => {\n messages[key].push([msg, obj ?? null]);\n return logFn(msg, obj);\n };\n }\n\n return {\n messages,\n logger: logging.logger,\n info: makeLogFn(logging.info, \"info\"),\n debug: makeLogFn(logging.debug, \"debug\"),\n warn: makeLogFn(logging.warn, \"warn\"),\n error: makeLogFn(logging.error, \"error\"),\n getSections(): string[] {\n return logging.getSections();\n },\n withSection(section, context) {\n return makeLoggingWithRecord({\n logger: this.logger,\n section,\n messages,\n context,\n });\n },\n };\n}\n\n// INTERNAL\n\nfunction loggerFor(givenLogger: PreparedLogger, section: string | null, context: PlainObject | null) {\n if (!context && (!section || loggerHasSection(givenLogger, section))) {\n return givenLogger;\n }\n\n const childBindings: Record<string, PlainObjectValue> = { ...context };\n if (section) {\n childBindings.logSections = [...getSectionsFromLogger(givenLogger), section];\n }\n\n return givenLogger.child(childBindings);\n}\n\nfunction getSectionsFromLogger(logger: PreparedLogger) {\n return logger.bindings().logSections ?? [];\n}\n\nfunction loggerHasSection(logger: PreparedLogger, section: string) {\n const sections = getSectionsFromLogger(logger);\n return sections.includes(section);\n}\n"],"mappings":";AAAA,OAAO,WAAW;AAClB,SAAS,gBAAgB;AAEzB,SAAS,YAAY;AAQd,IAAM,aAAa,CAAC,SAAS,SAAS,QAAQ,QAAQ,SAAS,SAAS,QAAQ;AAUhF,SAAS,uBAAuB,OAAkC,cAAkC;AACzG,MAAI,CAAC;AAAO,WAAO;AAEnB,QAAM,aAAa,MAAM,YAAY;AACrC,MAAI,WAAW,SAAS,UAAU,GAAG;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,MAAM,sBAAsB,KAAK,EAAE;AAC/C;AAmBO,SAAS,gBAAgB,OAAiB,aAAwC;AACvF,QAAM,QAAQ,eAAe,CAAC,6BAA6B,oBAAoB;AAC/E,SAAO,KAAK;AAAA,IACV;AAAA,IACA,WAAW,KAAK,iBAAiB;AAAA,IACjC,QAAQ,EAAE,OAAO,QAAQ,aAAa;AAAA,EACxC,CAAC;AACH;AAKO,SAAS,gBAAgB,OAAiB,eAA6C;AAC5F,QAAM,WAAW,iBAAiB,CAAC;AACnC,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AAAA,IAAC;AAAA,IACb,OAAO,MAAM;AAAA,IAAC;AAAA,IACd,MAAM,MAAM;AAAA,IAAC;AAAA,IACb,OAAO,MAAM;AAAA,IAAC;AAAA,IACd,OAAO,CAAC,kBAAgC,gBAAgB,OAAO,EAAE,GAAG,UAAU,GAAG,cAAc,CAAC;AAAA,IAChG,UAAU,MAAM;AAAA,EAClB;AACF;AAKO,SAAS,WAAW,KAAsB;AAC/C,SAAO,KAAK,UAAU,KAAK,MAAM,CAAC;AACpC;AAKO,SAAS,iBAAiB,KAAsB;AACrD,SAAO,SAAS,WAAW,GAAG,CAAC;AACjC;AAMO,IAAM,kBAAkB;AAmBxB,SAAS,gBAAgB,MAA+C;AAC7E,MAAI,SAAS,eAAe;AAC1B,WAAO;AAAA,MACL,MAAM,CAAC,MAAc,MAAM,KAAK,CAAC;AAAA,MACjC,MAAM,CAAC,MAAc,MAAM,KAAK,CAAC;AAAA,MACjC,MAAM,CAAC,MAAc,MAAM,KAAK,CAAC;AAAA,MACjC,SAAS,CAAC,MAAc,MAAM,QAAQ,CAAC;AAAA,MACvC,KAAK,CAAC,MAAc,MAAM,IAAI,CAAC;AAAA,MAC/B,QAAQ,CAAC,MAAc,MAAM,OAAO,CAAC;AAAA,IACvC;AAAA,EACF;AAEA,MAAI,SAAS,SAAS;AACpB,WAAO;AAAA,MACL,MAAM,CAAC,MAAc;AAAA,MACrB,MAAM,CAAC,MAAc;AAAA,MACrB,MAAM,CAAC,MAAc;AAAA,MACrB,SAAS,CAAC,MAAc;AAAA,MACxB,KAAK,CAAC,MAAc;AAAA,MACpB,QAAQ,CAAC,MAAc;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,SAAgB;AACtB,QAAM,IAAI,MAAM,iBAAiB,MAAM,EAAE;AAC3C;AAsCO,SAAS,qBAAqB,KAAyC;AAC5E,SAAO,cAAc;AACvB;AAKO,SAAS,YAAY,MAIhB;AACV,QAAM,SAAS,UAAU,KAAK,QAAQ,KAAK,WAAW,MAAM,KAAK,WAAW,IAAI;AAEhF,SAAO;AAAA,IACL;AAAA,IACA,MAAM,CAAC,KAAK,QAAQ,OAAO,KAAK,KAAK,GAAG;AAAA,IACxC,OAAO,CAAC,KAAK,QAAQ,OAAO,MAAM,KAAK,GAAG;AAAA,IAC1C,MAAM,CAAC,KAAK,QAAQ,OAAO,KAAK,KAAK,GAAG;AAAA,IACxC,OAAO,CAAC,KAAK,QAAQ,OAAO,MAAM,KAAK,GAAG;AAAA,IAC1C,cAAwB;AACtB,aAAO,sBAAsB,MAAM;AAAA,IACrC;AAAA,IACA,YAAY,SAAS,SAAS;AAC5B,UAAI,KAAK,YAAY,EAAE,SAAS,OAAO;AAAG,eAAO;AAEjD,aAAO,YAAY,EAAE,QAAQ,KAAK,QAAQ,SAAS,QAAQ,CAAC;AAAA,IAC9D;AAAA,EACF;AACF;AAKO,SAAS,sBAAsB,MAKf;AACrB,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,WAAW,KAAK,YAAY;AAAA,IAChC,MAAM,CAAC;AAAA,IACP,MAAM,CAAC;AAAA,IACP,OAAO,CAAC;AAAA,IACR,OAAO,CAAC;AAAA,EACV;AAEA,WAAS,UAAU,OAAc,KAAmC;AAClE,WAAO,CAAC,KAAK,QAAQ;AACnB,eAAS,GAAG,EAAE,KAAK,CAAC,KAAK,OAAO,IAAI,CAAC;AACrC,aAAO,MAAM,KAAK,GAAG;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,QAAQ;AAAA,IAChB,MAAM,UAAU,QAAQ,MAAM,MAAM;AAAA,IACpC,OAAO,UAAU,QAAQ,OAAO,OAAO;AAAA,IACvC,MAAM,UAAU,QAAQ,MAAM,MAAM;AAAA,IACpC,OAAO,UAAU,QAAQ,OAAO,OAAO;AAAA,IACvC,cAAwB;AACtB,aAAO,QAAQ,YAAY;AAAA,IAC7B;AAAA,IACA,YAAY,SAAS,SAAS;AAC5B,aAAO,sBAAsB;AAAA,QAC3B,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAIA,SAAS,UAAU,aAA6B,SAAwB,SAA6B;AACnG,MAAI,CAAC,YAAY,CAAC,WAAW,iBAAiB,aAAa,OAAO,IAAI;AACpE,WAAO;AAAA,EACT;AAEA,QAAM,gBAAkD,EAAE,GAAG,QAAQ;AACrE,MAAI,SAAS;AACX,kBAAc,cAAc,CAAC,GAAG,sBAAsB,WAAW,GAAG,OAAO;AAAA,EAC7E;AAEA,SAAO,YAAY,MAAM,aAAa;AACxC;AAEA,SAAS,sBAAsB,QAAwB;AACrD,SAAO,OAAO,SAAS,EAAE,eAAe,CAAC;AAC3C;AAEA,SAAS,iBAAiB,QAAwB,SAAiB;AACjE,QAAM,WAAW,sBAAsB,MAAM;AAC7C,SAAO,SAAS,SAAS,OAAO;AAClC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@plandek-utils/logging",
3
+ "version": "0.6.0",
4
+ "description": "TypeScript utils for Logging. Includes prettifying of JSON, logging utils, and colour utils.",
5
+ "main": "dist/mod.js",
6
+ "types": "dist/mod.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "commit": "git-cz",
10
+ "build": "npm run build:tsup && npm run build:dts",
11
+ "build:tsup": "tsup",
12
+ "build:dts": "echo 'emitting Declaration using tsc' && tsc --emitDeclarationOnly",
13
+ "fix": "npm run fix:biome",
14
+ "fix:biome": "biome format --write src && biome check --apply src",
15
+ "check": "npm run check:biome && npm run check:tsc",
16
+ "check:biome": "biome check --apply src",
17
+ "check:tsc": "tsc --noEmit",
18
+ "test": "vitest run --coverage",
19
+ "test:watch": "vitest",
20
+ "prepare-release": "npm run fix && npm run check && npm run test && npm run build",
21
+ "prepare": "husky || true"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "LICENSE",
26
+ "README.md"
27
+ ],
28
+ "keywords": [
29
+ "logging",
30
+ "typescript",
31
+ "pino",
32
+ "json",
33
+ "color",
34
+ "colour"
35
+ ],
36
+ "author": "Plandek Ltd.",
37
+ "license": "MIT",
38
+ "dependencies": {
39
+ "@plandek-utils/plain-object": "^1.1.0",
40
+ "chalk": "^5.3.0",
41
+ "json-colorizer": "^3.0.1",
42
+ "pino": "^9.5.0",
43
+ "simplytyped": "^3.3.0"
44
+ },
45
+ "devDependencies": {
46
+ "@biomejs/biome": "1.5.3",
47
+ "@types/node": "20.11.0",
48
+ "@vitest/coverage-v8": "1.1.3",
49
+ "biome": "0.3.3",
50
+ "tsup": "8.0.1",
51
+ "typescript": "5.3.3",
52
+ "vitest": "1.1.3"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/plandek-utils/logging.git"
60
+ },
61
+ "bugs": {
62
+ "url": "https://github.com/plandek-utils/logging/issues"
63
+ },
64
+ "homepage": "https://github.com/plandek-utils/logging#readme"
65
+ }