@lakutata/logger 0.0.0-stage → 3.0.0-beta.1
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 +23 -0
- package/README.md +137 -2
- package/THIRD_PARTY_NOTICES.md +158 -0
- package/dist/cjs/Logger.d.ts +291 -0
- package/dist/cjs/Logger.js +185 -0
- package/dist/cjs/exports/Logger.d.ts +2 -0
- package/dist/cjs/exports/Logger.js +20 -0
- package/dist/cjs/lib/Colors.d.ts +8 -0
- package/dist/cjs/lib/Colors.js +48 -0
- package/dist/cjs/lib/Destination.d.ts +38 -0
- package/dist/cjs/lib/Destination.js +103 -0
- package/dist/cjs/lib/Format.d.ts +7 -0
- package/dist/cjs/lib/Format.js +106 -0
- package/dist/cjs/lib/Pretty.d.ts +9 -0
- package/dist/cjs/lib/Pretty.js +265 -0
- package/dist/cjs/lib/Record.d.ts +25 -0
- package/dist/cjs/lib/Record.js +139 -0
- package/dist/cjs/lib/Serializers.d.ts +26 -0
- package/dist/cjs/lib/Serializers.js +150 -0
- package/dist/cjs/lib/Stringify.d.ts +5 -0
- package/dist/cjs/lib/Stringify.js +110 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Logger.js +182 -0
- package/dist/esm/exports/Logger.js +1 -0
- package/dist/esm/lib/Colors.js +45 -0
- package/dist/esm/lib/Destination.js +99 -0
- package/dist/esm/lib/Format.js +103 -0
- package/dist/esm/lib/Pretty.js +262 -0
- package/dist/esm/lib/Record.js +134 -0
- package/dist/esm/lib/Serializers.js +143 -0
- package/dist/esm/lib/Stringify.js +107 -0
- package/dist/types/Logger.d.ts +291 -0
- package/dist/types/exports/Logger.d.ts +2 -0
- package/dist/types/lib/Colors.d.ts +8 -0
- package/dist/types/lib/Destination.d.ts +38 -0
- package/dist/types/lib/Format.d.ts +7 -0
- package/dist/types/lib/Pretty.d.ts +9 -0
- package/dist/types/lib/Record.d.ts +25 -0
- package/dist/types/lib/Serializers.d.ts +26 -0
- package/dist/types/lib/Stringify.d.ts +5 -0
- 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,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,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,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;
|