@plandek-utils/logging 0.6.0 → 1.0.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.
@@ -1,11 +1,18 @@
1
- // src/mod.ts
1
+ // src/index.ts
2
2
  import chalk from "chalk";
3
3
  import { colorize } from "json-colorizer";
4
4
  import { pino } from "pino";
5
- var LOG_LEVELS = ["fatal", "error", "warn", "info", "debug", "trace", "silent"];
5
+ var LOG_LEVELS = [
6
+ "fatal",
7
+ "error",
8
+ "warn",
9
+ "info",
10
+ "debug",
11
+ "trace",
12
+ "silent"
13
+ ];
6
14
  function parseLogLevelOrDefault(level, defaultLevel) {
7
- if (!level)
8
- return defaultLevel;
15
+ if (!level) return defaultLevel;
9
16
  const lowerLevel = level.toLowerCase();
10
17
  if (LOG_LEVELS.includes(lowerLevel)) {
11
18
  return lowerLevel;
@@ -13,7 +20,10 @@ function parseLogLevelOrDefault(level, defaultLevel) {
13
20
  throw new Error(`Invalid log level: ${level}`);
14
21
  }
15
22
  function buildPinoLogger(level, redactPaths) {
16
- const paths = redactPaths ?? ["req.headers.authorization", "req.headers.cookie"];
23
+ const paths = redactPaths ?? [
24
+ "req.headers.authorization",
25
+ "req.headers.cookie"
26
+ ];
17
27
  return pino({
18
28
  level,
19
29
  timestamp: pino.stdTimeFunctions.isoTime,
@@ -71,7 +81,11 @@ function isLoggingWithRecords(obj) {
71
81
  return "messages" in obj;
72
82
  }
73
83
  function makeLogging(opts) {
74
- const logger = loggerFor(opts.logger, opts.section ?? null, opts.context ?? null);
84
+ const logger = loggerFor(
85
+ opts.logger,
86
+ opts.section ?? null,
87
+ opts.context ?? null
88
+ );
75
89
  return {
76
90
  logger,
77
91
  info: (msg, obj) => logger.info(obj, msg),
@@ -82,8 +96,7 @@ function makeLogging(opts) {
82
96
  return getSectionsFromLogger(logger);
83
97
  },
84
98
  withSection(section, context) {
85
- if (this.getSections().includes(section))
86
- return this;
99
+ if (this.getSections().includes(section)) return this;
87
100
  return makeLogging({ logger: this.logger, section, context });
88
101
  }
89
102
  };
@@ -128,7 +141,10 @@ function loggerFor(givenLogger, section, context) {
128
141
  }
129
142
  const childBindings = { ...context };
130
143
  if (section) {
131
- childBindings.logSections = [...getSectionsFromLogger(givenLogger), section];
144
+ childBindings.logSections = [
145
+ ...getSectionsFromLogger(givenLogger),
146
+ section
147
+ ];
132
148
  }
133
149
  return givenLogger.child(childBindings);
134
150
  }
@@ -152,4 +168,4 @@ export {
152
168
  parseLogLevelOrDefault,
153
169
  prettyJSON
154
170
  };
155
- //# sourceMappingURL=mod.js.map
171
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.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 {\n\tPlainObject,\n\tPlainObjectValue,\n} from \"@plandek-utils/plain-object\";\n\n/**\n * Allowed log levels for the logger.\n */\nexport const LOG_LEVELS = [\n\t\"fatal\",\n\t\"error\",\n\t\"warn\",\n\t\"info\",\n\t\"debug\",\n\t\"trace\",\n\t\"silent\",\n] 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(\n\tlevel: string | null | undefined,\n\tdefaultLevel: LogLevel,\n): LogLevel {\n\tif (!level) return defaultLevel;\n\n\tconst lowerLevel = level.toLowerCase() as LogLevel;\n\tif (LOG_LEVELS.includes(lowerLevel)) {\n\t\treturn lowerLevel;\n\t}\n\n\tthrow 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<\n\tPinoLogger,\n\t\"level\" | \"info\" | \"debug\" | \"warn\" | \"error\" | \"bindings\"\n> & {\n\tchild: (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(\n\tlevel: LogLevel,\n\tredactPaths?: string[],\n): PreparedLogger {\n\tconst paths = redactPaths ?? [\n\t\t\"req.headers.authorization\",\n\t\t\"req.headers.cookie\",\n\t];\n\treturn pino({\n\t\tlevel,\n\t\ttimestamp: pino.stdTimeFunctions.isoTime,\n\t\tredact: { paths, censor: \"[REDACTED]\" },\n\t});\n}\n\n/**\n * A mock logger that does nothing, holds no binding (sections).\n */\nexport function buildSinkLogger(\n\tlevel: LogLevel,\n\tgivenBindings?: PlainObject,\n): PreparedLogger {\n\tconst bindings = givenBindings ?? {};\n\treturn {\n\t\tlevel,\n\t\tinfo: () => {},\n\t\tdebug: () => {},\n\t\twarn: () => {},\n\t\terror: () => {},\n\t\tchild: (childBindings?: PlainObject) =>\n\t\t\tbuildSinkLogger(level, { ...bindings, ...childBindings }),\n\t\tbindings: () => bindings,\n\t};\n}\n\n/**\n * Util to serialise as JSON the given value, pretty-printed (2 spaces).\n */\nexport function prettyJSON(obj: unknown): string {\n\treturn JSON.stringify(obj, null, 2);\n}\n\n/**\n * Calls `prettyJSON` and then colourises the output.\n */\nexport function colourPrettyJSON(obj: unknown): string {\n\treturn 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\tblue: (x: string) => string;\n\tcyan: (x: string) => string;\n\tgray: (x: string) => string;\n\tmagenta: (x: string) => string;\n\tred: (x: string) => string;\n\tyellow: (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\tif (mode === \"with-colour\") {\n\t\treturn {\n\t\t\tblue: (x: string) => chalk.blue(x),\n\t\t\tcyan: (x: string) => chalk.cyan(x),\n\t\t\tgray: (x: string) => chalk.gray(x),\n\t\t\tmagenta: (x: string) => chalk.magenta(x),\n\t\t\tred: (x: string) => chalk.red(x),\n\t\t\tyellow: (x: string) => chalk.yellow(x),\n\t\t};\n\t}\n\n\tif (mode === \"plain\") {\n\t\treturn {\n\t\t\tblue: (x: string) => x,\n\t\t\tcyan: (x: string) => x,\n\t\t\tgray: (x: string) => x,\n\t\t\tmagenta: (x: string) => x,\n\t\t\tred: (x: string) => x,\n\t\t\tyellow: (x: string) => x,\n\t\t};\n\t}\n\n\tconst _never: never = mode;\n\tthrow 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\tlogger: PreparedLogger;\n\tinfo: LogFn;\n\twarn: LogFn;\n\tdebug: LogFn;\n\terror: LogFn;\n\tgetSections(this: Logging): string[];\n\twithSection(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\twithSection(\n\t\tthis: Logging,\n\t\tsection: string,\n\t\tcontext?: PlainObject,\n\t): LoggingWithRecords;\n\tmessages: {\n\t\tinfo: Array<[string, PlainObject | null]>;\n\t\twarn: Array<[string, PlainObject | null]>;\n\t\tdebug: Array<[string, PlainObject | null]>;\n\t\terror: Array<[string, PlainObject | null]>;\n\t};\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\treturn \"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\tsection?: string;\n\tcontext?: PlainObject;\n\tlogger: PreparedLogger;\n}): Logging {\n\tconst logger = loggerFor(\n\t\topts.logger,\n\t\topts.section ?? null,\n\t\topts.context ?? null,\n\t);\n\n\treturn {\n\t\tlogger,\n\t\tinfo: (msg, obj) => logger.info(obj, msg),\n\t\tdebug: (msg, obj) => logger.debug(obj, msg),\n\t\twarn: (msg, obj) => logger.warn(obj, msg),\n\t\terror: (msg, obj) => logger.error(obj, msg),\n\t\tgetSections(): string[] {\n\t\t\treturn getSectionsFromLogger(logger);\n\t\t},\n\t\twithSection(section, context) {\n\t\t\tif (this.getSections().includes(section)) return this;\n\n\t\t\treturn makeLogging({ logger: this.logger, section, context });\n\t\t},\n\t};\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\tlogger: PreparedLogger;\n\tsection?: string;\n\tcontext?: PlainObject;\n\tmessages?: LoggingWithRecords[\"messages\"];\n}): LoggingWithRecords {\n\tconst logging = makeLogging(opts);\n\tconst messages = opts.messages ?? {\n\t\tinfo: [],\n\t\twarn: [],\n\t\tdebug: [],\n\t\terror: [],\n\t};\n\n\tfunction makeLogFn(logFn: LogFn, key: keyof typeof messages): LogFn {\n\t\treturn (msg, obj) => {\n\t\t\tmessages[key].push([msg, obj ?? null]);\n\t\t\treturn logFn(msg, obj);\n\t\t};\n\t}\n\n\treturn {\n\t\tmessages,\n\t\tlogger: logging.logger,\n\t\tinfo: makeLogFn(logging.info, \"info\"),\n\t\tdebug: makeLogFn(logging.debug, \"debug\"),\n\t\twarn: makeLogFn(logging.warn, \"warn\"),\n\t\terror: makeLogFn(logging.error, \"error\"),\n\t\tgetSections(): string[] {\n\t\t\treturn logging.getSections();\n\t\t},\n\t\twithSection(section, context) {\n\t\t\treturn makeLoggingWithRecord({\n\t\t\t\tlogger: this.logger,\n\t\t\t\tsection,\n\t\t\t\tmessages,\n\t\t\t\tcontext,\n\t\t\t});\n\t\t},\n\t};\n}\n\n// INTERNAL\n\nfunction loggerFor(\n\tgivenLogger: PreparedLogger,\n\tsection: string | null,\n\tcontext: PlainObject | null,\n) {\n\tif (!context && (!section || loggerHasSection(givenLogger, section))) {\n\t\treturn givenLogger;\n\t}\n\n\tconst childBindings: Record<string, PlainObjectValue> = { ...context };\n\tif (section) {\n\t\tchildBindings.logSections = [\n\t\t\t...getSectionsFromLogger(givenLogger),\n\t\t\tsection,\n\t\t];\n\t}\n\n\treturn givenLogger.child(childBindings);\n}\n\nfunction getSectionsFromLogger(logger: PreparedLogger) {\n\treturn logger.bindings().logSections ?? [];\n}\n\nfunction loggerHasSection(logger: PreparedLogger, section: string) {\n\tconst sections = getSectionsFromLogger(logger);\n\treturn sections.includes(section);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
2
  "name": "@plandek-utils/logging",
3
- "version": "0.6.0",
3
+ "version": "1.0.0",
4
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",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
8
  "scripts": {
9
9
  "commit": "git-cz",
10
10
  "build": "npm run build:tsup && npm run build:dts",
11
11
  "build:tsup": "tsup",
12
12
  "build:dts": "echo 'emitting Declaration using tsc' && tsc --emitDeclarationOnly",
13
13
  "fix": "npm run fix:biome",
14
- "fix:biome": "biome format --write src && biome check --apply src",
14
+ "fix:biome": "biome check --write src",
15
15
  "check": "npm run check:biome && npm run check:tsc",
16
- "check:biome": "biome check --apply src",
16
+ "check:biome": "biome check src",
17
17
  "check:tsc": "tsc --noEmit",
18
18
  "test": "vitest run --coverage",
19
19
  "test:watch": "vitest",
20
- "prepare-release": "npm run fix && npm run check && npm run test && npm run build",
21
- "prepare": "husky || true"
20
+ "prepare-release": "npm run fix && npm run check && npm run test && npm run build"
22
21
  },
23
22
  "files": [
24
23
  "dist",
24
+ "src",
25
25
  "LICENSE",
26
26
  "README.md"
27
27
  ],
@@ -36,20 +36,22 @@
36
36
  "author": "Plandek Ltd.",
37
37
  "license": "MIT",
38
38
  "dependencies": {
39
- "@plandek-utils/plain-object": "^1.1.0",
40
- "chalk": "^5.3.0",
39
+ "chalk": "^5.4.1",
41
40
  "json-colorizer": "^3.0.1",
42
- "pino": "^9.5.0",
43
41
  "simplytyped": "^3.3.0"
44
42
  },
43
+ "peerDependencies": {
44
+ "@plandek-utils/plain-object": "^2.0.0",
45
+ "pino": "^9.6.0"
46
+ },
45
47
  "devDependencies": {
46
- "@biomejs/biome": "1.5.3",
47
- "@types/node": "20.11.0",
48
- "@vitest/coverage-v8": "1.1.3",
48
+ "@biomejs/biome": "1.9.4",
49
+ "@types/node": "22.10.9",
50
+ "@vitest/coverage-v8": "3.0.4",
49
51
  "biome": "0.3.3",
50
- "tsup": "8.0.1",
51
- "typescript": "5.3.3",
52
- "vitest": "1.1.3"
52
+ "tsup": "8.3.5",
53
+ "typescript": "5.7.3",
54
+ "vitest": "3.0.4"
53
55
  },
54
56
  "publishConfig": {
55
57
  "access": "public"
@@ -0,0 +1,210 @@
1
+ import { colorize as colorizeJson } from "json-colorizer";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import {
5
+ buildPinoLogger,
6
+ buildSinkLogger,
7
+ colorPrettyJSON,
8
+ colourPrettyJSON,
9
+ isLoggingWithRecords,
10
+ makeColourUtils,
11
+ makeLogging,
12
+ makeLoggingWithRecord,
13
+ parseLogLevelOrDefault,
14
+ } from "..";
15
+
16
+ describe("colorPrettyJSON()", () => {
17
+ it("with null: returns 'null'", () => {
18
+ expect(colorPrettyJSON(null)).toBe(colorizeJson("null"));
19
+ expect(colourPrettyJSON(null)).toBe(colorizeJson("null"));
20
+ });
21
+
22
+ it("with 1: returns '1'", () => {
23
+ expect(colorPrettyJSON(1)).toBe(colorizeJson("1"));
24
+ expect(colourPrettyJSON(1)).toBe(colorizeJson("1"));
25
+ });
26
+
27
+ it("with 'abc': returns '\"abc\"'", () => {
28
+ expect(colorPrettyJSON("abc")).toBe(colorizeJson('"abc"'));
29
+ expect(colourPrettyJSON("abc")).toBe(colorizeJson('"abc"'));
30
+ });
31
+
32
+ it("with []: returns '[]'", () => {
33
+ expect(colorPrettyJSON([])).toBe(colorizeJson("[]"));
34
+ expect(colourPrettyJSON([])).toBe(colorizeJson("[]"));
35
+ });
36
+
37
+ it("with {}: returns '{}'", () => {
38
+ expect(colorPrettyJSON({})).toBe(colorizeJson("{}"));
39
+ expect(colourPrettyJSON({})).toBe(colorizeJson("{}"));
40
+ });
41
+
42
+ it("with { a: 1 }: returns '{ a: 1 }' formatted", () => {
43
+ expect(colorPrettyJSON({ a: 1 })).toBe(colorizeJson('{\n "a": 1\n}'));
44
+ expect(colourPrettyJSON({ a: 1 })).toBe(colorizeJson('{\n "a": 1\n}'));
45
+ });
46
+ });
47
+
48
+ describe("makeColourUtils", () => {
49
+ it('should return color functions when mode is "with-colour"', () => {
50
+ const colorUtils = makeColourUtils("with-colour");
51
+
52
+ expect(typeof colorUtils.blue).toBe("function");
53
+ expect(typeof colorUtils.cyan).toBe("function");
54
+ expect(typeof colorUtils.gray).toBe("function");
55
+ expect(typeof colorUtils.magenta).toBe("function");
56
+ expect(typeof colorUtils.red).toBe("function");
57
+ expect(typeof colorUtils.yellow).toBe("function");
58
+ });
59
+
60
+ it('should return identity functions when mode is "plain"', () => {
61
+ const colorUtils = makeColourUtils("plain");
62
+
63
+ expect(colorUtils.blue("test")).toBe("test");
64
+ expect(colorUtils.cyan("test")).toBe("test");
65
+ expect(colorUtils.gray("test")).toBe("test");
66
+ expect(colorUtils.magenta("test")).toBe("test");
67
+ expect(colorUtils.red("test")).toBe("test");
68
+ expect(colorUtils.yellow("test")).toBe("test");
69
+ });
70
+
71
+ it("should handle unknown modes", () => {
72
+ expect(() =>
73
+ makeColourUtils("unknown" as "with-colour" | "plain"),
74
+ ).toThrow();
75
+ });
76
+ });
77
+
78
+ describe("buildPinoLogger", () => {
79
+ it("should create a Pino logger with the given log level", () => {
80
+ const logger = buildPinoLogger("debug");
81
+ expect(logger.level).toBe("debug");
82
+ });
83
+ });
84
+
85
+ describe("isLoggingWithRecords", () => {
86
+ it("should return true for LoggingWithRecords", () => {
87
+ const logger = buildSinkLogger("debug");
88
+ const loggingWithRecords = makeLoggingWithRecord({ logger });
89
+ expect(isLoggingWithRecords(loggingWithRecords)).toBe(true);
90
+ });
91
+
92
+ it("should return false for Logging", () => {
93
+ const logger = buildSinkLogger("debug");
94
+ const logging = makeLogging({ logger });
95
+ expect(isLoggingWithRecords(logging)).toBe(false);
96
+ });
97
+ });
98
+
99
+ describe("makeLogging", () => {
100
+ it("should create a Logging object with default settings", () => {
101
+ const logger = buildSinkLogger("debug");
102
+ const logging = makeLogging({ logger });
103
+ expect(typeof logging.info).toBe("function");
104
+ expect(typeof logging.debug).toBe("function");
105
+ expect(typeof logging.warn).toBe("function");
106
+ expect(typeof logging.error).toBe("function");
107
+ });
108
+
109
+ it("should create a Logging object with custom section", () => {
110
+ const logger = buildSinkLogger("debug");
111
+ const logging = makeLogging({ logger, section: "custom" });
112
+ expect(logging.getSections()).toEqual(["custom"]);
113
+ expect(typeof logging.info).toBe("function");
114
+ expect(typeof logging.debug).toBe("function");
115
+ expect(typeof logging.warn).toBe("function");
116
+ expect(typeof logging.error).toBe("function");
117
+
118
+ const newLogging = logging.withSection("another");
119
+ const sameLogging = logging.withSection("custom");
120
+ expect(newLogging).not.toBe(logging);
121
+ expect(newLogging.getSections()).toEqual(["custom", "another"]);
122
+ expect(sameLogging).toBe(logging);
123
+ expect(sameLogging.getSections()).toEqual(["custom"]);
124
+ });
125
+ });
126
+
127
+ describe("makeLoggingWithRecord", () => {
128
+ it("should create a LoggingWithRecords object", () => {
129
+ const logger = buildSinkLogger("debug");
130
+ const loggingWithRecords = makeLoggingWithRecord({ logger });
131
+ expect(loggingWithRecords.messages).toBeDefined();
132
+ expect(typeof loggingWithRecords.info).toBe("function");
133
+ expect(typeof loggingWithRecords.debug).toBe("function");
134
+ expect(typeof loggingWithRecords.warn).toBe("function");
135
+ expect(typeof loggingWithRecords.error).toBe("function");
136
+ });
137
+
138
+ it("should preserve existing messages", () => {
139
+ const logger = buildSinkLogger("debug");
140
+ const loggingWithRecords = makeLoggingWithRecord({
141
+ logger,
142
+ messages: {
143
+ info: [["some-test", null]],
144
+ warn: [["some-test-warn", { a: 1 }]],
145
+ debug: [],
146
+ error: [],
147
+ },
148
+ });
149
+ loggingWithRecords.info("another test", { with: "context" });
150
+ loggingWithRecords.warn("another test2", { with: "context2" });
151
+ loggingWithRecords.debug("some debug");
152
+ loggingWithRecords.error("some error");
153
+ expect(loggingWithRecords.messages.info).toEqual([
154
+ ["some-test", null],
155
+ ["another test", { with: "context" }],
156
+ ]);
157
+ expect(loggingWithRecords.messages.warn).toEqual([
158
+ ["some-test-warn", { a: 1 }],
159
+ [
160
+ "another test2",
161
+ {
162
+ with: "context2",
163
+ },
164
+ ],
165
+ ]);
166
+ expect(loggingWithRecords.messages.debug).toEqual([["some debug", null]]);
167
+ expect(loggingWithRecords.messages.error).toEqual([["some error", null]]);
168
+ });
169
+
170
+ it("should create a new LoggingWithRecords instance when withSection is called", () => {
171
+ const logger = buildSinkLogger("debug");
172
+ const loggingWithRecords = makeLoggingWithRecord({
173
+ logger,
174
+ section: "first",
175
+ });
176
+ const newLoggingWithRecords = loggingWithRecords.withSection("custom");
177
+ expect(newLoggingWithRecords).not.toBe(loggingWithRecords);
178
+ expect(newLoggingWithRecords.getSections()).toEqual(["first", "custom"]);
179
+ expect(newLoggingWithRecords.messages).toEqual({
180
+ info: [],
181
+ warn: [],
182
+ debug: [],
183
+ error: [],
184
+ });
185
+ });
186
+ });
187
+
188
+ describe("parseLogLevelOrDefault()", () => {
189
+ it("should return the default level when no level is provided", () => {
190
+ expect(parseLogLevelOrDefault(null, "debug")).toBe("debug");
191
+ expect(parseLogLevelOrDefault(undefined, "debug")).toBe("debug");
192
+ expect(parseLogLevelOrDefault("", "debug")).toBe("debug");
193
+ });
194
+
195
+ it("should parse valid log levels", () => {
196
+ expect(parseLogLevelOrDefault("fatal", "debug")).toBe("fatal");
197
+ expect(parseLogLevelOrDefault("error", "debug")).toBe("error");
198
+ expect(parseLogLevelOrDefault("warn", "debug")).toBe("warn");
199
+ expect(parseLogLevelOrDefault("info", "debug")).toBe("info");
200
+ expect(parseLogLevelOrDefault("debug", "debug")).toBe("debug");
201
+ expect(parseLogLevelOrDefault("trace", "debug")).toBe("trace");
202
+ expect(parseLogLevelOrDefault("silent", "debug")).toBe("silent");
203
+ });
204
+
205
+ it("should throw an error for invalid log levels", () => {
206
+ expect(() => parseLogLevelOrDefault("invalid", "debug")).toThrow(
207
+ "Invalid log level: invalid",
208
+ );
209
+ });
210
+ });
package/src/index.ts ADDED
@@ -0,0 +1,315 @@
1
+ import chalk from "chalk";
2
+ import { colorize } from "json-colorizer";
3
+ import type { Logger as PinoLogger } from "pino";
4
+ import { pino } from "pino";
5
+ import type { DeepReadonly } from "simplytyped";
6
+
7
+ import type {
8
+ PlainObject,
9
+ PlainObjectValue,
10
+ } from "@plandek-utils/plain-object";
11
+
12
+ /**
13
+ * Allowed log levels for the logger.
14
+ */
15
+ export const LOG_LEVELS = [
16
+ "fatal",
17
+ "error",
18
+ "warn",
19
+ "info",
20
+ "debug",
21
+ "trace",
22
+ "silent",
23
+ ] as const;
24
+
25
+ /**
26
+ * Possible log levels for preparing the pino logger.
27
+ */
28
+ export type LogLevel = (typeof LOG_LEVELS)[number];
29
+
30
+ /**
31
+ * 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.
32
+ */
33
+ export function parseLogLevelOrDefault(
34
+ level: string | null | undefined,
35
+ defaultLevel: LogLevel,
36
+ ): LogLevel {
37
+ if (!level) return defaultLevel;
38
+
39
+ const lowerLevel = level.toLowerCase() as LogLevel;
40
+ if (LOG_LEVELS.includes(lowerLevel)) {
41
+ return lowerLevel;
42
+ }
43
+
44
+ throw new Error(`Invalid log level: ${level}`);
45
+ }
46
+
47
+ /**
48
+ * Prepared pino logger, returned by `buildPinoLogger` or `buildSinkLogger`.
49
+ *
50
+ * @see buildPinoLogger
51
+ * @see buildSinkLogger
52
+ */
53
+ export type PreparedLogger = Pick<
54
+ PinoLogger,
55
+ "level" | "info" | "debug" | "warn" | "error" | "bindings"
56
+ > & {
57
+ child: (bindings: PlainObject) => PreparedLogger;
58
+ };
59
+
60
+ /**
61
+ * Prepares a Pino logger with the common configuration.
62
+ *
63
+ * @param level - The log level to use.
64
+ * @param redactPaths - Paths to redact from the logs. Defaults to `["req.headers.authorization", "req.headers.cookie"]`.
65
+ * @returns PinoLogger
66
+ */
67
+ export function buildPinoLogger(
68
+ level: LogLevel,
69
+ redactPaths?: string[],
70
+ ): PreparedLogger {
71
+ const paths = redactPaths ?? [
72
+ "req.headers.authorization",
73
+ "req.headers.cookie",
74
+ ];
75
+ return pino({
76
+ level,
77
+ timestamp: pino.stdTimeFunctions.isoTime,
78
+ redact: { paths, censor: "[REDACTED]" },
79
+ });
80
+ }
81
+
82
+ /**
83
+ * A mock logger that does nothing, holds no binding (sections).
84
+ */
85
+ export function buildSinkLogger(
86
+ level: LogLevel,
87
+ givenBindings?: PlainObject,
88
+ ): PreparedLogger {
89
+ const bindings = givenBindings ?? {};
90
+ return {
91
+ level,
92
+ info: () => {},
93
+ debug: () => {},
94
+ warn: () => {},
95
+ error: () => {},
96
+ child: (childBindings?: PlainObject) =>
97
+ buildSinkLogger(level, { ...bindings, ...childBindings }),
98
+ bindings: () => bindings,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Util to serialise as JSON the given value, pretty-printed (2 spaces).
104
+ */
105
+ export function prettyJSON(obj: unknown): string {
106
+ return JSON.stringify(obj, null, 2);
107
+ }
108
+
109
+ /**
110
+ * Calls `prettyJSON` and then colourises the output.
111
+ */
112
+ export function colourPrettyJSON(obj: unknown): string {
113
+ return colorize(prettyJSON(obj));
114
+ }
115
+
116
+ /**
117
+ * Alias for `colourPrettyJSON`.
118
+ * @see colourPrettyJSON
119
+ */
120
+ export const colorPrettyJSON = colourPrettyJSON;
121
+
122
+ /**
123
+ * Interface to provide colouring for logs.
124
+ */
125
+ export type LogColourUtils = {
126
+ blue: (x: string) => string;
127
+ cyan: (x: string) => string;
128
+ gray: (x: string) => string;
129
+ magenta: (x: string) => string;
130
+ red: (x: string) => string;
131
+ yellow: (x: string) => string;
132
+ };
133
+
134
+ /**
135
+ * Creates a LogColourUtils object, with actual colours or not depending on the given mode.
136
+ *
137
+ * @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.
138
+ */
139
+ export function makeColourUtils(mode: "with-colour" | "plain"): LogColourUtils {
140
+ if (mode === "with-colour") {
141
+ return {
142
+ blue: (x: string) => chalk.blue(x),
143
+ cyan: (x: string) => chalk.cyan(x),
144
+ gray: (x: string) => chalk.gray(x),
145
+ magenta: (x: string) => chalk.magenta(x),
146
+ red: (x: string) => chalk.red(x),
147
+ yellow: (x: string) => chalk.yellow(x),
148
+ };
149
+ }
150
+
151
+ if (mode === "plain") {
152
+ return {
153
+ blue: (x: string) => x,
154
+ cyan: (x: string) => x,
155
+ gray: (x: string) => x,
156
+ magenta: (x: string) => x,
157
+ red: (x: string) => x,
158
+ yellow: (x: string) => x,
159
+ };
160
+ }
161
+
162
+ const _never: never = mode;
163
+ throw new Error(`Unknown mode: ${_never}`);
164
+ }
165
+
166
+ /**
167
+ * Logging interface. Requires a message (string) and optionally an object to log, which is required to be a Plain Object to ensure serialisation.
168
+ */
169
+ type LogFn = (msg: string, obj?: DeepReadonly<PlainObject>) => void;
170
+
171
+ /**
172
+ * 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.
173
+ */
174
+ export type Logging = {
175
+ logger: PreparedLogger;
176
+ info: LogFn;
177
+ warn: LogFn;
178
+ debug: LogFn;
179
+ error: LogFn;
180
+ getSections(this: Logging): string[];
181
+ withSection(this: Logging, section: string, context?: PlainObject): Logging;
182
+ };
183
+
184
+ /**
185
+ * Variant of the Logging interface that stores the messages that have been logged.
186
+ */
187
+ export type LoggingWithRecords = Omit<Logging, "withSection"> & {
188
+ withSection(
189
+ this: Logging,
190
+ section: string,
191
+ context?: PlainObject,
192
+ ): LoggingWithRecords;
193
+ messages: {
194
+ info: Array<[string, PlainObject | null]>;
195
+ warn: Array<[string, PlainObject | null]>;
196
+ debug: Array<[string, PlainObject | null]>;
197
+ error: Array<[string, PlainObject | null]>;
198
+ };
199
+ };
200
+
201
+ /**
202
+ * Checks if the given Logging object is a LoggingWithRecords.
203
+ * @param obj
204
+ * @returns
205
+ */
206
+ export function isLoggingWithRecords(obj: Logging): obj is LoggingWithRecords {
207
+ return "messages" in obj;
208
+ }
209
+
210
+ /**
211
+ * Creates a Logging object. It requires an actual pino logger to be sent, and optionally a section and a context.
212
+ */
213
+ export function makeLogging(opts: {
214
+ section?: string;
215
+ context?: PlainObject;
216
+ logger: PreparedLogger;
217
+ }): Logging {
218
+ const logger = loggerFor(
219
+ opts.logger,
220
+ opts.section ?? null,
221
+ opts.context ?? null,
222
+ );
223
+
224
+ return {
225
+ logger,
226
+ info: (msg, obj) => logger.info(obj, msg),
227
+ debug: (msg, obj) => logger.debug(obj, msg),
228
+ warn: (msg, obj) => logger.warn(obj, msg),
229
+ error: (msg, obj) => logger.error(obj, msg),
230
+ getSections(): string[] {
231
+ return getSectionsFromLogger(logger);
232
+ },
233
+ withSection(section, context) {
234
+ if (this.getSections().includes(section)) return this;
235
+
236
+ return makeLogging({ logger: this.logger, section, context });
237
+ },
238
+ };
239
+ }
240
+
241
+ /**
242
+ * 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.
243
+ */
244
+ export function makeLoggingWithRecord(opts: {
245
+ logger: PreparedLogger;
246
+ section?: string;
247
+ context?: PlainObject;
248
+ messages?: LoggingWithRecords["messages"];
249
+ }): LoggingWithRecords {
250
+ const logging = makeLogging(opts);
251
+ const messages = opts.messages ?? {
252
+ info: [],
253
+ warn: [],
254
+ debug: [],
255
+ error: [],
256
+ };
257
+
258
+ function makeLogFn(logFn: LogFn, key: keyof typeof messages): LogFn {
259
+ return (msg, obj) => {
260
+ messages[key].push([msg, obj ?? null]);
261
+ return logFn(msg, obj);
262
+ };
263
+ }
264
+
265
+ return {
266
+ messages,
267
+ logger: logging.logger,
268
+ info: makeLogFn(logging.info, "info"),
269
+ debug: makeLogFn(logging.debug, "debug"),
270
+ warn: makeLogFn(logging.warn, "warn"),
271
+ error: makeLogFn(logging.error, "error"),
272
+ getSections(): string[] {
273
+ return logging.getSections();
274
+ },
275
+ withSection(section, context) {
276
+ return makeLoggingWithRecord({
277
+ logger: this.logger,
278
+ section,
279
+ messages,
280
+ context,
281
+ });
282
+ },
283
+ };
284
+ }
285
+
286
+ // INTERNAL
287
+
288
+ function loggerFor(
289
+ givenLogger: PreparedLogger,
290
+ section: string | null,
291
+ context: PlainObject | null,
292
+ ) {
293
+ if (!context && (!section || loggerHasSection(givenLogger, section))) {
294
+ return givenLogger;
295
+ }
296
+
297
+ const childBindings: Record<string, PlainObjectValue> = { ...context };
298
+ if (section) {
299
+ childBindings.logSections = [
300
+ ...getSectionsFromLogger(givenLogger),
301
+ section,
302
+ ];
303
+ }
304
+
305
+ return givenLogger.child(childBindings);
306
+ }
307
+
308
+ function getSectionsFromLogger(logger: PreparedLogger) {
309
+ return logger.bindings().logSections ?? [];
310
+ }
311
+
312
+ function loggerHasSection(logger: PreparedLogger, section: string) {
313
+ const sections = getSectionsFromLogger(logger);
314
+ return sections.includes(section);
315
+ }
package/dist/mod.js.map DELETED
@@ -1 +0,0 @@
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":"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","names":[]}
File without changes