@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
|
@@ -199,10 +199,11 @@ function looksBinary(s) {
|
|
|
199
199
|
|
|
200
200
|
// src/errors.ts
|
|
201
201
|
var BitfabError = class extends Error {
|
|
202
|
-
constructor(message, url, status) {
|
|
202
|
+
constructor(message, url, status, retryAfterMs) {
|
|
203
203
|
super(message);
|
|
204
204
|
this.url = url;
|
|
205
205
|
this.status = status;
|
|
206
|
+
this.retryAfterMs = retryAfterMs;
|
|
206
207
|
this.name = "BitfabError";
|
|
207
208
|
}
|
|
208
209
|
};
|
|
@@ -247,38 +248,63 @@ function toArrayBuffer(view) {
|
|
|
247
248
|
view.byteOffset + view.byteLength
|
|
248
249
|
);
|
|
249
250
|
}
|
|
251
|
+
function compressedRequest(body, rawBytes, compressed) {
|
|
252
|
+
if (compressed.byteLength >= rawBytes) {
|
|
253
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
|
|
257
|
+
contentEncoding: "gzip",
|
|
258
|
+
rawBytes,
|
|
259
|
+
wireBytes: compressed.byteLength
|
|
260
|
+
};
|
|
261
|
+
}
|
|
250
262
|
async function gzipViaStream(bytes) {
|
|
251
263
|
const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
252
264
|
return await new Response(stream).arrayBuffer();
|
|
253
265
|
}
|
|
254
266
|
function encodeRequestBody(body) {
|
|
255
267
|
if (readEnv(DISABLE_COMPRESSION_ENV)) {
|
|
256
|
-
|
|
268
|
+
const rawBytes = new TextEncoder().encode(body).byteLength;
|
|
269
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
257
270
|
}
|
|
258
271
|
const bytes = new TextEncoder().encode(body);
|
|
259
272
|
if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
|
|
260
|
-
return {
|
|
273
|
+
return {
|
|
274
|
+
body,
|
|
275
|
+
rawBytes: bytes.byteLength,
|
|
276
|
+
wireBytes: bytes.byteLength
|
|
277
|
+
};
|
|
261
278
|
}
|
|
262
279
|
if (gzipNode) {
|
|
263
280
|
return gzipNode(bytes).then(
|
|
264
|
-
(compressed) => (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
281
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
282
|
+
() => ({
|
|
283
|
+
body,
|
|
284
|
+
rawBytes: bytes.byteLength,
|
|
285
|
+
wireBytes: bytes.byteLength
|
|
286
|
+
})
|
|
269
287
|
);
|
|
270
288
|
}
|
|
271
289
|
if (typeof CompressionStream === "undefined") {
|
|
272
|
-
return {
|
|
290
|
+
return {
|
|
291
|
+
body,
|
|
292
|
+
rawBytes: bytes.byteLength,
|
|
293
|
+
wireBytes: bytes.byteLength
|
|
294
|
+
};
|
|
273
295
|
}
|
|
274
296
|
return gzipViaStream(bytes).then(
|
|
275
|
-
(compressed) => (
|
|
276
|
-
() => ({
|
|
297
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
298
|
+
() => ({
|
|
299
|
+
body,
|
|
300
|
+
rawBytes: bytes.byteLength,
|
|
301
|
+
wireBytes: bytes.byteLength
|
|
302
|
+
})
|
|
277
303
|
);
|
|
278
304
|
}
|
|
279
305
|
|
|
280
306
|
// src/version.generated.ts
|
|
281
|
-
var __version__ = "0.36.
|
|
307
|
+
var __version__ = "0.36.8";
|
|
282
308
|
|
|
283
309
|
// src/constants.ts
|
|
284
310
|
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
@@ -351,6 +377,7 @@ function runWithReplayContext(ctx, fn) {
|
|
|
351
377
|
|
|
352
378
|
// src/payloadBudget.ts
|
|
353
379
|
var MAX_SPAN_CARRIER_BYTES = 28e5;
|
|
380
|
+
var MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
|
|
354
381
|
var textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
355
382
|
function byteLength(value) {
|
|
356
383
|
return textEncoder ? textEncoder.encode(value).length : value.length;
|
|
@@ -374,15 +401,15 @@ function carrierBytesOf(encoded, body) {
|
|
|
374
401
|
return encoded.length + extra;
|
|
375
402
|
}
|
|
376
403
|
var MAX_BYTES_PER_UNIT = 3;
|
|
377
|
-
function fitsCarrierBudget(body) {
|
|
404
|
+
function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
378
405
|
const units = body.length;
|
|
379
|
-
if (units * MAX_BYTES_PER_UNIT + 2 <=
|
|
406
|
+
if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
|
|
380
407
|
return true;
|
|
381
408
|
}
|
|
382
|
-
if (units + 2 >
|
|
409
|
+
if (units + 2 > maxBytes) {
|
|
383
410
|
return false;
|
|
384
411
|
}
|
|
385
|
-
return carrierByteLength(body) <=
|
|
412
|
+
return carrierByteLength(body) <= maxBytes;
|
|
386
413
|
}
|
|
387
414
|
var STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
388
415
|
"name",
|
|
@@ -432,7 +459,7 @@ function collectCandidates(containers) {
|
|
|
432
459
|
}
|
|
433
460
|
return candidates.sort((a, b) => b.size - a.size);
|
|
434
461
|
}
|
|
435
|
-
function trimPayloadToBudget(payload, encode) {
|
|
462
|
+
function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
436
463
|
const { copy, containers } = cloneTrimmable(payload);
|
|
437
464
|
const candidates = collectCandidates(containers);
|
|
438
465
|
if (candidates.length === 0) {
|
|
@@ -448,20 +475,20 @@ function trimPayloadToBudget(payload, encode) {
|
|
|
448
475
|
} catch {
|
|
449
476
|
return void 0;
|
|
450
477
|
}
|
|
451
|
-
if (fitsCarrierBudget(body)) {
|
|
478
|
+
if (fitsCarrierBudget(body, maxBytes)) {
|
|
452
479
|
return { value: copy, trimmed };
|
|
453
480
|
}
|
|
454
481
|
}
|
|
455
482
|
return void 0;
|
|
456
483
|
}
|
|
457
|
-
function markPayloadTrimmed(value, trimmed) {
|
|
484
|
+
function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
458
485
|
const existing = Array.isArray(value.errors) ? value.errors : [];
|
|
459
486
|
value.errors = [
|
|
460
487
|
...existing,
|
|
461
488
|
{
|
|
462
489
|
source: "sdk",
|
|
463
490
|
step: "payload_budget",
|
|
464
|
-
error: `trimmed oversized field(s) to fit the ${
|
|
491
|
+
error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
|
|
465
492
|
...new Set(trimmed)
|
|
466
493
|
].join(", ")}`
|
|
467
494
|
}
|
|
@@ -482,30 +509,31 @@ function warnOnce(key, message) {
|
|
|
482
509
|
}
|
|
483
510
|
|
|
484
511
|
// src/serializePayload.ts
|
|
485
|
-
function serializePayloadBody(payload) {
|
|
512
|
+
function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
486
513
|
const encoded = encodePayloadBody(payload);
|
|
487
|
-
if (fitsCarrierBudget(encoded.body)) {
|
|
514
|
+
if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
|
|
488
515
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
489
516
|
}
|
|
490
|
-
return applyPayloadBudget(encoded);
|
|
517
|
+
return applyPayloadBudget(encoded, maxCarrierBytes);
|
|
491
518
|
}
|
|
492
|
-
function applyPayloadBudget(encoded) {
|
|
519
|
+
function applyPayloadBudget(encoded, maxCarrierBytes) {
|
|
493
520
|
const result = encoded.value ? trimPayloadToBudget(
|
|
494
521
|
encoded.value,
|
|
495
|
-
(value) => encodePayloadBody(value).body
|
|
522
|
+
(value) => encodePayloadBody(value).body,
|
|
523
|
+
maxCarrierBytes
|
|
496
524
|
) : void 0;
|
|
497
525
|
if (!result) {
|
|
498
526
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
499
527
|
}
|
|
500
528
|
warnOnce(
|
|
501
529
|
"payload:over-budget",
|
|
502
|
-
`a span payload exceeded the ${
|
|
530
|
+
`a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
|
|
503
531
|
...new Set(result.trimmed)
|
|
504
532
|
].join(
|
|
505
533
|
", "
|
|
506
534
|
)}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
|
|
507
535
|
);
|
|
508
|
-
markPayloadTrimmed(result.value, result.trimmed);
|
|
536
|
+
markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
|
|
509
537
|
return {
|
|
510
538
|
body: encodePayloadBody(result.value).body,
|
|
511
539
|
dropped: encoded.dropped
|
|
@@ -616,6 +644,17 @@ import {
|
|
|
616
644
|
BatchSpanProcessor
|
|
617
645
|
} from "@opentelemetry/sdk-trace-base";
|
|
618
646
|
|
|
647
|
+
// src/transportTypes.ts
|
|
648
|
+
var DeliveryError = class extends Error {
|
|
649
|
+
constructor(message, options = {}) {
|
|
650
|
+
super(message);
|
|
651
|
+
this.name = "DeliveryError";
|
|
652
|
+
this.retryable = options.retryable ?? false;
|
|
653
|
+
this.oversized = options.oversized ?? false;
|
|
654
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
619
658
|
// src/unrefTimer.ts
|
|
620
659
|
function unrefTimer(timer) {
|
|
621
660
|
const handle = timer;
|
|
@@ -627,8 +666,8 @@ function unrefTimer(timer) {
|
|
|
627
666
|
// src/otel.ts
|
|
628
667
|
var OPERATION_ATTRIBUTE = "bitfab.operation";
|
|
629
668
|
var PAYLOAD_ATTRIBUTE = "bitfab.payload";
|
|
630
|
-
var OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
631
669
|
var MAX_EXPORT_REQUEST_BYTES = 3e6;
|
|
670
|
+
var MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
|
|
632
671
|
var MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
|
|
633
672
|
var EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
|
|
634
673
|
var MAX_QUEUE_SIZE = 8192;
|
|
@@ -638,14 +677,12 @@ var DEFAULT_EXPORT_CONCURRENCY = 32;
|
|
|
638
677
|
var MAX_EXPORT_CONCURRENCY = 64;
|
|
639
678
|
var SCHEDULE_DELAY_MILLIS = 5e3;
|
|
640
679
|
var EXPORT_TIMEOUT_MILLIS = 3e4;
|
|
641
|
-
var
|
|
680
|
+
var RETRY_BASE_DELAY_MILLIS = 100;
|
|
681
|
+
var RETRY_BACKOFF_CEILING_MILLIS = 5e3;
|
|
642
682
|
var MAX_SEND_ATTEMPTS = 3;
|
|
643
683
|
var DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
644
|
-
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
645
684
|
var liveTransports = /* @__PURE__ */ new Set();
|
|
646
|
-
var
|
|
647
|
-
var replayTraceSubmissions = /* @__PURE__ */ new Set();
|
|
648
|
-
var submissionCounter = 0;
|
|
685
|
+
var carrierRefs = /* @__PURE__ */ new WeakMap();
|
|
649
686
|
function readBoundedIntEnv(name, max, fallback, warnKey) {
|
|
650
687
|
const raw = readEnv(name);
|
|
651
688
|
if (raw === void 0) {
|
|
@@ -671,59 +708,6 @@ function logError(message, error) {
|
|
|
671
708
|
} catch {
|
|
672
709
|
}
|
|
673
710
|
}
|
|
674
|
-
function recordTraceSubmission(operation, payload) {
|
|
675
|
-
const sourceTraceId = resolveSourceTraceId(payload);
|
|
676
|
-
if (sourceTraceId === void 0) {
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
if (operation === "external_span") {
|
|
680
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
681
|
-
if (typeof rawSpan?.id !== "string") {
|
|
682
|
-
submissionCounter += 1;
|
|
683
|
-
}
|
|
684
|
-
const sourceSpanId = typeof rawSpan?.id === "string" ? rawSpan.id : `submission-${submissionCounter}`;
|
|
685
|
-
const existing = traceSubmissionSpanIds.get(sourceTraceId);
|
|
686
|
-
if (existing) {
|
|
687
|
-
existing.add(sourceSpanId);
|
|
688
|
-
} else {
|
|
689
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set([sourceSpanId]));
|
|
690
|
-
}
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
if (payload.completed !== true) {
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
if (typeof payload.testRunId === "string") {
|
|
697
|
-
replayTraceSubmissions.add(sourceTraceId);
|
|
698
|
-
if (!traceSubmissionSpanIds.has(sourceTraceId)) {
|
|
699
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set());
|
|
700
|
-
}
|
|
701
|
-
} else {
|
|
702
|
-
traceSubmissionSpanIds.delete(sourceTraceId);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
function takeReplaySpanCounts(traceIds) {
|
|
706
|
-
const counts = {};
|
|
707
|
-
for (const traceId of traceIds) {
|
|
708
|
-
if (!replayTraceSubmissions.has(traceId)) {
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
counts[traceId] = traceSubmissionSpanIds.get(traceId)?.size ?? 0;
|
|
712
|
-
traceSubmissionSpanIds.delete(traceId);
|
|
713
|
-
replayTraceSubmissions.delete(traceId);
|
|
714
|
-
}
|
|
715
|
-
return counts;
|
|
716
|
-
}
|
|
717
|
-
function asRecord2(value) {
|
|
718
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
719
|
-
}
|
|
720
|
-
function resolveSourceTraceId(payload) {
|
|
721
|
-
if (typeof payload.sourceTraceId === "string") {
|
|
722
|
-
return payload.sourceTraceId;
|
|
723
|
-
}
|
|
724
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
725
|
-
return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
|
|
726
|
-
}
|
|
727
711
|
function otlpValue(value) {
|
|
728
712
|
if (typeof value === "boolean") {
|
|
729
713
|
return { boolValue: value };
|
|
@@ -782,7 +766,36 @@ function spanToOtlp(span) {
|
|
|
782
766
|
var SPAN_SEPARATOR_BYTES = 1;
|
|
783
767
|
function encodeSpan(span) {
|
|
784
768
|
const json = JSON.stringify(spanToOtlp(span));
|
|
785
|
-
return {
|
|
769
|
+
return {
|
|
770
|
+
json,
|
|
771
|
+
size: byteLength(json),
|
|
772
|
+
ref: carrierRefs.get(span)
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function trimEncodedSpan(span) {
|
|
776
|
+
try {
|
|
777
|
+
const carrier = JSON.parse(span.json);
|
|
778
|
+
const attribute = carrier.attributes?.find(
|
|
779
|
+
(entry) => entry.key === PAYLOAD_ATTRIBUTE
|
|
780
|
+
);
|
|
781
|
+
const payloadBody = attribute?.value?.stringValue;
|
|
782
|
+
if (!attribute?.value || payloadBody === void 0) {
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
785
|
+
const payload = JSON.parse(payloadBody);
|
|
786
|
+
attribute.value.stringValue = serializePayloadBody(
|
|
787
|
+
payload,
|
|
788
|
+
MAX_SPAN_CARRIER_BYTES
|
|
789
|
+
).body;
|
|
790
|
+
const json = JSON.stringify(carrier);
|
|
791
|
+
return { json, size: byteLength(json) };
|
|
792
|
+
} catch {
|
|
793
|
+
return void 0;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function prepareRequest(body) {
|
|
797
|
+
const prepared = encodeRequestBody(body);
|
|
798
|
+
return prepared instanceof Promise ? await prepared : prepared;
|
|
786
799
|
}
|
|
787
800
|
function requestEnvelope(first) {
|
|
788
801
|
const scope = first.instrumentationScope;
|
|
@@ -840,26 +853,35 @@ async function mapWithConcurrency(items, limit, task) {
|
|
|
840
853
|
await Promise.all(workers);
|
|
841
854
|
return results;
|
|
842
855
|
}
|
|
843
|
-
var OtlpPayloadTooLargeError = class extends Error {
|
|
844
|
-
};
|
|
845
|
-
var OtlpPartialSuccessError = class extends Error {
|
|
846
|
-
};
|
|
847
|
-
function responseStatus(error) {
|
|
848
|
-
return error instanceof BitfabError ? error.status : void 0;
|
|
849
|
-
}
|
|
850
856
|
function isRetryable(error) {
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
857
|
+
return error instanceof DeliveryError && error.retryable;
|
|
858
|
+
}
|
|
859
|
+
function isOversized(error) {
|
|
860
|
+
return error instanceof DeliveryError && error.oversized;
|
|
861
|
+
}
|
|
862
|
+
function retryWaitMillis(error, attempt, remainingMillis) {
|
|
863
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
864
|
+
const affordable = remainingMillis / 2;
|
|
865
|
+
if (requested !== void 0) {
|
|
866
|
+
return requested < affordable ? requested : null;
|
|
854
867
|
}
|
|
855
|
-
|
|
868
|
+
const backoff = Math.min(
|
|
869
|
+
RETRY_BASE_DELAY_MILLIS * 2 ** attempt,
|
|
870
|
+
RETRY_BACKOFF_CEILING_MILLIS
|
|
871
|
+
);
|
|
872
|
+
const jittered = backoff / 2 + Math.random() * (backoff / 2);
|
|
873
|
+
return jittered < affordable ? jittered : null;
|
|
856
874
|
}
|
|
857
875
|
var BitfabSpanExporter = class {
|
|
858
|
-
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency) {
|
|
876
|
+
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency, onDelivered, exportTimeoutMillis = EXPORT_TIMEOUT_MILLIS) {
|
|
859
877
|
this.directSender = directSender;
|
|
860
878
|
this.maxRequestBytes = maxRequestBytes;
|
|
861
879
|
this.maxRequestBatchSize = maxRequestBatchSize;
|
|
862
880
|
this.exportConcurrency = exportConcurrency;
|
|
881
|
+
this.onDelivered = onDelivered;
|
|
882
|
+
this.exportTimeoutMillis = exportTimeoutMillis;
|
|
883
|
+
/** Epoch ms until which the server has asked this exporter to stay away. */
|
|
884
|
+
this.throttledUntil = 0;
|
|
863
885
|
}
|
|
864
886
|
export(spans, resultCallback) {
|
|
865
887
|
void this.exportAsync(spans).then(
|
|
@@ -914,25 +936,51 @@ var BitfabSpanExporter = class {
|
|
|
914
936
|
return batches;
|
|
915
937
|
}
|
|
916
938
|
async send(envelope, batch) {
|
|
917
|
-
if (batch.size > this.maxRequestBytes) {
|
|
918
|
-
logError(
|
|
919
|
-
"a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
|
|
920
|
-
);
|
|
921
|
-
return false;
|
|
922
|
-
}
|
|
923
939
|
try {
|
|
924
|
-
|
|
925
|
-
|
|
940
|
+
let requestSpans = batch.spans;
|
|
941
|
+
let requestRawBytes = batch.size;
|
|
942
|
+
let alreadyTrimmed = false;
|
|
943
|
+
while (true) {
|
|
944
|
+
if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
|
|
945
|
+
const prepared = await prepareRequest(
|
|
946
|
+
encodeRequest(envelope, requestSpans)
|
|
947
|
+
);
|
|
948
|
+
if (prepared.wireBytes <= this.maxRequestBytes) {
|
|
949
|
+
await this.sendWithRetries(prepared);
|
|
950
|
+
this.reportDelivered(batch.spans);
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (batch.spans.length !== 1) {
|
|
955
|
+
logError(
|
|
956
|
+
"an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
|
|
957
|
+
);
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
if (alreadyTrimmed) {
|
|
961
|
+
logError(
|
|
962
|
+
"a single OpenTelemetry span exceeded the configured request-size target after trimming"
|
|
963
|
+
);
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
const trimmed = trimEncodedSpan(batch.spans[0]);
|
|
967
|
+
if (!trimmed) {
|
|
968
|
+
logError(
|
|
969
|
+
"a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
|
|
970
|
+
);
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
requestSpans = [trimmed];
|
|
974
|
+
requestRawBytes = envelope.size + trimmed.size;
|
|
975
|
+
alreadyTrimmed = true;
|
|
976
|
+
}
|
|
926
977
|
} catch (error) {
|
|
927
|
-
if (error
|
|
978
|
+
if (isOversized(error)) {
|
|
928
979
|
logError(
|
|
929
980
|
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"
|
|
930
981
|
);
|
|
931
982
|
return false;
|
|
932
983
|
}
|
|
933
|
-
if (error instanceof OtlpPartialSuccessError) {
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
984
|
logError("failed to export an OpenTelemetry span batch", error);
|
|
937
985
|
return false;
|
|
938
986
|
}
|
|
@@ -950,37 +998,79 @@ var BitfabSpanExporter = class {
|
|
|
950
998
|
* the server does not yet understand. The fix is a client-supplied
|
|
951
999
|
* idempotency key that ingestion dedupes on.
|
|
952
1000
|
*/
|
|
953
|
-
|
|
1001
|
+
/**
|
|
1002
|
+
* Remember a throttle the server asked for, so the requests fanned out
|
|
1003
|
+
* alongside this one respect it too. Delaying only the request that was
|
|
1004
|
+
* refused leaves the other seven in the window hitting a server that just
|
|
1005
|
+
* asked for room.
|
|
1006
|
+
*/
|
|
1007
|
+
recordThrottle(error) {
|
|
1008
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
1009
|
+
if (requested !== void 0) {
|
|
1010
|
+
this.throttledUntil = Math.max(
|
|
1011
|
+
this.throttledUntil,
|
|
1012
|
+
Date.now() + requested
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Waits out an active throttle, or reports the batch undeliverable when the
|
|
1018
|
+
* throttle outlasts what we are willing to hold it for. Either way nothing is
|
|
1019
|
+
* sent while the server has asked us to stay away.
|
|
1020
|
+
*/
|
|
1021
|
+
async awaitThrottle(deadline) {
|
|
1022
|
+
const remaining = this.throttledUntil - Date.now();
|
|
1023
|
+
if (remaining <= 0) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
if (remaining >= (deadline - Date.now()) / 2) {
|
|
1027
|
+
throw new DeliveryError(
|
|
1028
|
+
`OTLP ingestion is throttled for another ${remaining}ms, longer than the export budget`
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
await delay(remaining);
|
|
1032
|
+
}
|
|
1033
|
+
async sendWithRetries(request) {
|
|
1034
|
+
const deadline = Date.now() + this.exportTimeoutMillis;
|
|
954
1035
|
for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
|
|
955
1036
|
try {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
body,
|
|
959
|
-
EXPORT_TIMEOUT_MILLIS
|
|
960
|
-
);
|
|
961
|
-
const partialSuccess = asRecord2(response?.partialSuccess);
|
|
962
|
-
const rejected = partialSuccess?.rejectedSpans;
|
|
963
|
-
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
964
|
-
logError(
|
|
965
|
-
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
966
|
-
);
|
|
967
|
-
throw new OtlpPartialSuccessError();
|
|
968
|
-
}
|
|
1037
|
+
await this.awaitThrottle(deadline);
|
|
1038
|
+
await this.directSender(request, Math.max(0, deadline - Date.now()));
|
|
969
1039
|
return;
|
|
970
1040
|
} catch (error) {
|
|
971
|
-
if (error
|
|
1041
|
+
if (isOversized(error)) {
|
|
972
1042
|
throw error;
|
|
973
1043
|
}
|
|
974
|
-
|
|
975
|
-
throw new OtlpPayloadTooLargeError();
|
|
976
|
-
}
|
|
1044
|
+
this.recordThrottle(error);
|
|
977
1045
|
if (attempt === MAX_SEND_ATTEMPTS - 1 || !isRetryable(error)) {
|
|
978
1046
|
throw error;
|
|
979
1047
|
}
|
|
980
|
-
|
|
1048
|
+
const wait = retryWaitMillis(error, attempt, deadline - Date.now());
|
|
1049
|
+
if (wait === null) {
|
|
1050
|
+
throw error;
|
|
1051
|
+
}
|
|
1052
|
+
await delay(wait);
|
|
981
1053
|
}
|
|
982
1054
|
}
|
|
983
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Announce the carriers a request delivered. Wrapped because a listener that
|
|
1058
|
+
* throws must never turn a delivered batch into a failed export.
|
|
1059
|
+
*/
|
|
1060
|
+
reportDelivered(spans) {
|
|
1061
|
+
if (this.onDelivered === void 0) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const refs = spans.map((span) => span.ref).filter((ref) => ref !== void 0);
|
|
1065
|
+
if (refs.length === 0) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
try {
|
|
1069
|
+
this.onDelivered(refs);
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
logError("a delivery listener threw", error);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
984
1074
|
async shutdown() {
|
|
985
1075
|
}
|
|
986
1076
|
async forceFlush() {
|
|
@@ -1038,7 +1128,9 @@ var OtelBatchTransport = class {
|
|
|
1038
1128
|
options.directSender,
|
|
1039
1129
|
maxRequestBytes,
|
|
1040
1130
|
maxRequestBatchSize,
|
|
1041
|
-
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
|
|
1131
|
+
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY,
|
|
1132
|
+
options.onDelivered,
|
|
1133
|
+
options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
|
|
1042
1134
|
)
|
|
1043
1135
|
);
|
|
1044
1136
|
this.processor = new BatchSpanProcessor(this.deliveryTracker, {
|
|
@@ -1062,8 +1154,7 @@ var OtelBatchTransport = class {
|
|
|
1062
1154
|
this.tracer = this.provider.getTracer("bitfab", __version__);
|
|
1063
1155
|
liveTransports.add(this);
|
|
1064
1156
|
}
|
|
1065
|
-
submit(operation, payload) {
|
|
1066
|
-
recordTraceSubmission(operation, payload);
|
|
1157
|
+
submit(operation, payload, meta = {}) {
|
|
1067
1158
|
if (this.closed) {
|
|
1068
1159
|
warnOnce(
|
|
1069
1160
|
"otel-submit-after-shutdown",
|
|
@@ -1072,7 +1163,10 @@ var OtelBatchTransport = class {
|
|
|
1072
1163
|
return;
|
|
1073
1164
|
}
|
|
1074
1165
|
try {
|
|
1075
|
-
const { body, dropped } = serializePayloadBody(
|
|
1166
|
+
const { body, dropped } = serializePayloadBody(
|
|
1167
|
+
payload,
|
|
1168
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
|
|
1169
|
+
);
|
|
1076
1170
|
if (dropped.length > 0) {
|
|
1077
1171
|
warnOnce(
|
|
1078
1172
|
"otel-carrier-payload-stubbed",
|
|
@@ -1081,17 +1175,20 @@ var OtelBatchTransport = class {
|
|
|
1081
1175
|
].join(", ")}); they were stubbed so the span still ships, but the trace may be incomplete or not replayable.`
|
|
1082
1176
|
);
|
|
1083
1177
|
}
|
|
1084
|
-
const span = this.tracer.startSpan(
|
|
1178
|
+
const span = this.tracer.startSpan(meta.name ?? `bitfab.${operation}`, {
|
|
1085
1179
|
attributes: {
|
|
1086
1180
|
[OPERATION_ATTRIBUTE]: operation,
|
|
1087
1181
|
[PAYLOAD_ATTRIBUTE]: body
|
|
1088
1182
|
},
|
|
1089
|
-
startTime:
|
|
1183
|
+
startTime: meta.startTime
|
|
1090
1184
|
});
|
|
1091
|
-
if (
|
|
1185
|
+
if (meta.ref !== void 0) {
|
|
1186
|
+
carrierRefs.set(span, meta.ref);
|
|
1187
|
+
}
|
|
1188
|
+
if (meta.errored === true) {
|
|
1092
1189
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1093
1190
|
}
|
|
1094
|
-
endSpan(span,
|
|
1191
|
+
endSpan(span, meta.endTime);
|
|
1095
1192
|
} catch (error) {
|
|
1096
1193
|
logError("failed to queue an OpenTelemetry span", error);
|
|
1097
1194
|
}
|
|
@@ -1131,36 +1228,6 @@ var OtelBatchTransport = class {
|
|
|
1131
1228
|
function endSpan(span, endTime) {
|
|
1132
1229
|
span.end(endTime);
|
|
1133
1230
|
}
|
|
1134
|
-
function spanName(operation, payload) {
|
|
1135
|
-
if (operation === "external_span") {
|
|
1136
|
-
const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
|
|
1137
|
-
if (typeof spanData?.name === "string") {
|
|
1138
|
-
return spanData.name;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
if (typeof payload.traceFunctionKey === "string") {
|
|
1142
|
-
return payload.traceFunctionKey;
|
|
1143
|
-
}
|
|
1144
|
-
return `bitfab.${operation}`;
|
|
1145
|
-
}
|
|
1146
|
-
function payloadTimestamp(payload, field) {
|
|
1147
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
1148
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
1149
|
-
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
1150
|
-
if (typeof raw !== "string") {
|
|
1151
|
-
return void 0;
|
|
1152
|
-
}
|
|
1153
|
-
const parsed = Date.parse(raw);
|
|
1154
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1155
|
-
}
|
|
1156
|
-
function hasError(payload) {
|
|
1157
|
-
const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
|
|
1158
|
-
if (spanData?.error != null) {
|
|
1159
|
-
return true;
|
|
1160
|
-
}
|
|
1161
|
-
const errors = payload.errors;
|
|
1162
|
-
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
1163
|
-
}
|
|
1164
1231
|
function createOtelTransport(options) {
|
|
1165
1232
|
return new OtelBatchTransport({
|
|
1166
1233
|
...options,
|
|
@@ -1209,12 +1276,11 @@ function flushTraceTransports(timeoutMs) {
|
|
|
1209
1276
|
function shutdownTraceTransports(timeoutMs) {
|
|
1210
1277
|
return shutdownOtelTransports(timeoutMs);
|
|
1211
1278
|
}
|
|
1212
|
-
function takeReplaySpanCounts2(traceIds) {
|
|
1213
|
-
return takeReplaySpanCounts(traceIds);
|
|
1214
|
-
}
|
|
1215
1279
|
|
|
1216
1280
|
// src/http.ts
|
|
1217
1281
|
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
1282
|
+
var OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
1283
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1218
1284
|
var EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
1219
1285
|
var DEFAULT_LIFECYCLE_TIMEOUT_MS2 = 3e4;
|
|
1220
1286
|
var pendingTracePromises = /* @__PURE__ */ new Set();
|
|
@@ -1274,8 +1340,101 @@ if (typeof process !== "undefined" && process.versions != null && process.versio
|
|
|
1274
1340
|
});
|
|
1275
1341
|
});
|
|
1276
1342
|
}
|
|
1343
|
+
function readHeader(response, name) {
|
|
1344
|
+
try {
|
|
1345
|
+
return response.headers?.get(name) ?? null;
|
|
1346
|
+
} catch {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function parseRetryAfterMs(header) {
|
|
1351
|
+
const value = header?.trim();
|
|
1352
|
+
if (!value) {
|
|
1353
|
+
return void 0;
|
|
1354
|
+
}
|
|
1355
|
+
const seconds = Number(value);
|
|
1356
|
+
if (Number.isFinite(seconds)) {
|
|
1357
|
+
return seconds >= 0 ? seconds * 1e3 : void 0;
|
|
1358
|
+
}
|
|
1359
|
+
const at = Date.parse(value);
|
|
1360
|
+
if (Number.isNaN(at)) {
|
|
1361
|
+
return void 0;
|
|
1362
|
+
}
|
|
1363
|
+
return Math.max(0, at - Date.now());
|
|
1364
|
+
}
|
|
1365
|
+
function carrierMeta(operation, payload, ref) {
|
|
1366
|
+
return {
|
|
1367
|
+
ref,
|
|
1368
|
+
name: carrierName(operation, payload),
|
|
1369
|
+
startTime: payloadTimestamp(payload, "started_at"),
|
|
1370
|
+
endTime: payloadTimestamp(payload, "ended_at"),
|
|
1371
|
+
errored: payloadHasError(payload)
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function carrierName(operation, payload) {
|
|
1375
|
+
if (operation === "external_span") {
|
|
1376
|
+
const spanData = asPayloadRecord(
|
|
1377
|
+
asPayloadRecord(payload.rawSpan)?.span_data
|
|
1378
|
+
);
|
|
1379
|
+
if (typeof spanData?.name === "string") {
|
|
1380
|
+
return spanData.name;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
if (typeof payload.traceFunctionKey === "string") {
|
|
1384
|
+
return payload.traceFunctionKey;
|
|
1385
|
+
}
|
|
1386
|
+
return `bitfab.${operation}`;
|
|
1387
|
+
}
|
|
1388
|
+
function payloadTimestamp(payload, field) {
|
|
1389
|
+
const rawSpan = asPayloadRecord(payload.rawSpan);
|
|
1390
|
+
const rawTrace = asPayloadRecord(payload.externalTrace) ?? asPayloadRecord(payload.rawTrace);
|
|
1391
|
+
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
1392
|
+
if (typeof raw !== "string") {
|
|
1393
|
+
return void 0;
|
|
1394
|
+
}
|
|
1395
|
+
const parsed = Date.parse(raw);
|
|
1396
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1397
|
+
}
|
|
1398
|
+
function payloadHasError(payload) {
|
|
1399
|
+
const spanData = asPayloadRecord(asPayloadRecord(payload.rawSpan)?.span_data);
|
|
1400
|
+
if (spanData?.error != null) {
|
|
1401
|
+
return true;
|
|
1402
|
+
}
|
|
1403
|
+
const errors = payload.errors;
|
|
1404
|
+
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
1405
|
+
}
|
|
1406
|
+
function asPayloadRecord(value) {
|
|
1407
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1408
|
+
}
|
|
1409
|
+
function carrierRef(payload) {
|
|
1410
|
+
const traceId = sourceTraceIdOf(payload);
|
|
1411
|
+
if (traceId === void 0) {
|
|
1412
|
+
return void 0;
|
|
1413
|
+
}
|
|
1414
|
+
const rawSpan = payload.rawSpan;
|
|
1415
|
+
if (rawSpan === void 0) {
|
|
1416
|
+
return { traceId };
|
|
1417
|
+
}
|
|
1418
|
+
const spanId = rawSpan?.id;
|
|
1419
|
+
return {
|
|
1420
|
+
traceId,
|
|
1421
|
+
spanId: typeof spanId === "string" ? spanId : `submission-${++carrierSeq}`
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
function sourceTraceIdOf(payload) {
|
|
1425
|
+
if (typeof payload.sourceTraceId === "string") {
|
|
1426
|
+
return payload.sourceTraceId;
|
|
1427
|
+
}
|
|
1428
|
+
const rawTrace = payload.externalTrace ?? payload.rawTrace;
|
|
1429
|
+
const id = rawTrace?.id;
|
|
1430
|
+
return typeof id === "string" ? id : void 0;
|
|
1431
|
+
}
|
|
1432
|
+
var carrierSeq = 0;
|
|
1277
1433
|
var HttpClient = class {
|
|
1278
1434
|
constructor(config) {
|
|
1435
|
+
// Only traces a caller asked about are tracked, so ordinary tracing stores
|
|
1436
|
+
// nothing here.
|
|
1437
|
+
this.traceDeliveries = /* @__PURE__ */ new Map();
|
|
1279
1438
|
// Deferred span work owned by THIS client. The module-global set backs the
|
|
1280
1439
|
// process-wide `flushTraces()` and the exit hook, but per-client lifecycle
|
|
1281
1440
|
// must not wait on another client's slow finalize: a false `close()` failure
|
|
@@ -1311,13 +1470,131 @@ var HttpClient = class {
|
|
|
1311
1470
|
}
|
|
1312
1471
|
if (!this.traceTransport) {
|
|
1313
1472
|
this.traceTransport = createTraceTransport({
|
|
1314
|
-
directSender: (
|
|
1315
|
-
|
|
1316
|
-
})
|
|
1473
|
+
directSender: (request, timeoutMs) => this.deliverCarriers(request, timeoutMs),
|
|
1474
|
+
onDelivered: (refs) => this.recordDeliveredCarriers(refs)
|
|
1317
1475
|
});
|
|
1318
1476
|
}
|
|
1319
1477
|
return this.traceTransport;
|
|
1320
1478
|
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Post one encoded batch and decide what the server's answer means, so the
|
|
1481
|
+
* transport never reads a response. Rejections and permanent statuses come
|
|
1482
|
+
* back as a non-retryable {@link DeliveryError}; anything the server might
|
|
1483
|
+
* still accept on a second try comes back retryable.
|
|
1484
|
+
*/
|
|
1485
|
+
async deliverCarriers(request, timeoutMs) {
|
|
1486
|
+
let response;
|
|
1487
|
+
try {
|
|
1488
|
+
response = await this.sendPrepared(
|
|
1489
|
+
OTLP_TRACES_ENDPOINT,
|
|
1490
|
+
request,
|
|
1491
|
+
{ timeout: timeoutMs }
|
|
1492
|
+
);
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
const status = error instanceof BitfabError ? error.status : void 0;
|
|
1495
|
+
if (status === void 0) {
|
|
1496
|
+
throw new DeliveryError(`OTLP ingestion failed: ${String(error)}`, {
|
|
1497
|
+
retryable: true
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
throw new DeliveryError(`OTLP ingestion failed with HTTP ${status}`, {
|
|
1501
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
1502
|
+
oversized: status === 413,
|
|
1503
|
+
...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
const partialSuccess = asPayloadRecord(response?.partialSuccess);
|
|
1507
|
+
const rejected = partialSuccess?.rejectedSpans;
|
|
1508
|
+
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1509
|
+
throw new DeliveryError(
|
|
1510
|
+
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Start tracking delivery for `traceIds`. Nothing is recorded for a trace
|
|
1516
|
+
* that was never tracked, so ordinary tracing costs no bookkeeping at all.
|
|
1517
|
+
*/
|
|
1518
|
+
trackTraceDeliveries(traceIds) {
|
|
1519
|
+
for (const traceId of traceIds) {
|
|
1520
|
+
if (!this.traceDeliveries.has(traceId)) {
|
|
1521
|
+
this.traceDeliveries.set(traceId, {
|
|
1522
|
+
submittedSpanIds: /* @__PURE__ */ new Set(),
|
|
1523
|
+
ackedSpanIds: /* @__PURE__ */ new Set(),
|
|
1524
|
+
closed: false,
|
|
1525
|
+
closingAcked: false
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/** Whether any tracked trace has had its closing carrier submitted. */
|
|
1531
|
+
hasClosedDeliveries(traceIds) {
|
|
1532
|
+
return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Report what each tracked trace submitted and whether the server confirmed
|
|
1536
|
+
* it, and stop tracking them. Every id passed is freed, so a caller cannot
|
|
1537
|
+
* leak a record for a trace that never closed.
|
|
1538
|
+
*
|
|
1539
|
+
* `delivered` is only meaningful once a flush has settled: acks land before
|
|
1540
|
+
* an export resolves, so a flush that reported success has already collected
|
|
1541
|
+
* every ack it is going to collect.
|
|
1542
|
+
*/
|
|
1543
|
+
takeTraceDeliveries(traceIds) {
|
|
1544
|
+
const reports = {};
|
|
1545
|
+
for (const traceId of traceIds) {
|
|
1546
|
+
const delivery = this.traceDeliveries.get(traceId);
|
|
1547
|
+
if (delivery === void 0) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
this.traceDeliveries.delete(traceId);
|
|
1551
|
+
reports[traceId] = {
|
|
1552
|
+
spanCount: delivery.submittedSpanIds.size,
|
|
1553
|
+
closed: delivery.closed,
|
|
1554
|
+
delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
|
|
1555
|
+
(spanId) => delivery.ackedSpanIds.has(spanId)
|
|
1556
|
+
)
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
return reports;
|
|
1560
|
+
}
|
|
1561
|
+
/** Build a carrier's meta and record what it adds to its trace's expected set. */
|
|
1562
|
+
recordedMeta(operation, payload, ref) {
|
|
1563
|
+
this.recordSubmittedCarrier(ref);
|
|
1564
|
+
return carrierMeta(operation, payload, ref);
|
|
1565
|
+
}
|
|
1566
|
+
recordSubmittedCarrier(ref) {
|
|
1567
|
+
if (ref === void 0) {
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1571
|
+
if (delivery === void 0) {
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
if (ref.spanId === void 0) {
|
|
1575
|
+
delivery.closed = true;
|
|
1576
|
+
} else {
|
|
1577
|
+
delivery.submittedSpanIds.add(ref.spanId);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Ingestion commits every carrier in a request before it answers, so a
|
|
1582
|
+
* delivered ref is proof its row exists: the same fact the replay status
|
|
1583
|
+
* endpoint would report, already in hand.
|
|
1584
|
+
*/
|
|
1585
|
+
recordDeliveredCarriers(refs) {
|
|
1586
|
+
for (const ref of refs) {
|
|
1587
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1588
|
+
if (delivery === void 0) {
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (ref.spanId === void 0) {
|
|
1592
|
+
delivery.closingAcked = true;
|
|
1593
|
+
} else {
|
|
1594
|
+
delivery.ackedSpanIds.add(ref.spanId);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1321
1598
|
/**
|
|
1322
1599
|
* Track deferred span work so this client's own lifecycle waits for it, and
|
|
1323
1600
|
* so the process-wide flush and exit hook do too.
|
|
@@ -1398,13 +1675,16 @@ var HttpClient = class {
|
|
|
1398
1675
|
* same data twice.
|
|
1399
1676
|
*/
|
|
1400
1677
|
async sendEncoded(endpoint, body, options) {
|
|
1678
|
+
const prepared = encodeRequestBody(body);
|
|
1679
|
+
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1680
|
+
return this.sendPrepared(endpoint, encoded, options);
|
|
1681
|
+
}
|
|
1682
|
+
async sendPrepared(endpoint, encoded, options) {
|
|
1401
1683
|
const url = `${this.serviceUrl}${endpoint}`;
|
|
1402
1684
|
const timeout = options?.timeout ?? this.timeout;
|
|
1403
1685
|
const method = options?.method ?? "POST";
|
|
1404
1686
|
const controller = new AbortController();
|
|
1405
1687
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1406
|
-
const prepared = encodeRequestBody(body);
|
|
1407
|
-
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1408
1688
|
const headers = {
|
|
1409
1689
|
"Content-Type": "application/json",
|
|
1410
1690
|
Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
|
|
@@ -1424,7 +1704,8 @@ var HttpClient = class {
|
|
|
1424
1704
|
throw new BitfabError(
|
|
1425
1705
|
`HTTP ${response.status}: ${errorText.slice(0, 500)}`,
|
|
1426
1706
|
void 0,
|
|
1427
|
-
response.status
|
|
1707
|
+
response.status,
|
|
1708
|
+
parseRetryAfterMs(readHeader(response, "retry-after"))
|
|
1428
1709
|
);
|
|
1429
1710
|
}
|
|
1430
1711
|
const result = await response.json();
|
|
@@ -1510,11 +1791,12 @@ var HttpClient = class {
|
|
|
1510
1791
|
* the OTLP carrier has no path to carry it.
|
|
1511
1792
|
*/
|
|
1512
1793
|
sendInternalTrace(functionId, payload) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1794
|
+
const body = { ...payload, functionId, sdkVersion: __version__ };
|
|
1795
|
+
this.getTraceTransport()?.submit(
|
|
1796
|
+
"internal_trace",
|
|
1797
|
+
body,
|
|
1798
|
+
carrierMeta("internal_trace", body, void 0)
|
|
1799
|
+
);
|
|
1518
1800
|
}
|
|
1519
1801
|
/**
|
|
1520
1802
|
* Queue an external span (from withSpan wrapper or OpenAI tracing) onto this
|
|
@@ -1523,10 +1805,11 @@ var HttpClient = class {
|
|
|
1523
1805
|
* promise.
|
|
1524
1806
|
*/
|
|
1525
1807
|
sendExternalSpan(payload) {
|
|
1526
|
-
this.getTraceTransport()?.submit(
|
|
1527
|
-
|
|
1528
|
-
sdkVersion: __version__
|
|
1529
|
-
|
|
1808
|
+
this.getTraceTransport()?.submit(
|
|
1809
|
+
"external_span",
|
|
1810
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1811
|
+
this.recordedMeta("external_span", payload, carrierRef(payload))
|
|
1812
|
+
);
|
|
1530
1813
|
}
|
|
1531
1814
|
/**
|
|
1532
1815
|
* Queue an external trace completion (from OpenAI tracing) onto this
|
|
@@ -1535,10 +1818,15 @@ var HttpClient = class {
|
|
|
1535
1818
|
* server-authoritative barrier in `replay.ts`, not by awaiting this call.
|
|
1536
1819
|
*/
|
|
1537
1820
|
sendExternalTrace(payload) {
|
|
1538
|
-
this.getTraceTransport()?.submit(
|
|
1539
|
-
|
|
1540
|
-
sdkVersion: __version__
|
|
1541
|
-
|
|
1821
|
+
this.getTraceTransport()?.submit(
|
|
1822
|
+
"external_trace",
|
|
1823
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1824
|
+
this.recordedMeta(
|
|
1825
|
+
"external_trace",
|
|
1826
|
+
payload,
|
|
1827
|
+
payload.completed === true ? carrierRef(payload) : void 0
|
|
1828
|
+
)
|
|
1829
|
+
);
|
|
1542
1830
|
}
|
|
1543
1831
|
/**
|
|
1544
1832
|
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
@@ -1767,8 +2055,8 @@ function fallbackUuidV4() {
|
|
|
1767
2055
|
|
|
1768
2056
|
// src/serialize.ts
|
|
1769
2057
|
import superjson from "superjson";
|
|
1770
|
-
var MAX_SERIALIZED_BYTES =
|
|
1771
|
-
var MAX_FRAMEWORK_SERIALIZED_BYTES =
|
|
2058
|
+
var MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
2059
|
+
var MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
1772
2060
|
function describeValue(value) {
|
|
1773
2061
|
try {
|
|
1774
2062
|
const ctorName = value?.constructor?.name;
|
|
@@ -2216,15 +2504,29 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
|
|
|
2216
2504
|
REPLAY_PERSISTENCE_TIMEOUT_MS
|
|
2217
2505
|
);
|
|
2218
2506
|
if (!deferredSettled) {
|
|
2507
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2219
2508
|
throw new BitfabError(
|
|
2220
2509
|
`Replay could not settle deferred span work before the deadline, so the expected span counts are incomplete (testRunId ${testRunId}).`
|
|
2221
2510
|
);
|
|
2222
2511
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2512
|
+
if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
|
|
2513
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2225
2514
|
return;
|
|
2226
2515
|
}
|
|
2227
2516
|
const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
|
|
2517
|
+
const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2518
|
+
const expectedSpanCounts = {};
|
|
2519
|
+
let allDelivered = true;
|
|
2520
|
+
for (const [traceId, delivery] of Object.entries(deliveries)) {
|
|
2521
|
+
if (!delivery.closed) {
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
expectedSpanCounts[traceId] = delivery.spanCount;
|
|
2525
|
+
allDelivered = allDelivered && delivery.delivered;
|
|
2526
|
+
}
|
|
2527
|
+
if (allDelivered) {
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2228
2530
|
const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
|
|
2229
2531
|
let missing = Object.keys(expectedSpanCounts).length;
|
|
2230
2532
|
while (true) {
|
|
@@ -2333,6 +2635,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
2333
2635
|
...registeredOverrides
|
|
2334
2636
|
];
|
|
2335
2637
|
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
2638
|
+
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
2336
2639
|
const tasks = serverItems.map(
|
|
2337
2640
|
(serverItem, index) => () => processItem(
|
|
2338
2641
|
httpClient,
|
|
@@ -2547,6 +2850,7 @@ export {
|
|
|
2547
2850
|
ReplayError,
|
|
2548
2851
|
DbBranchReplayError,
|
|
2549
2852
|
serializeReplayResult,
|
|
2853
|
+
waitForReplayPersistence,
|
|
2550
2854
|
replay
|
|
2551
2855
|
};
|
|
2552
|
-
//# sourceMappingURL=chunk-
|
|
2856
|
+
//# sourceMappingURL=chunk-QUX6N7ON.js.map
|