@lakutata/logger 0.0.0-stage → 3.0.0-beta.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.
Files changed (41) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +137 -2
  3. package/THIRD_PARTY_NOTICES.md +158 -0
  4. package/dist/cjs/Logger.d.ts +291 -0
  5. package/dist/cjs/Logger.js +185 -0
  6. package/dist/cjs/exports/Logger.d.ts +2 -0
  7. package/dist/cjs/exports/Logger.js +20 -0
  8. package/dist/cjs/lib/Colors.d.ts +8 -0
  9. package/dist/cjs/lib/Colors.js +48 -0
  10. package/dist/cjs/lib/Destination.d.ts +38 -0
  11. package/dist/cjs/lib/Destination.js +103 -0
  12. package/dist/cjs/lib/Format.d.ts +7 -0
  13. package/dist/cjs/lib/Format.js +106 -0
  14. package/dist/cjs/lib/Pretty.d.ts +9 -0
  15. package/dist/cjs/lib/Pretty.js +265 -0
  16. package/dist/cjs/lib/Record.d.ts +25 -0
  17. package/dist/cjs/lib/Record.js +139 -0
  18. package/dist/cjs/lib/Serializers.d.ts +26 -0
  19. package/dist/cjs/lib/Serializers.js +150 -0
  20. package/dist/cjs/lib/Stringify.d.ts +5 -0
  21. package/dist/cjs/lib/Stringify.js +110 -0
  22. package/dist/cjs/package.json +1 -0
  23. package/dist/esm/Logger.js +182 -0
  24. package/dist/esm/exports/Logger.js +1 -0
  25. package/dist/esm/lib/Colors.js +45 -0
  26. package/dist/esm/lib/Destination.js +99 -0
  27. package/dist/esm/lib/Format.js +103 -0
  28. package/dist/esm/lib/Pretty.js +262 -0
  29. package/dist/esm/lib/Record.js +134 -0
  30. package/dist/esm/lib/Serializers.js +143 -0
  31. package/dist/esm/lib/Stringify.js +107 -0
  32. package/dist/types/Logger.d.ts +291 -0
  33. package/dist/types/exports/Logger.d.ts +2 -0
  34. package/dist/types/lib/Colors.d.ts +8 -0
  35. package/dist/types/lib/Destination.d.ts +38 -0
  36. package/dist/types/lib/Format.d.ts +7 -0
  37. package/dist/types/lib/Pretty.d.ts +9 -0
  38. package/dist/types/lib/Record.d.ts +25 -0
  39. package/dist/types/lib/Serializers.d.ts +26 -0
  40. package/dist/types/lib/Stringify.d.ts +5 -0
  41. package/package.json +43 -4
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Logger = void 0;
13
+ const core_1 = require("@lakutata/core");
14
+ const di_1 = require("@lakutata/core/decorator/di");
15
+ const node_stream_1 = require("node:stream");
16
+ const Record_js_1 = require("./lib/Record.js");
17
+ const Serializers_js_1 = require("./lib/Serializers.js");
18
+ const Pretty_js_1 = require("./lib/Pretty.js");
19
+ const Colors_js_1 = require("./lib/Colors.js");
20
+ const Destination_js_1 = require("./lib/Destination.js");
21
+ //The loggers with buffered lines to write when the process exits
22
+ const activeLoggers = new Set();
23
+ process.once('exit', () => activeLoggers.forEach((logger) => logger.flush()));
24
+ /**
25
+ * The logger component of the applications built with `lakutata`: the package presets it as the `log` component (and
26
+ * creates it first), so the framework and the application log through it. It writes the pino log format: one JSON
27
+ * object per line, with `level` (10 trace, 20 debug, 30 info, 40 warn, 50 error, 60 fatal), `time` (milliseconds since the epoch),
28
+ * `pid`, `hostname`, `name` (the application's name), the properties of the logged object and `msg`; with `pretty` on
29
+ * (the default), it writes readable lines instead: `[HH:MM:ss.SSS] LEVEL (name/pid): message`, then one indented line
30
+ * per property, in the local time zone.
31
+ *
32
+ * Configure it through the `log` entry of the application's `components` (no `class` needed:
33
+ * `log: {level: 'info', pretty: false}`), and get it with `@Inject('log')` or `getObject('log')`; it is a singleton.
34
+ * The lines are buffered and written at the next turn of the event loop (or once 16 KB are pending), and the pending
35
+ * lines are written synchronously when the process exits or the component is destroyed; `sync: true` writes each line
36
+ * at once instead. A stream with a file descriptor (stdout, stderr, an opened file) is written synchronously through
37
+ * it, so the lines keep their order.
38
+ *
39
+ * An HTTP request or response logged as the object of a line is written under `req` or `res` with the values of its
40
+ * credential headers (`Authorization`, `Cookie`, `Set-Cookie`, `Proxy-Authorization`) replaced by `[Redacted]` (see
41
+ * the `redactedHeaders` option). Nothing else is redacted and there are no child loggers: the other properties are
42
+ * written as is, so log the fields you need rather than secrets or whole objects holding them.
43
+ * @example
44
+ * ```typescript
45
+ * import {Application, Component} from 'lakutata'
46
+ * import {Inject} from 'lakutata/decorator/di'
47
+ * import {Logger} from 'lakutata/com/logger'
48
+ *
49
+ * class Orders extends Component {
50
+ * @Inject('log')
51
+ * protected readonly log: Logger
52
+ *
53
+ * public pay(orderId: number, amount: number): void {
54
+ * //{"level":30,...,"orderId":42,"msg":"order 42 paid: 9.90"}
55
+ * this.log.info({orderId: orderId}, 'order %d paid: %s', orderId, amount.toFixed(2))
56
+ * }
57
+ *
58
+ * public fail(error: Error): void {
59
+ * //The error serialized under "err", its message as the message unless one is given
60
+ * this.log.error(error, 'payment failed')
61
+ * }
62
+ * }
63
+ *
64
+ * Application.run(() => ({
65
+ * id: 'shop.app',
66
+ * name: 'Shop',
67
+ * components: {
68
+ * //The preset logger: JSON lines from the info level in production
69
+ * log: {level: 'info', pretty: process.env.NODE_ENV !== 'production'},
70
+ * orders: {class: Orders}
71
+ * }
72
+ * }))
73
+ * ```
74
+ */
75
+ let Logger = class Logger extends core_1.Component {
76
+ #threshold;
77
+ #bindings;
78
+ #pretty;
79
+ #outputs;
80
+ #redacted;
81
+ /**
82
+ * Set up the destinations and the level, and register the logger to flush its pending lines when the process exits.
83
+ * @protected
84
+ */
85
+ async init() {
86
+ this.#threshold = Record_js_1.LEVELS[this.level];
87
+ this.#bindings = (0, Record_js_1.logBindings)(core_1.Application.appName);
88
+ this.#redacted = new Set(this.redactedHeaders.map((name) => name.toLowerCase()));
89
+ this.#outputs = this.destinations.map((stream) => ({
90
+ destination: new Destination_js_1.Destination(stream, this.sync),
91
+ palette: this.pretty ? (stream === process.stdout && this.colorize ? Colors_js_1.COLOR_PALETTE : Colors_js_1.PLAIN_PALETTE) : undefined
92
+ }));
93
+ this.#pretty = this.#outputs.some((output) => !!output.palette);
94
+ activeLoggers.add(this);
95
+ }
96
+ /**
97
+ * Write the pending lines and stop flushing at the process exit.
98
+ * @protected
99
+ */
100
+ async destroy() {
101
+ activeLoggers.delete(this);
102
+ this.flush();
103
+ }
104
+ /**
105
+ * Build a log line and write it to every destination, unless its level is below the configured `level`.
106
+ * @param level The numeric level of the line: 10 trace, 20 debug, 30 info, 40 warn, 50 error, 60 fatal.
107
+ * @param args The arguments of the log method: `[obj, msg, ...args]` or `[msg, ...args]`.
108
+ * @protected
109
+ */
110
+ write(level, args) {
111
+ if (level < this.#threshold)
112
+ return;
113
+ const record = (0, Record_js_1.buildRecord)(level, this.#bindings, args, this.#pretty, this.#redacted);
114
+ let prettyLines;
115
+ for (const output of this.#outputs) {
116
+ if (!output.palette) {
117
+ output.destination.write(record.line);
118
+ continue;
119
+ }
120
+ prettyLines ??= new Map();
121
+ let line = prettyLines.get(output.palette);
122
+ if (line === undefined) {
123
+ //The parsed line is changed by the formatting, it is used once
124
+ line = (0, Pretty_js_1.pretty)(record.line.slice(0, -1), output.palette, prettyLines.size ? undefined : record.log);
125
+ prettyLines.set(output.palette, line);
126
+ }
127
+ output.destination.write(line);
128
+ }
129
+ }
130
+ /**
131
+ * Write the buffered lines now, synchronously for the streams with a file descriptor (before a `process.exit()`
132
+ * in a signal handler, for instance). Does nothing with `sync: true`.
133
+ */
134
+ flush() {
135
+ this.#outputs?.forEach((output) => output.destination.flush());
136
+ }
137
+ fatal(obj, ...msg) {
138
+ this.write(60, [obj, ...msg]);
139
+ //The process may end without a turn of the event loop
140
+ this.flush();
141
+ }
142
+ error(obj, ...msg) {
143
+ return this.write(50, [obj, ...msg]);
144
+ }
145
+ warn(obj, ...msg) {
146
+ return this.write(40, [obj, ...msg]);
147
+ }
148
+ info(obj, ...msg) {
149
+ return this.write(30, [obj, ...msg]);
150
+ }
151
+ debug(obj, ...msg) {
152
+ return this.write(20, [obj, ...msg]);
153
+ }
154
+ trace(obj, ...msg) {
155
+ return this.write(10, [obj, ...msg]);
156
+ }
157
+ };
158
+ exports.Logger = Logger;
159
+ __decorate([
160
+ (0, di_1.Configurable)(core_1.DTO.Boolean().optional().default(true)),
161
+ __metadata("design:type", Boolean)
162
+ ], Logger.prototype, "pretty", void 0);
163
+ __decorate([
164
+ (0, di_1.Configurable)(core_1.DTO.String().valid(...Object.keys(Record_js_1.LEVELS)).optional().default('trace')),
165
+ __metadata("design:type", String)
166
+ ], Logger.prototype, "level", void 0);
167
+ __decorate([
168
+ (0, di_1.Configurable)(core_1.DTO.Boolean().optional().default(true)),
169
+ __metadata("design:type", Boolean)
170
+ ], Logger.prototype, "colorize", void 0);
171
+ __decorate([
172
+ (0, di_1.Configurable)(core_1.DTO.Boolean().optional().default(false)),
173
+ __metadata("design:type", Boolean)
174
+ ], Logger.prototype, "sync", void 0);
175
+ __decorate([
176
+ (0, di_1.Configurable)(core_1.DTO.Array(core_1.DTO.InstanceOf(node_stream_1.Stream)).optional().default(() => [process.stdout])),
177
+ __metadata("design:type", Array)
178
+ ], Logger.prototype, "destinations", void 0);
179
+ __decorate([
180
+ (0, di_1.Configurable)(core_1.DTO.Array(core_1.DTO.String()).optional().default(() => [...Serializers_js_1.REDACTED_HEADERS])),
181
+ __metadata("design:type", Array)
182
+ ], Logger.prototype, "redactedHeaders", void 0);
183
+ exports.Logger = Logger = __decorate([
184
+ (0, di_1.Singleton)()
185
+ ], Logger);
@@ -0,0 +1,2 @@
1
+ export * from '../Logger.js';
2
+ export type { ILogger, ILogMethod } from '@lakutata/core/com/logger';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../Logger.js"), exports);
18
+
19
+ //The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
20
+ if (!Object.prototype.hasOwnProperty.call(exports, 'default')) Object.defineProperty(exports, 'default', {value: exports})
@@ -0,0 +1,8 @@
1
+ export type Colorize = (input: unknown) => string;
2
+ export type Palette = {
3
+ level: Record<string, Colorize>;
4
+ message: Colorize;
5
+ property: Colorize;
6
+ };
7
+ export declare const PLAIN_PALETTE: Palette;
8
+ export declare const COLOR_PALETTE: Palette;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COLOR_PALETTE = exports.PLAIN_PALETTE = void 0;
4
+ /**
5
+ * Replace the close sequences of a text by the open sequence, so a color is not closed early by a nested one
6
+ * @param text
7
+ * @param index
8
+ * @param close
9
+ * @param open
10
+ */
11
+ function replaceClose(text, index, close, open) {
12
+ let result = '';
13
+ let tail = text;
14
+ while (index >= 0) {
15
+ result += tail.substring(0, index) + open;
16
+ tail = tail.substring(index + close.length);
17
+ index = tail.indexOf(close);
18
+ }
19
+ return result + tail;
20
+ }
21
+ /**
22
+ * An ANSI color
23
+ * @param open
24
+ * @param close
25
+ */
26
+ function color(open, close) {
27
+ const openSequence = `\x1b[${open}m`;
28
+ const closeSequence = `\x1b[${close}m`;
29
+ return (input) => {
30
+ //Empty texts stay uncolored
31
+ if (input === '' || input === undefined)
32
+ return '';
33
+ const text = `${input}`;
34
+ const index = text.indexOf(closeSequence, openSequence.length + 1);
35
+ return index < 0 ? openSequence + text + closeSequence : openSequence + replaceClose(text, index, closeSequence, openSequence) + closeSequence;
36
+ };
37
+ }
38
+ const plain = (input) => input;
39
+ exports.PLAIN_PALETTE = {
40
+ level: { default: plain, 60: plain, 50: plain, 40: plain, 30: plain, 20: plain, 10: plain },
41
+ message: plain,
42
+ property: plain
43
+ };
44
+ exports.COLOR_PALETTE = {
45
+ level: { default: color(37, 39), 60: color(41, 49), 50: color(31, 39), 40: color(33, 39), 30: color(32, 39), 20: color(34, 39), 10: color(90, 39) },
46
+ message: color(36, 39),
47
+ property: color(35, 39)
48
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A destination of the log lines, buffered unless synchronous
3
+ * The buffered lines are written in the next event loop iteration, and synchronously when the process exits
4
+ * The streams with a file descriptor (stdout, stderr, files) are written through it, so the lines stay in order
5
+ */
6
+ export declare class Destination {
7
+ #private;
8
+ protected readonly stream: NodeJS.WritableStream;
9
+ protected readonly sync: boolean;
10
+ constructor(stream: NodeJS.WritableStream, sync: boolean);
11
+ /**
12
+ * The file descriptor of the stream, if it has one
13
+ * @protected
14
+ */
15
+ protected get fd(): number | undefined;
16
+ /**
17
+ * Write a text to the file descriptor, retrying when the descriptor is busy
18
+ * @param fd
19
+ * @param text
20
+ * @protected
21
+ */
22
+ protected writeFd(fd: number, text: string): void;
23
+ /**
24
+ * Write a text
25
+ * @param text
26
+ */
27
+ write(text: string): void;
28
+ /**
29
+ * Write a text to the stream, through its file descriptor if it has one
30
+ * @param text
31
+ * @protected
32
+ */
33
+ protected output(text: string): void;
34
+ /**
35
+ * Write the buffered text
36
+ */
37
+ flush(): void;
38
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Destination = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ //The buffered text written at once
6
+ const HIGH_WATER_MARK = 16384;
7
+ /**
8
+ * The array the writes wait on while a pipe is full, created when first needed: SharedArrayBuffer is missing where the
9
+ * logger may be loaded without logging (a renderer of Electron which is not cross-origin isolated)
10
+ */
11
+ let sleeper;
12
+ /**
13
+ * Wait synchronously
14
+ * @param ms
15
+ */
16
+ function sleepSync(ms) {
17
+ if (typeof SharedArrayBuffer === 'function') {
18
+ sleeper ??= new Int32Array(new SharedArrayBuffer(4));
19
+ Atomics.wait(sleeper, 0, 0, ms);
20
+ return;
21
+ }
22
+ const until = Date.now() + ms;
23
+ while (Date.now() < until) {
24
+ //Busy wait, without SharedArrayBuffer
25
+ }
26
+ }
27
+ /**
28
+ * A destination of the log lines, buffered unless synchronous
29
+ * The buffered lines are written in the next event loop iteration, and synchronously when the process exits
30
+ * The streams with a file descriptor (stdout, stderr, files) are written through it, so the lines stay in order
31
+ */
32
+ class Destination {
33
+ #buffer = '';
34
+ #scheduled = false;
35
+ constructor(stream, sync) {
36
+ this.stream = stream;
37
+ this.sync = sync;
38
+ }
39
+ /**
40
+ * The file descriptor of the stream, if it has one
41
+ * @protected
42
+ */
43
+ get fd() {
44
+ const fd = this.stream.fd;
45
+ return typeof fd === 'number' ? fd : undefined;
46
+ }
47
+ /**
48
+ * Write a text to the file descriptor, retrying when the descriptor is busy
49
+ * @param fd
50
+ * @param text
51
+ * @protected
52
+ */
53
+ writeFd(fd, text) {
54
+ let data = Buffer.from(text);
55
+ while (data.length) {
56
+ try {
57
+ data = data.subarray((0, node_fs_1.writeSync)(fd, data));
58
+ }
59
+ catch (e) {
60
+ if (e.code !== 'EAGAIN')
61
+ throw e;
62
+ //A pipe is full, wait for its reader
63
+ sleepSync(10);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Write a text
69
+ * @param text
70
+ */
71
+ write(text) {
72
+ if (this.sync)
73
+ return this.output(text);
74
+ this.#buffer += text;
75
+ if (this.#buffer.length >= HIGH_WATER_MARK)
76
+ return this.flush();
77
+ if (this.#scheduled)
78
+ return;
79
+ this.#scheduled = true;
80
+ setImmediate(() => this.flush());
81
+ }
82
+ /**
83
+ * Write a text to the stream, through its file descriptor if it has one
84
+ * @param text
85
+ * @protected
86
+ */
87
+ output(text) {
88
+ const fd = this.fd;
89
+ fd === undefined ? this.stream.write(text) : this.writeFd(fd, text);
90
+ }
91
+ /**
92
+ * Write the buffered text
93
+ */
94
+ flush() {
95
+ this.#scheduled = false;
96
+ if (!this.#buffer)
97
+ return;
98
+ const text = this.#buffer;
99
+ this.#buffer = '';
100
+ this.output(text);
101
+ }
102
+ }
103
+ exports.Destination = Destination;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Format a log message with its arguments: %s, %d, %f, %i, %o, %O, %j and %%
3
+ * The arguments without placeholder are ignored
4
+ * @param template
5
+ * @param args
6
+ */
7
+ export declare function format(template: unknown, args: unknown[]): unknown;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.format = format;
4
+ //Derived from the upstream projects listed in THIRD_PARTY_NOTICES.md (rewritten in TypeScript)
5
+ const Stringify_js_1 = require("./Stringify.js");
6
+ /**
7
+ * Format a log message with its arguments: %s, %d, %f, %i, %o, %O, %j and %%
8
+ * The arguments without placeholder are ignored
9
+ * @param template
10
+ * @param args
11
+ */
12
+ function format(template, args) {
13
+ if (typeof template === 'object' && template !== null) {
14
+ const count = args.length + 1;
15
+ if (count === 1)
16
+ return template;
17
+ //Serialize the object and the arguments after the first one (the behavior of 2.x)
18
+ const objects = new Array(count);
19
+ objects[0] = (0, Stringify_js_1.stringify)(template);
20
+ for (let index = 1; index < count; index++)
21
+ objects[index] = (0, Stringify_js_1.stringify)(args[index]);
22
+ return objects.join(' ');
23
+ }
24
+ if (typeof template !== 'string' || !args.length)
25
+ return template;
26
+ let result = '';
27
+ let argument = 0;
28
+ let last = -1;
29
+ const length = template.length;
30
+ for (let index = 0; index < length;) {
31
+ if (template.charCodeAt(index) === 37 && index + 1 < length) {
32
+ last = last > -1 ? last : 0;
33
+ switch (template.charCodeAt(index + 1)) {
34
+ //d, f
35
+ case 100:
36
+ case 102:
37
+ if (argument >= args.length || args[argument] == null)
38
+ break;
39
+ if (last < index)
40
+ result += template.slice(last, index);
41
+ result += Number(args[argument]);
42
+ last = index + 2;
43
+ index++;
44
+ break;
45
+ //i
46
+ case 105:
47
+ if (argument >= args.length || args[argument] == null)
48
+ break;
49
+ if (last < index)
50
+ result += template.slice(last, index);
51
+ result += Math.floor(Number(args[argument]));
52
+ last = index + 2;
53
+ index++;
54
+ break;
55
+ //O, o, j
56
+ case 79:
57
+ case 111:
58
+ case 106: {
59
+ if (argument >= args.length || args[argument] === undefined)
60
+ break;
61
+ if (last < index)
62
+ result += template.slice(last, index);
63
+ const value = args[argument];
64
+ if (typeof value === 'string') {
65
+ result += `'${value}'`;
66
+ }
67
+ else if (typeof value === 'function') {
68
+ result += value.name || '<anonymous>';
69
+ }
70
+ else {
71
+ result += (0, Stringify_js_1.stringify)(value);
72
+ }
73
+ last = index + 2;
74
+ index++;
75
+ break;
76
+ }
77
+ //s
78
+ case 115:
79
+ if (argument >= args.length)
80
+ break;
81
+ if (last < index)
82
+ result += template.slice(last, index);
83
+ result += String(args[argument]);
84
+ last = index + 2;
85
+ index++;
86
+ break;
87
+ //%
88
+ case 37:
89
+ if (last < index)
90
+ result += template.slice(last, index);
91
+ result += '%';
92
+ last = index + 2;
93
+ index++;
94
+ argument--;
95
+ break;
96
+ }
97
+ ++argument;
98
+ }
99
+ ++index;
100
+ }
101
+ if (last === -1)
102
+ return template;
103
+ if (last < length)
104
+ result += template.slice(last);
105
+ return result;
106
+ }
@@ -0,0 +1,9 @@
1
+ import { Palette } from './Colors.js';
2
+ /**
3
+ * Format a JSON log line for humans:
4
+ * [time] LEVEL (name/pid): message, then the properties on their own lines
5
+ * @param line the JSON log line, without its line terminator
6
+ * @param palette
7
+ * @param parsed the parsed line, if it is known (the log is changed)
8
+ */
9
+ export declare function pretty(line: string, palette: Palette, parsed?: Record<string, unknown>): string;