@logtape/testing 2.3.0-dev.813 → 2.3.0-dev.816

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/dist/mod.js CHANGED
@@ -1,326 +1,4 @@
1
- import { getTextFormatter } from "@logtape/logtape";
1
+ import { createLogRecorder } from "./recorder.js";
2
+ import { createFailureLogReporter } from "./reporter.js";
2
3
 
3
- //#region src/mod.ts
4
- const messageFormatter = getTextFormatter({
5
- format: ({ message }) => message,
6
- lineEnding: "lf",
7
- timestamp: "none"
8
- });
9
- /**
10
- * Creates a LogTape test recorder.
11
- *
12
- * @example
13
- * ```ts
14
- * import { configure, getLogger, reset } from "@logtape/logtape";
15
- * import { createLogRecorder } from "@logtape/testing";
16
- *
17
- * const recorder = createLogRecorder();
18
- *
19
- * try {
20
- * await configure({
21
- * sinks: { recorder: recorder.sink },
22
- * loggers: [
23
- * { category: ["my-lib"], lowestLevel: "debug", sinks: ["recorder"] },
24
- * ],
25
- * });
26
- *
27
- * getLogger(["my-lib"]).info("User {userId} logged in.", {
28
- * userId: 123,
29
- * });
30
- *
31
- * recorder.assertLogged({
32
- * category: ["my-lib"],
33
- * level: "info",
34
- * message: "User 123 logged in.",
35
- * properties: { userId: 123 },
36
- * });
37
- * } finally {
38
- * await reset();
39
- * }
40
- * ```
41
- *
42
- * @returns A recorder with a sink and assertion helpers.
43
- * @since 2.2.0
44
- */
45
- function createLogRecorder() {
46
- const records = [];
47
- const sink = (record) => {
48
- records.push(materializeLogRecord(record));
49
- };
50
- return {
51
- sink,
52
- get records() {
53
- return records.slice();
54
- },
55
- clear() {
56
- records.length = 0;
57
- },
58
- take() {
59
- return records.splice(0);
60
- },
61
- find(match) {
62
- return records.find((record) => matchesLogRecord(record, match));
63
- },
64
- filter(match) {
65
- return records.filter((record) => matchesLogRecord(record, match));
66
- },
67
- assertLogged(match) {
68
- if (records.some((record) => matchesLogRecord(record, match))) return;
69
- throw new Error([
70
- "Expected a LogTape record matching:",
71
- formatMatcher(match),
72
- "",
73
- `Recorded ${formatCount(records.length, "record")}:`,
74
- formatRecords(records)
75
- ].join("\n"));
76
- },
77
- assertNotLogged(match) {
78
- const matching = records.filter((record) => matchesLogRecord(record, match));
79
- if (matching.length < 1) return;
80
- throw new Error([
81
- "Expected no LogTape record matching:",
82
- formatMatcher(match),
83
- "",
84
- `Found ${formatCount(matching.length, "matching record")}:`,
85
- formatRecords(matching)
86
- ].join("\n"));
87
- }
88
- };
89
- }
90
- function materializeLogRecord(record) {
91
- const message = record.message;
92
- const rawMessage = record.rawMessage;
93
- const descriptors = Object.getOwnPropertyDescriptors(record);
94
- if (!hasStringAccessorDescriptor(descriptors)) return record;
95
- const messageDescriptor = descriptors.message;
96
- const rawMessageDescriptor = descriptors.rawMessage;
97
- const snapshotDescriptors = Object.create(null);
98
- for (const key of Reflect.ownKeys(descriptors)) {
99
- if (isLogRecordKey(key)) continue;
100
- const descriptor = descriptors[key];
101
- if (descriptor == null) continue;
102
- snapshotDescriptors[key] = isDataDescriptor(descriptor) ? descriptor : materializedDescriptor(Reflect.get(record, key), descriptor);
103
- }
104
- Object.assign(snapshotDescriptors, {
105
- category: materializedDescriptor(record.category, descriptors.category),
106
- level: materializedDescriptor(record.level, descriptors.level),
107
- message: materializedDescriptor(message, messageDescriptor),
108
- rawMessage: materializedDescriptor(rawMessage, rawMessageDescriptor),
109
- timestamp: materializedDescriptor(record.timestamp, descriptors.timestamp),
110
- properties: materializedDescriptor(record.properties, descriptors.properties)
111
- });
112
- return Object.defineProperties(Object.create(Object.getPrototypeOf(record)), snapshotDescriptors);
113
- }
114
- function hasStringAccessorDescriptor(descriptors) {
115
- for (const key of Object.getOwnPropertyNames(descriptors)) {
116
- const descriptor = descriptors[key];
117
- if (descriptor != null && !isDataDescriptor(descriptor)) return true;
118
- }
119
- return false;
120
- }
121
- function isDataDescriptor(descriptor) {
122
- return descriptor != null && "value" in descriptor;
123
- }
124
- function isLogRecordKey(key) {
125
- return key === "category" || key === "level" || key === "message" || key === "rawMessage" || key === "timestamp" || key === "properties";
126
- }
127
- function materializedDescriptor(value, descriptor) {
128
- return {
129
- configurable: descriptor?.configurable ?? true,
130
- enumerable: descriptor?.enumerable ?? true,
131
- value,
132
- writable: isDataDescriptor(descriptor) ? descriptor.writable : true
133
- };
134
- }
135
- function matchesLogRecord(record, match) {
136
- if (match.category != null && !matchesCategory(record.category, match.category)) return false;
137
- if (match.categoryPrefix != null && !matchesCategoryPrefix(record.category, match.categoryPrefix)) return false;
138
- if (match.level != null && record.level !== match.level) return false;
139
- if (match.message != null && !matchesMessage(record, match.message)) return false;
140
- if (match.rawMessage != null && !matchesText(renderRawMessage(record.rawMessage), match.rawMessage)) return false;
141
- if (match.properties != null && !matchesProperties(record.properties, record, match.properties)) return false;
142
- if (match.predicate != null && !match.predicate(record)) return false;
143
- return true;
144
- }
145
- function matchesCategory(category, expected) {
146
- const joinedCategory = category.join(".");
147
- if (expected instanceof RegExp) return testRegExp(expected, joinedCategory);
148
- if (typeof expected === "string") return joinedCategory === expected;
149
- const expectedCategory = parseCategory(expected);
150
- return category.length === expectedCategory.length && category.every((part, index) => part === expectedCategory[index]);
151
- }
152
- function matchesCategoryPrefix(category, prefix) {
153
- const expectedPrefix = parseCategory(prefix);
154
- return expectedPrefix.length <= category.length && expectedPrefix.every((part, index) => part === category[index]);
155
- }
156
- function parseCategory(category) {
157
- if (typeof category !== "string") return category;
158
- return category === "" ? [] : category.split(".");
159
- }
160
- function matchesMessage(record, matcher) {
161
- if (typeof matcher === "function") return matcher(record);
162
- return matchesText(renderMessage(record), matcher);
163
- }
164
- function matchesText(text, matcher) {
165
- return typeof matcher === "string" ? text === matcher : testRegExp(matcher, text);
166
- }
167
- function matchesProperties(properties, record, matcher) {
168
- const props = properties ?? {};
169
- if (typeof matcher === "function") return matcher(props, record);
170
- for (const key of Object.keys(matcher)) {
171
- if (!Object.prototype.hasOwnProperty.call(props, key)) return false;
172
- if (!matchesPropertyValue(props[key], matcher[key])) return false;
173
- }
174
- return true;
175
- }
176
- function matchesPropertyValue(actual, expected) {
177
- if (actual instanceof Date && expected instanceof Date) return Object.is(actual.getTime(), expected.getTime());
178
- if (typeof actual === "string" && expected instanceof RegExp) return testRegExp(expected, actual);
179
- return Object.is(actual, expected);
180
- }
181
- function testRegExp(pattern, text) {
182
- if (!pattern.global && !pattern.sticky) return pattern.test(text);
183
- const clone = new RegExp(pattern.source, pattern.flags);
184
- return clone.test(text);
185
- }
186
- function renderRawMessage(rawMessage) {
187
- return typeof rawMessage === "string" ? rawMessage : rawMessage.join("");
188
- }
189
- function renderMessage(record) {
190
- return messageFormatter(record).slice(0, -1);
191
- }
192
- function formatMatcher(match) {
193
- const lines = [];
194
- if (match.category != null) lines.push(` category: ${formatCategoryMatcher(match.category)}`);
195
- if (match.categoryPrefix != null) lines.push(` categoryPrefix: ${formatCategoryValue(parseCategory(match.categoryPrefix))}`);
196
- if (match.level != null) lines.push(` level: ${formatValue(match.level)}`);
197
- if (match.message != null) lines.push(` message: ${formatMessageMatcher(match.message)}`);
198
- if (match.rawMessage != null) lines.push(` rawMessage: ${formatTextMatcher(match.rawMessage)}`);
199
- if (match.properties != null) lines.push(...formatPropertiesMatcher(match.properties));
200
- if (match.predicate != null) lines.push(" predicate: <predicate>");
201
- return lines.length < 1 ? " <any record>" : lines.join("\n");
202
- }
203
- function formatCategoryMatcher(category) {
204
- return category instanceof RegExp ? String(category) : typeof category === "string" ? formatValue(category) : formatCategoryValue(category);
205
- }
206
- function formatCategoryValue(category) {
207
- return `[${category.map((part) => formatValue(part)).join(", ")}]`;
208
- }
209
- function formatMessageMatcher(matcher) {
210
- return typeof matcher === "function" ? "<predicate>" : formatTextMatcher(matcher);
211
- }
212
- function formatTextMatcher(matcher) {
213
- return typeof matcher === "string" ? formatValue(matcher) : String(matcher);
214
- }
215
- function formatPropertiesMatcher(matcher) {
216
- if (typeof matcher === "function") return [" properties: <predicate>"];
217
- const lines = Object.keys(matcher).map((key) => ` properties.${key}: ${formatPropertyValue(matcher, key)}`);
218
- return lines.length < 1 ? [" properties: {}"] : lines;
219
- }
220
- function formatRecords(records) {
221
- if (records.length < 1) return " <none>";
222
- const lines = records.slice(0, 3).map(formatRecord);
223
- if (records.length > 3) lines.push(` ... ${records.length - 3} more`);
224
- return lines.join("\n");
225
- }
226
- function formatRecord(record) {
227
- const category = formatCategory(record.category);
228
- return ` [${record.level}] ${category}: ${formatMessage(record)}${formatProperties(record.properties)}`;
229
- }
230
- function formatMessage(record) {
231
- try {
232
- return renderMessage(record);
233
- } catch (error) {
234
- return formatAccessError(error);
235
- }
236
- }
237
- function formatCategory(category) {
238
- return category.length < 1 ? "<root>" : category.join(".");
239
- }
240
- function formatProperties(properties) {
241
- const props = properties ?? {};
242
- const entries = Object.keys(props);
243
- if (entries.length < 1) return "";
244
- const summary = entries.slice(0, 3).map((key) => `${key}: ${formatPropertyValue(props, key)}`);
245
- if (entries.length > 3) summary.push(`... ${entries.length - 3} more`);
246
- return ` {${summary.join(", ")}}`;
247
- }
248
- function formatPropertyValue(properties, key) {
249
- try {
250
- return formatValue(properties[key]);
251
- } catch (error) {
252
- return formatAccessError(error);
253
- }
254
- }
255
- function formatAccessError(error) {
256
- return `<error: ${error instanceof Error ? error.message : safeString(error)}>`;
257
- }
258
- function formatCount(count, noun) {
259
- return `${count} ${noun}${count === 1 ? "" : "s"}`;
260
- }
261
- function formatValue(value) {
262
- if (typeof value === "string") return JSON.stringify(value);
263
- if (typeof value === "number" && !Number.isFinite(value)) return String(value);
264
- if (typeof value === "bigint") return `${value}n`;
265
- if (typeof value === "symbol") return String(value);
266
- if (value instanceof RegExp) return String(value);
267
- if (value instanceof Error) return `${value.name}: ${value.message}`;
268
- if (value instanceof Map) return formatMap(value);
269
- if (value instanceof Set) return formatSet(value);
270
- try {
271
- return safeJsonStringify(value) ?? safeString(value);
272
- } catch {
273
- return safeString(value);
274
- }
275
- }
276
- function formatMap(value) {
277
- const label = `Map(${value.size})`;
278
- try {
279
- const contents = formatMapContents(value);
280
- return `${label} ${safeJsonStringify(contents) ?? safeString(contents)}`;
281
- } catch {
282
- return label;
283
- }
284
- }
285
- function formatSet(value) {
286
- const label = `Set(${value.size})`;
287
- try {
288
- const contents = Array.from(value);
289
- return `${label} ${safeJsonStringify(contents) ?? safeString(contents)}`;
290
- } catch {
291
- return label;
292
- }
293
- }
294
- function safeJsonStringify(value) {
295
- const ancestors = [];
296
- return JSON.stringify(value, function(_key, item) {
297
- if (typeof item === "bigint") return `${item}n`;
298
- if (item instanceof RegExp) return String(item);
299
- if (item instanceof Error) return `${item.name}: ${item.message}`;
300
- if (typeof item === "object" && item != null) {
301
- while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop();
302
- if (ancestors.includes(item)) return "[Circular]";
303
- ancestors.push(item);
304
- if (item instanceof Map) return formatMapContents(item);
305
- if (item instanceof Set) return Array.from(item);
306
- }
307
- return item;
308
- });
309
- }
310
- function formatMapContents(value) {
311
- const entries = Array.from(value, ([key, entryValue]) => [safeString(key), entryValue]);
312
- const keys = entries.map(([key]) => key);
313
- const uniqueKeys = new Set(keys);
314
- return uniqueKeys.size === keys.length ? Object.fromEntries(entries) : entries;
315
- }
316
- function safeString(value) {
317
- try {
318
- return String(value);
319
- } catch {
320
- return Object.prototype.toString.call(value);
321
- }
322
- }
323
-
324
- //#endregion
325
- export { createLogRecorder };
326
- //# sourceMappingURL=mod.js.map
4
+ export { createFailureLogReporter, createLogRecorder };
@@ -0,0 +1,282 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_snapshot = require('./snapshot.cjs');
3
+ const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
4
+
5
+ //#region src/recorder.ts
6
+ const messageFormatter = (0, __logtape_logtape.getTextFormatter)({
7
+ format: ({ message }) => message,
8
+ lineEnding: "lf",
9
+ timestamp: "none"
10
+ });
11
+ /**
12
+ * Creates a LogTape test recorder.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { configure, getLogger, reset } from "@logtape/logtape";
17
+ * import { createLogRecorder } from "@logtape/testing/recorder";
18
+ *
19
+ * const recorder = createLogRecorder();
20
+ *
21
+ * try {
22
+ * await configure({
23
+ * sinks: { recorder: recorder.sink },
24
+ * loggers: [
25
+ * { category: ["my-lib"], lowestLevel: "debug", sinks: ["recorder"] },
26
+ * ],
27
+ * });
28
+ *
29
+ * getLogger(["my-lib"]).info("User {userId} logged in.", {
30
+ * userId: 123,
31
+ * });
32
+ *
33
+ * recorder.assertLogged({
34
+ * category: ["my-lib"],
35
+ * level: "info",
36
+ * message: "User 123 logged in.",
37
+ * properties: { userId: 123 },
38
+ * });
39
+ * } finally {
40
+ * await reset();
41
+ * }
42
+ * ```
43
+ *
44
+ * @returns A recorder with a sink and assertion helpers.
45
+ * @since 2.2.0
46
+ */
47
+ function createLogRecorder() {
48
+ const records = [];
49
+ const sink = (record) => {
50
+ records.push(require_snapshot.materializeLogRecord(record));
51
+ };
52
+ return {
53
+ sink,
54
+ get records() {
55
+ return records.slice();
56
+ },
57
+ clear() {
58
+ records.length = 0;
59
+ },
60
+ take() {
61
+ return records.splice(0);
62
+ },
63
+ find(match) {
64
+ return records.find((record) => matchesLogRecord(record, match));
65
+ },
66
+ filter(match) {
67
+ return records.filter((record) => matchesLogRecord(record, match));
68
+ },
69
+ assertLogged(match) {
70
+ if (records.some((record) => matchesLogRecord(record, match))) return;
71
+ throw new Error([
72
+ "Expected a LogTape record matching:",
73
+ formatMatcher(match),
74
+ "",
75
+ `Recorded ${formatCount(records.length, "record")}:`,
76
+ formatRecords(records)
77
+ ].join("\n"));
78
+ },
79
+ assertNotLogged(match) {
80
+ const matching = records.filter((record) => matchesLogRecord(record, match));
81
+ if (matching.length < 1) return;
82
+ throw new Error([
83
+ "Expected no LogTape record matching:",
84
+ formatMatcher(match),
85
+ "",
86
+ `Found ${formatCount(matching.length, "matching record")}:`,
87
+ formatRecords(matching)
88
+ ].join("\n"));
89
+ }
90
+ };
91
+ }
92
+ function matchesLogRecord(record, match) {
93
+ if (match.category != null && !matchesCategory(record.category, match.category)) return false;
94
+ if (match.categoryPrefix != null && !matchesCategoryPrefix(record.category, match.categoryPrefix)) return false;
95
+ if (match.level != null && record.level !== match.level) return false;
96
+ if (match.message != null && !matchesMessage(record, match.message)) return false;
97
+ if (match.rawMessage != null && !matchesText(renderRawMessage(record.rawMessage), match.rawMessage)) return false;
98
+ if (match.properties != null && !matchesProperties(record.properties, record, match.properties)) return false;
99
+ if (match.predicate != null && !match.predicate(record)) return false;
100
+ return true;
101
+ }
102
+ function matchesCategory(category, expected) {
103
+ const joinedCategory = category.join(".");
104
+ if (expected instanceof RegExp) return testRegExp(expected, joinedCategory);
105
+ if (typeof expected === "string") return joinedCategory === expected;
106
+ const expectedCategory = parseCategory(expected);
107
+ return category.length === expectedCategory.length && category.every((part, index) => part === expectedCategory[index]);
108
+ }
109
+ function matchesCategoryPrefix(category, prefix) {
110
+ const expectedPrefix = parseCategory(prefix);
111
+ return expectedPrefix.length <= category.length && expectedPrefix.every((part, index) => part === category[index]);
112
+ }
113
+ function parseCategory(category) {
114
+ if (typeof category !== "string") return category;
115
+ return category === "" ? [] : category.split(".");
116
+ }
117
+ function matchesMessage(record, matcher) {
118
+ if (typeof matcher === "function") return matcher(record);
119
+ return matchesText(renderMessage(record), matcher);
120
+ }
121
+ function matchesText(text, matcher) {
122
+ return typeof matcher === "string" ? text === matcher : testRegExp(matcher, text);
123
+ }
124
+ function matchesProperties(properties, record, matcher) {
125
+ const props = properties ?? {};
126
+ if (typeof matcher === "function") return matcher(props, record);
127
+ for (const key of Object.keys(matcher)) {
128
+ if (!Object.prototype.hasOwnProperty.call(props, key)) return false;
129
+ if (!matchesPropertyValue(props[key], matcher[key])) return false;
130
+ }
131
+ return true;
132
+ }
133
+ function matchesPropertyValue(actual, expected) {
134
+ if (actual instanceof Date && expected instanceof Date) return Object.is(actual.getTime(), expected.getTime());
135
+ if (typeof actual === "string" && expected instanceof RegExp) return testRegExp(expected, actual);
136
+ return Object.is(actual, expected);
137
+ }
138
+ function testRegExp(pattern, text) {
139
+ if (!pattern.global && !pattern.sticky) return pattern.test(text);
140
+ const clone = new RegExp(pattern.source, pattern.flags);
141
+ return clone.test(text);
142
+ }
143
+ function renderRawMessage(rawMessage) {
144
+ return typeof rawMessage === "string" ? rawMessage : rawMessage.join("");
145
+ }
146
+ function renderMessage(record) {
147
+ return messageFormatter(record).slice(0, -1);
148
+ }
149
+ function formatMatcher(match) {
150
+ const lines = [];
151
+ if (match.category != null) lines.push(` category: ${formatCategoryMatcher(match.category)}`);
152
+ if (match.categoryPrefix != null) lines.push(` categoryPrefix: ${formatCategoryValue(parseCategory(match.categoryPrefix))}`);
153
+ if (match.level != null) lines.push(` level: ${formatValue(match.level)}`);
154
+ if (match.message != null) lines.push(` message: ${formatMessageMatcher(match.message)}`);
155
+ if (match.rawMessage != null) lines.push(` rawMessage: ${formatTextMatcher(match.rawMessage)}`);
156
+ if (match.properties != null) lines.push(...formatPropertiesMatcher(match.properties));
157
+ if (match.predicate != null) lines.push(" predicate: <predicate>");
158
+ return lines.length < 1 ? " <any record>" : lines.join("\n");
159
+ }
160
+ function formatCategoryMatcher(category) {
161
+ return category instanceof RegExp ? String(category) : typeof category === "string" ? formatValue(category) : formatCategoryValue(category);
162
+ }
163
+ function formatCategoryValue(category) {
164
+ return `[${category.map((part) => formatValue(part)).join(", ")}]`;
165
+ }
166
+ function formatMessageMatcher(matcher) {
167
+ return typeof matcher === "function" ? "<predicate>" : formatTextMatcher(matcher);
168
+ }
169
+ function formatTextMatcher(matcher) {
170
+ return typeof matcher === "string" ? formatValue(matcher) : String(matcher);
171
+ }
172
+ function formatPropertiesMatcher(matcher) {
173
+ if (typeof matcher === "function") return [" properties: <predicate>"];
174
+ const lines = Object.keys(matcher).map((key) => ` properties.${key}: ${formatPropertyValue(matcher, key)}`);
175
+ return lines.length < 1 ? [" properties: {}"] : lines;
176
+ }
177
+ function formatRecords(records) {
178
+ if (records.length < 1) return " <none>";
179
+ const lines = records.slice(0, 3).map(formatRecord);
180
+ if (records.length > 3) lines.push(` ... ${records.length - 3} more`);
181
+ return lines.join("\n");
182
+ }
183
+ function formatRecord(record) {
184
+ const category = formatCategory(record.category);
185
+ return ` [${record.level}] ${category}: ${formatMessage(record)}${formatProperties(record.properties)}`;
186
+ }
187
+ function formatMessage(record) {
188
+ try {
189
+ return renderMessage(record);
190
+ } catch (error) {
191
+ return formatAccessError(error);
192
+ }
193
+ }
194
+ function formatCategory(category) {
195
+ return category.length < 1 ? "<root>" : category.join(".");
196
+ }
197
+ function formatProperties(properties) {
198
+ const props = properties ?? {};
199
+ const entries = Object.keys(props);
200
+ if (entries.length < 1) return "";
201
+ const summary = entries.slice(0, 3).map((key) => `${key}: ${formatPropertyValue(props, key)}`);
202
+ if (entries.length > 3) summary.push(`... ${entries.length - 3} more`);
203
+ return ` {${summary.join(", ")}}`;
204
+ }
205
+ function formatPropertyValue(properties, key) {
206
+ try {
207
+ return formatValue(properties[key]);
208
+ } catch (error) {
209
+ return formatAccessError(error);
210
+ }
211
+ }
212
+ function formatAccessError(error) {
213
+ return `<error: ${error instanceof Error ? error.message : safeString(error)}>`;
214
+ }
215
+ function formatCount(count, noun) {
216
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
217
+ }
218
+ function formatValue(value) {
219
+ if (typeof value === "string") return JSON.stringify(value);
220
+ if (typeof value === "number" && !Number.isFinite(value)) return String(value);
221
+ if (typeof value === "bigint") return `${value}n`;
222
+ if (typeof value === "symbol") return String(value);
223
+ if (value instanceof RegExp) return String(value);
224
+ if (value instanceof Error) return `${value.name}: ${value.message}`;
225
+ if (value instanceof Map) return formatMap(value);
226
+ if (value instanceof Set) return formatSet(value);
227
+ try {
228
+ return safeJsonStringify(value) ?? safeString(value);
229
+ } catch {
230
+ return safeString(value);
231
+ }
232
+ }
233
+ function formatMap(value) {
234
+ const label = `Map(${value.size})`;
235
+ try {
236
+ const contents = formatMapContents(value);
237
+ return `${label} ${safeJsonStringify(contents) ?? safeString(contents)}`;
238
+ } catch {
239
+ return label;
240
+ }
241
+ }
242
+ function formatSet(value) {
243
+ const label = `Set(${value.size})`;
244
+ try {
245
+ const contents = Array.from(value);
246
+ return `${label} ${safeJsonStringify(contents) ?? safeString(contents)}`;
247
+ } catch {
248
+ return label;
249
+ }
250
+ }
251
+ function safeJsonStringify(value) {
252
+ const ancestors = [];
253
+ return JSON.stringify(value, function(_key, item) {
254
+ if (typeof item === "bigint") return `${item}n`;
255
+ if (item instanceof RegExp) return String(item);
256
+ if (item instanceof Error) return `${item.name}: ${item.message}`;
257
+ if (typeof item === "object" && item != null) {
258
+ while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop();
259
+ if (ancestors.includes(item)) return "[Circular]";
260
+ ancestors.push(item);
261
+ if (item instanceof Map) return formatMapContents(item);
262
+ if (item instanceof Set) return Array.from(item);
263
+ }
264
+ return item;
265
+ });
266
+ }
267
+ function formatMapContents(value) {
268
+ const entries = Array.from(value, ([key, entryValue]) => [safeString(key), entryValue]);
269
+ const keys = entries.map(([key]) => key);
270
+ const uniqueKeys = new Set(keys);
271
+ return uniqueKeys.size === keys.length ? Object.fromEntries(entries) : entries;
272
+ }
273
+ function safeString(value) {
274
+ try {
275
+ return String(value);
276
+ } catch {
277
+ return Object.prototype.toString.call(value);
278
+ }
279
+ }
280
+
281
+ //#endregion
282
+ exports.createLogRecorder = createLogRecorder;