@logbrew/sdk 0.1.38 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +30 -2810
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-exception.cjs +13 -8
  33. package/metric-validation.cjs +95 -0
  34. package/network-milestone.cjs +155 -0
  35. package/null-value.cjs +4 -0
  36. package/opentelemetry-bridge.cjs +330 -0
  37. package/opentelemetry-context.cjs +245 -0
  38. package/opentelemetry-exceptions.cjs +69 -0
  39. package/opentelemetry-keys.cjs +183 -0
  40. package/opentelemetry-span.cjs +286 -0
  41. package/opentelemetry-summary.cjs +213 -0
  42. package/opentelemetry-time.cjs +131 -0
  43. package/opentelemetry.cjs +99 -1068
  44. package/package.json +55 -1
  45. package/pino-destination.cjs +108 -0
  46. package/pino-record.cjs +237 -0
  47. package/product-action.cjs +163 -0
  48. package/provided-breadcrumbs.cjs +117 -0
  49. package/random-hex.cjs +29 -0
  50. package/react-native.js +1 -1
  51. package/recording-transport.cjs +68 -0
  52. package/release-artifacts-build.cjs +263 -84
  53. package/release-artifacts-common.js +347 -66
  54. package/release-artifacts-debug-id.js +178 -0
  55. package/release-artifacts-inspect.js +213 -0
  56. package/release-artifacts-issue-event.js +99 -0
  57. package/release-artifacts-manifest-artifact.js +107 -0
  58. package/release-artifacts-manifest.js +77 -0
  59. package/release-artifacts-mappings.js +265 -0
  60. package/release-artifacts-prepare.js +297 -0
  61. package/release-artifacts-source-context.js +210 -0
  62. package/release-artifacts-symbolication.js +272 -421
  63. package/release-artifacts-upload-files.js +122 -0
  64. package/release-artifacts-upload-request.js +246 -0
  65. package/release-artifacts-upload.js +155 -300
  66. package/release-artifacts-urls.js +49 -0
  67. package/release-artifacts.js +189 -662
  68. package/retry-delay.cjs +32 -0
  69. package/span-validation.cjs +191 -0
  70. package/stored-events.cjs +243 -0
  71. package/types/client.d.ts +67 -0
  72. package/types/delivery.d.ts +125 -0
  73. package/types/issues.d.ts +249 -0
  74. package/types/logging.d.ts +175 -0
  75. package/types/opentelemetry.d.ts +205 -0
  76. package/types/product.d.ts +128 -0
  77. package/types/recording-transport.d.ts +15 -0
  78. package/types/sdk-error.d.ts +9 -0
  79. package/types/telemetry.d.ts +171 -0
  80. package/types/trace-context.d.ts +110 -0
  81. package/types/transport-error.d.ts +10 -0
  82. package/utf8.cjs +37 -0
  83. package/validation.cjs +49 -1
  84. package/vite-release-artifacts-config.cjs +120 -0
  85. package/vite-release-artifacts.cjs +199 -99
  86. package/vite-release-artifacts.d.cts +65 -40
  87. package/vite-release-artifacts.d.ts +65 -40
  88. package/vite-release-artifacts.js +2 -2
  89. package/winston-info.cjs +205 -0
  90. package/winston.cjs +189 -289
package/client.cjs ADDED
@@ -0,0 +1,447 @@
1
+ const { AutomaticDelivery } = require("./automatic-delivery.cjs");
2
+ const { clientConfig, publicSettings, validateTransport } = require("./client-options.cjs");
3
+ const { deliveryHealthSnapshot, lifecycleOf } = require("./delivery-health.cjs");
4
+ const { propertyOf } = require("./error-details.cjs");
5
+ const { EVENT_ADMISSIONS, EventPipeline } = require("./event-pipeline.cjs");
6
+ const { validateAction, validateEnvironment, validateIssue, validateLog, validateRelease } = require("./event-validation.cjs");
7
+ const { MAX_ISSUE_BREADCRUMBS, validateIssueBreadcrumb } = require("./issue-diagnostics.cjs");
8
+ const { validateMetric } = require("./metric-validation.cjs");
9
+ const { providedBreadcrumbs, withIssueBreadcrumbs } = require("./provided-breadcrumbs.cjs");
10
+ const { SdkError } = require("./sdk-error.cjs");
11
+ const { validateSpan } = require("./span-validation.cjs");
12
+ const { requireSynchronousStoreResult } = require("./stored-events.cjs");
13
+ const { mergeTelemetryContexts } = require("./telemetry-context.cjs");
14
+ const { isCallable, requireNonEmpty, requireTimestamp } = require("./validation.cjs");
15
+
16
+ /**
17
+ * @import { Callable, DeepReadonly } from "./validation.cjs"
18
+ * @import { BatchTransport } from "./batch-sender.cjs"
19
+ * @import { ClientConfig, ClientOptions } from "./client-options.cjs"
20
+ * @import { EventAdmission, FlushResult } from "./event-pipeline.cjs"
21
+ * @import { EventAttributes, EventType } from "./event-validation.cjs"
22
+ * @import { IssueBreadcrumb, TelemetryContext } from "./index.cjs"
23
+ * @typedef {Readonly<Record<string, unknown>>} Attributes
24
+ * @typedef {Readonly<{ type: EventType, id: unknown, timestamp: unknown }>} EventHead
25
+ * @typedef {Readonly<{ controls: boolean, transport: BatchTransport }>} FlushPlan
26
+ */
27
+
28
+ const NO_EVENTS = 0;
29
+ const NO_OPERATIONS = 0;
30
+
31
+ class LogBrewClient {
32
+ /** @readonly */
33
+ static #issueCapture = propertyOf(LogBrewClient.prototype, "issue");
34
+
35
+ apiKey = "";
36
+
37
+ automaticDelivery = false;
38
+
39
+ /** @type {Callable | undefined} */
40
+ eventFilter;
41
+
42
+ /** @type {Callable | undefined} */
43
+ onEventDropped;
44
+
45
+ /** @type {TelemetryContext | undefined} */
46
+ context;
47
+
48
+ /** @type {Callable | undefined} */
49
+ contextProvider;
50
+
51
+ /** @type {Callable | undefined} */
52
+ breadcrumbProvider;
53
+
54
+ /** @type {BatchTransport | undefined} */
55
+ transport;
56
+
57
+ closing = false;
58
+
59
+ closed = false;
60
+
61
+ /** @type {IssueBreadcrumb[]} */
62
+ issueBreadcrumbs = [];
63
+
64
+ issueBreadcrumbsTruncated = false;
65
+
66
+ /**
67
+ * @readonly
68
+ * @type {InstanceType<typeof EventPipeline>}
69
+ */
70
+ #pipeline;
71
+
72
+ /**
73
+ * @readonly
74
+ * @type {InstanceType<typeof AutomaticDelivery>}
75
+ */
76
+ #delivery;
77
+
78
+ /**
79
+ * @param {ClientOptions} options
80
+ * @returns {LogBrewClient}
81
+ */
82
+ static create(options) {
83
+ return new LogBrewClient(clientConfig(options));
84
+ }
85
+
86
+ /**
87
+ * @param {ClientConfig} config
88
+ */
89
+ constructor(config) {
90
+ Object.assign(this, publicSettings(config));
91
+ this.#pipeline = new EventPipeline({
92
+ ...config,
93
+ apiKey: () => this.apiKey,
94
+ hooks: { eventFilter: () => this.eventFilter, onEventDropped: () => this.onEventDropped, receiver: this }
95
+ });
96
+ this.#delivery = new AutomaticDelivery({
97
+ canDeliver: () => this.automaticDelivery && !this.closed && !this.closing && this.#pipeline.queue.events.length > NO_EVENTS,
98
+ deliveryIntervalMs: config.deliveryIntervalMs,
99
+ deliveryQueueThreshold: config.deliveryQueueThreshold,
100
+ flush: async () => this.#pipeline.flushWithHealth(this.#automaticTransport()),
101
+ isOpen: () => !this.closed && !this.closing,
102
+ queueLength: () => this.#pipeline.queue.events.length,
103
+ serialize: async (operation) => this.#pipeline.runSerialized(operation)
104
+ });
105
+ this.#delivery.schedule();
106
+ }
107
+
108
+ /**
109
+ * @returns {number}
110
+ */
111
+ pendingEvents() {
112
+ return this.#pipeline.queue.events.length;
113
+ }
114
+
115
+ /**
116
+ * @returns {number}
117
+ */
118
+ pendingBytes() {
119
+ return this.#pipeline.queue.queuedEventBytes;
120
+ }
121
+
122
+ /**
123
+ * @returns {number}
124
+ */
125
+ droppedEvents() {
126
+ return this.#pipeline.ledger.droppedEventCount;
127
+ }
128
+
129
+ /**
130
+ * @returns {Readonly<Record<string, unknown>>}
131
+ */
132
+ deliveryHealth() {
133
+ return deliveryHealthSnapshot({
134
+ ...this.#pipeline.health(),
135
+ ...this.#delivery.health(),
136
+ automaticDelivery: this.automaticDelivery,
137
+ lifecycle: lifecycleOf(this.closed, this.closing)
138
+ });
139
+ }
140
+
141
+ /**
142
+ * @returns {string}
143
+ */
144
+ previewJson() {
145
+ return this.#pipeline.queue.previewJson();
146
+ }
147
+
148
+ /**
149
+ * @returns {number}
150
+ */
151
+ purgePendingEvents() {
152
+ this.#requireOpen();
153
+ if (this.#pipeline.pendingOperations > NO_OPERATIONS || this.#delivery.automaticFlushActive) {
154
+ throw new SdkError("persistence_error", "cannot purge while a delivery operation is active");
155
+ }
156
+ const purgedEvents = this.#pipeline.queue.purge();
157
+ this.#delivery.clearTimer();
158
+ this.#delivery.automaticFlushPending = false;
159
+ return purgedEvents;
160
+ }
161
+
162
+ /**
163
+ * @param {unknown} breadcrumb
164
+ * @param {unknown} [timestamp]
165
+ * @returns {void}
166
+ */
167
+ addBreadcrumb(breadcrumb, timestamp = new Date().toISOString()) {
168
+ this.#requireOpen();
169
+ const validated = validateIssueBreadcrumb(breadcrumb, timestamp);
170
+ if (this.issueBreadcrumbs.length === MAX_ISSUE_BREADCRUMBS) {
171
+ this.issueBreadcrumbs.shift();
172
+ this.issueBreadcrumbsTruncated = true;
173
+ }
174
+ this.issueBreadcrumbs.push(validated);
175
+ }
176
+
177
+ /**
178
+ * @returns {number}
179
+ */
180
+ clearBreadcrumbs() {
181
+ const cleared = this.issueBreadcrumbs.length;
182
+ this.issueBreadcrumbs.splice(NO_EVENTS, cleared);
183
+ this.issueBreadcrumbsTruncated = false;
184
+ return cleared;
185
+ }
186
+
187
+ /**
188
+ * @param {unknown} id
189
+ * @param {unknown} timestamp
190
+ * @param {Attributes} attributes
191
+ * @returns {void}
192
+ */
193
+ release(id, timestamp, attributes) {
194
+ this.#admit({ id, timestamp, type: "release" }, validateRelease(attributes));
195
+ }
196
+
197
+ /**
198
+ * @param {unknown} id
199
+ * @param {unknown} timestamp
200
+ * @param {Attributes} attributes
201
+ * @returns {void}
202
+ */
203
+ environment(id, timestamp, attributes) {
204
+ this.#admit({ id, timestamp, type: "environment" }, validateEnvironment(attributes));
205
+ }
206
+
207
+ /**
208
+ * @param {unknown} id
209
+ * @param {unknown} timestamp
210
+ * @param {Attributes} attributes
211
+ * @returns {void}
212
+ */
213
+ issue(id, timestamp, attributes) {
214
+ this.#pushIssue({ id, timestamp, type: "issue" }, attributes);
215
+ }
216
+
217
+ /**
218
+ * @param {unknown} id
219
+ * @param {unknown} timestamp
220
+ * @param {Attributes} attributes
221
+ * @returns {EventAdmission}
222
+ */
223
+ issueWithAdmission(id, timestamp, attributes) {
224
+ const issue = propertyOf(this, "issue");
225
+ if (issue !== LogBrewClient.#issueCapture) {
226
+ if (!isCallable(issue)) {
227
+ throw new TypeError("issue is not a function");
228
+ }
229
+ Reflect.apply(issue, this, [id, timestamp, attributes]);
230
+ return EVENT_ADMISSIONS.unavailable;
231
+ }
232
+ return this.#pushIssue({ id, timestamp, type: "issue" }, attributes);
233
+ }
234
+
235
+ /**
236
+ * @param {unknown} id
237
+ * @param {unknown} timestamp
238
+ * @param {Attributes} attributes
239
+ * @returns {void}
240
+ */
241
+ log(id, timestamp, attributes) {
242
+ this.#admit({ id, timestamp, type: "log" }, validateLog(attributes));
243
+ }
244
+
245
+ /**
246
+ * @param {unknown} id
247
+ * @param {unknown} timestamp
248
+ * @param {Attributes} attributes
249
+ * @returns {void}
250
+ */
251
+ span(id, timestamp, attributes) {
252
+ this.#admit({ id, timestamp, type: "span" }, validateSpan(attributes));
253
+ }
254
+
255
+ /**
256
+ * @param {unknown} id
257
+ * @param {unknown} timestamp
258
+ * @param {Attributes} attributes
259
+ * @returns {void}
260
+ */
261
+ action(id, timestamp, attributes) {
262
+ this.#admit({ id, timestamp, type: "action" }, validateAction(attributes));
263
+ }
264
+
265
+ /**
266
+ * @param {unknown} id
267
+ * @param {unknown} timestamp
268
+ * @param {Attributes} attributes
269
+ * @returns {void}
270
+ */
271
+ metric(id, timestamp, attributes) {
272
+ this.#admit({ id, timestamp, type: "metric" }, validateMetric(attributes));
273
+ }
274
+
275
+ /**
276
+ * @param {unknown} [transport]
277
+ * @returns {Promise<FlushResult>}
278
+ */
279
+ async flush(transport) {
280
+ const plan = this.#planFlush(transport);
281
+ return this.#pipeline.runSerialized(async () => {
282
+ this.#delivery.clearTimer();
283
+ try {
284
+ const response = await this.#pipeline.flushWithHealth(plan.transport);
285
+ this.#recordControlledSuccess(plan.controls);
286
+ return response;
287
+ } catch (error) {
288
+ if (plan.controls) {
289
+ this.#delivery.recordFailure(error);
290
+ }
291
+ throw error;
292
+ } finally {
293
+ if (this.automaticDelivery && !this.closed && !this.closing) {
294
+ this.#delivery.resume();
295
+ }
296
+ }
297
+ });
298
+ }
299
+
300
+ /**
301
+ * @param {unknown} [transport]
302
+ * @returns {Promise<FlushResult>}
303
+ */
304
+ async shutdown(transport) {
305
+ const plan = this.#planFlush(transport);
306
+ this.#delivery.automaticFlushPending = false;
307
+ this.closing = true;
308
+ try {
309
+ const response = await this.#pipeline.runSerialized(this.#pipeline.flushWithHealth.bind(this.#pipeline, plan.transport));
310
+ this.#finishShutdown(plan.controls);
311
+ return response;
312
+ } catch (error) {
313
+ this.#abortShutdown(error, plan.controls);
314
+ throw error;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * @param {boolean} controls
320
+ * @returns {void}
321
+ */
322
+ #recordControlledSuccess(controls) {
323
+ if (controls) {
324
+ this.#delivery.recordSuccess();
325
+ }
326
+ }
327
+
328
+ /**
329
+ * @param {boolean} controls
330
+ * @returns {void}
331
+ */
332
+ #finishShutdown(controls) {
333
+ this.#recordControlledSuccess(controls);
334
+ this.#pipeline.ledger.save(true);
335
+ const { eventStore } = this.#pipeline.queue;
336
+ try {
337
+ if (eventStore !== undefined) {
338
+ requireSynchronousStoreResult("close", eventStore.close());
339
+ }
340
+ } finally {
341
+ this.closed = true;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * @param {unknown} reason
347
+ * @param {boolean} controls
348
+ * @returns {void}
349
+ */
350
+ #abortShutdown(reason, controls) {
351
+ if (this.closed) {
352
+ return;
353
+ }
354
+ this.closing = false;
355
+ if (controls) {
356
+ this.#delivery.recordFailure(reason);
357
+ }
358
+ this.#delivery.resume();
359
+ }
360
+
361
+ /**
362
+ * @param {unknown} transport
363
+ * @returns {FlushPlan}
364
+ */
365
+ #planFlush(transport) {
366
+ this.#requireOpen();
367
+ const validated = validateTransport(transport ?? this.transport);
368
+ if (validated === undefined) {
369
+ throw new SdkError("validation_error", "flush and shutdown require transport");
370
+ }
371
+ const controls = this.automaticDelivery && validated === this.transport;
372
+ this.#delivery.clearTimer();
373
+ return { controls, transport: validated };
374
+ }
375
+
376
+ /**
377
+ * @returns {BatchTransport}
378
+ */
379
+ #automaticTransport() {
380
+ if (this.transport === undefined) {
381
+ throw new SdkError("validation_error", "flush and shutdown require transport");
382
+ }
383
+ return this.transport;
384
+ }
385
+
386
+ /**
387
+ * @returns {void}
388
+ */
389
+ #requireOpen() {
390
+ if (this.closed) {
391
+ throw new SdkError("shutdown_error", "client is already shut down");
392
+ }
393
+ if (this.closing) {
394
+ throw new SdkError("shutdown_error", "client is shutting down");
395
+ }
396
+ }
397
+
398
+ /**
399
+ * @param {EventHead} head
400
+ * @param {Attributes} attributes
401
+ * @returns {EventAdmission}
402
+ */
403
+ #pushIssue(head, attributes) {
404
+ if (attributes["breadcrumbs"] !== undefined) {
405
+ return this.#admit(head, validateIssue(attributes));
406
+ }
407
+ const provided = providedBreadcrumbs(this.breadcrumbProvider, this);
408
+ const local = { breadcrumbs: this.issueBreadcrumbs, truncated: this.issueBreadcrumbsTruncated };
409
+ return this.#admit(head, validateIssue(withIssueBreadcrumbs(attributes, local, provided)));
410
+ }
411
+
412
+ /**
413
+ * @returns {TelemetryContext | undefined}
414
+ */
415
+ #providedContext() {
416
+ const provider = this.contextProvider;
417
+ return mergeTelemetryContexts(this.context, isCallable(provider) ? Reflect.apply(provider, this, []) : undefined);
418
+ }
419
+
420
+ /**
421
+ * @param {EventHead} head
422
+ * @param {DeepReadonly<EventAttributes>} attributes
423
+ * @returns {EventAdmission}
424
+ */
425
+ #admit(head, attributes) {
426
+ this.#requireOpen();
427
+ const id = requireNonEmpty("event id", head.id);
428
+ const timestamp = requireTimestamp(head.timestamp);
429
+ const context = mergeTelemetryContexts(this.#providedContext(), attributes.context);
430
+ const event = {
431
+ type: head.type,
432
+
433
+ id,
434
+
435
+ timestamp,
436
+
437
+ attributes: context === undefined ? attributes : { ...attributes, context }
438
+ };
439
+ const admission = this.#pipeline.admit(event);
440
+ if (admission === EVENT_ADMISSIONS.admitted) {
441
+ this.#delivery.schedule();
442
+ }
443
+ return admission;
444
+ }
445
+ }
446
+
447
+ module.exports = { LogBrewClient };
@@ -0,0 +1,151 @@
1
+ const { compactMetadata } = require("./log-context.cjs");
2
+ const { SdkError } = require("./sdk-error.cjs");
3
+ const { isTruthy } = require("./validation.cjs");
4
+
5
+ /**
6
+ * @import { ConsoleMethodName, Severity } from "./index.cjs"
7
+ * @typedef {Readonly<{ includeErrorStack?: unknown, logger?: unknown, metadata?: unknown }>} ConsoleArgumentsOptions
8
+ * @typedef {{ message: string, level: Severity, logger?: unknown, metadata: Record<string, unknown> }} ConsoleLogAttributes
9
+ */
10
+
11
+ const NO_ARGUMENTS = 0;
12
+ /** @type {ReadonlySet<ConsoleMethodName>} */
13
+ const CONSOLE_METHODS = new Set(["debug", "info", "log", "warn", "error"]);
14
+
15
+ /**
16
+ * @param {unknown} method
17
+ * @returns {Severity}
18
+ */
19
+ function logbrewLevelFromConsoleMethod(method) {
20
+ switch (method) {
21
+ case "debug": {
22
+ return "info";
23
+ }
24
+ case "warn": {
25
+ return "warning";
26
+ }
27
+ case "error": {
28
+ return "error";
29
+ }
30
+ case "info":
31
+ case "log": {
32
+ return "info";
33
+ }
34
+ default: {
35
+ throw new SdkError("validation_error", `console method must be one of: ${[...CONSOLE_METHODS].join(", ")}`);
36
+ }
37
+ }
38
+ }
39
+
40
+ /** @type {(value: unknown) => string} */
41
+ const stringFromValue = String;
42
+
43
+ /**
44
+ * @param {unknown} value
45
+ * @returns {string | undefined}
46
+ */
47
+ function jsonText(value) {
48
+ return JSON.stringify(value);
49
+ }
50
+
51
+ /**
52
+ * @param {Readonly<Error>} error
53
+ * @param {boolean} includeErrorStack
54
+ * @returns {unknown}
55
+ */
56
+ function formatConsoleError(error, includeErrorStack) {
57
+ if (includeErrorStack && isTruthy(error.stack)) {
58
+ return error.stack;
59
+ }
60
+ return isTruthy(error.message) ? `${error.name}: ${error.message}` : error.name;
61
+ }
62
+
63
+ /**
64
+ * @param {unknown} value
65
+ * @returns {string}
66
+ */
67
+ function formatConsoleObject(value) {
68
+ try {
69
+ return jsonText(value) ?? stringFromValue(value);
70
+ } catch {
71
+ return Object.prototype.toString.call(value);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @param {unknown} value
77
+ * @param {boolean} includeErrorStack
78
+ * @returns {unknown}
79
+ */
80
+ function formatConsoleArgument(value, includeErrorStack) {
81
+ if (value instanceof Error) {
82
+ return formatConsoleError(value, includeErrorStack);
83
+ }
84
+ if (typeof value === "string") {
85
+ return value;
86
+ }
87
+ if (value === null || value === undefined || typeof value !== "object" && typeof value !== "function") {
88
+ return String(value);
89
+ }
90
+ return formatConsoleObject(value);
91
+ }
92
+
93
+ /**
94
+ * @param {unknown} args
95
+ * @param {boolean} includeErrorStack
96
+ * @returns {string}
97
+ */
98
+ function consoleMessage(args, includeErrorStack) {
99
+ /** @type {readonly unknown[]} */
100
+ const values = Array.isArray(args) ? args : [];
101
+ const message = values.map((value) => formatConsoleArgument(value, includeErrorStack)).join(" ");
102
+ return message.trim() === "" ? "console event" : message;
103
+ }
104
+
105
+ /**
106
+ * @param {Readonly<Record<string, unknown>>} metadata
107
+ * @param {readonly unknown[]} args
108
+ * @param {boolean} includeErrorStack
109
+ * @returns {Record<string, unknown>}
110
+ */
111
+ function withConsoleErrorMetadata(metadata, args, includeErrorStack) {
112
+ const error = args.find((value) => value instanceof Error);
113
+ if (!(error instanceof Error)) {
114
+ return metadata;
115
+ }
116
+ return {
117
+ ...metadata,
118
+ errorName: isTruthy(error.name) ? error.name : "Error",
119
+ ...(isTruthy(error.message) ? { errorMessage: error.message } : {}),
120
+ ...(includeErrorStack && isTruthy(error.stack) ? { errorStack: error.stack } : {})
121
+ };
122
+ }
123
+
124
+ /**
125
+ * @param {unknown} method
126
+ * @param {unknown} args
127
+ * @param {ConsoleArgumentsOptions} [options]
128
+ * @returns {ConsoleLogAttributes}
129
+ */
130
+ function logAttributesFromConsoleArgs(method, args, options = {}) {
131
+ const level = logbrewLevelFromConsoleMethod(method);
132
+ const includeErrorStack = options.includeErrorStack === true;
133
+ const message = consoleMessage(args, includeErrorStack);
134
+ /** @type {readonly unknown[]} */
135
+ const values = Array.isArray(args) ? args : [];
136
+ const metadata = withConsoleErrorMetadata({
137
+ ...compactMetadata(options.metadata),
138
+ consoleMethod: method,
139
+
140
+ argumentCount: Array.isArray(args) ? values.length : NO_ARGUMENTS
141
+ }, values, includeErrorStack);
142
+ return {
143
+ message,
144
+
145
+ level,
146
+ ...(isTruthy(options.logger) ? { logger: options.logger } : {}),
147
+ metadata
148
+ };
149
+ }
150
+
151
+ module.exports = { CONSOLE_METHODS, logAttributesFromConsoleArgs, logbrewLevelFromConsoleMethod };