@logtape/testing 2.3.0-dev.815 → 2.3.0-dev.818
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/README.md +21 -2
- package/dist/mod.cjs +4 -325
- package/dist/mod.d.cts +3 -153
- package/dist/mod.d.ts +3 -153
- package/dist/mod.js +3 -325
- package/dist/recorder.cjs +282 -0
- package/dist/recorder.d.cts +153 -0
- package/dist/recorder.d.cts.map +1 -0
- package/dist/recorder.d.ts +153 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +282 -0
- package/dist/recorder.js.map +1 -0
- package/dist/reporter.cjs +71 -0
- package/dist/reporter.d.cts +88 -0
- package/dist/reporter.d.cts.map +1 -0
- package/dist/reporter.d.ts +88 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +71 -0
- package/dist/reporter.js.map +1 -0
- package/dist/snapshot.cjs +50 -0
- package/dist/snapshot.js +50 -0
- package/dist/snapshot.js.map +1 -0
- package/package.json +19 -3
- package/dist/mod.d.cts.map +0 -1
- package/dist/mod.d.ts.map +0 -1
- package/dist/mod.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ Testing utilities for LogTape
|
|
|
9
9
|
This package provides testing utilities for [LogTape]. It includes a log
|
|
10
10
|
recorder that collects `LogRecord` values in memory and provides matcher-based
|
|
11
11
|
assertions for category, level, rendered message, raw message, and structured
|
|
12
|
-
properties.
|
|
12
|
+
properties. It also includes a failure log reporter that buffers records while
|
|
13
|
+
a test callback runs and reports them only when the callback fails.
|
|
13
14
|
|
|
14
15
|
[JSR badge]: https://jsr.io/badges/@logtape/testing
|
|
15
16
|
[JSR]: https://jsr.io/@logtape/testing
|
|
@@ -40,7 +41,7 @@ Use `createLogRecorder()` as a sink in tests:
|
|
|
40
41
|
|
|
41
42
|
~~~~ typescript
|
|
42
43
|
import { configure, getLogger, reset } from "@logtape/logtape";
|
|
43
|
-
import { createLogRecorder } from "@logtape/testing";
|
|
44
|
+
import { createLogRecorder } from "@logtape/testing/recorder";
|
|
44
45
|
|
|
45
46
|
const recorder = createLogRecorder();
|
|
46
47
|
|
|
@@ -77,6 +78,24 @@ that need lower-level access. Most property values are compared with
|
|
|
77
78
|
matcher values match string property values. Rendered message matching uses
|
|
78
79
|
the same value rendering as LogTape's default text formatter.
|
|
79
80
|
|
|
81
|
+
Use `createFailureLogReporter()` when logs are useful only after a test fails:
|
|
82
|
+
|
|
83
|
+
~~~~ typescript
|
|
84
|
+
import { createFailureLogReporter } from "@logtape/testing/reporter";
|
|
85
|
+
|
|
86
|
+
const reporter = createFailureLogReporter({
|
|
87
|
+
lowestLevel: "debug",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("case", reporter.wrap(async () => {
|
|
91
|
+
// Logs emitted here are reported only if this callback throws.
|
|
92
|
+
}));
|
|
93
|
+
~~~~
|
|
94
|
+
|
|
95
|
+
The root `@logtape/testing` entry point re-exports both utilities for
|
|
96
|
+
compatibility, but new code can import `@logtape/testing/recorder` or
|
|
97
|
+
`@logtape/testing/reporter` to depend on only the relevant API surface.
|
|
98
|
+
|
|
80
99
|
|
|
81
100
|
Docs
|
|
82
101
|
----
|
package/dist/mod.cjs
CHANGED
|
@@ -1,326 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const require_recorder = require('./recorder.cjs');
|
|
2
|
+
const require_reporter = require('./reporter.cjs');
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
format: ({ message }) => message,
|
|
7
|
-
lineEnding: "lf",
|
|
8
|
-
timestamp: "none"
|
|
9
|
-
});
|
|
10
|
-
/**
|
|
11
|
-
* Creates a LogTape test recorder.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* import { configure, getLogger, reset } from "@logtape/logtape";
|
|
16
|
-
* import { createLogRecorder } from "@logtape/testing";
|
|
17
|
-
*
|
|
18
|
-
* const recorder = createLogRecorder();
|
|
19
|
-
*
|
|
20
|
-
* try {
|
|
21
|
-
* await configure({
|
|
22
|
-
* sinks: { recorder: recorder.sink },
|
|
23
|
-
* loggers: [
|
|
24
|
-
* { category: ["my-lib"], lowestLevel: "debug", sinks: ["recorder"] },
|
|
25
|
-
* ],
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* getLogger(["my-lib"]).info("User {userId} logged in.", {
|
|
29
|
-
* userId: 123,
|
|
30
|
-
* });
|
|
31
|
-
*
|
|
32
|
-
* recorder.assertLogged({
|
|
33
|
-
* category: ["my-lib"],
|
|
34
|
-
* level: "info",
|
|
35
|
-
* message: "User 123 logged in.",
|
|
36
|
-
* properties: { userId: 123 },
|
|
37
|
-
* });
|
|
38
|
-
* } finally {
|
|
39
|
-
* await reset();
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* @returns A recorder with a sink and assertion helpers.
|
|
44
|
-
* @since 2.2.0
|
|
45
|
-
*/
|
|
46
|
-
function createLogRecorder() {
|
|
47
|
-
const records = [];
|
|
48
|
-
const sink = (record) => {
|
|
49
|
-
records.push(materializeLogRecord(record));
|
|
50
|
-
};
|
|
51
|
-
return {
|
|
52
|
-
sink,
|
|
53
|
-
get records() {
|
|
54
|
-
return records.slice();
|
|
55
|
-
},
|
|
56
|
-
clear() {
|
|
57
|
-
records.length = 0;
|
|
58
|
-
},
|
|
59
|
-
take() {
|
|
60
|
-
return records.splice(0);
|
|
61
|
-
},
|
|
62
|
-
find(match) {
|
|
63
|
-
return records.find((record) => matchesLogRecord(record, match));
|
|
64
|
-
},
|
|
65
|
-
filter(match) {
|
|
66
|
-
return records.filter((record) => matchesLogRecord(record, match));
|
|
67
|
-
},
|
|
68
|
-
assertLogged(match) {
|
|
69
|
-
if (records.some((record) => matchesLogRecord(record, match))) return;
|
|
70
|
-
throw new Error([
|
|
71
|
-
"Expected a LogTape record matching:",
|
|
72
|
-
formatMatcher(match),
|
|
73
|
-
"",
|
|
74
|
-
`Recorded ${formatCount(records.length, "record")}:`,
|
|
75
|
-
formatRecords(records)
|
|
76
|
-
].join("\n"));
|
|
77
|
-
},
|
|
78
|
-
assertNotLogged(match) {
|
|
79
|
-
const matching = records.filter((record) => matchesLogRecord(record, match));
|
|
80
|
-
if (matching.length < 1) return;
|
|
81
|
-
throw new Error([
|
|
82
|
-
"Expected no LogTape record matching:",
|
|
83
|
-
formatMatcher(match),
|
|
84
|
-
"",
|
|
85
|
-
`Found ${formatCount(matching.length, "matching record")}:`,
|
|
86
|
-
formatRecords(matching)
|
|
87
|
-
].join("\n"));
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function materializeLogRecord(record) {
|
|
92
|
-
const message = record.message;
|
|
93
|
-
const rawMessage = record.rawMessage;
|
|
94
|
-
const descriptors = Object.getOwnPropertyDescriptors(record);
|
|
95
|
-
if (!hasStringAccessorDescriptor(descriptors)) return record;
|
|
96
|
-
const messageDescriptor = descriptors.message;
|
|
97
|
-
const rawMessageDescriptor = descriptors.rawMessage;
|
|
98
|
-
const snapshotDescriptors = Object.create(null);
|
|
99
|
-
for (const key of Reflect.ownKeys(descriptors)) {
|
|
100
|
-
if (isLogRecordKey(key)) continue;
|
|
101
|
-
const descriptor = descriptors[key];
|
|
102
|
-
if (descriptor == null) continue;
|
|
103
|
-
snapshotDescriptors[key] = isDataDescriptor(descriptor) ? descriptor : materializedDescriptor(Reflect.get(record, key), descriptor);
|
|
104
|
-
}
|
|
105
|
-
Object.assign(snapshotDescriptors, {
|
|
106
|
-
category: materializedDescriptor(record.category, descriptors.category),
|
|
107
|
-
level: materializedDescriptor(record.level, descriptors.level),
|
|
108
|
-
message: materializedDescriptor(message, messageDescriptor),
|
|
109
|
-
rawMessage: materializedDescriptor(rawMessage, rawMessageDescriptor),
|
|
110
|
-
timestamp: materializedDescriptor(record.timestamp, descriptors.timestamp),
|
|
111
|
-
properties: materializedDescriptor(record.properties, descriptors.properties)
|
|
112
|
-
});
|
|
113
|
-
return Object.defineProperties(Object.create(Object.getPrototypeOf(record)), snapshotDescriptors);
|
|
114
|
-
}
|
|
115
|
-
function hasStringAccessorDescriptor(descriptors) {
|
|
116
|
-
for (const key of Object.getOwnPropertyNames(descriptors)) {
|
|
117
|
-
const descriptor = descriptors[key];
|
|
118
|
-
if (descriptor != null && !isDataDescriptor(descriptor)) return true;
|
|
119
|
-
}
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
function isDataDescriptor(descriptor) {
|
|
123
|
-
return descriptor != null && "value" in descriptor;
|
|
124
|
-
}
|
|
125
|
-
function isLogRecordKey(key) {
|
|
126
|
-
return key === "category" || key === "level" || key === "message" || key === "rawMessage" || key === "timestamp" || key === "properties";
|
|
127
|
-
}
|
|
128
|
-
function materializedDescriptor(value, descriptor) {
|
|
129
|
-
return {
|
|
130
|
-
configurable: descriptor?.configurable ?? true,
|
|
131
|
-
enumerable: descriptor?.enumerable ?? true,
|
|
132
|
-
value,
|
|
133
|
-
writable: isDataDescriptor(descriptor) ? descriptor.writable : true
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function matchesLogRecord(record, match) {
|
|
137
|
-
if (match.category != null && !matchesCategory(record.category, match.category)) return false;
|
|
138
|
-
if (match.categoryPrefix != null && !matchesCategoryPrefix(record.category, match.categoryPrefix)) return false;
|
|
139
|
-
if (match.level != null && record.level !== match.level) return false;
|
|
140
|
-
if (match.message != null && !matchesMessage(record, match.message)) return false;
|
|
141
|
-
if (match.rawMessage != null && !matchesText(renderRawMessage(record.rawMessage), match.rawMessage)) return false;
|
|
142
|
-
if (match.properties != null && !matchesProperties(record.properties, record, match.properties)) return false;
|
|
143
|
-
if (match.predicate != null && !match.predicate(record)) return false;
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
function matchesCategory(category, expected) {
|
|
147
|
-
const joinedCategory = category.join(".");
|
|
148
|
-
if (expected instanceof RegExp) return testRegExp(expected, joinedCategory);
|
|
149
|
-
if (typeof expected === "string") return joinedCategory === expected;
|
|
150
|
-
const expectedCategory = parseCategory(expected);
|
|
151
|
-
return category.length === expectedCategory.length && category.every((part, index) => part === expectedCategory[index]);
|
|
152
|
-
}
|
|
153
|
-
function matchesCategoryPrefix(category, prefix) {
|
|
154
|
-
const expectedPrefix = parseCategory(prefix);
|
|
155
|
-
return expectedPrefix.length <= category.length && expectedPrefix.every((part, index) => part === category[index]);
|
|
156
|
-
}
|
|
157
|
-
function parseCategory(category) {
|
|
158
|
-
if (typeof category !== "string") return category;
|
|
159
|
-
return category === "" ? [] : category.split(".");
|
|
160
|
-
}
|
|
161
|
-
function matchesMessage(record, matcher) {
|
|
162
|
-
if (typeof matcher === "function") return matcher(record);
|
|
163
|
-
return matchesText(renderMessage(record), matcher);
|
|
164
|
-
}
|
|
165
|
-
function matchesText(text, matcher) {
|
|
166
|
-
return typeof matcher === "string" ? text === matcher : testRegExp(matcher, text);
|
|
167
|
-
}
|
|
168
|
-
function matchesProperties(properties, record, matcher) {
|
|
169
|
-
const props = properties ?? {};
|
|
170
|
-
if (typeof matcher === "function") return matcher(props, record);
|
|
171
|
-
for (const key of Object.keys(matcher)) {
|
|
172
|
-
if (!Object.prototype.hasOwnProperty.call(props, key)) return false;
|
|
173
|
-
if (!matchesPropertyValue(props[key], matcher[key])) return false;
|
|
174
|
-
}
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
function matchesPropertyValue(actual, expected) {
|
|
178
|
-
if (actual instanceof Date && expected instanceof Date) return Object.is(actual.getTime(), expected.getTime());
|
|
179
|
-
if (typeof actual === "string" && expected instanceof RegExp) return testRegExp(expected, actual);
|
|
180
|
-
return Object.is(actual, expected);
|
|
181
|
-
}
|
|
182
|
-
function testRegExp(pattern, text) {
|
|
183
|
-
if (!pattern.global && !pattern.sticky) return pattern.test(text);
|
|
184
|
-
const clone = new RegExp(pattern.source, pattern.flags);
|
|
185
|
-
return clone.test(text);
|
|
186
|
-
}
|
|
187
|
-
function renderRawMessage(rawMessage) {
|
|
188
|
-
return typeof rawMessage === "string" ? rawMessage : rawMessage.join("");
|
|
189
|
-
}
|
|
190
|
-
function renderMessage(record) {
|
|
191
|
-
return messageFormatter(record).slice(0, -1);
|
|
192
|
-
}
|
|
193
|
-
function formatMatcher(match) {
|
|
194
|
-
const lines = [];
|
|
195
|
-
if (match.category != null) lines.push(` category: ${formatCategoryMatcher(match.category)}`);
|
|
196
|
-
if (match.categoryPrefix != null) lines.push(` categoryPrefix: ${formatCategoryValue(parseCategory(match.categoryPrefix))}`);
|
|
197
|
-
if (match.level != null) lines.push(` level: ${formatValue(match.level)}`);
|
|
198
|
-
if (match.message != null) lines.push(` message: ${formatMessageMatcher(match.message)}`);
|
|
199
|
-
if (match.rawMessage != null) lines.push(` rawMessage: ${formatTextMatcher(match.rawMessage)}`);
|
|
200
|
-
if (match.properties != null) lines.push(...formatPropertiesMatcher(match.properties));
|
|
201
|
-
if (match.predicate != null) lines.push(" predicate: <predicate>");
|
|
202
|
-
return lines.length < 1 ? " <any record>" : lines.join("\n");
|
|
203
|
-
}
|
|
204
|
-
function formatCategoryMatcher(category) {
|
|
205
|
-
return category instanceof RegExp ? String(category) : typeof category === "string" ? formatValue(category) : formatCategoryValue(category);
|
|
206
|
-
}
|
|
207
|
-
function formatCategoryValue(category) {
|
|
208
|
-
return `[${category.map((part) => formatValue(part)).join(", ")}]`;
|
|
209
|
-
}
|
|
210
|
-
function formatMessageMatcher(matcher) {
|
|
211
|
-
return typeof matcher === "function" ? "<predicate>" : formatTextMatcher(matcher);
|
|
212
|
-
}
|
|
213
|
-
function formatTextMatcher(matcher) {
|
|
214
|
-
return typeof matcher === "string" ? formatValue(matcher) : String(matcher);
|
|
215
|
-
}
|
|
216
|
-
function formatPropertiesMatcher(matcher) {
|
|
217
|
-
if (typeof matcher === "function") return [" properties: <predicate>"];
|
|
218
|
-
const lines = Object.keys(matcher).map((key) => ` properties.${key}: ${formatPropertyValue(matcher, key)}`);
|
|
219
|
-
return lines.length < 1 ? [" properties: {}"] : lines;
|
|
220
|
-
}
|
|
221
|
-
function formatRecords(records) {
|
|
222
|
-
if (records.length < 1) return " <none>";
|
|
223
|
-
const lines = records.slice(0, 3).map(formatRecord);
|
|
224
|
-
if (records.length > 3) lines.push(` ... ${records.length - 3} more`);
|
|
225
|
-
return lines.join("\n");
|
|
226
|
-
}
|
|
227
|
-
function formatRecord(record) {
|
|
228
|
-
const category = formatCategory(record.category);
|
|
229
|
-
return ` [${record.level}] ${category}: ${formatMessage(record)}${formatProperties(record.properties)}`;
|
|
230
|
-
}
|
|
231
|
-
function formatMessage(record) {
|
|
232
|
-
try {
|
|
233
|
-
return renderMessage(record);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
return formatAccessError(error);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function formatCategory(category) {
|
|
239
|
-
return category.length < 1 ? "<root>" : category.join(".");
|
|
240
|
-
}
|
|
241
|
-
function formatProperties(properties) {
|
|
242
|
-
const props = properties ?? {};
|
|
243
|
-
const entries = Object.keys(props);
|
|
244
|
-
if (entries.length < 1) return "";
|
|
245
|
-
const summary = entries.slice(0, 3).map((key) => `${key}: ${formatPropertyValue(props, key)}`);
|
|
246
|
-
if (entries.length > 3) summary.push(`... ${entries.length - 3} more`);
|
|
247
|
-
return ` {${summary.join(", ")}}`;
|
|
248
|
-
}
|
|
249
|
-
function formatPropertyValue(properties, key) {
|
|
250
|
-
try {
|
|
251
|
-
return formatValue(properties[key]);
|
|
252
|
-
} catch (error) {
|
|
253
|
-
return formatAccessError(error);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
function formatAccessError(error) {
|
|
257
|
-
return `<error: ${error instanceof Error ? error.message : safeString(error)}>`;
|
|
258
|
-
}
|
|
259
|
-
function formatCount(count, noun) {
|
|
260
|
-
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
261
|
-
}
|
|
262
|
-
function formatValue(value) {
|
|
263
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
264
|
-
if (typeof value === "number" && !Number.isFinite(value)) return String(value);
|
|
265
|
-
if (typeof value === "bigint") return `${value}n`;
|
|
266
|
-
if (typeof value === "symbol") return String(value);
|
|
267
|
-
if (value instanceof RegExp) return String(value);
|
|
268
|
-
if (value instanceof Error) return `${value.name}: ${value.message}`;
|
|
269
|
-
if (value instanceof Map) return formatMap(value);
|
|
270
|
-
if (value instanceof Set) return formatSet(value);
|
|
271
|
-
try {
|
|
272
|
-
return safeJsonStringify(value) ?? safeString(value);
|
|
273
|
-
} catch {
|
|
274
|
-
return safeString(value);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
function formatMap(value) {
|
|
278
|
-
const label = `Map(${value.size})`;
|
|
279
|
-
try {
|
|
280
|
-
const contents = formatMapContents(value);
|
|
281
|
-
return `${label} ${safeJsonStringify(contents) ?? safeString(contents)}`;
|
|
282
|
-
} catch {
|
|
283
|
-
return label;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
function formatSet(value) {
|
|
287
|
-
const label = `Set(${value.size})`;
|
|
288
|
-
try {
|
|
289
|
-
const contents = Array.from(value);
|
|
290
|
-
return `${label} ${safeJsonStringify(contents) ?? safeString(contents)}`;
|
|
291
|
-
} catch {
|
|
292
|
-
return label;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function safeJsonStringify(value) {
|
|
296
|
-
const ancestors = [];
|
|
297
|
-
return JSON.stringify(value, function(_key, item) {
|
|
298
|
-
if (typeof item === "bigint") return `${item}n`;
|
|
299
|
-
if (item instanceof RegExp) return String(item);
|
|
300
|
-
if (item instanceof Error) return `${item.name}: ${item.message}`;
|
|
301
|
-
if (typeof item === "object" && item != null) {
|
|
302
|
-
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop();
|
|
303
|
-
if (ancestors.includes(item)) return "[Circular]";
|
|
304
|
-
ancestors.push(item);
|
|
305
|
-
if (item instanceof Map) return formatMapContents(item);
|
|
306
|
-
if (item instanceof Set) return Array.from(item);
|
|
307
|
-
}
|
|
308
|
-
return item;
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
function formatMapContents(value) {
|
|
312
|
-
const entries = Array.from(value, ([key, entryValue]) => [safeString(key), entryValue]);
|
|
313
|
-
const keys = entries.map(([key]) => key);
|
|
314
|
-
const uniqueKeys = new Set(keys);
|
|
315
|
-
return uniqueKeys.size === keys.length ? Object.fromEntries(entries) : entries;
|
|
316
|
-
}
|
|
317
|
-
function safeString(value) {
|
|
318
|
-
try {
|
|
319
|
-
return String(value);
|
|
320
|
-
} catch {
|
|
321
|
-
return Object.prototype.toString.call(value);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
//#endregion
|
|
326
|
-
exports.createLogRecorder = createLogRecorder;
|
|
4
|
+
exports.createFailureLogReporter = require_reporter.createFailureLogReporter;
|
|
5
|
+
exports.createLogRecorder = require_recorder.createLogRecorder;
|
package/dist/mod.d.cts
CHANGED
|
@@ -1,153 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A predicate that matches log record properties.
|
|
7
|
-
*
|
|
8
|
-
* The first argument is the resolved properties object. The second argument
|
|
9
|
-
* is the full log record for cases where the predicate needs category, level,
|
|
10
|
-
* or message context.
|
|
11
|
-
*
|
|
12
|
-
* @since 2.2.0
|
|
13
|
-
*/
|
|
14
|
-
type PropertyMatcher = (properties: Readonly<Record<string, unknown>>, record: LogRecord) => boolean;
|
|
15
|
-
/**
|
|
16
|
-
* A matcher for records collected by a {@link LogRecorder}.
|
|
17
|
-
*
|
|
18
|
-
* Object property matching is shallow: every own string key in the matcher
|
|
19
|
-
* must exist on the record properties and match by value. Most values are
|
|
20
|
-
* compared with `Object.is()`, `Date` values are compared by timestamp, and
|
|
21
|
-
* regular expression matcher values match string property values. Use a
|
|
22
|
-
* {@link PropertyMatcher} when a test needs absence checks or deeper matching.
|
|
23
|
-
*
|
|
24
|
-
* @since 2.2.0
|
|
25
|
-
*/
|
|
26
|
-
interface LogRecordMatch {
|
|
27
|
-
/**
|
|
28
|
-
* Exact category matcher. A string is matched against the dot-joined
|
|
29
|
-
* category, while an array is matched segment by segment. A regular
|
|
30
|
-
* expression is tested against the dot-joined category.
|
|
31
|
-
*/
|
|
32
|
-
readonly category?: string | readonly string[] | RegExp;
|
|
33
|
-
/**
|
|
34
|
-
* Category prefix matcher. A string is split on dots, while an array is
|
|
35
|
-
* matched segment by segment.
|
|
36
|
-
*/
|
|
37
|
-
readonly categoryPrefix?: string | readonly string[];
|
|
38
|
-
/**
|
|
39
|
-
* Exact severity level matcher.
|
|
40
|
-
*/
|
|
41
|
-
readonly level?: LogLevel;
|
|
42
|
-
/**
|
|
43
|
-
* Rendered message matcher. String and regular expression matchers are
|
|
44
|
-
* applied to the rendered message, using the same value rendering as
|
|
45
|
-
* LogTape's default text formatter. A predicate receives the full record.
|
|
46
|
-
*/
|
|
47
|
-
readonly message?: string | RegExp | ((record: LogRecord) => boolean);
|
|
48
|
-
/**
|
|
49
|
-
* Raw message matcher. A string record is matched directly. A tagged
|
|
50
|
-
* template record is matched against the concatenated template strings.
|
|
51
|
-
*/
|
|
52
|
-
readonly rawMessage?: string | RegExp;
|
|
53
|
-
/**
|
|
54
|
-
* Shallow property matcher or predicate.
|
|
55
|
-
*/
|
|
56
|
-
readonly properties?: Readonly<Record<string, unknown>> | PropertyMatcher;
|
|
57
|
-
/**
|
|
58
|
-
* Full-record predicate for custom checks.
|
|
59
|
-
*/
|
|
60
|
-
readonly predicate?: (record: LogRecord) => boolean;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* A test recorder for LogTape records.
|
|
64
|
-
*
|
|
65
|
-
* @since 2.2.0
|
|
66
|
-
*/
|
|
67
|
-
interface LogRecorder {
|
|
68
|
-
/**
|
|
69
|
-
* A sink that appends each received record to {@link LogRecorder.records}.
|
|
70
|
-
*/
|
|
71
|
-
readonly sink: Sink;
|
|
72
|
-
/**
|
|
73
|
-
* A snapshot of records collected so far, in sink call order.
|
|
74
|
-
*/
|
|
75
|
-
readonly records: readonly LogRecord[];
|
|
76
|
-
/**
|
|
77
|
-
* Removes all collected records.
|
|
78
|
-
*/
|
|
79
|
-
clear(): void;
|
|
80
|
-
/**
|
|
81
|
-
* Returns collected records and clears the recorder.
|
|
82
|
-
*/
|
|
83
|
-
take(): readonly LogRecord[];
|
|
84
|
-
/**
|
|
85
|
-
* Finds the first collected record matching the given matcher.
|
|
86
|
-
*
|
|
87
|
-
* @param match The matcher to apply.
|
|
88
|
-
* @returns The first matching record, or `undefined`.
|
|
89
|
-
*/
|
|
90
|
-
find(match: LogRecordMatch): LogRecord | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* Finds all collected records matching the given matcher.
|
|
93
|
-
*
|
|
94
|
-
* @param match The matcher to apply.
|
|
95
|
-
* @returns All matching records in collection order.
|
|
96
|
-
*/
|
|
97
|
-
filter(match: LogRecordMatch): readonly LogRecord[];
|
|
98
|
-
/**
|
|
99
|
-
* Asserts that at least one collected record matches the given matcher.
|
|
100
|
-
*
|
|
101
|
-
* @param match The matcher to apply.
|
|
102
|
-
* @throws {Error} If no matching record exists.
|
|
103
|
-
*/
|
|
104
|
-
assertLogged(match: LogRecordMatch): void;
|
|
105
|
-
/**
|
|
106
|
-
* Asserts that no collected record matches the given matcher.
|
|
107
|
-
*
|
|
108
|
-
* @param match The matcher to apply.
|
|
109
|
-
* @throws {Error} If a matching record exists.
|
|
110
|
-
*/
|
|
111
|
-
assertNotLogged(match: LogRecordMatch): void;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Creates a LogTape test recorder.
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* import { configure, getLogger, reset } from "@logtape/logtape";
|
|
119
|
-
* import { createLogRecorder } from "@logtape/testing";
|
|
120
|
-
*
|
|
121
|
-
* const recorder = createLogRecorder();
|
|
122
|
-
*
|
|
123
|
-
* try {
|
|
124
|
-
* await configure({
|
|
125
|
-
* sinks: { recorder: recorder.sink },
|
|
126
|
-
* loggers: [
|
|
127
|
-
* { category: ["my-lib"], lowestLevel: "debug", sinks: ["recorder"] },
|
|
128
|
-
* ],
|
|
129
|
-
* });
|
|
130
|
-
*
|
|
131
|
-
* getLogger(["my-lib"]).info("User {userId} logged in.", {
|
|
132
|
-
* userId: 123,
|
|
133
|
-
* });
|
|
134
|
-
*
|
|
135
|
-
* recorder.assertLogged({
|
|
136
|
-
* category: ["my-lib"],
|
|
137
|
-
* level: "info",
|
|
138
|
-
* message: "User 123 logged in.",
|
|
139
|
-
* properties: { userId: 123 },
|
|
140
|
-
* });
|
|
141
|
-
* } finally {
|
|
142
|
-
* await reset();
|
|
143
|
-
* }
|
|
144
|
-
* ```
|
|
145
|
-
*
|
|
146
|
-
* @returns A recorder with a sink and assertion helpers.
|
|
147
|
-
* @since 2.2.0
|
|
148
|
-
*/
|
|
149
|
-
declare function createLogRecorder(): LogRecorder;
|
|
150
|
-
//# sourceMappingURL=mod.d.ts.map
|
|
151
|
-
//#endregion
|
|
152
|
-
export { LogRecordMatch, LogRecorder, PropertyMatcher, createLogRecorder };
|
|
153
|
-
//# sourceMappingURL=mod.d.cts.map
|
|
1
|
+
import { LogRecordMatch, LogRecorder, PropertyMatcher, createLogRecorder } from "./recorder.cjs";
|
|
2
|
+
import { FailureLogReportMode, FailureLogReporter, FailureLogReporterOptions, createFailureLogReporter } from "./reporter.cjs";
|
|
3
|
+
export { FailureLogReportMode, FailureLogReporter, FailureLogReporterOptions, LogRecordMatch, LogRecorder, PropertyMatcher, createFailureLogReporter, createLogRecorder };
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,153 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A predicate that matches log record properties.
|
|
7
|
-
*
|
|
8
|
-
* The first argument is the resolved properties object. The second argument
|
|
9
|
-
* is the full log record for cases where the predicate needs category, level,
|
|
10
|
-
* or message context.
|
|
11
|
-
*
|
|
12
|
-
* @since 2.2.0
|
|
13
|
-
*/
|
|
14
|
-
type PropertyMatcher = (properties: Readonly<Record<string, unknown>>, record: LogRecord) => boolean;
|
|
15
|
-
/**
|
|
16
|
-
* A matcher for records collected by a {@link LogRecorder}.
|
|
17
|
-
*
|
|
18
|
-
* Object property matching is shallow: every own string key in the matcher
|
|
19
|
-
* must exist on the record properties and match by value. Most values are
|
|
20
|
-
* compared with `Object.is()`, `Date` values are compared by timestamp, and
|
|
21
|
-
* regular expression matcher values match string property values. Use a
|
|
22
|
-
* {@link PropertyMatcher} when a test needs absence checks or deeper matching.
|
|
23
|
-
*
|
|
24
|
-
* @since 2.2.0
|
|
25
|
-
*/
|
|
26
|
-
interface LogRecordMatch {
|
|
27
|
-
/**
|
|
28
|
-
* Exact category matcher. A string is matched against the dot-joined
|
|
29
|
-
* category, while an array is matched segment by segment. A regular
|
|
30
|
-
* expression is tested against the dot-joined category.
|
|
31
|
-
*/
|
|
32
|
-
readonly category?: string | readonly string[] | RegExp;
|
|
33
|
-
/**
|
|
34
|
-
* Category prefix matcher. A string is split on dots, while an array is
|
|
35
|
-
* matched segment by segment.
|
|
36
|
-
*/
|
|
37
|
-
readonly categoryPrefix?: string | readonly string[];
|
|
38
|
-
/**
|
|
39
|
-
* Exact severity level matcher.
|
|
40
|
-
*/
|
|
41
|
-
readonly level?: LogLevel;
|
|
42
|
-
/**
|
|
43
|
-
* Rendered message matcher. String and regular expression matchers are
|
|
44
|
-
* applied to the rendered message, using the same value rendering as
|
|
45
|
-
* LogTape's default text formatter. A predicate receives the full record.
|
|
46
|
-
*/
|
|
47
|
-
readonly message?: string | RegExp | ((record: LogRecord) => boolean);
|
|
48
|
-
/**
|
|
49
|
-
* Raw message matcher. A string record is matched directly. A tagged
|
|
50
|
-
* template record is matched against the concatenated template strings.
|
|
51
|
-
*/
|
|
52
|
-
readonly rawMessage?: string | RegExp;
|
|
53
|
-
/**
|
|
54
|
-
* Shallow property matcher or predicate.
|
|
55
|
-
*/
|
|
56
|
-
readonly properties?: Readonly<Record<string, unknown>> | PropertyMatcher;
|
|
57
|
-
/**
|
|
58
|
-
* Full-record predicate for custom checks.
|
|
59
|
-
*/
|
|
60
|
-
readonly predicate?: (record: LogRecord) => boolean;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* A test recorder for LogTape records.
|
|
64
|
-
*
|
|
65
|
-
* @since 2.2.0
|
|
66
|
-
*/
|
|
67
|
-
interface LogRecorder {
|
|
68
|
-
/**
|
|
69
|
-
* A sink that appends each received record to {@link LogRecorder.records}.
|
|
70
|
-
*/
|
|
71
|
-
readonly sink: Sink;
|
|
72
|
-
/**
|
|
73
|
-
* A snapshot of records collected so far, in sink call order.
|
|
74
|
-
*/
|
|
75
|
-
readonly records: readonly LogRecord[];
|
|
76
|
-
/**
|
|
77
|
-
* Removes all collected records.
|
|
78
|
-
*/
|
|
79
|
-
clear(): void;
|
|
80
|
-
/**
|
|
81
|
-
* Returns collected records and clears the recorder.
|
|
82
|
-
*/
|
|
83
|
-
take(): readonly LogRecord[];
|
|
84
|
-
/**
|
|
85
|
-
* Finds the first collected record matching the given matcher.
|
|
86
|
-
*
|
|
87
|
-
* @param match The matcher to apply.
|
|
88
|
-
* @returns The first matching record, or `undefined`.
|
|
89
|
-
*/
|
|
90
|
-
find(match: LogRecordMatch): LogRecord | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* Finds all collected records matching the given matcher.
|
|
93
|
-
*
|
|
94
|
-
* @param match The matcher to apply.
|
|
95
|
-
* @returns All matching records in collection order.
|
|
96
|
-
*/
|
|
97
|
-
filter(match: LogRecordMatch): readonly LogRecord[];
|
|
98
|
-
/**
|
|
99
|
-
* Asserts that at least one collected record matches the given matcher.
|
|
100
|
-
*
|
|
101
|
-
* @param match The matcher to apply.
|
|
102
|
-
* @throws {Error} If no matching record exists.
|
|
103
|
-
*/
|
|
104
|
-
assertLogged(match: LogRecordMatch): void;
|
|
105
|
-
/**
|
|
106
|
-
* Asserts that no collected record matches the given matcher.
|
|
107
|
-
*
|
|
108
|
-
* @param match The matcher to apply.
|
|
109
|
-
* @throws {Error} If a matching record exists.
|
|
110
|
-
*/
|
|
111
|
-
assertNotLogged(match: LogRecordMatch): void;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Creates a LogTape test recorder.
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* import { configure, getLogger, reset } from "@logtape/logtape";
|
|
119
|
-
* import { createLogRecorder } from "@logtape/testing";
|
|
120
|
-
*
|
|
121
|
-
* const recorder = createLogRecorder();
|
|
122
|
-
*
|
|
123
|
-
* try {
|
|
124
|
-
* await configure({
|
|
125
|
-
* sinks: { recorder: recorder.sink },
|
|
126
|
-
* loggers: [
|
|
127
|
-
* { category: ["my-lib"], lowestLevel: "debug", sinks: ["recorder"] },
|
|
128
|
-
* ],
|
|
129
|
-
* });
|
|
130
|
-
*
|
|
131
|
-
* getLogger(["my-lib"]).info("User {userId} logged in.", {
|
|
132
|
-
* userId: 123,
|
|
133
|
-
* });
|
|
134
|
-
*
|
|
135
|
-
* recorder.assertLogged({
|
|
136
|
-
* category: ["my-lib"],
|
|
137
|
-
* level: "info",
|
|
138
|
-
* message: "User 123 logged in.",
|
|
139
|
-
* properties: { userId: 123 },
|
|
140
|
-
* });
|
|
141
|
-
* } finally {
|
|
142
|
-
* await reset();
|
|
143
|
-
* }
|
|
144
|
-
* ```
|
|
145
|
-
*
|
|
146
|
-
* @returns A recorder with a sink and assertion helpers.
|
|
147
|
-
* @since 2.2.0
|
|
148
|
-
*/
|
|
149
|
-
declare function createLogRecorder(): LogRecorder;
|
|
150
|
-
//# sourceMappingURL=mod.d.ts.map
|
|
151
|
-
//#endregion
|
|
152
|
-
export { LogRecordMatch, LogRecorder, PropertyMatcher, createLogRecorder };
|
|
153
|
-
//# sourceMappingURL=mod.d.ts.map
|
|
1
|
+
import { LogRecordMatch, LogRecorder, PropertyMatcher, createLogRecorder } from "./recorder.js";
|
|
2
|
+
import { FailureLogReportMode, FailureLogReporter, FailureLogReporterOptions, createFailureLogReporter } from "./reporter.js";
|
|
3
|
+
export { FailureLogReportMode, FailureLogReporter, FailureLogReporterOptions, LogRecordMatch, LogRecorder, PropertyMatcher, createFailureLogReporter, createLogRecorder };
|