@logbrew/sdk 0.1.3 → 0.1.4
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 +336 -2
- package/examples/real-user-smoke.cjs +15 -1
- package/examples/real-user-smoke.mjs +15 -1
- package/index.cjs +1358 -28
- package/index.d.cts +461 -6
- package/index.d.ts +461 -6
- package/index.js +13 -0
- package/issue-stack.cjs +149 -0
- package/opentelemetry.cjs +1080 -0
- package/package.json +28 -1
- package/release-artifacts-build.cjs +227 -0
- package/release-artifacts-common.js +82 -0
- package/release-artifacts-symbolication.js +550 -0
- package/release-artifacts-upload.js +380 -0
- package/release-artifacts.js +766 -0
- package/support-ticket.cjs +175 -0
- package/trace-context.cjs +217 -0
- package/vite-release-artifacts.cjs +152 -0
- package/vite-release-artifacts.d.cts +44 -0
- package/vite-release-artifacts.d.ts +44 -0
- package/vite-release-artifacts.js +6 -0
package/index.cjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
const { buildCreateSupportTicketDraft } = require("./support-ticket.cjs");
|
|
2
|
+
const { buildIssueStackHelpers } = require("./issue-stack.cjs");
|
|
3
|
+
const { buildOpenTelemetryHelpers } = require("./opentelemetry.cjs");
|
|
4
|
+
const { buildTraceContextHelpers } = require("./trace-context.cjs");
|
|
5
|
+
|
|
1
6
|
const SEVERITY_ALIASES = new Map([
|
|
2
7
|
["trace", "info"],
|
|
3
8
|
["debug", "info"],
|
|
@@ -26,15 +31,54 @@ const WINSTON_RESERVED_FIELDS = new Set(["level", "message", "timestamp", "time"
|
|
|
26
31
|
const TRACEPARENT_PATTERN = /^([0-9a-fA-F]{2})-([0-9a-fA-F]{32})-([0-9a-fA-F]{16})-([0-9a-fA-F]{2})$/u;
|
|
27
32
|
const ZERO_TRACE_ID = "00000000000000000000000000000000";
|
|
28
33
|
const ZERO_SPAN_ID = "0000000000000000";
|
|
29
|
-
|
|
34
|
+
const DEFAULT_MAX_QUEUE_SIZE = 1000;
|
|
35
|
+
const DEFAULT_MAX_QUEUE_BYTES = 4 * 1024 * 1024;
|
|
36
|
+
const DEFAULT_MAX_BATCH_EVENTS = 100;
|
|
37
|
+
const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
|
|
38
|
+
const DEFAULT_DELIVERY_INTERVAL_MS = 5000;
|
|
39
|
+
const MAX_DELIVERY_INTERVAL_MS = 60 * 1000;
|
|
40
|
+
const DEFAULT_DELIVERY_QUEUE_THRESHOLD = 50;
|
|
41
|
+
const DELIVERY_HEALTH_SCHEMA_VERSION = 1;
|
|
42
|
+
const EVENT_QUEUE_FACTORY = Symbol.for("@logbrew/sdk.eventQueueFactory");
|
|
43
|
+
const MAX_ERROR_CAUSES = 5;
|
|
44
|
+
const BUILTIN_ERROR_NAMES = new Set([
|
|
45
|
+
"AggregateError",
|
|
46
|
+
"Error",
|
|
47
|
+
"EvalError",
|
|
48
|
+
"RangeError",
|
|
49
|
+
"ReferenceError",
|
|
50
|
+
"SyntaxError",
|
|
51
|
+
"TypeError",
|
|
52
|
+
"URIError"
|
|
53
|
+
]);
|
|
54
|
+
const MAX_SPAN_EVENTS = 8;
|
|
55
|
+
const MAX_SPAN_LINKS = 8;
|
|
56
|
+
const EVENT_VALIDATORS = new Map([
|
|
57
|
+
["release", validateRelease],
|
|
58
|
+
["environment", validateEnvironment],
|
|
59
|
+
["issue", validateIssue],
|
|
60
|
+
["log", validateLog],
|
|
61
|
+
["span", validateSpan],
|
|
62
|
+
["action", validateAction],
|
|
63
|
+
["metric", validateMetric]
|
|
64
|
+
]);
|
|
30
65
|
class SdkError extends Error {
|
|
31
|
-
constructor(code, message) {
|
|
66
|
+
constructor(code, message, details = {}) {
|
|
32
67
|
super(message);
|
|
33
68
|
this.name = "SdkError";
|
|
34
69
|
this.code = code;
|
|
70
|
+
const retryAfterMs = retryAfterMsOrUndefined(details?.retryAfterMs);
|
|
71
|
+
if (retryAfterMs !== undefined) {
|
|
72
|
+
this.retryAfterMs = retryAfterMs;
|
|
73
|
+
}
|
|
74
|
+
if (details?.retryable === true) {
|
|
75
|
+
this.retryable = true;
|
|
76
|
+
}
|
|
35
77
|
}
|
|
36
78
|
}
|
|
37
79
|
|
|
80
|
+
const { javascriptStackFrames, validateIssueStackFrames } = buildIssueStackHelpers({ SdkError });
|
|
81
|
+
|
|
38
82
|
class TransportError extends Error {
|
|
39
83
|
constructor(code, message, retryable = false) {
|
|
40
84
|
super(message);
|
|
@@ -74,48 +118,296 @@ class RecordingTransport {
|
|
|
74
118
|
throw next;
|
|
75
119
|
}
|
|
76
120
|
|
|
77
|
-
|
|
121
|
+
const retryAfterMs = retryAfterMsOrUndefined(next.retryAfterMs);
|
|
122
|
+
return retryAfterMs === undefined
|
|
123
|
+
? { statusCode: next.statusCode, attempts: 1 }
|
|
124
|
+
: { statusCode: next.statusCode, attempts: 1, retryAfterMs };
|
|
78
125
|
}
|
|
79
126
|
}
|
|
80
127
|
|
|
81
128
|
class LogBrewClient {
|
|
82
|
-
static create({
|
|
129
|
+
static create({
|
|
130
|
+
apiKey,
|
|
131
|
+
sdkName,
|
|
132
|
+
sdkVersion,
|
|
133
|
+
maxRetries = 2,
|
|
134
|
+
eventFilter,
|
|
135
|
+
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
|
|
136
|
+
maxQueueBytes = DEFAULT_MAX_QUEUE_BYTES,
|
|
137
|
+
maxBatchEvents = DEFAULT_MAX_BATCH_EVENTS,
|
|
138
|
+
maxBatchBytes = DEFAULT_MAX_BATCH_BYTES,
|
|
139
|
+
onEventDropped,
|
|
140
|
+
eventStore,
|
|
141
|
+
[EVENT_QUEUE_FACTORY]: eventQueueFactory,
|
|
142
|
+
transport,
|
|
143
|
+
automaticDelivery = transport !== undefined,
|
|
144
|
+
deliveryIntervalMs = DEFAULT_DELIVERY_INTERVAL_MS,
|
|
145
|
+
deliveryQueueThreshold = Math.min(DEFAULT_DELIVERY_QUEUE_THRESHOLD, maxQueueSize)
|
|
146
|
+
}) {
|
|
83
147
|
requireNonEmpty("apiKey", apiKey);
|
|
84
148
|
requireNonEmpty("sdkName", sdkName);
|
|
85
149
|
requireNonEmpty("sdkVersion", sdkVersion);
|
|
150
|
+
requireNonNegativeInteger("maxRetries", maxRetries);
|
|
86
151
|
if (eventFilter !== undefined && typeof eventFilter !== "function") {
|
|
87
152
|
throw new SdkError("validation_error", "eventFilter must be a function");
|
|
88
153
|
}
|
|
154
|
+
requirePositiveInteger("maxQueueSize", maxQueueSize);
|
|
155
|
+
requirePositiveInteger("maxQueueBytes", maxQueueBytes);
|
|
156
|
+
requirePositiveInteger("maxBatchEvents", maxBatchEvents);
|
|
157
|
+
requirePositiveInteger("maxBatchBytes", maxBatchBytes);
|
|
158
|
+
if (onEventDropped !== undefined && typeof onEventDropped !== "function") {
|
|
159
|
+
throw new SdkError("validation_error", "onEventDropped must be a function");
|
|
160
|
+
}
|
|
161
|
+
validateEventStore(eventStore);
|
|
162
|
+
if (eventQueueFactory !== undefined && typeof eventQueueFactory !== "function") {
|
|
163
|
+
throw new SdkError("validation_error", "event queue factory must be a function");
|
|
164
|
+
}
|
|
165
|
+
if (eventStore !== undefined && eventQueueFactory !== undefined) {
|
|
166
|
+
throw new SdkError("validation_error", "eventStore and event queue factory are mutually exclusive");
|
|
167
|
+
}
|
|
168
|
+
validateTransport(transport);
|
|
169
|
+
if (typeof automaticDelivery !== "boolean") {
|
|
170
|
+
throw new SdkError("validation_error", "automaticDelivery must be a boolean");
|
|
171
|
+
}
|
|
172
|
+
if (automaticDelivery && transport === undefined) {
|
|
173
|
+
throw new SdkError("validation_error", "automaticDelivery requires transport");
|
|
174
|
+
}
|
|
175
|
+
requirePositiveInteger("deliveryIntervalMs", deliveryIntervalMs);
|
|
176
|
+
if (deliveryIntervalMs > MAX_DELIVERY_INTERVAL_MS) {
|
|
177
|
+
throw new SdkError("validation_error", `deliveryIntervalMs must be at most ${MAX_DELIVERY_INTERVAL_MS}`);
|
|
178
|
+
}
|
|
179
|
+
requirePositiveInteger("deliveryQueueThreshold", deliveryQueueThreshold);
|
|
180
|
+
if (deliveryQueueThreshold > maxQueueSize) {
|
|
181
|
+
throw new SdkError("validation_error", "deliveryQueueThreshold must not exceed maxQueueSize");
|
|
182
|
+
}
|
|
89
183
|
|
|
90
184
|
return new LogBrewClient({
|
|
91
185
|
apiKey,
|
|
186
|
+
automaticDelivery,
|
|
187
|
+
deliveryIntervalMs,
|
|
188
|
+
deliveryQueueThreshold,
|
|
189
|
+
eventStore,
|
|
190
|
+
eventQueueFactory,
|
|
92
191
|
eventFilter,
|
|
192
|
+
maxBatchBytes,
|
|
193
|
+
maxBatchEvents,
|
|
194
|
+
maxQueueBytes,
|
|
195
|
+
maxQueueSize,
|
|
196
|
+
onEventDropped,
|
|
93
197
|
sdk: {
|
|
94
198
|
name: sdkName,
|
|
95
199
|
language: "javascript",
|
|
96
200
|
version: sdkVersion
|
|
97
201
|
},
|
|
98
|
-
maxRetries
|
|
202
|
+
maxRetries,
|
|
203
|
+
transport
|
|
99
204
|
});
|
|
100
205
|
}
|
|
101
206
|
|
|
102
|
-
constructor({
|
|
207
|
+
constructor({
|
|
208
|
+
apiKey,
|
|
209
|
+
sdk,
|
|
210
|
+
maxRetries,
|
|
211
|
+
eventFilter,
|
|
212
|
+
maxBatchBytes,
|
|
213
|
+
maxBatchEvents,
|
|
214
|
+
maxQueueBytes,
|
|
215
|
+
maxQueueSize,
|
|
216
|
+
onEventDropped,
|
|
217
|
+
eventStore,
|
|
218
|
+
eventQueueFactory,
|
|
219
|
+
transport,
|
|
220
|
+
automaticDelivery,
|
|
221
|
+
deliveryIntervalMs,
|
|
222
|
+
deliveryQueueThreshold
|
|
223
|
+
}) {
|
|
103
224
|
this.apiKey = apiKey;
|
|
225
|
+
this.automaticDelivery = automaticDelivery;
|
|
226
|
+
this.deliveryIntervalMs = deliveryIntervalMs;
|
|
227
|
+
this.deliveryQueueThreshold = deliveryQueueThreshold;
|
|
104
228
|
this.eventFilter = eventFilter;
|
|
229
|
+
this.maxBatchBytes = maxBatchBytes;
|
|
230
|
+
this.maxBatchEvents = maxBatchEvents;
|
|
231
|
+
this.maxQueueBytes = maxQueueBytes;
|
|
232
|
+
this.maxQueueSize = maxQueueSize;
|
|
233
|
+
this.onEventDropped = onEventDropped;
|
|
234
|
+
this.transport = transport;
|
|
105
235
|
this.sdk = sdk;
|
|
106
236
|
this.maxRetries = maxRetries;
|
|
107
|
-
this.
|
|
237
|
+
this.batchPrefix = `{"sdk":${JSON.stringify(this.sdk)},"events":[`;
|
|
238
|
+
this.batchPrefixBytes = utf8ByteLength(this.batchPrefix);
|
|
239
|
+
this.batchSuffix = "]}";
|
|
240
|
+
this.batchSuffixBytes = utf8ByteLength(this.batchSuffix);
|
|
241
|
+
const ownsEventStore = eventQueueFactory !== undefined;
|
|
242
|
+
const resolvedEventStore = ownsEventStore
|
|
243
|
+
? createEventStoreFromQueueFactory(eventQueueFactory, {
|
|
244
|
+
batchPrefixBytes: this.batchPrefixBytes,
|
|
245
|
+
batchSuffixBytes: this.batchSuffixBytes,
|
|
246
|
+
maxBatchBytes,
|
|
247
|
+
maxQueueBytes,
|
|
248
|
+
maxQueueSize
|
|
249
|
+
})
|
|
250
|
+
: eventStore;
|
|
251
|
+
this.eventStore = resolvedEventStore;
|
|
252
|
+
let recovered;
|
|
253
|
+
try {
|
|
254
|
+
recovered = loadStoredEvents({
|
|
255
|
+
batchPrefixBytes: this.batchPrefixBytes,
|
|
256
|
+
batchSuffixBytes: this.batchSuffixBytes,
|
|
257
|
+
eventStore: resolvedEventStore,
|
|
258
|
+
maxBatchBytes,
|
|
259
|
+
maxQueueBytes,
|
|
260
|
+
maxQueueSize
|
|
261
|
+
});
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (ownsEventStore) {
|
|
264
|
+
try {
|
|
265
|
+
requireSynchronousStoreResult("close", resolvedEventStore.close());
|
|
266
|
+
} catch {
|
|
267
|
+
// Preserve the queue construction or recovery failure.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
this.events = recovered.events;
|
|
273
|
+
this.serializedEvents = recovered.serializedEvents;
|
|
274
|
+
this.serializedEventBytes = recovered.serializedEventBytes;
|
|
275
|
+
this.queuedEventBytes = recovered.queuedEventBytes;
|
|
276
|
+
this.operationTail = Promise.resolve();
|
|
277
|
+
this.pendingOperations = 0;
|
|
278
|
+
this.closing = false;
|
|
108
279
|
this.closed = false;
|
|
280
|
+
this.droppedEventCount = 0;
|
|
281
|
+
this.droppedEventsByReason = {
|
|
282
|
+
event_too_large: 0,
|
|
283
|
+
queue_bytes_overflow: 0,
|
|
284
|
+
queue_overflow: 0
|
|
285
|
+
};
|
|
286
|
+
this.lastDropReason = "none";
|
|
287
|
+
this.storage = resolvedEventStore ? "persistent" : "memory";
|
|
288
|
+
this.hydratedEventCount = recovered.events.length;
|
|
289
|
+
this.hydratedEventBytes = recovered.queuedEventBytes;
|
|
290
|
+
this.deliveryTimer = undefined;
|
|
291
|
+
this.automaticFlushActive = false;
|
|
292
|
+
this.automaticFlushPending = false;
|
|
293
|
+
this.deliveryInFlight = false;
|
|
294
|
+
this.lastDeliveryOutcome = "idle";
|
|
295
|
+
this.automaticPauseReason = "none";
|
|
296
|
+
this.consecutiveDeliveryFailures = 0;
|
|
297
|
+
this.retryDelayMs = 0;
|
|
298
|
+
this.successfulFlushCount = 0;
|
|
299
|
+
this.failedFlushCount = 0;
|
|
300
|
+
this.deliveryAttemptCount = 0;
|
|
301
|
+
this.acceptedBatchCount = 0;
|
|
302
|
+
this.acceptedEventCount = 0;
|
|
303
|
+
this.lastStatusClass = "none";
|
|
304
|
+
this.lastAttemptAtUnixMs = 0;
|
|
305
|
+
this.lastAcceptedAtUnixMs = 0;
|
|
306
|
+
this.lastDroppedAtUnixMs = 0;
|
|
307
|
+
this.failedBatch = undefined;
|
|
308
|
+
this.#scheduleAutomaticDelivery();
|
|
109
309
|
}
|
|
110
310
|
|
|
111
311
|
pendingEvents() {
|
|
112
312
|
return this.events.length;
|
|
113
313
|
}
|
|
114
314
|
|
|
315
|
+
pendingBytes() {
|
|
316
|
+
return this.queuedEventBytes;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
droppedEvents() {
|
|
320
|
+
return this.droppedEventCount;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
deliveryHealth() {
|
|
324
|
+
const droppedByReason = Object.freeze({ ...this.droppedEventsByReason });
|
|
325
|
+
return Object.freeze({
|
|
326
|
+
schemaVersion: DELIVERY_HEALTH_SCHEMA_VERSION,
|
|
327
|
+
automaticDelivery: this.automaticDelivery,
|
|
328
|
+
lifecycle: this.closed ? "closed" : this.closing ? "shutting_down" : "active",
|
|
329
|
+
deliveryState: this.#deliveryState(),
|
|
330
|
+
storage: this.storage,
|
|
331
|
+
queueEvents: this.events.length,
|
|
332
|
+
queueBytes: this.queuedEventBytes,
|
|
333
|
+
hydratedEvents: this.hydratedEventCount,
|
|
334
|
+
hydratedBytes: this.hydratedEventBytes,
|
|
335
|
+
droppedEvents: this.droppedEventCount,
|
|
336
|
+
droppedByReason,
|
|
337
|
+
lastDropReason: this.lastDropReason,
|
|
338
|
+
scheduled: this.deliveryTimer !== undefined,
|
|
339
|
+
inFlight: this.deliveryInFlight,
|
|
340
|
+
coalesced: this.automaticFlushPending,
|
|
341
|
+
pendingOperations: this.pendingOperations,
|
|
342
|
+
lastOutcome: this.lastDeliveryOutcome,
|
|
343
|
+
lastStatusClass: this.lastStatusClass,
|
|
344
|
+
pausedReason: this.automaticPauseReason,
|
|
345
|
+
consecutiveFailures: this.consecutiveDeliveryFailures,
|
|
346
|
+
retryDelayMs: this.retryDelayMs,
|
|
347
|
+
flushes: this.successfulFlushCount,
|
|
348
|
+
failures: this.failedFlushCount,
|
|
349
|
+
attempts: this.deliveryAttemptCount,
|
|
350
|
+
batches: this.acceptedBatchCount,
|
|
351
|
+
acceptedEvents: this.acceptedEventCount,
|
|
352
|
+
lastAttemptAtUnixMs: this.lastAttemptAtUnixMs,
|
|
353
|
+
lastAcceptedAtUnixMs: this.lastAcceptedAtUnixMs,
|
|
354
|
+
lastDroppedAtUnixMs: this.lastDroppedAtUnixMs
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
#deliveryState() {
|
|
359
|
+
if (this.deliveryInFlight) {
|
|
360
|
+
return "in_flight";
|
|
361
|
+
}
|
|
362
|
+
if (this.retryDelayMs > 0) {
|
|
363
|
+
return "retrying";
|
|
364
|
+
}
|
|
365
|
+
if (this.automaticPauseReason !== "none") {
|
|
366
|
+
return "paused";
|
|
367
|
+
}
|
|
368
|
+
if (this.events.length > 0) {
|
|
369
|
+
return this.deliveryTimer !== undefined ? "scheduled" : "queued";
|
|
370
|
+
}
|
|
371
|
+
if (this.lastDeliveryOutcome === "accepted") {
|
|
372
|
+
return "accepted";
|
|
373
|
+
}
|
|
374
|
+
if (this.lastDeliveryOutcome === "failed") {
|
|
375
|
+
return "failed";
|
|
376
|
+
}
|
|
377
|
+
if (this.droppedEventCount > 0) {
|
|
378
|
+
return "dropped";
|
|
379
|
+
}
|
|
380
|
+
return "idle";
|
|
381
|
+
}
|
|
382
|
+
|
|
115
383
|
previewJson() {
|
|
116
384
|
return JSON.stringify({ sdk: this.sdk, events: this.events }, null, 2);
|
|
117
385
|
}
|
|
118
386
|
|
|
387
|
+
purgePendingEvents() {
|
|
388
|
+
if (this.closed) {
|
|
389
|
+
throw new SdkError("shutdown_error", "client is already shut down");
|
|
390
|
+
}
|
|
391
|
+
if (this.closing) {
|
|
392
|
+
throw new SdkError("shutdown_error", "client is shutting down");
|
|
393
|
+
}
|
|
394
|
+
if (this.pendingOperations > 0 || this.automaticFlushActive) {
|
|
395
|
+
throw new SdkError("persistence_error", "cannot purge while a delivery operation is active");
|
|
396
|
+
}
|
|
397
|
+
const purgedEvents = this.events.length;
|
|
398
|
+
if (this.eventStore) {
|
|
399
|
+
requireSynchronousStoreResult("purge", this.eventStore.purge());
|
|
400
|
+
}
|
|
401
|
+
this.#clearDeliveryTimer();
|
|
402
|
+
this.automaticFlushPending = false;
|
|
403
|
+
this.failedBatch = undefined;
|
|
404
|
+
this.events.splice(0, this.events.length);
|
|
405
|
+
this.serializedEvents.splice(0, this.serializedEvents.length);
|
|
406
|
+
this.serializedEventBytes.splice(0, this.serializedEventBytes.length);
|
|
407
|
+
this.queuedEventBytes = 0;
|
|
408
|
+
return purgedEvents;
|
|
409
|
+
}
|
|
410
|
+
|
|
119
411
|
release(id, timestamp, attributes) {
|
|
120
412
|
this.#pushEvent("release", id, timestamp, validateRelease(attributes));
|
|
121
413
|
}
|
|
@@ -148,64 +440,420 @@ class LogBrewClient {
|
|
|
148
440
|
if (this.closed) {
|
|
149
441
|
throw new SdkError("shutdown_error", "client is already shut down");
|
|
150
442
|
}
|
|
151
|
-
|
|
443
|
+
if (this.closing) {
|
|
444
|
+
throw new SdkError("shutdown_error", "client is shutting down");
|
|
445
|
+
}
|
|
446
|
+
const resolvedTransport = this.#resolveTransport(transport);
|
|
447
|
+
const controlsAutomaticDelivery = this.automaticDelivery && resolvedTransport === this.transport;
|
|
448
|
+
this.#clearDeliveryTimer();
|
|
449
|
+
return this.#runSerialized(async () => {
|
|
450
|
+
this.#clearDeliveryTimer();
|
|
451
|
+
try {
|
|
452
|
+
const response = await this.#flushWithHealth(resolvedTransport);
|
|
453
|
+
if (controlsAutomaticDelivery) {
|
|
454
|
+
this.#recordAutomaticSuccess();
|
|
455
|
+
}
|
|
456
|
+
return response;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
if (controlsAutomaticDelivery) {
|
|
459
|
+
this.#recordAutomaticFailure(error);
|
|
460
|
+
}
|
|
461
|
+
throw error;
|
|
462
|
+
} finally {
|
|
463
|
+
if (this.automaticDelivery && !this.closed && !this.closing) {
|
|
464
|
+
this.#resumeAutomaticDelivery();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
152
468
|
}
|
|
153
469
|
|
|
154
470
|
async shutdown(transport) {
|
|
155
471
|
if (this.closed) {
|
|
156
472
|
throw new SdkError("shutdown_error", "client is already shut down");
|
|
157
473
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
474
|
+
if (this.closing) {
|
|
475
|
+
throw new SdkError("shutdown_error", "client is shutting down");
|
|
476
|
+
}
|
|
477
|
+
const resolvedTransport = this.#resolveTransport(transport);
|
|
478
|
+
const controlsAutomaticDelivery = this.automaticDelivery && resolvedTransport === this.transport;
|
|
479
|
+
this.#clearDeliveryTimer();
|
|
480
|
+
this.automaticFlushPending = false;
|
|
481
|
+
this.closing = true;
|
|
482
|
+
try {
|
|
483
|
+
const response = await this.#runSerialized(() => this.#flushWithHealth(resolvedTransport));
|
|
484
|
+
if (controlsAutomaticDelivery) {
|
|
485
|
+
this.#recordAutomaticSuccess();
|
|
486
|
+
}
|
|
487
|
+
if (this.eventStore) {
|
|
488
|
+
try {
|
|
489
|
+
requireSynchronousStoreResult("close", this.eventStore.close());
|
|
490
|
+
} catch (error) {
|
|
491
|
+
this.closed = true;
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
this.closed = true;
|
|
496
|
+
return response;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (!this.closed) {
|
|
499
|
+
this.closing = false;
|
|
500
|
+
if (controlsAutomaticDelivery) {
|
|
501
|
+
this.#recordAutomaticFailure(error);
|
|
502
|
+
}
|
|
503
|
+
this.#resumeAutomaticDelivery();
|
|
504
|
+
}
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
161
507
|
}
|
|
162
508
|
|
|
163
509
|
#pushEvent(eventType, id, timestamp, attributes) {
|
|
164
510
|
if (this.closed) {
|
|
165
511
|
throw new SdkError("shutdown_error", "client is already shut down");
|
|
166
512
|
}
|
|
513
|
+
if (this.closing) {
|
|
514
|
+
throw new SdkError("shutdown_error", "client is shutting down");
|
|
515
|
+
}
|
|
167
516
|
requireNonEmpty("event id", id);
|
|
168
517
|
requireTimestamp(timestamp);
|
|
169
518
|
const event = { type: eventType, id, timestamp, attributes };
|
|
170
519
|
if (this.eventFilter && this.eventFilter(cloneEvent(event)) === false) {
|
|
171
520
|
return;
|
|
172
521
|
}
|
|
522
|
+
const serializedEvent = JSON.stringify(event);
|
|
523
|
+
const eventBytes = utf8ByteLength(serializedEvent);
|
|
524
|
+
if (this.batchPrefixBytes + eventBytes + this.batchSuffixBytes > this.maxBatchBytes) {
|
|
525
|
+
this.#recordDroppedEvent(event, "event_too_large");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (this.events.length >= this.maxQueueSize) {
|
|
529
|
+
this.#recordDroppedEvent(event, "queue_overflow");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (this.queuedEventBytes + eventBytes > this.maxQueueBytes) {
|
|
533
|
+
this.#recordDroppedEvent(event, "queue_bytes_overflow");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (this.eventStore) {
|
|
537
|
+
requireSynchronousStoreResult("append", this.eventStore.append({
|
|
538
|
+
event: cloneEvent(event),
|
|
539
|
+
eventBytes,
|
|
540
|
+
serializedEvent
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
173
543
|
this.events.push(event);
|
|
544
|
+
this.serializedEvents.push(serializedEvent);
|
|
545
|
+
this.serializedEventBytes.push(eventBytes);
|
|
546
|
+
this.queuedEventBytes += eventBytes;
|
|
547
|
+
this.#scheduleAutomaticDelivery();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
#recordDroppedEvent(event, reason) {
|
|
551
|
+
this.droppedEventCount = incrementBounded(this.droppedEventCount);
|
|
552
|
+
this.droppedEventsByReason[reason] = incrementBounded(this.droppedEventsByReason[reason]);
|
|
553
|
+
this.lastDropReason = reason;
|
|
554
|
+
this.lastDroppedAtUnixMs = nextBoundedTimestamp(this.lastDroppedAtUnixMs);
|
|
555
|
+
if (!this.onEventDropped) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
this.onEventDropped({
|
|
560
|
+
droppedEvents: this.droppedEventCount,
|
|
561
|
+
eventId: event.id,
|
|
562
|
+
eventType: event.type,
|
|
563
|
+
reason
|
|
564
|
+
});
|
|
565
|
+
} catch {
|
|
566
|
+
// Drop callbacks are advisory and must not interrupt application logging.
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
#runSerialized(operation) {
|
|
571
|
+
this.pendingOperations = incrementBounded(this.pendingOperations);
|
|
572
|
+
const result = this.operationTail.then(operation).finally(() => {
|
|
573
|
+
this.pendingOperations = Math.max(0, this.pendingOperations - 1);
|
|
574
|
+
});
|
|
575
|
+
this.operationTail = result.then(
|
|
576
|
+
() => undefined,
|
|
577
|
+
() => undefined
|
|
578
|
+
);
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
#resolveTransport(transport) {
|
|
583
|
+
const resolved = transport ?? this.transport;
|
|
584
|
+
if (resolved === undefined) {
|
|
585
|
+
throw new SdkError("validation_error", "flush and shutdown require transport");
|
|
586
|
+
}
|
|
587
|
+
validateTransport(resolved);
|
|
588
|
+
return resolved;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async #flushWithHealth(transport) {
|
|
592
|
+
this.deliveryInFlight = true;
|
|
593
|
+
try {
|
|
594
|
+
const response = await this.#flushSnapshot(transport);
|
|
595
|
+
this.successfulFlushCount = incrementBounded(this.successfulFlushCount);
|
|
596
|
+
this.lastDeliveryOutcome = response.batches === 0 ? "empty" : "accepted";
|
|
597
|
+
return response;
|
|
598
|
+
} catch (error) {
|
|
599
|
+
this.failedFlushCount = incrementBounded(this.failedFlushCount);
|
|
600
|
+
this.lastDeliveryOutcome = "failed";
|
|
601
|
+
throw error;
|
|
602
|
+
} finally {
|
|
603
|
+
this.deliveryInFlight = false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
#scheduleAutomaticDelivery() {
|
|
608
|
+
if (!this.automaticDelivery || this.closed || this.closing || this.events.length === 0 || this.automaticPauseReason !== "none") {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (this.automaticFlushActive) {
|
|
612
|
+
this.automaticFlushPending = true;
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (this.retryDelayMs > 0) {
|
|
616
|
+
this.#armAutomaticDeliveryTimer(this.retryDelayMs);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (this.events.length >= this.deliveryQueueThreshold) {
|
|
620
|
+
this.#requestAutomaticFlush();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
this.#armAutomaticDeliveryTimer();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#armAutomaticDeliveryTimer(delayMs = this.deliveryIntervalMs) {
|
|
627
|
+
if (!this.automaticDelivery || this.closed || this.closing || this.events.length === 0 || this.automaticPauseReason !== "none") {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (this.deliveryTimer !== undefined) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const timer = setTimeout(() => {
|
|
634
|
+
if (this.deliveryTimer !== timer) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
this.deliveryTimer = undefined;
|
|
638
|
+
this.retryDelayMs = 0;
|
|
639
|
+
this.#requestAutomaticFlush();
|
|
640
|
+
}, delayMs);
|
|
641
|
+
this.deliveryTimer = timer;
|
|
642
|
+
if (timer && typeof timer === "object" && typeof timer.unref === "function") {
|
|
643
|
+
timer.unref();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
#clearDeliveryTimer() {
|
|
648
|
+
if (this.deliveryTimer === undefined) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
globalThis.clearTimeout(this.deliveryTimer);
|
|
652
|
+
this.deliveryTimer = undefined;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#requestAutomaticFlush() {
|
|
656
|
+
if (!this.automaticDelivery || this.closed || this.closing || this.events.length === 0 || this.automaticPauseReason !== "none") {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.#clearDeliveryTimer();
|
|
660
|
+
if (this.automaticFlushActive) {
|
|
661
|
+
this.automaticFlushPending = true;
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
this.automaticFlushActive = true;
|
|
665
|
+
void Promise.resolve().then(() => this.#runAutomaticFlush());
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async #runAutomaticFlush() {
|
|
669
|
+
if (this.closed || this.closing) {
|
|
670
|
+
this.automaticFlushActive = false;
|
|
671
|
+
this.automaticFlushPending = false;
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
let succeeded = false;
|
|
675
|
+
try {
|
|
676
|
+
await this.#runSerialized(() => this.#flushWithHealth(this.transport));
|
|
677
|
+
this.#recordAutomaticSuccess();
|
|
678
|
+
succeeded = true;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
this.#recordAutomaticFailure(error);
|
|
681
|
+
} finally {
|
|
682
|
+
this.automaticFlushActive = false;
|
|
683
|
+
if (this.closed || this.closing) {
|
|
684
|
+
this.automaticFlushPending = false;
|
|
685
|
+
} else {
|
|
686
|
+
const drainCoalesced = succeeded && this.automaticFlushPending && this.events.length > 0;
|
|
687
|
+
this.automaticFlushPending = false;
|
|
688
|
+
if (drainCoalesced) {
|
|
689
|
+
this.#requestAutomaticFlush();
|
|
690
|
+
} else if (!succeeded) {
|
|
691
|
+
this.#resumeAutomaticDelivery();
|
|
692
|
+
} else {
|
|
693
|
+
this.#scheduleAutomaticDelivery();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
174
697
|
}
|
|
175
698
|
|
|
176
|
-
async #
|
|
177
|
-
|
|
178
|
-
|
|
699
|
+
async #flushSnapshot(transport) {
|
|
700
|
+
let remainingEvents = this.events.length;
|
|
701
|
+
if (remainingEvents === 0) {
|
|
702
|
+
return { statusCode: 204, attempts: 0, batches: 0 };
|
|
179
703
|
}
|
|
180
704
|
|
|
181
|
-
|
|
705
|
+
let attempts = 0;
|
|
706
|
+
let batches = 0;
|
|
707
|
+
let statusCode = 204;
|
|
708
|
+
while (remainingEvents > 0) {
|
|
709
|
+
const batch = this.failedBatch ?? this.#nextBatch(Math.min(remainingEvents, this.maxBatchEvents));
|
|
710
|
+
let response;
|
|
711
|
+
try {
|
|
712
|
+
response = await this.#sendBatch(transport, batch.body);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
this.failedBatch ??= Object.freeze({ body: batch.body, eventsCount: batch.eventsCount });
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
this.failedBatch = undefined;
|
|
718
|
+
this.#acknowledge(batch.eventsCount);
|
|
719
|
+
this.acceptedBatchCount = incrementBounded(this.acceptedBatchCount);
|
|
720
|
+
remainingEvents -= batch.eventsCount;
|
|
721
|
+
attempts += response.attempts;
|
|
722
|
+
batches += 1;
|
|
723
|
+
statusCode = response.statusCode;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return { statusCode, attempts, batches };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
#nextBatch(maxEvents) {
|
|
730
|
+
let bodyBytes = this.batchPrefixBytes + this.batchSuffixBytes;
|
|
731
|
+
let eventsCount = 0;
|
|
732
|
+
for (let index = 0; index < maxEvents; index += 1) {
|
|
733
|
+
const separatorBytes = eventsCount === 0 ? 0 : 1;
|
|
734
|
+
const nextBodyBytes = bodyBytes + separatorBytes + this.serializedEventBytes[index];
|
|
735
|
+
if (nextBodyBytes > this.maxBatchBytes) {
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
bodyBytes = nextBodyBytes;
|
|
739
|
+
eventsCount += 1;
|
|
740
|
+
}
|
|
741
|
+
if (eventsCount === 0) {
|
|
742
|
+
throw new SdkError("transport_error", "queued event cannot fit the configured batch byte limit");
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
body: `${this.batchPrefix}${this.serializedEvents.slice(0, eventsCount).join(",")}${this.batchSuffix}`,
|
|
746
|
+
eventsCount
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
#acknowledge(eventsCount) {
|
|
751
|
+
if (this.eventStore) {
|
|
752
|
+
requireSynchronousStoreResult("acknowledge", this.eventStore.acknowledge(eventsCount));
|
|
753
|
+
}
|
|
754
|
+
let acknowledgedBytes = 0;
|
|
755
|
+
for (let index = 0; index < eventsCount; index += 1) {
|
|
756
|
+
acknowledgedBytes += this.serializedEventBytes[index];
|
|
757
|
+
}
|
|
758
|
+
this.events.splice(0, eventsCount);
|
|
759
|
+
this.serializedEvents.splice(0, eventsCount);
|
|
760
|
+
this.serializedEventBytes.splice(0, eventsCount);
|
|
761
|
+
this.queuedEventBytes -= acknowledgedBytes;
|
|
762
|
+
this.acceptedEventCount = addBounded(this.acceptedEventCount, eventsCount);
|
|
763
|
+
this.lastAcceptedAtUnixMs = nextBoundedTimestamp(Math.max(
|
|
764
|
+
this.lastAcceptedAtUnixMs,
|
|
765
|
+
this.lastAttemptAtUnixMs
|
|
766
|
+
));
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
#recordAutomaticSuccess() {
|
|
770
|
+
this.automaticPauseReason = "none";
|
|
771
|
+
this.consecutiveDeliveryFailures = 0;
|
|
772
|
+
this.retryDelayMs = 0;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
#recordAutomaticFailure(error) {
|
|
776
|
+
this.consecutiveDeliveryFailures = incrementBounded(this.consecutiveDeliveryFailures);
|
|
777
|
+
this.retryDelayMs = 0;
|
|
778
|
+
if (error instanceof SdkError && error.code === "unauthenticated") {
|
|
779
|
+
this.automaticPauseReason = "authentication";
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (error instanceof SdkError && error.code === "rate_limited") {
|
|
783
|
+
this.automaticPauseReason = "rate_limit";
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (error instanceof SdkError && error.retryable === true) {
|
|
787
|
+
this.automaticPauseReason = "none";
|
|
788
|
+
this.retryDelayMs = automaticRetryDelayMs(this.deliveryIntervalMs, this.consecutiveDeliveryFailures);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
this.automaticPauseReason = "non_retryable";
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
#resumeAutomaticDelivery() {
|
|
795
|
+
if (this.automaticPauseReason !== "none") {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (this.retryDelayMs > 0) {
|
|
799
|
+
this.#armAutomaticDeliveryTimer(this.retryDelayMs);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
this.#scheduleAutomaticDelivery();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async #sendBatch(transport, body) {
|
|
182
806
|
const maxAttempts = this.maxRetries + 1;
|
|
183
807
|
let attempts = 0;
|
|
184
808
|
|
|
185
809
|
while (attempts < maxAttempts) {
|
|
186
810
|
attempts += 1;
|
|
811
|
+
this.deliveryAttemptCount = incrementBounded(this.deliveryAttemptCount);
|
|
812
|
+
this.lastAttemptAtUnixMs = nextBoundedTimestamp(this.lastAttemptAtUnixMs);
|
|
813
|
+
this.lastStatusClass = "transport_error";
|
|
187
814
|
try {
|
|
188
815
|
const response = await transport.send(this.apiKey, body);
|
|
816
|
+
if (
|
|
817
|
+
!response
|
|
818
|
+
|| Array.isArray(response)
|
|
819
|
+
|| typeof response !== "object"
|
|
820
|
+
|| !Number.isSafeInteger(response.statusCode)
|
|
821
|
+
|| response.statusCode < 100
|
|
822
|
+
|| response.statusCode > 599
|
|
823
|
+
) {
|
|
824
|
+
this.lastStatusClass = "invalid_response";
|
|
825
|
+
throw new SdkError("transport_error", "invalid transport response");
|
|
826
|
+
}
|
|
827
|
+
this.lastStatusClass = statusClass(response.statusCode);
|
|
189
828
|
if (response.statusCode === 401) {
|
|
190
829
|
throw new SdkError("unauthenticated", "transport rejected the API key");
|
|
191
830
|
}
|
|
831
|
+
if (response.statusCode === 429) {
|
|
832
|
+
throw new SdkError("rate_limited", "transport rate limited the batch", {
|
|
833
|
+
retryAfterMs: response.retryAfterMs
|
|
834
|
+
});
|
|
835
|
+
}
|
|
192
836
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
193
|
-
this.events = [];
|
|
194
837
|
return { statusCode: response.statusCode, attempts };
|
|
195
838
|
}
|
|
196
|
-
|
|
839
|
+
const retryableStatus = response.statusCode === 408 || response.statusCode >= 500;
|
|
840
|
+
if (retryableStatus && attempts < maxAttempts) {
|
|
197
841
|
continue;
|
|
198
842
|
}
|
|
199
|
-
throw new SdkError("transport_error", `unexpected transport status ${response.statusCode}
|
|
843
|
+
throw new SdkError("transport_error", `unexpected transport status ${response.statusCode}`, {
|
|
844
|
+
retryable: retryableStatus
|
|
845
|
+
});
|
|
200
846
|
} catch (error) {
|
|
201
847
|
if (error instanceof SdkError) {
|
|
202
848
|
throw error;
|
|
203
849
|
}
|
|
204
850
|
if (error instanceof TransportError && error.retryable && attempts < maxAttempts) {
|
|
851
|
+
this.lastStatusClass = "network_error";
|
|
205
852
|
continue;
|
|
206
853
|
}
|
|
207
854
|
if (error instanceof TransportError) {
|
|
208
|
-
|
|
855
|
+
this.lastStatusClass = "network_error";
|
|
856
|
+
throw new SdkError(error.code, error.message, { retryable: error.retryable });
|
|
209
857
|
}
|
|
210
858
|
throw error;
|
|
211
859
|
}
|
|
@@ -215,6 +863,256 @@ class LogBrewClient {
|
|
|
215
863
|
}
|
|
216
864
|
}
|
|
217
865
|
|
|
866
|
+
function automaticRetryDelayMs(deliveryIntervalMs, consecutiveFailures) {
|
|
867
|
+
const exponent = Math.min(consecutiveFailures - 1, 30);
|
|
868
|
+
const maximumDelay = Math.min(MAX_DELIVERY_INTERVAL_MS, deliveryIntervalMs * (2 ** exponent));
|
|
869
|
+
const minimumDelay = Math.ceil(maximumDelay / 2);
|
|
870
|
+
return minimumDelay + Math.floor(Math.random() * (maximumDelay - minimumDelay + 1));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function statusClass(statusCode) {
|
|
874
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
875
|
+
return "success";
|
|
876
|
+
}
|
|
877
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
878
|
+
return "client_error";
|
|
879
|
+
}
|
|
880
|
+
if (statusCode >= 500) {
|
|
881
|
+
return "server_error";
|
|
882
|
+
}
|
|
883
|
+
return "other_status";
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function nextBoundedTimestamp(previous) {
|
|
887
|
+
const now = Date.now();
|
|
888
|
+
if (!Number.isFinite(now)) {
|
|
889
|
+
return previous;
|
|
890
|
+
}
|
|
891
|
+
const bounded = Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(now)));
|
|
892
|
+
return Math.max(previous, bounded);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function addBounded(current, increment) {
|
|
896
|
+
return Math.min(Number.MAX_SAFE_INTEGER, current + increment);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function utf8ByteLength(value) {
|
|
900
|
+
let bytes = 0;
|
|
901
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
902
|
+
const codeUnit = value.charCodeAt(index);
|
|
903
|
+
if (codeUnit < 0x80) {
|
|
904
|
+
bytes += 1;
|
|
905
|
+
} else if (codeUnit < 0x800) {
|
|
906
|
+
bytes += 2;
|
|
907
|
+
} else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff && index + 1 < value.length) {
|
|
908
|
+
const nextCodeUnit = value.charCodeAt(index + 1);
|
|
909
|
+
if (nextCodeUnit >= 0xdc00 && nextCodeUnit <= 0xdfff) {
|
|
910
|
+
bytes += 4;
|
|
911
|
+
index += 1;
|
|
912
|
+
} else {
|
|
913
|
+
bytes += 3;
|
|
914
|
+
}
|
|
915
|
+
} else {
|
|
916
|
+
bytes += 3;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return bytes;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function incrementBounded(value) {
|
|
923
|
+
return value >= Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : value + 1;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function validateTransport(transport) {
|
|
927
|
+
if (transport === undefined) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (!transport || Array.isArray(transport) || typeof transport !== "object" || typeof transport.send !== "function") {
|
|
931
|
+
throw new SdkError("validation_error", "transport.send must be a function");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function validateEventStore(eventStore) {
|
|
936
|
+
if (eventStore === undefined) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
if (!eventStore || Array.isArray(eventStore) || typeof eventStore !== "object") {
|
|
940
|
+
throw new SdkError("validation_error", "eventStore must be an object");
|
|
941
|
+
}
|
|
942
|
+
for (const method of ["load", "append", "acknowledge", "purge", "close"]) {
|
|
943
|
+
if (typeof eventStore[method] !== "function") {
|
|
944
|
+
throw new SdkError("validation_error", `eventStore.${method} must be a function`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function createEventStoreFromQueueFactory(eventQueueFactory, config) {
|
|
950
|
+
const queue = eventQueueFactory({
|
|
951
|
+
...config,
|
|
952
|
+
restoreEvent: restoreStoredEvent
|
|
953
|
+
});
|
|
954
|
+
validateEventQueue(queue);
|
|
955
|
+
return {
|
|
956
|
+
load() {
|
|
957
|
+
const events = queue.events();
|
|
958
|
+
const count = queue.length();
|
|
959
|
+
if (!Array.isArray(events) || events.length !== count) {
|
|
960
|
+
throw invalidStoredRecord();
|
|
961
|
+
}
|
|
962
|
+
return events.map((event, index) => ({
|
|
963
|
+
event,
|
|
964
|
+
eventBytes: queue.eventBytesAt(index),
|
|
965
|
+
serializedEvent: queue.serializedAt(index)
|
|
966
|
+
}));
|
|
967
|
+
},
|
|
968
|
+
append(record) {
|
|
969
|
+
return queue.append({
|
|
970
|
+
event: record.event,
|
|
971
|
+
byteCount: record.eventBytes,
|
|
972
|
+
serialized: record.serializedEvent
|
|
973
|
+
});
|
|
974
|
+
},
|
|
975
|
+
acknowledge(count) {
|
|
976
|
+
return queue.acknowledge(count);
|
|
977
|
+
},
|
|
978
|
+
purge() {
|
|
979
|
+
return queue.acknowledge(queue.length());
|
|
980
|
+
},
|
|
981
|
+
close() {
|
|
982
|
+
return queue.close();
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function validateEventQueue(queue) {
|
|
988
|
+
const methods = [
|
|
989
|
+
"acknowledge",
|
|
990
|
+
"append",
|
|
991
|
+
"byteCount",
|
|
992
|
+
"close",
|
|
993
|
+
"eventBytesAt",
|
|
994
|
+
"events",
|
|
995
|
+
"length",
|
|
996
|
+
"serializedAt"
|
|
997
|
+
];
|
|
998
|
+
if (!queue || Array.isArray(queue) || typeof queue !== "object" || methods.some((method) => typeof queue[method] !== "function")) {
|
|
999
|
+
throw new SdkError("validation_error", "event queue factory returned an invalid queue");
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function restoreStoredEvent(serializedEvent) {
|
|
1004
|
+
if (typeof serializedEvent !== "string" || serializedEvent === "") {
|
|
1005
|
+
throw invalidStoredRecord();
|
|
1006
|
+
}
|
|
1007
|
+
let event;
|
|
1008
|
+
try {
|
|
1009
|
+
event = JSON.parse(serializedEvent);
|
|
1010
|
+
} catch {
|
|
1011
|
+
throw invalidStoredRecord();
|
|
1012
|
+
}
|
|
1013
|
+
return normalizeStoredRecord({
|
|
1014
|
+
event,
|
|
1015
|
+
eventBytes: utf8ByteLength(serializedEvent),
|
|
1016
|
+
serializedEvent
|
|
1017
|
+
}).event;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function requireSynchronousStoreResult(operation, result) {
|
|
1021
|
+
if (result && typeof result.then === "function") {
|
|
1022
|
+
throw new SdkError("persistence_error", `eventStore.${operation} must complete synchronously`);
|
|
1023
|
+
}
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function loadStoredEvents({
|
|
1028
|
+
batchPrefixBytes,
|
|
1029
|
+
batchSuffixBytes,
|
|
1030
|
+
eventStore,
|
|
1031
|
+
maxBatchBytes,
|
|
1032
|
+
maxQueueBytes,
|
|
1033
|
+
maxQueueSize
|
|
1034
|
+
}) {
|
|
1035
|
+
if (!eventStore) {
|
|
1036
|
+
return {
|
|
1037
|
+
events: [],
|
|
1038
|
+
queuedEventBytes: 0,
|
|
1039
|
+
serializedEventBytes: [],
|
|
1040
|
+
serializedEvents: []
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const records = requireSynchronousStoreResult("load", eventStore.load());
|
|
1045
|
+
if (!Array.isArray(records)) {
|
|
1046
|
+
throw invalidStoredRecord();
|
|
1047
|
+
}
|
|
1048
|
+
if (records.length > maxQueueSize) {
|
|
1049
|
+
throw new SdkError("persistence_error", "recovered event count exceeds maxQueueSize");
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const events = [];
|
|
1053
|
+
const serializedEvents = [];
|
|
1054
|
+
const serializedEventBytes = [];
|
|
1055
|
+
let queuedEventBytes = 0;
|
|
1056
|
+
for (const record of records) {
|
|
1057
|
+
const normalized = normalizeStoredRecord(record);
|
|
1058
|
+
if (batchPrefixBytes + normalized.eventBytes + batchSuffixBytes > maxBatchBytes) {
|
|
1059
|
+
throw new SdkError("persistence_error", "recovered event exceeds maxBatchBytes");
|
|
1060
|
+
}
|
|
1061
|
+
queuedEventBytes += normalized.eventBytes;
|
|
1062
|
+
if (queuedEventBytes > maxQueueBytes) {
|
|
1063
|
+
throw new SdkError("persistence_error", "recovered event bytes exceed maxQueueBytes");
|
|
1064
|
+
}
|
|
1065
|
+
events.push(normalized.event);
|
|
1066
|
+
serializedEvents.push(normalized.serializedEvent);
|
|
1067
|
+
serializedEventBytes.push(normalized.eventBytes);
|
|
1068
|
+
}
|
|
1069
|
+
return { events, queuedEventBytes, serializedEventBytes, serializedEvents };
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function normalizeStoredRecord(record) {
|
|
1073
|
+
try {
|
|
1074
|
+
if (!record || Array.isArray(record) || typeof record !== "object") {
|
|
1075
|
+
throw invalidStoredRecord();
|
|
1076
|
+
}
|
|
1077
|
+
const { event, eventBytes, serializedEvent } = record;
|
|
1078
|
+
if (!event || Array.isArray(event) || typeof event !== "object") {
|
|
1079
|
+
throw invalidStoredRecord();
|
|
1080
|
+
}
|
|
1081
|
+
const validator = EVENT_VALIDATORS.get(event.type);
|
|
1082
|
+
if (!validator || !Number.isSafeInteger(eventBytes) || eventBytes <= 0 || typeof serializedEvent !== "string") {
|
|
1083
|
+
throw invalidStoredRecord();
|
|
1084
|
+
}
|
|
1085
|
+
requireNonEmpty("event id", event.id);
|
|
1086
|
+
requireTimestamp(event.timestamp);
|
|
1087
|
+
if (!event.attributes || Array.isArray(event.attributes) || typeof event.attributes !== "object") {
|
|
1088
|
+
throw invalidStoredRecord();
|
|
1089
|
+
}
|
|
1090
|
+
const normalizedEvent = {
|
|
1091
|
+
type: event.type,
|
|
1092
|
+
id: event.id,
|
|
1093
|
+
timestamp: event.timestamp,
|
|
1094
|
+
attributes: validator(event.attributes)
|
|
1095
|
+
};
|
|
1096
|
+
if (JSON.stringify(normalizedEvent) !== serializedEvent || utf8ByteLength(serializedEvent) !== eventBytes) {
|
|
1097
|
+
throw invalidStoredRecord();
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
event: cloneEvent(normalizedEvent),
|
|
1101
|
+
eventBytes,
|
|
1102
|
+
serializedEvent
|
|
1103
|
+
};
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
if (error instanceof SdkError && error.code === "persistence_error") {
|
|
1106
|
+
throw error;
|
|
1107
|
+
}
|
|
1108
|
+
throw invalidStoredRecord();
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function invalidStoredRecord() {
|
|
1113
|
+
return new SdkError("persistence_error", "event store returned an invalid record");
|
|
1114
|
+
}
|
|
1115
|
+
|
|
218
1116
|
function installLogBrewConsoleCapture(config) {
|
|
219
1117
|
if (!config || typeof config !== "object") {
|
|
220
1118
|
throw new SdkError("validation_error", "console capture config must be an object");
|
|
@@ -352,6 +1250,197 @@ function logAttributesFromConsoleArgs(method, args, options = {}) {
|
|
|
352
1250
|
};
|
|
353
1251
|
}
|
|
354
1252
|
|
|
1253
|
+
function createIssueAttributesFromError(error, options = {}) {
|
|
1254
|
+
if (!options || Array.isArray(options) || typeof options !== "object") {
|
|
1255
|
+
throw new SdkError("validation_error", "error issue options must be an object");
|
|
1256
|
+
}
|
|
1257
|
+
const details = errorDetails(error);
|
|
1258
|
+
const stackFrames = javascriptStackFrames(details.stack, options.debugIdMap);
|
|
1259
|
+
const frame = stackFrames[0] ?? null;
|
|
1260
|
+
const source = stringOrUndefined(options.source) ?? "javascript.error";
|
|
1261
|
+
const metadata = {
|
|
1262
|
+
...compactMetadata(options.metadata),
|
|
1263
|
+
source,
|
|
1264
|
+
errorName: details.name,
|
|
1265
|
+
...(details.message ? { errorMessage: details.message } : {}),
|
|
1266
|
+
...(frame ? {
|
|
1267
|
+
errorFrameFile: frame.filename,
|
|
1268
|
+
errorFrameLine: frame.line,
|
|
1269
|
+
errorFrameColumn: frame.column
|
|
1270
|
+
} : {}),
|
|
1271
|
+
...issueGroupingMetadata(source, details, frame, options.fingerprint),
|
|
1272
|
+
...errorCauseMetadata(error),
|
|
1273
|
+
...(stringOrUndefined(options.release) ? { release: options.release } : {}),
|
|
1274
|
+
...(stringOrUndefined(options.environment) ? { environment: options.environment } : {}),
|
|
1275
|
+
...(stringOrUndefined(options.service) ? { service: options.service } : {}),
|
|
1276
|
+
...(stringOrUndefined(options.runtime) ? { runtime: options.runtime } : {}),
|
|
1277
|
+
...(stringOrUndefined(options.platform) ? { platform: options.platform } : {}),
|
|
1278
|
+
...traceMetadata(options.trace),
|
|
1279
|
+
...releaseArtifactMetadata(frame),
|
|
1280
|
+
...(options.includeErrorStack === true && details.stack ? { errorStack: details.stack } : {})
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
return {
|
|
1284
|
+
title: stringOrUndefined(options.title) ?? details.name,
|
|
1285
|
+
level: normalizeSeverity("issue level", options.level ?? "error"),
|
|
1286
|
+
...(stringOrUndefined(options.message) ? { message: options.message } : details.message ? { message: details.message } : {}),
|
|
1287
|
+
...(stackFrames.length > 0 ? { stackFrames } : {}),
|
|
1288
|
+
metadata: compactMetadata(metadata)
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function errorDetails(error) {
|
|
1293
|
+
if (error instanceof Error) {
|
|
1294
|
+
return {
|
|
1295
|
+
name: stringOrUndefined(error.name) ?? "Error",
|
|
1296
|
+
message: stringOrUndefined(error.message),
|
|
1297
|
+
stack: typeof error.stack === "string" && error.stack.trim() !== "" ? error.stack : undefined
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
if (error && typeof error === "object") {
|
|
1301
|
+
const name = typeof error.name === "string" && error.name.trim() !== "" ? error.name : "Error";
|
|
1302
|
+
const message = typeof error.message === "string" && error.message.trim() !== "" ? error.message : undefined;
|
|
1303
|
+
const stack = typeof error.stack === "string" && error.stack.trim() !== "" ? error.stack : undefined;
|
|
1304
|
+
return { name, message, stack };
|
|
1305
|
+
}
|
|
1306
|
+
if (typeof error === "string" && error.trim() !== "") {
|
|
1307
|
+
return { name: "Error", message: error };
|
|
1308
|
+
}
|
|
1309
|
+
return { name: "Error" };
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function issueGroupingMetadata(source, details, frame, fingerprint) {
|
|
1313
|
+
const groupingKey = frame
|
|
1314
|
+
? `${source}:${details.name}:${frame.filename}`
|
|
1315
|
+
: `${source}:${details.name}`;
|
|
1316
|
+
const explicitFingerprint = issueFingerprintOrUndefined(fingerprint);
|
|
1317
|
+
return {
|
|
1318
|
+
issueGroupingKey: groupingKey,
|
|
1319
|
+
issueGroupingSource: explicitFingerprint ? "explicit_fingerprint" : frame ? "error_type_and_frame" : "error_type",
|
|
1320
|
+
...(explicitFingerprint ? { issueFingerprint: explicitFingerprint } : {})
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function issueFingerprintOrUndefined(value) {
|
|
1325
|
+
if (value === undefined || value === null) {
|
|
1326
|
+
return undefined;
|
|
1327
|
+
}
|
|
1328
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1329
|
+
throw new SdkError("validation_error", "issue fingerprint must be a non-empty string");
|
|
1330
|
+
}
|
|
1331
|
+
return value.trim();
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function errorCauseMetadata(error) {
|
|
1335
|
+
const state = {
|
|
1336
|
+
items: [],
|
|
1337
|
+
seen: new Set(),
|
|
1338
|
+
sawExceptionGroup: false,
|
|
1339
|
+
truncated: false
|
|
1340
|
+
};
|
|
1341
|
+
if (isObjectLike(error)) {
|
|
1342
|
+
state.seen.add(error);
|
|
1343
|
+
collectNestedErrorCauses(error, state);
|
|
1344
|
+
}
|
|
1345
|
+
if (state.items.length === 0) {
|
|
1346
|
+
return {};
|
|
1347
|
+
}
|
|
1348
|
+
return {
|
|
1349
|
+
errorCauseCount: state.items.length,
|
|
1350
|
+
errorCauseTypes: state.items.map((item) => item.type).join(","),
|
|
1351
|
+
errorCauseSources: state.items.map((item) => item.source).join(","),
|
|
1352
|
+
...(state.sawExceptionGroup ? { errorExceptionGroup: true } : {}),
|
|
1353
|
+
...(state.truncated ? { errorCauseTruncated: true } : {})
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function collectNestedErrorCauses(parent, state) {
|
|
1358
|
+
if (!isObjectLike(parent)) {
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if ("cause" in parent) {
|
|
1362
|
+
collectErrorCause(parent.cause, "cause", state);
|
|
1363
|
+
}
|
|
1364
|
+
if (Array.isArray(parent.errors)) {
|
|
1365
|
+
state.sawExceptionGroup = true;
|
|
1366
|
+
for (const [index, child] of parent.errors.entries()) {
|
|
1367
|
+
collectErrorCause(child, `errors[${index}]`, state);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function collectErrorCause(value, source, state) {
|
|
1373
|
+
if (value === undefined || value === null) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (state.items.length >= MAX_ERROR_CAUSES) {
|
|
1377
|
+
state.truncated = true;
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (isObjectLike(value)) {
|
|
1381
|
+
if (state.seen.has(value)) {
|
|
1382
|
+
state.truncated = true;
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
state.seen.add(value);
|
|
1386
|
+
}
|
|
1387
|
+
state.items.push({
|
|
1388
|
+
source,
|
|
1389
|
+
type: errorCauseType(value)
|
|
1390
|
+
});
|
|
1391
|
+
collectNestedErrorCauses(value, state);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
function errorCauseType(value) {
|
|
1395
|
+
if (isObjectLike(value)) {
|
|
1396
|
+
const constructorName = safeCauseTypeName(value.constructor?.name);
|
|
1397
|
+
if (value instanceof Error) {
|
|
1398
|
+
if (constructorName && constructorName !== "Error") {
|
|
1399
|
+
return constructorName;
|
|
1400
|
+
}
|
|
1401
|
+
const builtinName = BUILTIN_ERROR_NAMES.has(value.name) ? value.name : undefined;
|
|
1402
|
+
return builtinName ?? constructorName ?? "Error";
|
|
1403
|
+
}
|
|
1404
|
+
return constructorName ?? "Object";
|
|
1405
|
+
}
|
|
1406
|
+
return "NonError";
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function safeCauseTypeName(value) {
|
|
1410
|
+
return typeof value === "string" && /^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/u.test(value) ? value : undefined;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function isObjectLike(value) {
|
|
1414
|
+
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function traceMetadata(trace) {
|
|
1418
|
+
if (trace === undefined || trace === null) {
|
|
1419
|
+
return {};
|
|
1420
|
+
}
|
|
1421
|
+
const normalized = normalizeLogTraceContext(trace);
|
|
1422
|
+
if (!normalized) {
|
|
1423
|
+
return {};
|
|
1424
|
+
}
|
|
1425
|
+
return {
|
|
1426
|
+
traceId: normalized.traceId,
|
|
1427
|
+
spanId: normalized.spanId,
|
|
1428
|
+
...(normalized.parentSpanId ? { parentSpanId: normalized.parentSpanId } : {}),
|
|
1429
|
+
...(normalized.sampled !== undefined ? { sampled: normalized.sampled } : {})
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function releaseArtifactMetadata(frame) {
|
|
1434
|
+
if (!frame?.debugId) {
|
|
1435
|
+
return {};
|
|
1436
|
+
}
|
|
1437
|
+
return {
|
|
1438
|
+
releaseArtifactType: "sourcemap",
|
|
1439
|
+
releaseArtifactCodeFile: frame.filename,
|
|
1440
|
+
releaseArtifactDebugId: frame.debugId
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
355
1444
|
function logbrewLevelFromConsoleMethod(method) {
|
|
356
1445
|
switch (method) {
|
|
357
1446
|
case "debug":
|
|
@@ -450,6 +1539,44 @@ function createTraceparentHeaders(input) {
|
|
|
450
1539
|
return { traceparent: createTraceparent(input) };
|
|
451
1540
|
}
|
|
452
1541
|
|
|
1542
|
+
const {
|
|
1543
|
+
createBaggage,
|
|
1544
|
+
createTraceContextHeaders,
|
|
1545
|
+
createTracestate,
|
|
1546
|
+
parseBaggage,
|
|
1547
|
+
parseTracestate
|
|
1548
|
+
} = buildTraceContextHelpers({
|
|
1549
|
+
SdkError,
|
|
1550
|
+
createTraceparent
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
const createSupportTicketDraft = buildCreateSupportTicketDraft({
|
|
1554
|
+
SdkError,
|
|
1555
|
+
requireAllowedValue,
|
|
1556
|
+
requireNonEmpty,
|
|
1557
|
+
requireTraceId
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
const {
|
|
1561
|
+
createLogBrewOpenTelemetrySpanExporter,
|
|
1562
|
+
createLogBrewOpenTelemetrySpanProcessor,
|
|
1563
|
+
logbrewTraceContextFromCurrentOpenTelemetrySpan,
|
|
1564
|
+
logbrewTraceContextFromOpenTelemetrySpan,
|
|
1565
|
+
logbrewTraceContextFromOpenTelemetrySpanContext,
|
|
1566
|
+
spanAttributesFromOpenTelemetryReadableSpan
|
|
1567
|
+
} = buildOpenTelemetryHelpers({
|
|
1568
|
+
compactMetadata,
|
|
1569
|
+
isMetadataValue,
|
|
1570
|
+
LogBrewClient,
|
|
1571
|
+
maxSpanEvents: MAX_SPAN_EVENTS,
|
|
1572
|
+
maxSpanLinks: MAX_SPAN_LINKS,
|
|
1573
|
+
requireNonEmpty,
|
|
1574
|
+
requireSpanId,
|
|
1575
|
+
requireTraceId,
|
|
1576
|
+
SdkError,
|
|
1577
|
+
stringOrUndefined
|
|
1578
|
+
});
|
|
1579
|
+
|
|
453
1580
|
function spanAttributesFromTraceparent(traceparent, attributes) {
|
|
454
1581
|
if (!attributes || Array.isArray(attributes) || typeof attributes !== "object") {
|
|
455
1582
|
throw new SdkError("validation_error", "span attributes must be an object");
|
|
@@ -463,6 +1590,8 @@ function spanAttributesFromTraceparent(traceparent, attributes) {
|
|
|
463
1590
|
throw new SdkError("validation_error", "span durationMs must be non-negative");
|
|
464
1591
|
}
|
|
465
1592
|
}
|
|
1593
|
+
const events = validateSpanEvents(attributes.events);
|
|
1594
|
+
const links = validateSpanLinks(attributes.links);
|
|
466
1595
|
|
|
467
1596
|
return {
|
|
468
1597
|
name: attributes.name,
|
|
@@ -471,6 +1600,8 @@ function spanAttributesFromTraceparent(traceparent, attributes) {
|
|
|
471
1600
|
parentSpanId: context.parentSpanId,
|
|
472
1601
|
status: attributes.status,
|
|
473
1602
|
...(attributes.durationMs !== undefined ? { durationMs: attributes.durationMs } : {}),
|
|
1603
|
+
...(events !== undefined ? { events } : {}),
|
|
1604
|
+
...(links !== undefined ? { links } : {}),
|
|
474
1605
|
...(attributes.metadata !== undefined ? { metadata: compactMetadata(attributes.metadata) } : {})
|
|
475
1606
|
};
|
|
476
1607
|
}
|
|
@@ -495,6 +1626,7 @@ function createLogBrewPinoDestination(config) {
|
|
|
495
1626
|
: () => new Date().toISOString();
|
|
496
1627
|
const eventIdPrefix = config.eventIdPrefix ?? "pino";
|
|
497
1628
|
const onError = typeof config.onError === "function" ? config.onError : () => {};
|
|
1629
|
+
const traceProvider = typeof config.traceProvider === "function" ? config.traceProvider : null;
|
|
498
1630
|
const state = {
|
|
499
1631
|
captured: 0,
|
|
500
1632
|
pendingFlush: Promise.resolve(null)
|
|
@@ -513,7 +1645,8 @@ function createLogBrewPinoDestination(config) {
|
|
|
513
1645
|
logAttributesFromPinoRecord(record, {
|
|
514
1646
|
includeErrorStack,
|
|
515
1647
|
logger,
|
|
516
|
-
metadata
|
|
1648
|
+
metadata,
|
|
1649
|
+
trace: traceFromProvider(traceProvider, onError)
|
|
517
1650
|
})
|
|
518
1651
|
);
|
|
519
1652
|
if (flushOnWrite && transport) {
|
|
@@ -552,7 +1685,8 @@ function logAttributesFromPinoRecord(record, options = {}) {
|
|
|
552
1685
|
const metadata = {
|
|
553
1686
|
...compactMetadata(options.metadata),
|
|
554
1687
|
pinoLevel: pinoLevelLabel(record.level),
|
|
555
|
-
...pinoContextMetadata(record)
|
|
1688
|
+
...pinoContextMetadata(record),
|
|
1689
|
+
...traceMetadataFromLogContext(options.trace)
|
|
556
1690
|
};
|
|
557
1691
|
if (typeof record.level === "number" && Number.isFinite(record.level)) {
|
|
558
1692
|
metadata.pinoLevelNumber = record.level;
|
|
@@ -587,6 +1721,7 @@ function createLogBrewWinstonTransport(config) {
|
|
|
587
1721
|
: () => new Date().toISOString();
|
|
588
1722
|
const eventIdPrefix = config.eventIdPrefix ?? "winston";
|
|
589
1723
|
const onError = typeof config.onError === "function" ? config.onError : () => {};
|
|
1724
|
+
const traceProvider = typeof config.traceProvider === "function" ? config.traceProvider : null;
|
|
590
1725
|
const state = {
|
|
591
1726
|
captured: 0,
|
|
592
1727
|
pendingFlush: Promise.resolve(null)
|
|
@@ -608,6 +1743,7 @@ function createLogBrewWinstonTransport(config) {
|
|
|
608
1743
|
onError,
|
|
609
1744
|
state,
|
|
610
1745
|
timestamp,
|
|
1746
|
+
traceProvider,
|
|
611
1747
|
transport
|
|
612
1748
|
});
|
|
613
1749
|
} catch (error) {
|
|
@@ -663,7 +1799,8 @@ function captureWinstonInfo(config) {
|
|
|
663
1799
|
logAttributesFromWinstonInfo(config.info, {
|
|
664
1800
|
includeErrorStack: config.includeErrorStack,
|
|
665
1801
|
logger: config.logger,
|
|
666
|
-
metadata: config.metadata
|
|
1802
|
+
metadata: config.metadata,
|
|
1803
|
+
trace: traceFromProvider(config.traceProvider, config.onError)
|
|
667
1804
|
})
|
|
668
1805
|
);
|
|
669
1806
|
if (config.flushOnWrite && config.transport) {
|
|
@@ -683,7 +1820,8 @@ function logAttributesFromWinstonInfo(info, options = {}) {
|
|
|
683
1820
|
const metadata = {
|
|
684
1821
|
...compactMetadata(options.metadata),
|
|
685
1822
|
winstonLevel: winstonLevelLabel(info.level),
|
|
686
|
-
...winstonContextMetadata(info)
|
|
1823
|
+
...winstonContextMetadata(info),
|
|
1824
|
+
...traceMetadataFromLogContext(options.trace)
|
|
687
1825
|
};
|
|
688
1826
|
addWinstonErrorMetadata(metadata, info, options.includeErrorStack === true);
|
|
689
1827
|
|
|
@@ -942,6 +2080,67 @@ function addPinoErrorMetadata(metadata, error, includeErrorStack) {
|
|
|
942
2080
|
}
|
|
943
2081
|
}
|
|
944
2082
|
|
|
2083
|
+
function traceFromProvider(provider, onError) {
|
|
2084
|
+
if (!provider) {
|
|
2085
|
+
return undefined;
|
|
2086
|
+
}
|
|
2087
|
+
try {
|
|
2088
|
+
return provider();
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
onError(error);
|
|
2091
|
+
return undefined;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
function traceMetadataFromLogContext(trace) {
|
|
2096
|
+
const normalized = normalizeLogTraceContext(trace);
|
|
2097
|
+
if (!normalized) {
|
|
2098
|
+
return {};
|
|
2099
|
+
}
|
|
2100
|
+
return {
|
|
2101
|
+
traceId: normalized.traceId,
|
|
2102
|
+
spanId: normalized.spanId,
|
|
2103
|
+
...(normalized.parentSpanId !== undefined ? { parentSpanId: normalized.parentSpanId } : {}),
|
|
2104
|
+
...(normalized.sampled !== undefined ? { sampled: normalized.sampled } : {})
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function normalizeLogTraceContext(trace) {
|
|
2109
|
+
if (!trace || Array.isArray(trace) || typeof trace !== "object") {
|
|
2110
|
+
return undefined;
|
|
2111
|
+
}
|
|
2112
|
+
const traceId = normalizeTraceId(trace.traceId);
|
|
2113
|
+
const spanId = normalizeSpanId(trace.spanId);
|
|
2114
|
+
if (!traceId || !spanId) {
|
|
2115
|
+
return undefined;
|
|
2116
|
+
}
|
|
2117
|
+
const parentSpanId = normalizeSpanId(trace.parentSpanId);
|
|
2118
|
+
return {
|
|
2119
|
+
traceId,
|
|
2120
|
+
spanId,
|
|
2121
|
+
...(parentSpanId !== undefined ? { parentSpanId } : {}),
|
|
2122
|
+
...(typeof trace.sampled === "boolean" ? { sampled: trace.sampled } : {})
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
function normalizeTraceId(traceId) {
|
|
2127
|
+
try {
|
|
2128
|
+
requireTraceId(traceId);
|
|
2129
|
+
} catch {
|
|
2130
|
+
return undefined;
|
|
2131
|
+
}
|
|
2132
|
+
return traceId.toLowerCase();
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function normalizeSpanId(spanId) {
|
|
2136
|
+
try {
|
|
2137
|
+
requireSpanId("trace spanId", spanId);
|
|
2138
|
+
} catch {
|
|
2139
|
+
return undefined;
|
|
2140
|
+
}
|
|
2141
|
+
return spanId.toLowerCase();
|
|
2142
|
+
}
|
|
2143
|
+
|
|
945
2144
|
function requireNonEmpty(label, value) {
|
|
946
2145
|
if (typeof value !== "string" || value.trim() === "") {
|
|
947
2146
|
throw new SdkError("validation_error", `${label} must be non-empty`);
|
|
@@ -964,6 +2163,25 @@ function requireFiniteNumber(label, value) {
|
|
|
964
2163
|
}
|
|
965
2164
|
}
|
|
966
2165
|
|
|
2166
|
+
function requirePositiveInteger(label, value) {
|
|
2167
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
2168
|
+
throw new SdkError("validation_error", `${label} must be a positive integer`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function requireNonNegativeInteger(label, value) {
|
|
2173
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2174
|
+
throw new SdkError("validation_error", `${label} must be a non-negative integer`);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
function retryAfterMsOrUndefined(value) {
|
|
2179
|
+
if (value === undefined) {
|
|
2180
|
+
return undefined;
|
|
2181
|
+
}
|
|
2182
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
967
2185
|
function requireTraceId(traceId) {
|
|
968
2186
|
if (typeof traceId !== "string" || !/^[0-9a-fA-F]{32}$/u.test(traceId)) {
|
|
969
2187
|
throw new SdkError("validation_error", "traceId must be 32 lowercase or uppercase hex characters");
|
|
@@ -1013,10 +2231,29 @@ function cloneMetadata(metadata) {
|
|
|
1013
2231
|
return { ...metadata };
|
|
1014
2232
|
}
|
|
1015
2233
|
|
|
2234
|
+
function cloneSpanEvents(events) {
|
|
2235
|
+
return events.map((event) => event.metadata === undefined
|
|
2236
|
+
? { ...event }
|
|
2237
|
+
: { ...event, metadata: { ...event.metadata } });
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
function cloneSpanLinks(links) {
|
|
2241
|
+
return links.map((link) => link.metadata === undefined
|
|
2242
|
+
? { ...link }
|
|
2243
|
+
: { ...link, metadata: { ...link.metadata } });
|
|
2244
|
+
}
|
|
2245
|
+
|
|
1016
2246
|
function cloneEvent(event) {
|
|
1017
|
-
const attributes = event.attributes
|
|
1018
|
-
|
|
1019
|
-
|
|
2247
|
+
const attributes = { ...event.attributes };
|
|
2248
|
+
if (event.attributes.metadata !== undefined) {
|
|
2249
|
+
attributes.metadata = { ...event.attributes.metadata };
|
|
2250
|
+
}
|
|
2251
|
+
if (Array.isArray(event.attributes.events)) {
|
|
2252
|
+
attributes.events = cloneSpanEvents(event.attributes.events);
|
|
2253
|
+
}
|
|
2254
|
+
if (Array.isArray(event.attributes.links)) {
|
|
2255
|
+
attributes.links = cloneSpanLinks(event.attributes.links);
|
|
2256
|
+
}
|
|
1020
2257
|
return { ...event, attributes };
|
|
1021
2258
|
}
|
|
1022
2259
|
|
|
@@ -1043,10 +2280,12 @@ function validateEnvironment(attributes) {
|
|
|
1043
2280
|
function validateIssue(attributes) {
|
|
1044
2281
|
requireNonEmpty("issue title", attributes.title);
|
|
1045
2282
|
const level = normalizeSeverity("issue level", attributes.level);
|
|
2283
|
+
const stackFrames = validateIssueStackFrames(attributes.stackFrames);
|
|
1046
2284
|
return withMetadata({
|
|
1047
2285
|
title: attributes.title,
|
|
1048
2286
|
level,
|
|
1049
|
-
...(attributes.message !== undefined ? { message: attributes.message } : {})
|
|
2287
|
+
...(attributes.message !== undefined ? { message: attributes.message } : {}),
|
|
2288
|
+
...(stackFrames !== undefined ? { stackFrames } : {})
|
|
1050
2289
|
}, attributes.metadata);
|
|
1051
2290
|
}
|
|
1052
2291
|
|
|
@@ -1078,16 +2317,94 @@ function validateSpan(attributes) {
|
|
|
1078
2317
|
throw new SdkError("validation_error", "span durationMs must be non-negative");
|
|
1079
2318
|
}
|
|
1080
2319
|
}
|
|
2320
|
+
const events = validateSpanEvents(attributes.events);
|
|
2321
|
+
const links = validateSpanLinks(attributes.links);
|
|
1081
2322
|
return withMetadata({
|
|
1082
2323
|
name: attributes.name,
|
|
1083
2324
|
traceId: attributes.traceId,
|
|
1084
2325
|
spanId: attributes.spanId,
|
|
1085
2326
|
status: attributes.status,
|
|
1086
2327
|
...(attributes.parentSpanId !== undefined ? { parentSpanId: attributes.parentSpanId } : {}),
|
|
1087
|
-
...(attributes.durationMs !== undefined ? { durationMs: attributes.durationMs } : {})
|
|
2328
|
+
...(attributes.durationMs !== undefined ? { durationMs: attributes.durationMs } : {}),
|
|
2329
|
+
...(events !== undefined ? { events } : {}),
|
|
2330
|
+
...(links !== undefined ? { links } : {})
|
|
1088
2331
|
}, attributes.metadata);
|
|
1089
2332
|
}
|
|
1090
2333
|
|
|
2334
|
+
function validateSpanEvents(events) {
|
|
2335
|
+
if (events === undefined) {
|
|
2336
|
+
return undefined;
|
|
2337
|
+
}
|
|
2338
|
+
if (!Array.isArray(events)) {
|
|
2339
|
+
throw new SdkError("validation_error", "span events must be an array");
|
|
2340
|
+
}
|
|
2341
|
+
if (events.length > MAX_SPAN_EVENTS) {
|
|
2342
|
+
throw new SdkError("validation_error", `span events must contain at most ${MAX_SPAN_EVENTS} entries`);
|
|
2343
|
+
}
|
|
2344
|
+
if (events.length === 0) {
|
|
2345
|
+
return undefined;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
return events.map((event) => {
|
|
2349
|
+
if (!event || Array.isArray(event) || typeof event !== "object") {
|
|
2350
|
+
throw new SdkError("validation_error", "span event must be an object");
|
|
2351
|
+
}
|
|
2352
|
+
requireNonEmpty("span event name", event.name);
|
|
2353
|
+
if (event.timestamp !== undefined) {
|
|
2354
|
+
requireTimestamp(event.timestamp);
|
|
2355
|
+
}
|
|
2356
|
+
const summary = {
|
|
2357
|
+
name: event.name,
|
|
2358
|
+
...(event.timestamp !== undefined ? { timestamp: event.timestamp } : {})
|
|
2359
|
+
};
|
|
2360
|
+
if (event.metadata !== undefined) {
|
|
2361
|
+
const metadata = compactMetadata(event.metadata);
|
|
2362
|
+
if (Object.keys(metadata).length > 0) {
|
|
2363
|
+
summary.metadata = metadata;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
return summary;
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
function validateSpanLinks(links) {
|
|
2371
|
+
if (links === undefined) {
|
|
2372
|
+
return undefined;
|
|
2373
|
+
}
|
|
2374
|
+
if (!Array.isArray(links)) {
|
|
2375
|
+
throw new SdkError("validation_error", "span links must be an array");
|
|
2376
|
+
}
|
|
2377
|
+
if (links.length > MAX_SPAN_LINKS) {
|
|
2378
|
+
throw new SdkError("validation_error", `span links must contain at most ${MAX_SPAN_LINKS} entries`);
|
|
2379
|
+
}
|
|
2380
|
+
if (links.length === 0) {
|
|
2381
|
+
return undefined;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
return links.map((link) => {
|
|
2385
|
+
if (!link || Array.isArray(link) || typeof link !== "object") {
|
|
2386
|
+
throw new SdkError("validation_error", "span link must be an object");
|
|
2387
|
+
}
|
|
2388
|
+
requireTraceId(link.traceId);
|
|
2389
|
+
requireSpanId("span link spanId", link.spanId);
|
|
2390
|
+
if (link.sampled !== undefined && typeof link.sampled !== "boolean") {
|
|
2391
|
+
throw new SdkError("validation_error", "span link sampled must be a boolean");
|
|
2392
|
+
}
|
|
2393
|
+
const summary = {
|
|
2394
|
+
traceId: link.traceId.toLowerCase(),
|
|
2395
|
+
spanId: link.spanId.toLowerCase(),
|
|
2396
|
+
...(link.sampled !== undefined ? { sampled: link.sampled } : {})
|
|
2397
|
+
};
|
|
2398
|
+
if (link.metadata !== undefined) {
|
|
2399
|
+
const metadata = compactMetadata(link.metadata);
|
|
2400
|
+
if (Object.keys(metadata).length > 0) {
|
|
2401
|
+
summary.metadata = metadata;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
return summary;
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
|
|
1091
2408
|
function validateAction(attributes) {
|
|
1092
2409
|
requireNonEmpty("action name", attributes.name);
|
|
1093
2410
|
requireAllowedValue("action status", attributes.status, ACTION_STATUSES);
|
|
@@ -1317,21 +2634,34 @@ function formatConsoleArgument(value, includeErrorStack) {
|
|
|
1317
2634
|
}
|
|
1318
2635
|
|
|
1319
2636
|
module.exports = {
|
|
2637
|
+
createBaggage,
|
|
2638
|
+
createIssueAttributesFromError,
|
|
1320
2639
|
createNetworkMilestoneAttributes,
|
|
1321
2640
|
createProductActionAttributes,
|
|
2641
|
+
createLogBrewOpenTelemetrySpanExporter,
|
|
2642
|
+
createLogBrewOpenTelemetrySpanProcessor,
|
|
2643
|
+
createSupportTicketDraft,
|
|
2644
|
+
createTraceContextHeaders,
|
|
1322
2645
|
createTraceparent,
|
|
1323
2646
|
createTraceparentHeaders,
|
|
2647
|
+
createTracestate,
|
|
1324
2648
|
createLogBrewPinoDestination,
|
|
1325
2649
|
createLogBrewWinstonTransport,
|
|
1326
2650
|
installLogBrewConsoleCapture,
|
|
1327
2651
|
LogBrewClient,
|
|
2652
|
+
logbrewTraceContextFromCurrentOpenTelemetrySpan,
|
|
2653
|
+
logbrewTraceContextFromOpenTelemetrySpan,
|
|
2654
|
+
logbrewTraceContextFromOpenTelemetrySpanContext,
|
|
1328
2655
|
logAttributesFromConsoleArgs,
|
|
1329
2656
|
logAttributesFromPinoRecord,
|
|
1330
2657
|
logAttributesFromWinstonInfo,
|
|
1331
2658
|
logbrewLevelFromConsoleMethod,
|
|
2659
|
+
parseBaggage,
|
|
1332
2660
|
parseTraceparent,
|
|
2661
|
+
parseTracestate,
|
|
1333
2662
|
RecordingTransport,
|
|
1334
2663
|
SdkError,
|
|
2664
|
+
spanAttributesFromOpenTelemetryReadableSpan,
|
|
1335
2665
|
spanAttributesFromTraceparent,
|
|
1336
2666
|
TransportError
|
|
1337
2667
|
};
|