@bitfab/sdk 0.36.6 → 0.36.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BXWUPEC4.js → chunk-B4HK3BPE.js} +3 -3
- package/dist/{chunk-ENOQ2K2H.js → chunk-QUX6N7ON.js} +499 -195
- package/dist/chunk-QUX6N7ON.js.map +1 -0
- package/dist/index.cjs +511 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -8
- package/dist/index.d.ts +57 -8
- package/dist/index.js +2 -2
- package/dist/node.cjs +511 -199
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +2 -2
- package/dist/{replay-A757QXXM.js → replay-7PUB6ZOG.js} +6 -4
- package/package.json +1 -1
- package/dist/chunk-ENOQ2K2H.js.map +0 -1
- /package/dist/{chunk-BXWUPEC4.js.map → chunk-B4HK3BPE.js.map} +0 -0
- /package/dist/{replay-A757QXXM.js.map → replay-7PUB6ZOG.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var __version__;
|
|
|
42
42
|
var init_version_generated = __esm({
|
|
43
43
|
"src/version.generated.ts"() {
|
|
44
44
|
"use strict";
|
|
45
|
-
__version__ = "0.36.
|
|
45
|
+
__version__ = "0.36.8";
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
@@ -76,33 +76,58 @@ function toArrayBuffer(view) {
|
|
|
76
76
|
view.byteOffset + view.byteLength
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
|
+
function compressedRequest(body, rawBytes, compressed) {
|
|
80
|
+
if (compressed.byteLength >= rawBytes) {
|
|
81
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
|
|
85
|
+
contentEncoding: "gzip",
|
|
86
|
+
rawBytes,
|
|
87
|
+
wireBytes: compressed.byteLength
|
|
88
|
+
};
|
|
89
|
+
}
|
|
79
90
|
async function gzipViaStream(bytes) {
|
|
80
91
|
const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
81
92
|
return await new Response(stream).arrayBuffer();
|
|
82
93
|
}
|
|
83
94
|
function encodeRequestBody(body) {
|
|
84
95
|
if (readEnv(DISABLE_COMPRESSION_ENV)) {
|
|
85
|
-
|
|
96
|
+
const rawBytes = new TextEncoder().encode(body).byteLength;
|
|
97
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
86
98
|
}
|
|
87
99
|
const bytes = new TextEncoder().encode(body);
|
|
88
100
|
if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
|
|
89
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
body,
|
|
103
|
+
rawBytes: bytes.byteLength,
|
|
104
|
+
wireBytes: bytes.byteLength
|
|
105
|
+
};
|
|
90
106
|
}
|
|
91
107
|
if (gzipNode) {
|
|
92
108
|
return gzipNode(bytes).then(
|
|
93
|
-
(compressed) => (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
110
|
+
() => ({
|
|
111
|
+
body,
|
|
112
|
+
rawBytes: bytes.byteLength,
|
|
113
|
+
wireBytes: bytes.byteLength
|
|
114
|
+
})
|
|
98
115
|
);
|
|
99
116
|
}
|
|
100
117
|
if (typeof CompressionStream === "undefined") {
|
|
101
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
body,
|
|
120
|
+
rawBytes: bytes.byteLength,
|
|
121
|
+
wireBytes: bytes.byteLength
|
|
122
|
+
};
|
|
102
123
|
}
|
|
103
124
|
return gzipViaStream(bytes).then(
|
|
104
|
-
(compressed) => (
|
|
105
|
-
() => ({
|
|
125
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
126
|
+
() => ({
|
|
127
|
+
body,
|
|
128
|
+
rawBytes: bytes.byteLength,
|
|
129
|
+
wireBytes: bytes.byteLength
|
|
130
|
+
})
|
|
106
131
|
);
|
|
107
132
|
}
|
|
108
133
|
var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
|
|
@@ -143,10 +168,11 @@ var init_errors = __esm({
|
|
|
143
168
|
"src/errors.ts"() {
|
|
144
169
|
"use strict";
|
|
145
170
|
BitfabError = class extends Error {
|
|
146
|
-
constructor(message, url, status) {
|
|
171
|
+
constructor(message, url, status, retryAfterMs) {
|
|
147
172
|
super(message);
|
|
148
173
|
this.url = url;
|
|
149
174
|
this.status = status;
|
|
175
|
+
this.retryAfterMs = retryAfterMs;
|
|
150
176
|
this.name = "BitfabError";
|
|
151
177
|
}
|
|
152
178
|
};
|
|
@@ -247,15 +273,15 @@ function carrierBytesOf(encoded, body) {
|
|
|
247
273
|
}
|
|
248
274
|
return encoded.length + extra;
|
|
249
275
|
}
|
|
250
|
-
function fitsCarrierBudget(body) {
|
|
276
|
+
function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
251
277
|
const units = body.length;
|
|
252
|
-
if (units * MAX_BYTES_PER_UNIT + 2 <=
|
|
278
|
+
if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
|
|
253
279
|
return true;
|
|
254
280
|
}
|
|
255
|
-
if (units + 2 >
|
|
281
|
+
if (units + 2 > maxBytes) {
|
|
256
282
|
return false;
|
|
257
283
|
}
|
|
258
|
-
return carrierByteLength(body) <=
|
|
284
|
+
return carrierByteLength(body) <= maxBytes;
|
|
259
285
|
}
|
|
260
286
|
function asRecord(value) {
|
|
261
287
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
@@ -299,7 +325,7 @@ function collectCandidates(containers) {
|
|
|
299
325
|
}
|
|
300
326
|
return candidates.sort((a, b) => b.size - a.size);
|
|
301
327
|
}
|
|
302
|
-
function trimPayloadToBudget(payload, encode) {
|
|
328
|
+
function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
303
329
|
const { copy, containers } = cloneTrimmable(payload);
|
|
304
330
|
const candidates = collectCandidates(containers);
|
|
305
331
|
if (candidates.length === 0) {
|
|
@@ -315,30 +341,31 @@ function trimPayloadToBudget(payload, encode) {
|
|
|
315
341
|
} catch {
|
|
316
342
|
return void 0;
|
|
317
343
|
}
|
|
318
|
-
if (fitsCarrierBudget(body)) {
|
|
344
|
+
if (fitsCarrierBudget(body, maxBytes)) {
|
|
319
345
|
return { value: copy, trimmed };
|
|
320
346
|
}
|
|
321
347
|
}
|
|
322
348
|
return void 0;
|
|
323
349
|
}
|
|
324
|
-
function markPayloadTrimmed(value, trimmed) {
|
|
350
|
+
function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
325
351
|
const existing = Array.isArray(value.errors) ? value.errors : [];
|
|
326
352
|
value.errors = [
|
|
327
353
|
...existing,
|
|
328
354
|
{
|
|
329
355
|
source: "sdk",
|
|
330
356
|
step: "payload_budget",
|
|
331
|
-
error: `trimmed oversized field(s) to fit the ${
|
|
357
|
+
error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
|
|
332
358
|
...new Set(trimmed)
|
|
333
359
|
].join(", ")}`
|
|
334
360
|
}
|
|
335
361
|
];
|
|
336
362
|
}
|
|
337
|
-
var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
|
|
363
|
+
var MAX_SPAN_CARRIER_BYTES, MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
|
|
338
364
|
var init_payloadBudget = __esm({
|
|
339
365
|
"src/payloadBudget.ts"() {
|
|
340
366
|
"use strict";
|
|
341
367
|
MAX_SPAN_CARRIER_BYTES = 28e5;
|
|
368
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
|
|
342
369
|
textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
343
370
|
MAX_BYTES_PER_UNIT = 3;
|
|
344
371
|
STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -370,30 +397,31 @@ var init_warnOnce = __esm({
|
|
|
370
397
|
});
|
|
371
398
|
|
|
372
399
|
// src/serializePayload.ts
|
|
373
|
-
function serializePayloadBody(payload) {
|
|
400
|
+
function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
374
401
|
const encoded = encodePayloadBody(payload);
|
|
375
|
-
if (fitsCarrierBudget(encoded.body)) {
|
|
402
|
+
if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
|
|
376
403
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
377
404
|
}
|
|
378
|
-
return applyPayloadBudget(encoded);
|
|
405
|
+
return applyPayloadBudget(encoded, maxCarrierBytes);
|
|
379
406
|
}
|
|
380
|
-
function applyPayloadBudget(encoded) {
|
|
407
|
+
function applyPayloadBudget(encoded, maxCarrierBytes) {
|
|
381
408
|
const result = encoded.value ? trimPayloadToBudget(
|
|
382
409
|
encoded.value,
|
|
383
|
-
(value) => encodePayloadBody(value).body
|
|
410
|
+
(value) => encodePayloadBody(value).body,
|
|
411
|
+
maxCarrierBytes
|
|
384
412
|
) : void 0;
|
|
385
413
|
if (!result) {
|
|
386
414
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
387
415
|
}
|
|
388
416
|
warnOnce(
|
|
389
417
|
"payload:over-budget",
|
|
390
|
-
`a span payload exceeded the ${
|
|
418
|
+
`a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
|
|
391
419
|
...new Set(result.trimmed)
|
|
392
420
|
].join(
|
|
393
421
|
", "
|
|
394
422
|
)}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
|
|
395
423
|
);
|
|
396
|
-
markPayloadTrimmed(result.value, result.trimmed);
|
|
424
|
+
markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
|
|
397
425
|
return {
|
|
398
426
|
body: encodePayloadBody(result.value).body,
|
|
399
427
|
dropped: encoded.dropped
|
|
@@ -499,6 +527,23 @@ var init_serializePayload = __esm({
|
|
|
499
527
|
}
|
|
500
528
|
});
|
|
501
529
|
|
|
530
|
+
// src/transportTypes.ts
|
|
531
|
+
var DeliveryError;
|
|
532
|
+
var init_transportTypes = __esm({
|
|
533
|
+
"src/transportTypes.ts"() {
|
|
534
|
+
"use strict";
|
|
535
|
+
DeliveryError = class extends Error {
|
|
536
|
+
constructor(message, options = {}) {
|
|
537
|
+
super(message);
|
|
538
|
+
this.name = "DeliveryError";
|
|
539
|
+
this.retryable = options.retryable ?? false;
|
|
540
|
+
this.oversized = options.oversized ?? false;
|
|
541
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
502
547
|
// src/unrefTimer.ts
|
|
503
548
|
function unrefTimer(timer) {
|
|
504
549
|
const handle = timer;
|
|
@@ -538,59 +583,6 @@ function logError(message, error) {
|
|
|
538
583
|
} catch {
|
|
539
584
|
}
|
|
540
585
|
}
|
|
541
|
-
function recordTraceSubmission(operation, payload) {
|
|
542
|
-
const sourceTraceId = resolveSourceTraceId(payload);
|
|
543
|
-
if (sourceTraceId === void 0) {
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
if (operation === "external_span") {
|
|
547
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
548
|
-
if (typeof rawSpan?.id !== "string") {
|
|
549
|
-
submissionCounter += 1;
|
|
550
|
-
}
|
|
551
|
-
const sourceSpanId = typeof rawSpan?.id === "string" ? rawSpan.id : `submission-${submissionCounter}`;
|
|
552
|
-
const existing = traceSubmissionSpanIds.get(sourceTraceId);
|
|
553
|
-
if (existing) {
|
|
554
|
-
existing.add(sourceSpanId);
|
|
555
|
-
} else {
|
|
556
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set([sourceSpanId]));
|
|
557
|
-
}
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
if (payload.completed !== true) {
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
if (typeof payload.testRunId === "string") {
|
|
564
|
-
replayTraceSubmissions.add(sourceTraceId);
|
|
565
|
-
if (!traceSubmissionSpanIds.has(sourceTraceId)) {
|
|
566
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set());
|
|
567
|
-
}
|
|
568
|
-
} else {
|
|
569
|
-
traceSubmissionSpanIds.delete(sourceTraceId);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
function takeReplaySpanCounts(traceIds) {
|
|
573
|
-
const counts = {};
|
|
574
|
-
for (const traceId of traceIds) {
|
|
575
|
-
if (!replayTraceSubmissions.has(traceId)) {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
counts[traceId] = traceSubmissionSpanIds.get(traceId)?.size ?? 0;
|
|
579
|
-
traceSubmissionSpanIds.delete(traceId);
|
|
580
|
-
replayTraceSubmissions.delete(traceId);
|
|
581
|
-
}
|
|
582
|
-
return counts;
|
|
583
|
-
}
|
|
584
|
-
function asRecord2(value) {
|
|
585
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
586
|
-
}
|
|
587
|
-
function resolveSourceTraceId(payload) {
|
|
588
|
-
if (typeof payload.sourceTraceId === "string") {
|
|
589
|
-
return payload.sourceTraceId;
|
|
590
|
-
}
|
|
591
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
592
|
-
return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
|
|
593
|
-
}
|
|
594
586
|
function otlpValue(value) {
|
|
595
587
|
if (typeof value === "boolean") {
|
|
596
588
|
return { boolValue: value };
|
|
@@ -648,7 +640,36 @@ function spanToOtlp(span) {
|
|
|
648
640
|
}
|
|
649
641
|
function encodeSpan(span) {
|
|
650
642
|
const json = JSON.stringify(spanToOtlp(span));
|
|
651
|
-
return {
|
|
643
|
+
return {
|
|
644
|
+
json,
|
|
645
|
+
size: byteLength(json),
|
|
646
|
+
ref: carrierRefs.get(span)
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function trimEncodedSpan(span) {
|
|
650
|
+
try {
|
|
651
|
+
const carrier = JSON.parse(span.json);
|
|
652
|
+
const attribute = carrier.attributes?.find(
|
|
653
|
+
(entry) => entry.key === PAYLOAD_ATTRIBUTE
|
|
654
|
+
);
|
|
655
|
+
const payloadBody = attribute?.value?.stringValue;
|
|
656
|
+
if (!attribute?.value || payloadBody === void 0) {
|
|
657
|
+
return void 0;
|
|
658
|
+
}
|
|
659
|
+
const payload = JSON.parse(payloadBody);
|
|
660
|
+
attribute.value.stringValue = serializePayloadBody(
|
|
661
|
+
payload,
|
|
662
|
+
MAX_SPAN_CARRIER_BYTES
|
|
663
|
+
).body;
|
|
664
|
+
const json = JSON.stringify(carrier);
|
|
665
|
+
return { json, size: byteLength(json) };
|
|
666
|
+
} catch {
|
|
667
|
+
return void 0;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async function prepareRequest(body) {
|
|
671
|
+
const prepared = encodeRequestBody(body);
|
|
672
|
+
return prepared instanceof Promise ? await prepared : prepared;
|
|
652
673
|
}
|
|
653
674
|
function requestEnvelope(first) {
|
|
654
675
|
const scope = first.instrumentationScope;
|
|
@@ -706,48 +727,27 @@ async function mapWithConcurrency(items, limit, task) {
|
|
|
706
727
|
await Promise.all(workers);
|
|
707
728
|
return results;
|
|
708
729
|
}
|
|
709
|
-
function responseStatus(error) {
|
|
710
|
-
return error instanceof BitfabError ? error.status : void 0;
|
|
711
|
-
}
|
|
712
730
|
function isRetryable(error) {
|
|
713
|
-
|
|
714
|
-
if (status === void 0) {
|
|
715
|
-
return true;
|
|
716
|
-
}
|
|
717
|
-
return RETRYABLE_STATUSES.has(status) || status >= 500;
|
|
731
|
+
return error instanceof DeliveryError && error.retryable;
|
|
718
732
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
733
|
+
function isOversized(error) {
|
|
734
|
+
return error instanceof DeliveryError && error.oversized;
|
|
721
735
|
}
|
|
722
|
-
function
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
736
|
+
function retryWaitMillis(error, attempt, remainingMillis) {
|
|
737
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
738
|
+
const affordable = remainingMillis / 2;
|
|
739
|
+
if (requested !== void 0) {
|
|
740
|
+
return requested < affordable ? requested : null;
|
|
728
741
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
736
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
737
|
-
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
738
|
-
if (typeof raw !== "string") {
|
|
739
|
-
return void 0;
|
|
740
|
-
}
|
|
741
|
-
const parsed = Date.parse(raw);
|
|
742
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
742
|
+
const backoff = Math.min(
|
|
743
|
+
RETRY_BASE_DELAY_MILLIS * 2 ** attempt,
|
|
744
|
+
RETRY_BACKOFF_CEILING_MILLIS
|
|
745
|
+
);
|
|
746
|
+
const jittered = backoff / 2 + Math.random() * (backoff / 2);
|
|
747
|
+
return jittered < affordable ? jittered : null;
|
|
743
748
|
}
|
|
744
|
-
function
|
|
745
|
-
|
|
746
|
-
if (spanData?.error != null) {
|
|
747
|
-
return true;
|
|
748
|
-
}
|
|
749
|
-
const errors = payload.errors;
|
|
750
|
-
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
749
|
+
function endSpan(span, endTime) {
|
|
750
|
+
span.end(endTime);
|
|
751
751
|
}
|
|
752
752
|
function createOtelTransport(options) {
|
|
753
753
|
return new OtelBatchTransport({
|
|
@@ -786,7 +786,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
|
|
|
786
786
|
(transport, remaining) => transport.shutdown(remaining)
|
|
787
787
|
);
|
|
788
788
|
}
|
|
789
|
-
var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE,
|
|
789
|
+
var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, MAX_EXPORT_REQUEST_BYTES, MAX_DECOMPRESSED_REQUEST_BYTES, MAX_REQUEST_BYTES_ENV, EXPORT_CONCURRENCY_ENV, MAX_QUEUE_SIZE, DIRECT_MAX_EXPORT_BATCH_SIZE, DIRECT_MAX_REQUEST_BATCH_SIZE, DEFAULT_EXPORT_CONCURRENCY, MAX_EXPORT_CONCURRENCY, SCHEDULE_DELAY_MILLIS, EXPORT_TIMEOUT_MILLIS, RETRY_BASE_DELAY_MILLIS, RETRY_BACKOFF_CEILING_MILLIS, MAX_SEND_ATTEMPTS, DEFAULT_LIFECYCLE_TIMEOUT_MS, liveTransports, carrierRefs, SPAN_SEPARATOR_BYTES, BitfabSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
|
|
790
790
|
var init_otel = __esm({
|
|
791
791
|
"src/otel.ts"() {
|
|
792
792
|
"use strict";
|
|
@@ -794,17 +794,19 @@ var init_otel = __esm({
|
|
|
794
794
|
import_core = require("@opentelemetry/core");
|
|
795
795
|
import_resources = require("@opentelemetry/resources");
|
|
796
796
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
797
|
+
init_compress();
|
|
797
798
|
init_constants();
|
|
798
799
|
init_errors();
|
|
799
800
|
init_payloadBudget();
|
|
800
801
|
init_readEnv();
|
|
801
802
|
init_serializePayload();
|
|
803
|
+
init_transportTypes();
|
|
802
804
|
init_unrefTimer();
|
|
803
805
|
init_warnOnce();
|
|
804
806
|
OPERATION_ATTRIBUTE = "bitfab.operation";
|
|
805
807
|
PAYLOAD_ATTRIBUTE = "bitfab.payload";
|
|
806
|
-
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
807
808
|
MAX_EXPORT_REQUEST_BYTES = 3e6;
|
|
809
|
+
MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
|
|
808
810
|
MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
|
|
809
811
|
EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
|
|
810
812
|
MAX_QUEUE_SIZE = 8192;
|
|
@@ -814,25 +816,23 @@ var init_otel = __esm({
|
|
|
814
816
|
MAX_EXPORT_CONCURRENCY = 64;
|
|
815
817
|
SCHEDULE_DELAY_MILLIS = 5e3;
|
|
816
818
|
EXPORT_TIMEOUT_MILLIS = 3e4;
|
|
817
|
-
|
|
819
|
+
RETRY_BASE_DELAY_MILLIS = 100;
|
|
820
|
+
RETRY_BACKOFF_CEILING_MILLIS = 5e3;
|
|
818
821
|
MAX_SEND_ATTEMPTS = 3;
|
|
819
822
|
DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
820
|
-
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
821
823
|
liveTransports = /* @__PURE__ */ new Set();
|
|
822
|
-
|
|
823
|
-
replayTraceSubmissions = /* @__PURE__ */ new Set();
|
|
824
|
-
submissionCounter = 0;
|
|
824
|
+
carrierRefs = /* @__PURE__ */ new WeakMap();
|
|
825
825
|
SPAN_SEPARATOR_BYTES = 1;
|
|
826
|
-
OtlpPayloadTooLargeError = class extends Error {
|
|
827
|
-
};
|
|
828
|
-
OtlpPartialSuccessError = class extends Error {
|
|
829
|
-
};
|
|
830
826
|
BitfabSpanExporter = class {
|
|
831
|
-
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency) {
|
|
827
|
+
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency, onDelivered, exportTimeoutMillis = EXPORT_TIMEOUT_MILLIS) {
|
|
832
828
|
this.directSender = directSender;
|
|
833
829
|
this.maxRequestBytes = maxRequestBytes;
|
|
834
830
|
this.maxRequestBatchSize = maxRequestBatchSize;
|
|
835
831
|
this.exportConcurrency = exportConcurrency;
|
|
832
|
+
this.onDelivered = onDelivered;
|
|
833
|
+
this.exportTimeoutMillis = exportTimeoutMillis;
|
|
834
|
+
/** Epoch ms until which the server has asked this exporter to stay away. */
|
|
835
|
+
this.throttledUntil = 0;
|
|
836
836
|
}
|
|
837
837
|
export(spans, resultCallback) {
|
|
838
838
|
void this.exportAsync(spans).then(
|
|
@@ -887,25 +887,51 @@ var init_otel = __esm({
|
|
|
887
887
|
return batches;
|
|
888
888
|
}
|
|
889
889
|
async send(envelope, batch) {
|
|
890
|
-
if (batch.size > this.maxRequestBytes) {
|
|
891
|
-
logError(
|
|
892
|
-
"a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
|
|
893
|
-
);
|
|
894
|
-
return false;
|
|
895
|
-
}
|
|
896
890
|
try {
|
|
897
|
-
|
|
898
|
-
|
|
891
|
+
let requestSpans = batch.spans;
|
|
892
|
+
let requestRawBytes = batch.size;
|
|
893
|
+
let alreadyTrimmed = false;
|
|
894
|
+
while (true) {
|
|
895
|
+
if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
|
|
896
|
+
const prepared = await prepareRequest(
|
|
897
|
+
encodeRequest(envelope, requestSpans)
|
|
898
|
+
);
|
|
899
|
+
if (prepared.wireBytes <= this.maxRequestBytes) {
|
|
900
|
+
await this.sendWithRetries(prepared);
|
|
901
|
+
this.reportDelivered(batch.spans);
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (batch.spans.length !== 1) {
|
|
906
|
+
logError(
|
|
907
|
+
"an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
|
|
908
|
+
);
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
if (alreadyTrimmed) {
|
|
912
|
+
logError(
|
|
913
|
+
"a single OpenTelemetry span exceeded the configured request-size target after trimming"
|
|
914
|
+
);
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
const trimmed = trimEncodedSpan(batch.spans[0]);
|
|
918
|
+
if (!trimmed) {
|
|
919
|
+
logError(
|
|
920
|
+
"a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
|
|
921
|
+
);
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
requestSpans = [trimmed];
|
|
925
|
+
requestRawBytes = envelope.size + trimmed.size;
|
|
926
|
+
alreadyTrimmed = true;
|
|
927
|
+
}
|
|
899
928
|
} catch (error) {
|
|
900
|
-
if (error
|
|
929
|
+
if (isOversized(error)) {
|
|
901
930
|
logError(
|
|
902
931
|
batch.spans.length === 1 ? "a single OpenTelemetry span exceeded the ingestion request limit and could not be exported" : "an OpenTelemetry span batch exceeded the ingestion request limit and could not be exported"
|
|
903
932
|
);
|
|
904
933
|
return false;
|
|
905
934
|
}
|
|
906
|
-
if (error instanceof OtlpPartialSuccessError) {
|
|
907
|
-
return false;
|
|
908
|
-
}
|
|
909
935
|
logError("failed to export an OpenTelemetry span batch", error);
|
|
910
936
|
return false;
|
|
911
937
|
}
|
|
@@ -923,37 +949,79 @@ var init_otel = __esm({
|
|
|
923
949
|
* the server does not yet understand. The fix is a client-supplied
|
|
924
950
|
* idempotency key that ingestion dedupes on.
|
|
925
951
|
*/
|
|
926
|
-
|
|
952
|
+
/**
|
|
953
|
+
* Remember a throttle the server asked for, so the requests fanned out
|
|
954
|
+
* alongside this one respect it too. Delaying only the request that was
|
|
955
|
+
* refused leaves the other seven in the window hitting a server that just
|
|
956
|
+
* asked for room.
|
|
957
|
+
*/
|
|
958
|
+
recordThrottle(error) {
|
|
959
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
960
|
+
if (requested !== void 0) {
|
|
961
|
+
this.throttledUntil = Math.max(
|
|
962
|
+
this.throttledUntil,
|
|
963
|
+
Date.now() + requested
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Waits out an active throttle, or reports the batch undeliverable when the
|
|
969
|
+
* throttle outlasts what we are willing to hold it for. Either way nothing is
|
|
970
|
+
* sent while the server has asked us to stay away.
|
|
971
|
+
*/
|
|
972
|
+
async awaitThrottle(deadline) {
|
|
973
|
+
const remaining = this.throttledUntil - Date.now();
|
|
974
|
+
if (remaining <= 0) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (remaining >= (deadline - Date.now()) / 2) {
|
|
978
|
+
throw new DeliveryError(
|
|
979
|
+
`OTLP ingestion is throttled for another ${remaining}ms, longer than the export budget`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
await delay(remaining);
|
|
983
|
+
}
|
|
984
|
+
async sendWithRetries(request) {
|
|
985
|
+
const deadline = Date.now() + this.exportTimeoutMillis;
|
|
927
986
|
for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
|
|
928
987
|
try {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
body,
|
|
932
|
-
EXPORT_TIMEOUT_MILLIS
|
|
933
|
-
);
|
|
934
|
-
const partialSuccess = asRecord2(response?.partialSuccess);
|
|
935
|
-
const rejected = partialSuccess?.rejectedSpans;
|
|
936
|
-
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
937
|
-
logError(
|
|
938
|
-
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
939
|
-
);
|
|
940
|
-
throw new OtlpPartialSuccessError();
|
|
941
|
-
}
|
|
988
|
+
await this.awaitThrottle(deadline);
|
|
989
|
+
await this.directSender(request, Math.max(0, deadline - Date.now()));
|
|
942
990
|
return;
|
|
943
991
|
} catch (error) {
|
|
944
|
-
if (error
|
|
992
|
+
if (isOversized(error)) {
|
|
945
993
|
throw error;
|
|
946
994
|
}
|
|
947
|
-
|
|
948
|
-
throw new OtlpPayloadTooLargeError();
|
|
949
|
-
}
|
|
995
|
+
this.recordThrottle(error);
|
|
950
996
|
if (attempt === MAX_SEND_ATTEMPTS - 1 || !isRetryable(error)) {
|
|
951
997
|
throw error;
|
|
952
998
|
}
|
|
953
|
-
|
|
999
|
+
const wait = retryWaitMillis(error, attempt, deadline - Date.now());
|
|
1000
|
+
if (wait === null) {
|
|
1001
|
+
throw error;
|
|
1002
|
+
}
|
|
1003
|
+
await delay(wait);
|
|
954
1004
|
}
|
|
955
1005
|
}
|
|
956
1006
|
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Announce the carriers a request delivered. Wrapped because a listener that
|
|
1009
|
+
* throws must never turn a delivered batch into a failed export.
|
|
1010
|
+
*/
|
|
1011
|
+
reportDelivered(spans) {
|
|
1012
|
+
if (this.onDelivered === void 0) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const refs = spans.map((span) => span.ref).filter((ref) => ref !== void 0);
|
|
1016
|
+
if (refs.length === 0) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
this.onDelivered(refs);
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
logError("a delivery listener threw", error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
957
1025
|
async shutdown() {
|
|
958
1026
|
}
|
|
959
1027
|
async forceFlush() {
|
|
@@ -1011,7 +1079,9 @@ var init_otel = __esm({
|
|
|
1011
1079
|
options.directSender,
|
|
1012
1080
|
maxRequestBytes,
|
|
1013
1081
|
maxRequestBatchSize,
|
|
1014
|
-
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
|
|
1082
|
+
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY,
|
|
1083
|
+
options.onDelivered,
|
|
1084
|
+
options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
|
|
1015
1085
|
)
|
|
1016
1086
|
);
|
|
1017
1087
|
this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
|
|
@@ -1035,8 +1105,7 @@ var init_otel = __esm({
|
|
|
1035
1105
|
this.tracer = this.provider.getTracer("bitfab", __version__);
|
|
1036
1106
|
liveTransports.add(this);
|
|
1037
1107
|
}
|
|
1038
|
-
submit(operation, payload) {
|
|
1039
|
-
recordTraceSubmission(operation, payload);
|
|
1108
|
+
submit(operation, payload, meta = {}) {
|
|
1040
1109
|
if (this.closed) {
|
|
1041
1110
|
warnOnce(
|
|
1042
1111
|
"otel-submit-after-shutdown",
|
|
@@ -1045,7 +1114,10 @@ var init_otel = __esm({
|
|
|
1045
1114
|
return;
|
|
1046
1115
|
}
|
|
1047
1116
|
try {
|
|
1048
|
-
const { body, dropped } = serializePayloadBody(
|
|
1117
|
+
const { body, dropped } = serializePayloadBody(
|
|
1118
|
+
payload,
|
|
1119
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
|
|
1120
|
+
);
|
|
1049
1121
|
if (dropped.length > 0) {
|
|
1050
1122
|
warnOnce(
|
|
1051
1123
|
"otel-carrier-payload-stubbed",
|
|
@@ -1054,17 +1126,20 @@ var init_otel = __esm({
|
|
|
1054
1126
|
].join(", ")}); they were stubbed so the span still ships, but the trace may be incomplete or not replayable.`
|
|
1055
1127
|
);
|
|
1056
1128
|
}
|
|
1057
|
-
const span = this.tracer.startSpan(
|
|
1129
|
+
const span = this.tracer.startSpan(meta.name ?? `bitfab.${operation}`, {
|
|
1058
1130
|
attributes: {
|
|
1059
1131
|
[OPERATION_ATTRIBUTE]: operation,
|
|
1060
1132
|
[PAYLOAD_ATTRIBUTE]: body
|
|
1061
1133
|
},
|
|
1062
|
-
startTime:
|
|
1134
|
+
startTime: meta.startTime
|
|
1063
1135
|
});
|
|
1064
|
-
if (
|
|
1136
|
+
if (meta.ref !== void 0) {
|
|
1137
|
+
carrierRefs.set(span, meta.ref);
|
|
1138
|
+
}
|
|
1139
|
+
if (meta.errored === true) {
|
|
1065
1140
|
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
1066
1141
|
}
|
|
1067
|
-
endSpan(span,
|
|
1142
|
+
endSpan(span, meta.endTime);
|
|
1068
1143
|
} catch (error) {
|
|
1069
1144
|
logError("failed to queue an OpenTelemetry span", error);
|
|
1070
1145
|
}
|
|
@@ -1114,9 +1189,6 @@ function flushTraceTransports(timeoutMs) {
|
|
|
1114
1189
|
function shutdownTraceTransports(timeoutMs) {
|
|
1115
1190
|
return shutdownOtelTransports(timeoutMs);
|
|
1116
1191
|
}
|
|
1117
|
-
function takeReplaySpanCounts2(traceIds) {
|
|
1118
|
-
return takeReplaySpanCounts(traceIds);
|
|
1119
|
-
}
|
|
1120
1192
|
var init_transport = __esm({
|
|
1121
1193
|
"src/transport.ts"() {
|
|
1122
1194
|
"use strict";
|
|
@@ -1165,7 +1237,96 @@ async function waitForPromises(promises, timeoutMs) {
|
|
|
1165
1237
|
}
|
|
1166
1238
|
}
|
|
1167
1239
|
}
|
|
1168
|
-
|
|
1240
|
+
function readHeader(response, name) {
|
|
1241
|
+
try {
|
|
1242
|
+
return response.headers?.get(name) ?? null;
|
|
1243
|
+
} catch {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
function parseRetryAfterMs(header) {
|
|
1248
|
+
const value = header?.trim();
|
|
1249
|
+
if (!value) {
|
|
1250
|
+
return void 0;
|
|
1251
|
+
}
|
|
1252
|
+
const seconds = Number(value);
|
|
1253
|
+
if (Number.isFinite(seconds)) {
|
|
1254
|
+
return seconds >= 0 ? seconds * 1e3 : void 0;
|
|
1255
|
+
}
|
|
1256
|
+
const at = Date.parse(value);
|
|
1257
|
+
if (Number.isNaN(at)) {
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
return Math.max(0, at - Date.now());
|
|
1261
|
+
}
|
|
1262
|
+
function carrierMeta(operation, payload, ref) {
|
|
1263
|
+
return {
|
|
1264
|
+
ref,
|
|
1265
|
+
name: carrierName(operation, payload),
|
|
1266
|
+
startTime: payloadTimestamp(payload, "started_at"),
|
|
1267
|
+
endTime: payloadTimestamp(payload, "ended_at"),
|
|
1268
|
+
errored: payloadHasError(payload)
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
function carrierName(operation, payload) {
|
|
1272
|
+
if (operation === "external_span") {
|
|
1273
|
+
const spanData = asPayloadRecord(
|
|
1274
|
+
asPayloadRecord(payload.rawSpan)?.span_data
|
|
1275
|
+
);
|
|
1276
|
+
if (typeof spanData?.name === "string") {
|
|
1277
|
+
return spanData.name;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (typeof payload.traceFunctionKey === "string") {
|
|
1281
|
+
return payload.traceFunctionKey;
|
|
1282
|
+
}
|
|
1283
|
+
return `bitfab.${operation}`;
|
|
1284
|
+
}
|
|
1285
|
+
function payloadTimestamp(payload, field) {
|
|
1286
|
+
const rawSpan = asPayloadRecord(payload.rawSpan);
|
|
1287
|
+
const rawTrace = asPayloadRecord(payload.externalTrace) ?? asPayloadRecord(payload.rawTrace);
|
|
1288
|
+
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
1289
|
+
if (typeof raw !== "string") {
|
|
1290
|
+
return void 0;
|
|
1291
|
+
}
|
|
1292
|
+
const parsed = Date.parse(raw);
|
|
1293
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1294
|
+
}
|
|
1295
|
+
function payloadHasError(payload) {
|
|
1296
|
+
const spanData = asPayloadRecord(asPayloadRecord(payload.rawSpan)?.span_data);
|
|
1297
|
+
if (spanData?.error != null) {
|
|
1298
|
+
return true;
|
|
1299
|
+
}
|
|
1300
|
+
const errors = payload.errors;
|
|
1301
|
+
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
1302
|
+
}
|
|
1303
|
+
function asPayloadRecord(value) {
|
|
1304
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1305
|
+
}
|
|
1306
|
+
function carrierRef(payload) {
|
|
1307
|
+
const traceId = sourceTraceIdOf(payload);
|
|
1308
|
+
if (traceId === void 0) {
|
|
1309
|
+
return void 0;
|
|
1310
|
+
}
|
|
1311
|
+
const rawSpan = payload.rawSpan;
|
|
1312
|
+
if (rawSpan === void 0) {
|
|
1313
|
+
return { traceId };
|
|
1314
|
+
}
|
|
1315
|
+
const spanId = rawSpan?.id;
|
|
1316
|
+
return {
|
|
1317
|
+
traceId,
|
|
1318
|
+
spanId: typeof spanId === "string" ? spanId : `submission-${++carrierSeq}`
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function sourceTraceIdOf(payload) {
|
|
1322
|
+
if (typeof payload.sourceTraceId === "string") {
|
|
1323
|
+
return payload.sourceTraceId;
|
|
1324
|
+
}
|
|
1325
|
+
const rawTrace = payload.externalTrace ?? payload.rawTrace;
|
|
1326
|
+
const id = rawTrace?.id;
|
|
1327
|
+
return typeof id === "string" ? id : void 0;
|
|
1328
|
+
}
|
|
1329
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
|
|
1169
1330
|
var init_http = __esm({
|
|
1170
1331
|
"src/http.ts"() {
|
|
1171
1332
|
"use strict";
|
|
@@ -1175,9 +1336,12 @@ var init_http = __esm({
|
|
|
1175
1336
|
init_replayContext();
|
|
1176
1337
|
init_serializePayload();
|
|
1177
1338
|
init_transport();
|
|
1339
|
+
init_transportTypes();
|
|
1178
1340
|
init_unrefTimer();
|
|
1179
1341
|
init_warnOnce();
|
|
1180
1342
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
1343
|
+
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
1344
|
+
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1181
1345
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
1182
1346
|
DEFAULT_LIFECYCLE_TIMEOUT_MS2 = 3e4;
|
|
1183
1347
|
pendingTracePromises = /* @__PURE__ */ new Set();
|
|
@@ -1197,8 +1361,12 @@ var init_http = __esm({
|
|
|
1197
1361
|
});
|
|
1198
1362
|
});
|
|
1199
1363
|
}
|
|
1364
|
+
carrierSeq = 0;
|
|
1200
1365
|
HttpClient = class {
|
|
1201
1366
|
constructor(config) {
|
|
1367
|
+
// Only traces a caller asked about are tracked, so ordinary tracing stores
|
|
1368
|
+
// nothing here.
|
|
1369
|
+
this.traceDeliveries = /* @__PURE__ */ new Map();
|
|
1202
1370
|
// Deferred span work owned by THIS client. The module-global set backs the
|
|
1203
1371
|
// process-wide `flushTraces()` and the exit hook, but per-client lifecycle
|
|
1204
1372
|
// must not wait on another client's slow finalize: a false `close()` failure
|
|
@@ -1234,13 +1402,131 @@ var init_http = __esm({
|
|
|
1234
1402
|
}
|
|
1235
1403
|
if (!this.traceTransport) {
|
|
1236
1404
|
this.traceTransport = createTraceTransport({
|
|
1237
|
-
directSender: (
|
|
1238
|
-
|
|
1239
|
-
})
|
|
1405
|
+
directSender: (request, timeoutMs) => this.deliverCarriers(request, timeoutMs),
|
|
1406
|
+
onDelivered: (refs) => this.recordDeliveredCarriers(refs)
|
|
1240
1407
|
});
|
|
1241
1408
|
}
|
|
1242
1409
|
return this.traceTransport;
|
|
1243
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Post one encoded batch and decide what the server's answer means, so the
|
|
1413
|
+
* transport never reads a response. Rejections and permanent statuses come
|
|
1414
|
+
* back as a non-retryable {@link DeliveryError}; anything the server might
|
|
1415
|
+
* still accept on a second try comes back retryable.
|
|
1416
|
+
*/
|
|
1417
|
+
async deliverCarriers(request, timeoutMs) {
|
|
1418
|
+
let response;
|
|
1419
|
+
try {
|
|
1420
|
+
response = await this.sendPrepared(
|
|
1421
|
+
OTLP_TRACES_ENDPOINT,
|
|
1422
|
+
request,
|
|
1423
|
+
{ timeout: timeoutMs }
|
|
1424
|
+
);
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
const status = error instanceof BitfabError ? error.status : void 0;
|
|
1427
|
+
if (status === void 0) {
|
|
1428
|
+
throw new DeliveryError(`OTLP ingestion failed: ${String(error)}`, {
|
|
1429
|
+
retryable: true
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
throw new DeliveryError(`OTLP ingestion failed with HTTP ${status}`, {
|
|
1433
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
1434
|
+
oversized: status === 413,
|
|
1435
|
+
...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
const partialSuccess = asPayloadRecord(response?.partialSuccess);
|
|
1439
|
+
const rejected = partialSuccess?.rejectedSpans;
|
|
1440
|
+
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1441
|
+
throw new DeliveryError(
|
|
1442
|
+
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Start tracking delivery for `traceIds`. Nothing is recorded for a trace
|
|
1448
|
+
* that was never tracked, so ordinary tracing costs no bookkeeping at all.
|
|
1449
|
+
*/
|
|
1450
|
+
trackTraceDeliveries(traceIds) {
|
|
1451
|
+
for (const traceId of traceIds) {
|
|
1452
|
+
if (!this.traceDeliveries.has(traceId)) {
|
|
1453
|
+
this.traceDeliveries.set(traceId, {
|
|
1454
|
+
submittedSpanIds: /* @__PURE__ */ new Set(),
|
|
1455
|
+
ackedSpanIds: /* @__PURE__ */ new Set(),
|
|
1456
|
+
closed: false,
|
|
1457
|
+
closingAcked: false
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/** Whether any tracked trace has had its closing carrier submitted. */
|
|
1463
|
+
hasClosedDeliveries(traceIds) {
|
|
1464
|
+
return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Report what each tracked trace submitted and whether the server confirmed
|
|
1468
|
+
* it, and stop tracking them. Every id passed is freed, so a caller cannot
|
|
1469
|
+
* leak a record for a trace that never closed.
|
|
1470
|
+
*
|
|
1471
|
+
* `delivered` is only meaningful once a flush has settled: acks land before
|
|
1472
|
+
* an export resolves, so a flush that reported success has already collected
|
|
1473
|
+
* every ack it is going to collect.
|
|
1474
|
+
*/
|
|
1475
|
+
takeTraceDeliveries(traceIds) {
|
|
1476
|
+
const reports = {};
|
|
1477
|
+
for (const traceId of traceIds) {
|
|
1478
|
+
const delivery = this.traceDeliveries.get(traceId);
|
|
1479
|
+
if (delivery === void 0) {
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
this.traceDeliveries.delete(traceId);
|
|
1483
|
+
reports[traceId] = {
|
|
1484
|
+
spanCount: delivery.submittedSpanIds.size,
|
|
1485
|
+
closed: delivery.closed,
|
|
1486
|
+
delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
|
|
1487
|
+
(spanId) => delivery.ackedSpanIds.has(spanId)
|
|
1488
|
+
)
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
return reports;
|
|
1492
|
+
}
|
|
1493
|
+
/** Build a carrier's meta and record what it adds to its trace's expected set. */
|
|
1494
|
+
recordedMeta(operation, payload, ref) {
|
|
1495
|
+
this.recordSubmittedCarrier(ref);
|
|
1496
|
+
return carrierMeta(operation, payload, ref);
|
|
1497
|
+
}
|
|
1498
|
+
recordSubmittedCarrier(ref) {
|
|
1499
|
+
if (ref === void 0) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1503
|
+
if (delivery === void 0) {
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
if (ref.spanId === void 0) {
|
|
1507
|
+
delivery.closed = true;
|
|
1508
|
+
} else {
|
|
1509
|
+
delivery.submittedSpanIds.add(ref.spanId);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Ingestion commits every carrier in a request before it answers, so a
|
|
1514
|
+
* delivered ref is proof its row exists: the same fact the replay status
|
|
1515
|
+
* endpoint would report, already in hand.
|
|
1516
|
+
*/
|
|
1517
|
+
recordDeliveredCarriers(refs) {
|
|
1518
|
+
for (const ref of refs) {
|
|
1519
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1520
|
+
if (delivery === void 0) {
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (ref.spanId === void 0) {
|
|
1524
|
+
delivery.closingAcked = true;
|
|
1525
|
+
} else {
|
|
1526
|
+
delivery.ackedSpanIds.add(ref.spanId);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1244
1530
|
/**
|
|
1245
1531
|
* Track deferred span work so this client's own lifecycle waits for it, and
|
|
1246
1532
|
* so the process-wide flush and exit hook do too.
|
|
@@ -1321,13 +1607,16 @@ var init_http = __esm({
|
|
|
1321
1607
|
* same data twice.
|
|
1322
1608
|
*/
|
|
1323
1609
|
async sendEncoded(endpoint, body, options) {
|
|
1610
|
+
const prepared = encodeRequestBody(body);
|
|
1611
|
+
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1612
|
+
return this.sendPrepared(endpoint, encoded, options);
|
|
1613
|
+
}
|
|
1614
|
+
async sendPrepared(endpoint, encoded, options) {
|
|
1324
1615
|
const url = `${this.serviceUrl}${endpoint}`;
|
|
1325
1616
|
const timeout = options?.timeout ?? this.timeout;
|
|
1326
1617
|
const method = options?.method ?? "POST";
|
|
1327
1618
|
const controller = new AbortController();
|
|
1328
1619
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1329
|
-
const prepared = encodeRequestBody(body);
|
|
1330
|
-
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1331
1620
|
const headers = {
|
|
1332
1621
|
"Content-Type": "application/json",
|
|
1333
1622
|
Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
|
|
@@ -1347,7 +1636,8 @@ var init_http = __esm({
|
|
|
1347
1636
|
throw new BitfabError(
|
|
1348
1637
|
`HTTP ${response.status}: ${errorText.slice(0, 500)}`,
|
|
1349
1638
|
void 0,
|
|
1350
|
-
response.status
|
|
1639
|
+
response.status,
|
|
1640
|
+
parseRetryAfterMs(readHeader(response, "retry-after"))
|
|
1351
1641
|
);
|
|
1352
1642
|
}
|
|
1353
1643
|
const result = await response.json();
|
|
@@ -1433,11 +1723,12 @@ var init_http = __esm({
|
|
|
1433
1723
|
* the OTLP carrier has no path to carry it.
|
|
1434
1724
|
*/
|
|
1435
1725
|
sendInternalTrace(functionId, payload) {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1726
|
+
const body = { ...payload, functionId, sdkVersion: __version__ };
|
|
1727
|
+
this.getTraceTransport()?.submit(
|
|
1728
|
+
"internal_trace",
|
|
1729
|
+
body,
|
|
1730
|
+
carrierMeta("internal_trace", body, void 0)
|
|
1731
|
+
);
|
|
1441
1732
|
}
|
|
1442
1733
|
/**
|
|
1443
1734
|
* Queue an external span (from withSpan wrapper or OpenAI tracing) onto this
|
|
@@ -1446,10 +1737,11 @@ var init_http = __esm({
|
|
|
1446
1737
|
* promise.
|
|
1447
1738
|
*/
|
|
1448
1739
|
sendExternalSpan(payload) {
|
|
1449
|
-
this.getTraceTransport()?.submit(
|
|
1450
|
-
|
|
1451
|
-
sdkVersion: __version__
|
|
1452
|
-
|
|
1740
|
+
this.getTraceTransport()?.submit(
|
|
1741
|
+
"external_span",
|
|
1742
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1743
|
+
this.recordedMeta("external_span", payload, carrierRef(payload))
|
|
1744
|
+
);
|
|
1453
1745
|
}
|
|
1454
1746
|
/**
|
|
1455
1747
|
* Queue an external trace completion (from OpenAI tracing) onto this
|
|
@@ -1458,10 +1750,15 @@ var init_http = __esm({
|
|
|
1458
1750
|
* server-authoritative barrier in `replay.ts`, not by awaiting this call.
|
|
1459
1751
|
*/
|
|
1460
1752
|
sendExternalTrace(payload) {
|
|
1461
|
-
this.getTraceTransport()?.submit(
|
|
1462
|
-
|
|
1463
|
-
sdkVersion: __version__
|
|
1464
|
-
|
|
1753
|
+
this.getTraceTransport()?.submit(
|
|
1754
|
+
"external_trace",
|
|
1755
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1756
|
+
this.recordedMeta(
|
|
1757
|
+
"external_trace",
|
|
1758
|
+
payload,
|
|
1759
|
+
payload.completed === true ? carrierRef(payload) : void 0
|
|
1760
|
+
)
|
|
1761
|
+
);
|
|
1465
1762
|
}
|
|
1466
1763
|
/**
|
|
1467
1764
|
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
@@ -1801,8 +2098,8 @@ var init_serialize = __esm({
|
|
|
1801
2098
|
import_superjson = __toESM(require("superjson"), 1);
|
|
1802
2099
|
init_payloadBudget();
|
|
1803
2100
|
init_warnOnce();
|
|
1804
|
-
MAX_SERIALIZED_BYTES =
|
|
1805
|
-
MAX_FRAMEWORK_SERIALIZED_BYTES =
|
|
2101
|
+
MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
2102
|
+
MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
1806
2103
|
MAX_SAFE_DEPTH = 6;
|
|
1807
2104
|
}
|
|
1808
2105
|
});
|
|
@@ -2057,7 +2354,8 @@ __export(replay_exports, {
|
|
|
2057
2354
|
ReplayError: () => ReplayError,
|
|
2058
2355
|
replay: () => replay,
|
|
2059
2356
|
reportReplayProgress: () => reportReplayProgress,
|
|
2060
|
-
serializeReplayResult: () => serializeReplayResult
|
|
2357
|
+
serializeReplayResult: () => serializeReplayResult,
|
|
2358
|
+
waitForReplayPersistence: () => waitForReplayPersistence
|
|
2061
2359
|
});
|
|
2062
2360
|
function dbBranchEnabled(dbBranch) {
|
|
2063
2361
|
return dbBranch !== void 0 && dbBranch !== false;
|
|
@@ -2345,15 +2643,29 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
|
|
|
2345
2643
|
REPLAY_PERSISTENCE_TIMEOUT_MS
|
|
2346
2644
|
);
|
|
2347
2645
|
if (!deferredSettled) {
|
|
2646
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2348
2647
|
throw new BitfabError(
|
|
2349
2648
|
`Replay could not settle deferred span work before the deadline, so the expected span counts are incomplete (testRunId ${testRunId}).`
|
|
2350
2649
|
);
|
|
2351
2650
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2651
|
+
if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
|
|
2652
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2354
2653
|
return;
|
|
2355
2654
|
}
|
|
2356
2655
|
const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
|
|
2656
|
+
const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2657
|
+
const expectedSpanCounts = {};
|
|
2658
|
+
let allDelivered = true;
|
|
2659
|
+
for (const [traceId, delivery] of Object.entries(deliveries)) {
|
|
2660
|
+
if (!delivery.closed) {
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
expectedSpanCounts[traceId] = delivery.spanCount;
|
|
2664
|
+
allDelivered = allDelivered && delivery.delivered;
|
|
2665
|
+
}
|
|
2666
|
+
if (allDelivered) {
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2357
2669
|
const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
|
|
2358
2670
|
let missing = Object.keys(expectedSpanCounts).length;
|
|
2359
2671
|
while (true) {
|
|
@@ -2462,6 +2774,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
2462
2774
|
...registeredOverrides
|
|
2463
2775
|
];
|
|
2464
2776
|
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
2777
|
+
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
2465
2778
|
const tasks = serverItems.map(
|
|
2466
2779
|
(serverItem, index) => () => processItem(
|
|
2467
2780
|
httpClient,
|
|
@@ -2659,7 +2972,6 @@ var init_replay = __esm({
|
|
|
2659
2972
|
init_randomUuid();
|
|
2660
2973
|
init_replayContext();
|
|
2661
2974
|
init_serialize();
|
|
2662
|
-
init_transport();
|
|
2663
2975
|
init_unrefTimer();
|
|
2664
2976
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
2665
2977
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|