@logtape/logtape 0.1.0-dev.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +194 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/config.js +108 -0
- package/esm/filter.js +42 -0
- package/esm/formatter.js +86 -0
- package/esm/logger.js +285 -0
- package/esm/mod.js +5 -0
- package/esm/package.json +3 -0
- package/esm/record.js +1 -0
- package/esm/sink.js +59 -0
- package/package.json +49 -0
- package/script/_dnt.shims.js +65 -0
- package/script/config.js +114 -0
- package/script/filter.js +47 -0
- package/script/formatter.js +91 -0
- package/script/logger.js +292 -0
- package/script/mod.js +15 -0
- package/script/package.json +3 -0
- package/script/record.js +2 -0
- package/script/sink.js +64 -0
- package/types/_dnt.shims.d.ts +10 -0
- package/types/_dnt.shims.d.ts.map +1 -0
- package/types/_dnt.test_shims.d.ts.map +1 -0
- package/types/config.d.ts +101 -0
- package/types/config.d.ts.map +1 -0
- package/types/config.test.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/_constants.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/_diff.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/_format.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_false.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_greater_or_equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_is_error.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_less_or_equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_strict_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assert_throws.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/assertion_error.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/0.222.1/equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/async/0.222.1/delay.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/fmt/0.222.1/colors.d.ts.map +1 -0
- package/types/filter.d.ts +30 -0
- package/types/filter.d.ts.map +1 -0
- package/types/filter.test.d.ts.map +1 -0
- package/types/fixtures.d.ts.map +1 -0
- package/types/formatter.d.ts +38 -0
- package/types/formatter.d.ts.map +1 -0
- package/types/formatter.test.d.ts.map +1 -0
- package/types/logger.d.ts +363 -0
- package/types/logger.d.ts.map +1 -0
- package/types/logger.test.d.ts.map +1 -0
- package/types/mod.d.ts +7 -0
- package/types/mod.d.ts.map +1 -0
- package/types/record.d.ts +33 -0
- package/types/record.d.ts.map +1 -0
- package/types/sink.d.ts +55 -0
- package/types/sink.d.ts.map +1 -0
- package/types/sink.test.d.ts.map +1 -0
package/esm/logger.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get a logger with the given category.
|
|
3
|
+
*
|
|
4
|
+
* ```typescript
|
|
5
|
+
* const logger = getLogger(["my-app"]);
|
|
6
|
+
* ```
|
|
7
|
+
*
|
|
8
|
+
* @param category The category of the logger. It can be a string or an array
|
|
9
|
+
* of strings. If it is a string, it is equivalent to an array
|
|
10
|
+
* with a single element.
|
|
11
|
+
* @returns The logger.
|
|
12
|
+
*/
|
|
13
|
+
export function getLogger(category = []) {
|
|
14
|
+
return LoggerImpl.getLogger(category);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The root logger.
|
|
18
|
+
*/
|
|
19
|
+
let rootLogger = null;
|
|
20
|
+
/**
|
|
21
|
+
* A logger implementation. Do not use this directly; use {@link getLogger}
|
|
22
|
+
* instead. This class is exported for testing purposes.
|
|
23
|
+
*/
|
|
24
|
+
export class LoggerImpl {
|
|
25
|
+
static getLogger(category = []) {
|
|
26
|
+
if (rootLogger == null) {
|
|
27
|
+
rootLogger = new LoggerImpl(null, []);
|
|
28
|
+
}
|
|
29
|
+
if (typeof category === "string")
|
|
30
|
+
return rootLogger.getChild(category);
|
|
31
|
+
if (category.length === 0)
|
|
32
|
+
return rootLogger;
|
|
33
|
+
return rootLogger.getChild(category);
|
|
34
|
+
}
|
|
35
|
+
constructor(parent, category) {
|
|
36
|
+
Object.defineProperty(this, "parent", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: void 0
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(this, "children", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
configurable: true,
|
|
45
|
+
writable: true,
|
|
46
|
+
value: void 0
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(this, "category", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: void 0
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(this, "sinks", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
configurable: true,
|
|
57
|
+
writable: true,
|
|
58
|
+
value: void 0
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(this, "filters", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: void 0
|
|
65
|
+
});
|
|
66
|
+
this.parent = parent;
|
|
67
|
+
this.children = {};
|
|
68
|
+
this.category = category;
|
|
69
|
+
this.sinks = [];
|
|
70
|
+
this.filters = [];
|
|
71
|
+
}
|
|
72
|
+
getChild(subcategory) {
|
|
73
|
+
const name = typeof subcategory === "string" ? subcategory : subcategory[0];
|
|
74
|
+
let child = this.children[name]?.deref();
|
|
75
|
+
if (child == null) {
|
|
76
|
+
child = new LoggerImpl(this, [...this.category, name]);
|
|
77
|
+
this.children[name] = new WeakRef(child);
|
|
78
|
+
}
|
|
79
|
+
if (typeof subcategory === "string" || subcategory.length === 1) {
|
|
80
|
+
return child;
|
|
81
|
+
}
|
|
82
|
+
return child.getChild(subcategory.slice(1));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reset the logger. This removes all sinks and filters from the logger.
|
|
86
|
+
*/
|
|
87
|
+
reset() {
|
|
88
|
+
while (this.sinks.length > 0)
|
|
89
|
+
this.sinks.shift();
|
|
90
|
+
while (this.filters.length > 0)
|
|
91
|
+
this.filters.shift();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reset the logger and all its descendants. This removes all sinks and
|
|
95
|
+
* filters from the logger and all its descendants.
|
|
96
|
+
*/
|
|
97
|
+
resetDescendants() {
|
|
98
|
+
for (const child of Object.values(this.children)) {
|
|
99
|
+
const logger = child.deref();
|
|
100
|
+
if (logger != null)
|
|
101
|
+
logger.resetDescendants();
|
|
102
|
+
}
|
|
103
|
+
this.reset();
|
|
104
|
+
}
|
|
105
|
+
filter(record) {
|
|
106
|
+
for (const filter of this.filters) {
|
|
107
|
+
if (!filter(record))
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (this.filters.length < 1)
|
|
111
|
+
return this.parent?.filter(record) ?? true;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
*getSinks() {
|
|
115
|
+
if (this.parent != null) {
|
|
116
|
+
for (const sink of this.parent.getSinks())
|
|
117
|
+
yield sink;
|
|
118
|
+
}
|
|
119
|
+
for (const sink of this.sinks)
|
|
120
|
+
yield sink;
|
|
121
|
+
}
|
|
122
|
+
emit(record, bypassSinks) {
|
|
123
|
+
if (!this.filter(record))
|
|
124
|
+
return;
|
|
125
|
+
for (const sink of this.getSinks()) {
|
|
126
|
+
if (bypassSinks?.has(sink))
|
|
127
|
+
continue;
|
|
128
|
+
try {
|
|
129
|
+
sink(record);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const bypassSinks2 = new Set(bypassSinks);
|
|
133
|
+
bypassSinks2.add(sink);
|
|
134
|
+
metaLogger.log("fatal", "Failed to emit a log record to sink {sink}: {error}", { sink, error, record }, bypassSinks2);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
log(level, message, properties, bypassSinks) {
|
|
139
|
+
let cachedProps = undefined;
|
|
140
|
+
const record = typeof properties === "function"
|
|
141
|
+
? {
|
|
142
|
+
category: this.category,
|
|
143
|
+
level,
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
get message() {
|
|
146
|
+
return parseMessageTemplate(message, this.properties);
|
|
147
|
+
},
|
|
148
|
+
get properties() {
|
|
149
|
+
if (cachedProps == null)
|
|
150
|
+
cachedProps = properties();
|
|
151
|
+
return cachedProps;
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
: {
|
|
155
|
+
category: this.category,
|
|
156
|
+
level,
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
message: parseMessageTemplate(message, properties),
|
|
159
|
+
properties,
|
|
160
|
+
};
|
|
161
|
+
this.emit(record, bypassSinks);
|
|
162
|
+
}
|
|
163
|
+
logLazily(level, callback) {
|
|
164
|
+
let msg = undefined;
|
|
165
|
+
this.emit({
|
|
166
|
+
category: this.category,
|
|
167
|
+
level,
|
|
168
|
+
get message() {
|
|
169
|
+
if (msg == null) {
|
|
170
|
+
msg = callback((tpl, ...values) => renderMessage(tpl, values));
|
|
171
|
+
}
|
|
172
|
+
return msg;
|
|
173
|
+
},
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
properties: {},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
logTemplate(level, messageTemplate, values) {
|
|
179
|
+
this.emit({
|
|
180
|
+
category: this.category,
|
|
181
|
+
level,
|
|
182
|
+
message: renderMessage(messageTemplate, values),
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
properties: {},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
debug(message, ...values) {
|
|
188
|
+
if (typeof message === "string") {
|
|
189
|
+
this.log("debug", message, (values[0] ?? {}));
|
|
190
|
+
}
|
|
191
|
+
else if (typeof message === "function") {
|
|
192
|
+
this.logLazily("debug", message);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.logTemplate("debug", message, values);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
info(message, ...values) {
|
|
199
|
+
if (typeof message === "string") {
|
|
200
|
+
this.log("info", message, (values[0] ?? {}));
|
|
201
|
+
}
|
|
202
|
+
else if (typeof message === "function") {
|
|
203
|
+
this.logLazily("info", message);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.logTemplate("info", message, values);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
warn(message, ...values) {
|
|
210
|
+
if (typeof message === "string") {
|
|
211
|
+
this.log("warning", message, (values[0] ?? {}));
|
|
212
|
+
}
|
|
213
|
+
else if (typeof message === "function") {
|
|
214
|
+
this.logLazily("warning", message);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.logTemplate("warning", message, values);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
error(message, ...values) {
|
|
221
|
+
if (typeof message === "string") {
|
|
222
|
+
this.log("error", message, (values[0] ?? {}));
|
|
223
|
+
}
|
|
224
|
+
else if (typeof message === "function") {
|
|
225
|
+
this.logLazily("error", message);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.logTemplate("error", message, values);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
fatal(message, ...values) {
|
|
232
|
+
if (typeof message === "string") {
|
|
233
|
+
this.log("fatal", message, (values[0] ?? {}));
|
|
234
|
+
}
|
|
235
|
+
else if (typeof message === "function") {
|
|
236
|
+
this.logLazily("fatal", message);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.logTemplate("fatal", message, values);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The meta logger. It is a logger with the category `["logtape", "meta"]`.
|
|
245
|
+
*/
|
|
246
|
+
const metaLogger = LoggerImpl.getLogger(["logtape", "meta"]);
|
|
247
|
+
const MESSAGE_TEMPLATE_PATTERN = /\{([^}]*)\}/g;
|
|
248
|
+
/**
|
|
249
|
+
* Parse a message template into a message template array and a values array.
|
|
250
|
+
* @param template The message template.
|
|
251
|
+
* @param properties The values to replace placeholders with.
|
|
252
|
+
* @returns The message template array and the values array.
|
|
253
|
+
*/
|
|
254
|
+
export function parseMessageTemplate(template, properties) {
|
|
255
|
+
let lastPos = 0;
|
|
256
|
+
const message = [];
|
|
257
|
+
while (true) {
|
|
258
|
+
const match = MESSAGE_TEMPLATE_PATTERN.exec(template);
|
|
259
|
+
if (match == null) {
|
|
260
|
+
message.push(template.substring(lastPos));
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
message.push(template.substring(lastPos, match.index));
|
|
264
|
+
const key = match[1];
|
|
265
|
+
message.push(properties[key]);
|
|
266
|
+
lastPos = match.index + match[0].length;
|
|
267
|
+
}
|
|
268
|
+
return message;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Render a message template with values.
|
|
272
|
+
* @param template The message template.
|
|
273
|
+
* @param values The message template values.
|
|
274
|
+
* @returns The message template values interleaved between the substitution
|
|
275
|
+
* values.
|
|
276
|
+
*/
|
|
277
|
+
export function renderMessage(template, values) {
|
|
278
|
+
const args = [];
|
|
279
|
+
for (let i = 0; i < template.length; i++) {
|
|
280
|
+
args.push(template[i]);
|
|
281
|
+
if (i < values.length)
|
|
282
|
+
args.push(values[i]);
|
|
283
|
+
}
|
|
284
|
+
return args;
|
|
285
|
+
}
|
package/esm/mod.js
ADDED
package/esm/package.json
ADDED
package/esm/record.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/sink.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defaultConsoleFormatter, defaultTextFormatter, } from "./formatter.js";
|
|
2
|
+
/**
|
|
3
|
+
* A factory that returns a sink that writes to a {@link WritableStream}.
|
|
4
|
+
*
|
|
5
|
+
* Note that the `stream` is of Web Streams API, which is different from
|
|
6
|
+
* Node.js streams. You can convert a Node.js stream to a Web Streams API
|
|
7
|
+
* stream using [`stream.Writable.toWeb()`] method.
|
|
8
|
+
*
|
|
9
|
+
* [`stream.Writable.toWeb()`]: https://nodejs.org/api/stream.html#streamwritabletowebstreamwritable
|
|
10
|
+
*
|
|
11
|
+
* @example Sink to the standard error in Deno
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const stderrSink = getStreamSink(Deno.stderr.writable);
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example Sink to the standard error in Node.js
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import stream from "node:stream";
|
|
19
|
+
* const stderrSink = getStreamSink(stream.Writable.toWeb(process.stderr));
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @param stream The stream to write to.
|
|
23
|
+
* @param formatter The text formatter to use. Defaults to
|
|
24
|
+
* {@link defaultTextFormatter}.
|
|
25
|
+
* @param encoder The text encoder to use. Defaults to an instance of
|
|
26
|
+
* {@link TextEncoder}.
|
|
27
|
+
* @returns A sink that writes to the stream.
|
|
28
|
+
*/
|
|
29
|
+
export function getStreamSink(stream, formatter = defaultTextFormatter, encoder = new TextEncoder()) {
|
|
30
|
+
const writer = stream.getWriter();
|
|
31
|
+
return (record) => {
|
|
32
|
+
const bytes = encoder.encode(formatter(record));
|
|
33
|
+
writer.ready.then(() => writer.write(bytes));
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A console sink factory that returns a sink that logs to the console.
|
|
38
|
+
*
|
|
39
|
+
* @param formatter A console formatter. Defaults to
|
|
40
|
+
* {@link defaultConsoleFormatter}.
|
|
41
|
+
* @param console The console to log to. Defaults to {@link console}.
|
|
42
|
+
* @returns A sink that logs to the console.
|
|
43
|
+
*/
|
|
44
|
+
export function getConsoleSink(formatter = defaultConsoleFormatter, console = globalThis.console) {
|
|
45
|
+
return (record) => {
|
|
46
|
+
const args = formatter(record);
|
|
47
|
+
if (record.level === "debug")
|
|
48
|
+
console.debug(...args);
|
|
49
|
+
else if (record.level === "info")
|
|
50
|
+
console.info(...args);
|
|
51
|
+
else if (record.level === "warning")
|
|
52
|
+
console.warn(...args);
|
|
53
|
+
else if (record.level === "error" || record.level === "fatal") {
|
|
54
|
+
console.error(...args);
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
throw new TypeError(`Invalid log level: ${record.level}.`);
|
|
58
|
+
};
|
|
59
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@logtape/logtape",
|
|
3
|
+
"version": "0.1.0-dev.10+d4dd53d2",
|
|
4
|
+
"description": "Simple logging library for Deno/Node.js/Bun/browsers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"logging",
|
|
7
|
+
"log",
|
|
8
|
+
"logger"
|
|
9
|
+
],
|
|
10
|
+
"author": {
|
|
11
|
+
"name": "Hong Minhee",
|
|
12
|
+
"email": "hong@minhee.org",
|
|
13
|
+
"url": "https://hongminhee.org/"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/dahlia/logtape",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/dahlia/logtape.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/dahlia/logtape/issues"
|
|
23
|
+
},
|
|
24
|
+
"main": "./script/mod.js",
|
|
25
|
+
"module": "./esm/mod.js",
|
|
26
|
+
"types": "./types/mod.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./types/mod.d.ts",
|
|
31
|
+
"default": "./esm/mod.js"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./types/mod.d.ts",
|
|
35
|
+
"default": "./script/mod.js"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "node test_runner.js"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.9.0",
|
|
44
|
+
"picocolors": "^1.0.0",
|
|
45
|
+
"consolemock": "^1.1.0",
|
|
46
|
+
"@deno/shim-deno": "~0.18.0"
|
|
47
|
+
},
|
|
48
|
+
"_generatedBy": "dnt@dev"
|
|
49
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dntGlobalThis = exports.WritableStream = void 0;
|
|
4
|
+
const web_1 = require("node:stream/web");
|
|
5
|
+
var web_2 = require("node:stream/web");
|
|
6
|
+
Object.defineProperty(exports, "WritableStream", { enumerable: true, get: function () { return web_2.WritableStream; } });
|
|
7
|
+
const dntGlobals = {
|
|
8
|
+
WritableStream: web_1.WritableStream,
|
|
9
|
+
};
|
|
10
|
+
exports.dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
11
|
+
function createMergeProxy(baseObj, extObj) {
|
|
12
|
+
return new Proxy(baseObj, {
|
|
13
|
+
get(_target, prop, _receiver) {
|
|
14
|
+
if (prop in extObj) {
|
|
15
|
+
return extObj[prop];
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return baseObj[prop];
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
set(_target, prop, value) {
|
|
22
|
+
if (prop in extObj) {
|
|
23
|
+
delete extObj[prop];
|
|
24
|
+
}
|
|
25
|
+
baseObj[prop] = value;
|
|
26
|
+
return true;
|
|
27
|
+
},
|
|
28
|
+
deleteProperty(_target, prop) {
|
|
29
|
+
let success = false;
|
|
30
|
+
if (prop in extObj) {
|
|
31
|
+
delete extObj[prop];
|
|
32
|
+
success = true;
|
|
33
|
+
}
|
|
34
|
+
if (prop in baseObj) {
|
|
35
|
+
delete baseObj[prop];
|
|
36
|
+
success = true;
|
|
37
|
+
}
|
|
38
|
+
return success;
|
|
39
|
+
},
|
|
40
|
+
ownKeys(_target) {
|
|
41
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
42
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
43
|
+
const extKeysSet = new Set(extKeys);
|
|
44
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
45
|
+
},
|
|
46
|
+
defineProperty(_target, prop, desc) {
|
|
47
|
+
if (prop in extObj) {
|
|
48
|
+
delete extObj[prop];
|
|
49
|
+
}
|
|
50
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
53
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
54
|
+
if (prop in extObj) {
|
|
55
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
has(_target, prop) {
|
|
62
|
+
return prop in extObj || prop in baseObj;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
package/script/config.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigError = exports.reset = exports.configure = void 0;
|
|
4
|
+
const filter_js_1 = require("./filter.js");
|
|
5
|
+
const logger_js_1 = require("./logger.js");
|
|
6
|
+
const sink_js_1 = require("./sink.js");
|
|
7
|
+
let configured = false;
|
|
8
|
+
/**
|
|
9
|
+
* Configure the loggers with the specified configuration.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* configure({
|
|
14
|
+
* sinks: {
|
|
15
|
+
* console: getConsoleSink(),
|
|
16
|
+
* },
|
|
17
|
+
* filters: {
|
|
18
|
+
* slow: (log) =>
|
|
19
|
+
* "duration" in log.properties &&
|
|
20
|
+
* log.properties.duration as number > 1000,
|
|
21
|
+
* },
|
|
22
|
+
* loggers: [
|
|
23
|
+
* {
|
|
24
|
+
* category: "my-app",
|
|
25
|
+
* sinks: ["console"],
|
|
26
|
+
* level: "info",
|
|
27
|
+
* },
|
|
28
|
+
* {
|
|
29
|
+
* category: ["my-app", "sql"],
|
|
30
|
+
* filters: ["slow"],
|
|
31
|
+
* level: "debug",
|
|
32
|
+
* },
|
|
33
|
+
* {
|
|
34
|
+
* category: "logtape",
|
|
35
|
+
* sinks: ["console"],
|
|
36
|
+
* level: "error",
|
|
37
|
+
* },
|
|
38
|
+
* ],
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param config The configuration.
|
|
43
|
+
*/
|
|
44
|
+
function configure(config) {
|
|
45
|
+
if (configured && !config.reset) {
|
|
46
|
+
throw new ConfigError("Already configured; if you want to reset, turn on the reset flag.");
|
|
47
|
+
}
|
|
48
|
+
configured = true;
|
|
49
|
+
logger_js_1.LoggerImpl.getLogger([]).resetDescendants();
|
|
50
|
+
let metaConfigured = false;
|
|
51
|
+
for (const cfg of config.loggers) {
|
|
52
|
+
if (cfg.category.length === 0 ||
|
|
53
|
+
(cfg.category.length === 1 && cfg.category[0] === "logtape") ||
|
|
54
|
+
(cfg.category.length === 2 &&
|
|
55
|
+
cfg.category[0] === "logtape" &&
|
|
56
|
+
cfg.category[1] === "meta")) {
|
|
57
|
+
metaConfigured = true;
|
|
58
|
+
}
|
|
59
|
+
const logger = logger_js_1.LoggerImpl.getLogger(cfg.category);
|
|
60
|
+
for (const sinkId of cfg.sinks ?? []) {
|
|
61
|
+
const sink = config.sinks[sinkId];
|
|
62
|
+
if (!sink) {
|
|
63
|
+
reset();
|
|
64
|
+
throw new ConfigError(`Sink not found: ${sinkId}.`);
|
|
65
|
+
}
|
|
66
|
+
logger.sinks.push(sink);
|
|
67
|
+
}
|
|
68
|
+
if (cfg.level !== undefined)
|
|
69
|
+
logger.filters.push((0, filter_js_1.toFilter)(cfg.level));
|
|
70
|
+
for (const filterId of cfg.filters ?? []) {
|
|
71
|
+
const filter = config.filters[filterId];
|
|
72
|
+
if (filter === undefined) {
|
|
73
|
+
reset();
|
|
74
|
+
throw new ConfigError(`Filter not found: ${filterId}.`);
|
|
75
|
+
}
|
|
76
|
+
logger.filters.push((0, filter_js_1.toFilter)(filter));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const meta = logger_js_1.LoggerImpl.getLogger(["logtape", "meta"]);
|
|
80
|
+
if (!metaConfigured) {
|
|
81
|
+
meta.sinks.push((0, sink_js_1.getConsoleSink)());
|
|
82
|
+
}
|
|
83
|
+
meta.info("LogTape loggers are configured. Note that LogTape itself uses the meta " +
|
|
84
|
+
"logger, which has category {metaLoggerCategory}. The meta logger " +
|
|
85
|
+
"purposes to log internal errors such as sink exceptions. If you " +
|
|
86
|
+
"are seeing this message, the meta logger is somehow configured. " +
|
|
87
|
+
"It's recommended to configure the meta logger with a separate sink " +
|
|
88
|
+
"so that you can easily notice if logging itself fails or is " +
|
|
89
|
+
"misconfigured. To turn off this message, configure the meta logger " +
|
|
90
|
+
"with higher log levels than {dismissLevel}.", { metaLoggerCategory: ["logtape", "meta"], dismissLevel: "info" });
|
|
91
|
+
}
|
|
92
|
+
exports.configure = configure;
|
|
93
|
+
/**
|
|
94
|
+
* Reset the configuration. Mostly for testing purposes.
|
|
95
|
+
*/
|
|
96
|
+
function reset() {
|
|
97
|
+
logger_js_1.LoggerImpl.getLogger([]).resetDescendants();
|
|
98
|
+
configured = false;
|
|
99
|
+
}
|
|
100
|
+
exports.reset = reset;
|
|
101
|
+
/**
|
|
102
|
+
* A configuration error.
|
|
103
|
+
*/
|
|
104
|
+
class ConfigError extends Error {
|
|
105
|
+
/**
|
|
106
|
+
* Constructs a new configuration error.
|
|
107
|
+
* @param message The error message.
|
|
108
|
+
*/
|
|
109
|
+
constructor(message) {
|
|
110
|
+
super(message);
|
|
111
|
+
this.name = "ConfigureError";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.ConfigError = ConfigError;
|
package/script/filter.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLevelFilter = exports.toFilter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Converts a {@link FilterLike} value to an actual {@link Filter}.
|
|
6
|
+
*
|
|
7
|
+
* @param filter The filter-like value to convert.
|
|
8
|
+
* @returns The actual filter.
|
|
9
|
+
*/
|
|
10
|
+
function toFilter(filter) {
|
|
11
|
+
if (typeof filter === "function")
|
|
12
|
+
return filter;
|
|
13
|
+
return getLevelFilter(filter);
|
|
14
|
+
}
|
|
15
|
+
exports.toFilter = toFilter;
|
|
16
|
+
/**
|
|
17
|
+
* Returns a filter that accepts log records with the specified level.
|
|
18
|
+
*
|
|
19
|
+
* @param level The level to filter by. If `null`, the filter will reject all
|
|
20
|
+
* records.
|
|
21
|
+
* @returns The filter.
|
|
22
|
+
*/
|
|
23
|
+
function getLevelFilter(level) {
|
|
24
|
+
if (level == null)
|
|
25
|
+
return () => false;
|
|
26
|
+
if (level === "fatal") {
|
|
27
|
+
return (record) => record.level === "fatal";
|
|
28
|
+
}
|
|
29
|
+
else if (level === "error") {
|
|
30
|
+
return (record) => record.level === "fatal" || record.level === "error";
|
|
31
|
+
}
|
|
32
|
+
else if (level === "warning") {
|
|
33
|
+
return (record) => record.level === "fatal" ||
|
|
34
|
+
record.level === "error" ||
|
|
35
|
+
record.level === "warning";
|
|
36
|
+
}
|
|
37
|
+
else if (level === "info") {
|
|
38
|
+
return (record) => record.level === "fatal" ||
|
|
39
|
+
record.level === "error" ||
|
|
40
|
+
record.level === "warning" ||
|
|
41
|
+
record.level === "info";
|
|
42
|
+
}
|
|
43
|
+
else if (level === "debug")
|
|
44
|
+
return () => true;
|
|
45
|
+
throw new TypeError(`Invalid log level: ${level}.`);
|
|
46
|
+
}
|
|
47
|
+
exports.getLevelFilter = getLevelFilter;
|