bitfab 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
|
@@ -51,7 +51,7 @@ var __version__;
|
|
|
51
51
|
var init_version_generated = __esm({
|
|
52
52
|
"src/version.generated.ts"() {
|
|
53
53
|
"use strict";
|
|
54
|
-
__version__ = "0.36.
|
|
54
|
+
__version__ = "0.36.8";
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
|
|
@@ -85,33 +85,58 @@ function toArrayBuffer(view) {
|
|
|
85
85
|
view.byteOffset + view.byteLength
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
|
+
function compressedRequest(body, rawBytes, compressed) {
|
|
89
|
+
if (compressed.byteLength >= rawBytes) {
|
|
90
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
|
|
94
|
+
contentEncoding: "gzip",
|
|
95
|
+
rawBytes,
|
|
96
|
+
wireBytes: compressed.byteLength
|
|
97
|
+
};
|
|
98
|
+
}
|
|
88
99
|
async function gzipViaStream(bytes) {
|
|
89
100
|
const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
90
101
|
return await new Response(stream).arrayBuffer();
|
|
91
102
|
}
|
|
92
103
|
function encodeRequestBody(body) {
|
|
93
104
|
if (readEnv(DISABLE_COMPRESSION_ENV)) {
|
|
94
|
-
|
|
105
|
+
const rawBytes = new TextEncoder().encode(body).byteLength;
|
|
106
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
95
107
|
}
|
|
96
108
|
const bytes = new TextEncoder().encode(body);
|
|
97
109
|
if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
|
|
98
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
body,
|
|
112
|
+
rawBytes: bytes.byteLength,
|
|
113
|
+
wireBytes: bytes.byteLength
|
|
114
|
+
};
|
|
99
115
|
}
|
|
100
116
|
if (gzipNode) {
|
|
101
117
|
return gzipNode(bytes).then(
|
|
102
|
-
(compressed) => (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
119
|
+
() => ({
|
|
120
|
+
body,
|
|
121
|
+
rawBytes: bytes.byteLength,
|
|
122
|
+
wireBytes: bytes.byteLength
|
|
123
|
+
})
|
|
107
124
|
);
|
|
108
125
|
}
|
|
109
126
|
if (typeof CompressionStream === "undefined") {
|
|
110
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
body,
|
|
129
|
+
rawBytes: bytes.byteLength,
|
|
130
|
+
wireBytes: bytes.byteLength
|
|
131
|
+
};
|
|
111
132
|
}
|
|
112
133
|
return gzipViaStream(bytes).then(
|
|
113
|
-
(compressed) => (
|
|
114
|
-
() => ({
|
|
134
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
135
|
+
() => ({
|
|
136
|
+
body,
|
|
137
|
+
rawBytes: bytes.byteLength,
|
|
138
|
+
wireBytes: bytes.byteLength
|
|
139
|
+
})
|
|
115
140
|
);
|
|
116
141
|
}
|
|
117
142
|
var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
|
|
@@ -152,10 +177,11 @@ var init_errors = __esm({
|
|
|
152
177
|
"src/errors.ts"() {
|
|
153
178
|
"use strict";
|
|
154
179
|
BitfabError = class extends Error {
|
|
155
|
-
constructor(message, url, status) {
|
|
180
|
+
constructor(message, url, status, retryAfterMs) {
|
|
156
181
|
super(message);
|
|
157
182
|
this.url = url;
|
|
158
183
|
this.status = status;
|
|
184
|
+
this.retryAfterMs = retryAfterMs;
|
|
159
185
|
this.name = "BitfabError";
|
|
160
186
|
}
|
|
161
187
|
};
|
|
@@ -256,15 +282,15 @@ function carrierBytesOf(encoded, body) {
|
|
|
256
282
|
}
|
|
257
283
|
return encoded.length + extra;
|
|
258
284
|
}
|
|
259
|
-
function fitsCarrierBudget(body) {
|
|
285
|
+
function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
260
286
|
const units = body.length;
|
|
261
|
-
if (units * MAX_BYTES_PER_UNIT + 2 <=
|
|
287
|
+
if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
|
|
262
288
|
return true;
|
|
263
289
|
}
|
|
264
|
-
if (units + 2 >
|
|
290
|
+
if (units + 2 > maxBytes) {
|
|
265
291
|
return false;
|
|
266
292
|
}
|
|
267
|
-
return carrierByteLength(body) <=
|
|
293
|
+
return carrierByteLength(body) <= maxBytes;
|
|
268
294
|
}
|
|
269
295
|
function asRecord(value) {
|
|
270
296
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
@@ -308,7 +334,7 @@ function collectCandidates(containers) {
|
|
|
308
334
|
}
|
|
309
335
|
return candidates.sort((a, b) => b.size - a.size);
|
|
310
336
|
}
|
|
311
|
-
function trimPayloadToBudget(payload, encode) {
|
|
337
|
+
function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
312
338
|
const { copy, containers } = cloneTrimmable(payload);
|
|
313
339
|
const candidates = collectCandidates(containers);
|
|
314
340
|
if (candidates.length === 0) {
|
|
@@ -324,30 +350,31 @@ function trimPayloadToBudget(payload, encode) {
|
|
|
324
350
|
} catch {
|
|
325
351
|
return void 0;
|
|
326
352
|
}
|
|
327
|
-
if (fitsCarrierBudget(body)) {
|
|
353
|
+
if (fitsCarrierBudget(body, maxBytes)) {
|
|
328
354
|
return { value: copy, trimmed };
|
|
329
355
|
}
|
|
330
356
|
}
|
|
331
357
|
return void 0;
|
|
332
358
|
}
|
|
333
|
-
function markPayloadTrimmed(value, trimmed) {
|
|
359
|
+
function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
334
360
|
const existing = Array.isArray(value.errors) ? value.errors : [];
|
|
335
361
|
value.errors = [
|
|
336
362
|
...existing,
|
|
337
363
|
{
|
|
338
364
|
source: "sdk",
|
|
339
365
|
step: "payload_budget",
|
|
340
|
-
error: `trimmed oversized field(s) to fit the ${
|
|
366
|
+
error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
|
|
341
367
|
...new Set(trimmed)
|
|
342
368
|
].join(", ")}`
|
|
343
369
|
}
|
|
344
370
|
];
|
|
345
371
|
}
|
|
346
|
-
var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
|
|
372
|
+
var MAX_SPAN_CARRIER_BYTES, MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
|
|
347
373
|
var init_payloadBudget = __esm({
|
|
348
374
|
"src/payloadBudget.ts"() {
|
|
349
375
|
"use strict";
|
|
350
376
|
MAX_SPAN_CARRIER_BYTES = 28e5;
|
|
377
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
|
|
351
378
|
textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
352
379
|
MAX_BYTES_PER_UNIT = 3;
|
|
353
380
|
STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -379,30 +406,31 @@ var init_warnOnce = __esm({
|
|
|
379
406
|
});
|
|
380
407
|
|
|
381
408
|
// src/serializePayload.ts
|
|
382
|
-
function serializePayloadBody(payload) {
|
|
409
|
+
function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
383
410
|
const encoded = encodePayloadBody(payload);
|
|
384
|
-
if (fitsCarrierBudget(encoded.body)) {
|
|
411
|
+
if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
|
|
385
412
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
386
413
|
}
|
|
387
|
-
return applyPayloadBudget(encoded);
|
|
414
|
+
return applyPayloadBudget(encoded, maxCarrierBytes);
|
|
388
415
|
}
|
|
389
|
-
function applyPayloadBudget(encoded) {
|
|
416
|
+
function applyPayloadBudget(encoded, maxCarrierBytes) {
|
|
390
417
|
const result = encoded.value ? trimPayloadToBudget(
|
|
391
418
|
encoded.value,
|
|
392
|
-
(value) => encodePayloadBody(value).body
|
|
419
|
+
(value) => encodePayloadBody(value).body,
|
|
420
|
+
maxCarrierBytes
|
|
393
421
|
) : void 0;
|
|
394
422
|
if (!result) {
|
|
395
423
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
396
424
|
}
|
|
397
425
|
warnOnce(
|
|
398
426
|
"payload:over-budget",
|
|
399
|
-
`a span payload exceeded the ${
|
|
427
|
+
`a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
|
|
400
428
|
...new Set(result.trimmed)
|
|
401
429
|
].join(
|
|
402
430
|
", "
|
|
403
431
|
)}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
|
|
404
432
|
);
|
|
405
|
-
markPayloadTrimmed(result.value, result.trimmed);
|
|
433
|
+
markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
|
|
406
434
|
return {
|
|
407
435
|
body: encodePayloadBody(result.value).body,
|
|
408
436
|
dropped: encoded.dropped
|
|
@@ -508,6 +536,23 @@ var init_serializePayload = __esm({
|
|
|
508
536
|
}
|
|
509
537
|
});
|
|
510
538
|
|
|
539
|
+
// src/transportTypes.ts
|
|
540
|
+
var DeliveryError;
|
|
541
|
+
var init_transportTypes = __esm({
|
|
542
|
+
"src/transportTypes.ts"() {
|
|
543
|
+
"use strict";
|
|
544
|
+
DeliveryError = class extends Error {
|
|
545
|
+
constructor(message, options = {}) {
|
|
546
|
+
super(message);
|
|
547
|
+
this.name = "DeliveryError";
|
|
548
|
+
this.retryable = options.retryable ?? false;
|
|
549
|
+
this.oversized = options.oversized ?? false;
|
|
550
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
511
556
|
// src/unrefTimer.ts
|
|
512
557
|
function unrefTimer(timer) {
|
|
513
558
|
const handle = timer;
|
|
@@ -547,59 +592,6 @@ function logError(message, error) {
|
|
|
547
592
|
} catch {
|
|
548
593
|
}
|
|
549
594
|
}
|
|
550
|
-
function recordTraceSubmission(operation, payload) {
|
|
551
|
-
const sourceTraceId = resolveSourceTraceId(payload);
|
|
552
|
-
if (sourceTraceId === void 0) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
if (operation === "external_span") {
|
|
556
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
557
|
-
if (typeof rawSpan?.id !== "string") {
|
|
558
|
-
submissionCounter += 1;
|
|
559
|
-
}
|
|
560
|
-
const sourceSpanId = typeof rawSpan?.id === "string" ? rawSpan.id : `submission-${submissionCounter}`;
|
|
561
|
-
const existing = traceSubmissionSpanIds.get(sourceTraceId);
|
|
562
|
-
if (existing) {
|
|
563
|
-
existing.add(sourceSpanId);
|
|
564
|
-
} else {
|
|
565
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set([sourceSpanId]));
|
|
566
|
-
}
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
if (payload.completed !== true) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
if (typeof payload.testRunId === "string") {
|
|
573
|
-
replayTraceSubmissions.add(sourceTraceId);
|
|
574
|
-
if (!traceSubmissionSpanIds.has(sourceTraceId)) {
|
|
575
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set());
|
|
576
|
-
}
|
|
577
|
-
} else {
|
|
578
|
-
traceSubmissionSpanIds.delete(sourceTraceId);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
function takeReplaySpanCounts(traceIds) {
|
|
582
|
-
const counts = {};
|
|
583
|
-
for (const traceId of traceIds) {
|
|
584
|
-
if (!replayTraceSubmissions.has(traceId)) {
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
counts[traceId] = traceSubmissionSpanIds.get(traceId)?.size ?? 0;
|
|
588
|
-
traceSubmissionSpanIds.delete(traceId);
|
|
589
|
-
replayTraceSubmissions.delete(traceId);
|
|
590
|
-
}
|
|
591
|
-
return counts;
|
|
592
|
-
}
|
|
593
|
-
function asRecord2(value) {
|
|
594
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
595
|
-
}
|
|
596
|
-
function resolveSourceTraceId(payload) {
|
|
597
|
-
if (typeof payload.sourceTraceId === "string") {
|
|
598
|
-
return payload.sourceTraceId;
|
|
599
|
-
}
|
|
600
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
601
|
-
return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
|
|
602
|
-
}
|
|
603
595
|
function otlpValue(value) {
|
|
604
596
|
if (typeof value === "boolean") {
|
|
605
597
|
return { boolValue: value };
|
|
@@ -657,7 +649,36 @@ function spanToOtlp(span) {
|
|
|
657
649
|
}
|
|
658
650
|
function encodeSpan(span) {
|
|
659
651
|
const json = JSON.stringify(spanToOtlp(span));
|
|
660
|
-
return {
|
|
652
|
+
return {
|
|
653
|
+
json,
|
|
654
|
+
size: byteLength(json),
|
|
655
|
+
ref: carrierRefs.get(span)
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function trimEncodedSpan(span) {
|
|
659
|
+
try {
|
|
660
|
+
const carrier = JSON.parse(span.json);
|
|
661
|
+
const attribute = carrier.attributes?.find(
|
|
662
|
+
(entry) => entry.key === PAYLOAD_ATTRIBUTE
|
|
663
|
+
);
|
|
664
|
+
const payloadBody = attribute?.value?.stringValue;
|
|
665
|
+
if (!attribute?.value || payloadBody === void 0) {
|
|
666
|
+
return void 0;
|
|
667
|
+
}
|
|
668
|
+
const payload = JSON.parse(payloadBody);
|
|
669
|
+
attribute.value.stringValue = serializePayloadBody(
|
|
670
|
+
payload,
|
|
671
|
+
MAX_SPAN_CARRIER_BYTES
|
|
672
|
+
).body;
|
|
673
|
+
const json = JSON.stringify(carrier);
|
|
674
|
+
return { json, size: byteLength(json) };
|
|
675
|
+
} catch {
|
|
676
|
+
return void 0;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function prepareRequest(body) {
|
|
680
|
+
const prepared = encodeRequestBody(body);
|
|
681
|
+
return prepared instanceof Promise ? await prepared : prepared;
|
|
661
682
|
}
|
|
662
683
|
function requestEnvelope(first) {
|
|
663
684
|
const scope = first.instrumentationScope;
|
|
@@ -715,48 +736,27 @@ async function mapWithConcurrency(items, limit, task) {
|
|
|
715
736
|
await Promise.all(workers);
|
|
716
737
|
return results;
|
|
717
738
|
}
|
|
718
|
-
function responseStatus(error) {
|
|
719
|
-
return error instanceof BitfabError ? error.status : void 0;
|
|
720
|
-
}
|
|
721
739
|
function isRetryable(error) {
|
|
722
|
-
|
|
723
|
-
if (status === void 0) {
|
|
724
|
-
return true;
|
|
725
|
-
}
|
|
726
|
-
return RETRYABLE_STATUSES.has(status) || status >= 500;
|
|
740
|
+
return error instanceof DeliveryError && error.retryable;
|
|
727
741
|
}
|
|
728
|
-
function
|
|
729
|
-
|
|
742
|
+
function isOversized(error) {
|
|
743
|
+
return error instanceof DeliveryError && error.oversized;
|
|
730
744
|
}
|
|
731
|
-
function
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
745
|
+
function retryWaitMillis(error, attempt, remainingMillis) {
|
|
746
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
747
|
+
const affordable = remainingMillis / 2;
|
|
748
|
+
if (requested !== void 0) {
|
|
749
|
+
return requested < affordable ? requested : null;
|
|
737
750
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
745
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
746
|
-
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
747
|
-
if (typeof raw !== "string") {
|
|
748
|
-
return void 0;
|
|
749
|
-
}
|
|
750
|
-
const parsed = Date.parse(raw);
|
|
751
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
751
|
+
const backoff = Math.min(
|
|
752
|
+
RETRY_BASE_DELAY_MILLIS * 2 ** attempt,
|
|
753
|
+
RETRY_BACKOFF_CEILING_MILLIS
|
|
754
|
+
);
|
|
755
|
+
const jittered = backoff / 2 + Math.random() * (backoff / 2);
|
|
756
|
+
return jittered < affordable ? jittered : null;
|
|
752
757
|
}
|
|
753
|
-
function
|
|
754
|
-
|
|
755
|
-
if (spanData?.error != null) {
|
|
756
|
-
return true;
|
|
757
|
-
}
|
|
758
|
-
const errors = payload.errors;
|
|
759
|
-
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
758
|
+
function endSpan(span, endTime) {
|
|
759
|
+
span.end(endTime);
|
|
760
760
|
}
|
|
761
761
|
function createOtelTransport(options) {
|
|
762
762
|
return new OtelBatchTransport({
|
|
@@ -795,7 +795,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
|
|
|
795
795
|
(transport, remaining) => transport.shutdown(remaining)
|
|
796
796
|
);
|
|
797
797
|
}
|
|
798
|
-
var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE,
|
|
798
|
+
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;
|
|
799
799
|
var init_otel = __esm({
|
|
800
800
|
"src/otel.ts"() {
|
|
801
801
|
"use strict";
|
|
@@ -803,17 +803,19 @@ var init_otel = __esm({
|
|
|
803
803
|
import_core = require("@opentelemetry/core");
|
|
804
804
|
import_resources = require("@opentelemetry/resources");
|
|
805
805
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
806
|
+
init_compress();
|
|
806
807
|
init_constants();
|
|
807
808
|
init_errors();
|
|
808
809
|
init_payloadBudget();
|
|
809
810
|
init_readEnv();
|
|
810
811
|
init_serializePayload();
|
|
812
|
+
init_transportTypes();
|
|
811
813
|
init_unrefTimer();
|
|
812
814
|
init_warnOnce();
|
|
813
815
|
OPERATION_ATTRIBUTE = "bitfab.operation";
|
|
814
816
|
PAYLOAD_ATTRIBUTE = "bitfab.payload";
|
|
815
|
-
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
816
817
|
MAX_EXPORT_REQUEST_BYTES = 3e6;
|
|
818
|
+
MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
|
|
817
819
|
MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
|
|
818
820
|
EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
|
|
819
821
|
MAX_QUEUE_SIZE = 8192;
|
|
@@ -823,25 +825,23 @@ var init_otel = __esm({
|
|
|
823
825
|
MAX_EXPORT_CONCURRENCY = 64;
|
|
824
826
|
SCHEDULE_DELAY_MILLIS = 5e3;
|
|
825
827
|
EXPORT_TIMEOUT_MILLIS = 3e4;
|
|
826
|
-
|
|
828
|
+
RETRY_BASE_DELAY_MILLIS = 100;
|
|
829
|
+
RETRY_BACKOFF_CEILING_MILLIS = 5e3;
|
|
827
830
|
MAX_SEND_ATTEMPTS = 3;
|
|
828
831
|
DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
829
|
-
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
830
832
|
liveTransports = /* @__PURE__ */ new Set();
|
|
831
|
-
|
|
832
|
-
replayTraceSubmissions = /* @__PURE__ */ new Set();
|
|
833
|
-
submissionCounter = 0;
|
|
833
|
+
carrierRefs = /* @__PURE__ */ new WeakMap();
|
|
834
834
|
SPAN_SEPARATOR_BYTES = 1;
|
|
835
|
-
OtlpPayloadTooLargeError = class extends Error {
|
|
836
|
-
};
|
|
837
|
-
OtlpPartialSuccessError = class extends Error {
|
|
838
|
-
};
|
|
839
835
|
BitfabSpanExporter = class {
|
|
840
|
-
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency) {
|
|
836
|
+
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency, onDelivered, exportTimeoutMillis = EXPORT_TIMEOUT_MILLIS) {
|
|
841
837
|
this.directSender = directSender;
|
|
842
838
|
this.maxRequestBytes = maxRequestBytes;
|
|
843
839
|
this.maxRequestBatchSize = maxRequestBatchSize;
|
|
844
840
|
this.exportConcurrency = exportConcurrency;
|
|
841
|
+
this.onDelivered = onDelivered;
|
|
842
|
+
this.exportTimeoutMillis = exportTimeoutMillis;
|
|
843
|
+
/** Epoch ms until which the server has asked this exporter to stay away. */
|
|
844
|
+
this.throttledUntil = 0;
|
|
845
845
|
}
|
|
846
846
|
export(spans, resultCallback) {
|
|
847
847
|
void this.exportAsync(spans).then(
|
|
@@ -896,25 +896,51 @@ var init_otel = __esm({
|
|
|
896
896
|
return batches;
|
|
897
897
|
}
|
|
898
898
|
async send(envelope, batch) {
|
|
899
|
-
if (batch.size > this.maxRequestBytes) {
|
|
900
|
-
logError(
|
|
901
|
-
"a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
|
|
902
|
-
);
|
|
903
|
-
return false;
|
|
904
|
-
}
|
|
905
899
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
900
|
+
let requestSpans = batch.spans;
|
|
901
|
+
let requestRawBytes = batch.size;
|
|
902
|
+
let alreadyTrimmed = false;
|
|
903
|
+
while (true) {
|
|
904
|
+
if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
|
|
905
|
+
const prepared = await prepareRequest(
|
|
906
|
+
encodeRequest(envelope, requestSpans)
|
|
907
|
+
);
|
|
908
|
+
if (prepared.wireBytes <= this.maxRequestBytes) {
|
|
909
|
+
await this.sendWithRetries(prepared);
|
|
910
|
+
this.reportDelivered(batch.spans);
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (batch.spans.length !== 1) {
|
|
915
|
+
logError(
|
|
916
|
+
"an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
|
|
917
|
+
);
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
if (alreadyTrimmed) {
|
|
921
|
+
logError(
|
|
922
|
+
"a single OpenTelemetry span exceeded the configured request-size target after trimming"
|
|
923
|
+
);
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
const trimmed = trimEncodedSpan(batch.spans[0]);
|
|
927
|
+
if (!trimmed) {
|
|
928
|
+
logError(
|
|
929
|
+
"a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
|
|
930
|
+
);
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
requestSpans = [trimmed];
|
|
934
|
+
requestRawBytes = envelope.size + trimmed.size;
|
|
935
|
+
alreadyTrimmed = true;
|
|
936
|
+
}
|
|
908
937
|
} catch (error) {
|
|
909
|
-
if (error
|
|
938
|
+
if (isOversized(error)) {
|
|
910
939
|
logError(
|
|
911
940
|
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"
|
|
912
941
|
);
|
|
913
942
|
return false;
|
|
914
943
|
}
|
|
915
|
-
if (error instanceof OtlpPartialSuccessError) {
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
944
|
logError("failed to export an OpenTelemetry span batch", error);
|
|
919
945
|
return false;
|
|
920
946
|
}
|
|
@@ -932,37 +958,79 @@ var init_otel = __esm({
|
|
|
932
958
|
* the server does not yet understand. The fix is a client-supplied
|
|
933
959
|
* idempotency key that ingestion dedupes on.
|
|
934
960
|
*/
|
|
935
|
-
|
|
961
|
+
/**
|
|
962
|
+
* Remember a throttle the server asked for, so the requests fanned out
|
|
963
|
+
* alongside this one respect it too. Delaying only the request that was
|
|
964
|
+
* refused leaves the other seven in the window hitting a server that just
|
|
965
|
+
* asked for room.
|
|
966
|
+
*/
|
|
967
|
+
recordThrottle(error) {
|
|
968
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
969
|
+
if (requested !== void 0) {
|
|
970
|
+
this.throttledUntil = Math.max(
|
|
971
|
+
this.throttledUntil,
|
|
972
|
+
Date.now() + requested
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Waits out an active throttle, or reports the batch undeliverable when the
|
|
978
|
+
* throttle outlasts what we are willing to hold it for. Either way nothing is
|
|
979
|
+
* sent while the server has asked us to stay away.
|
|
980
|
+
*/
|
|
981
|
+
async awaitThrottle(deadline) {
|
|
982
|
+
const remaining = this.throttledUntil - Date.now();
|
|
983
|
+
if (remaining <= 0) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
if (remaining >= (deadline - Date.now()) / 2) {
|
|
987
|
+
throw new DeliveryError(
|
|
988
|
+
`OTLP ingestion is throttled for another ${remaining}ms, longer than the export budget`
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
await delay(remaining);
|
|
992
|
+
}
|
|
993
|
+
async sendWithRetries(request) {
|
|
994
|
+
const deadline = Date.now() + this.exportTimeoutMillis;
|
|
936
995
|
for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
|
|
937
996
|
try {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
body,
|
|
941
|
-
EXPORT_TIMEOUT_MILLIS
|
|
942
|
-
);
|
|
943
|
-
const partialSuccess = asRecord2(response?.partialSuccess);
|
|
944
|
-
const rejected = partialSuccess?.rejectedSpans;
|
|
945
|
-
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
946
|
-
logError(
|
|
947
|
-
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
948
|
-
);
|
|
949
|
-
throw new OtlpPartialSuccessError();
|
|
950
|
-
}
|
|
997
|
+
await this.awaitThrottle(deadline);
|
|
998
|
+
await this.directSender(request, Math.max(0, deadline - Date.now()));
|
|
951
999
|
return;
|
|
952
1000
|
} catch (error) {
|
|
953
|
-
if (error
|
|
1001
|
+
if (isOversized(error)) {
|
|
954
1002
|
throw error;
|
|
955
1003
|
}
|
|
956
|
-
|
|
957
|
-
throw new OtlpPayloadTooLargeError();
|
|
958
|
-
}
|
|
1004
|
+
this.recordThrottle(error);
|
|
959
1005
|
if (attempt === MAX_SEND_ATTEMPTS - 1 || !isRetryable(error)) {
|
|
960
1006
|
throw error;
|
|
961
1007
|
}
|
|
962
|
-
|
|
1008
|
+
const wait = retryWaitMillis(error, attempt, deadline - Date.now());
|
|
1009
|
+
if (wait === null) {
|
|
1010
|
+
throw error;
|
|
1011
|
+
}
|
|
1012
|
+
await delay(wait);
|
|
963
1013
|
}
|
|
964
1014
|
}
|
|
965
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Announce the carriers a request delivered. Wrapped because a listener that
|
|
1018
|
+
* throws must never turn a delivered batch into a failed export.
|
|
1019
|
+
*/
|
|
1020
|
+
reportDelivered(spans) {
|
|
1021
|
+
if (this.onDelivered === void 0) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const refs = spans.map((span) => span.ref).filter((ref) => ref !== void 0);
|
|
1025
|
+
if (refs.length === 0) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
try {
|
|
1029
|
+
this.onDelivered(refs);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
logError("a delivery listener threw", error);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
966
1034
|
async shutdown() {
|
|
967
1035
|
}
|
|
968
1036
|
async forceFlush() {
|
|
@@ -1020,7 +1088,9 @@ var init_otel = __esm({
|
|
|
1020
1088
|
options.directSender,
|
|
1021
1089
|
maxRequestBytes,
|
|
1022
1090
|
maxRequestBatchSize,
|
|
1023
|
-
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
|
|
1091
|
+
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY,
|
|
1092
|
+
options.onDelivered,
|
|
1093
|
+
options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
|
|
1024
1094
|
)
|
|
1025
1095
|
);
|
|
1026
1096
|
this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
|
|
@@ -1044,8 +1114,7 @@ var init_otel = __esm({
|
|
|
1044
1114
|
this.tracer = this.provider.getTracer("bitfab", __version__);
|
|
1045
1115
|
liveTransports.add(this);
|
|
1046
1116
|
}
|
|
1047
|
-
submit(operation, payload) {
|
|
1048
|
-
recordTraceSubmission(operation, payload);
|
|
1117
|
+
submit(operation, payload, meta = {}) {
|
|
1049
1118
|
if (this.closed) {
|
|
1050
1119
|
warnOnce(
|
|
1051
1120
|
"otel-submit-after-shutdown",
|
|
@@ -1054,7 +1123,10 @@ var init_otel = __esm({
|
|
|
1054
1123
|
return;
|
|
1055
1124
|
}
|
|
1056
1125
|
try {
|
|
1057
|
-
const { body, dropped } = serializePayloadBody(
|
|
1126
|
+
const { body, dropped } = serializePayloadBody(
|
|
1127
|
+
payload,
|
|
1128
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
|
|
1129
|
+
);
|
|
1058
1130
|
if (dropped.length > 0) {
|
|
1059
1131
|
warnOnce(
|
|
1060
1132
|
"otel-carrier-payload-stubbed",
|
|
@@ -1063,17 +1135,20 @@ var init_otel = __esm({
|
|
|
1063
1135
|
].join(", ")}); they were stubbed so the span still ships, but the trace may be incomplete or not replayable.`
|
|
1064
1136
|
);
|
|
1065
1137
|
}
|
|
1066
|
-
const span = this.tracer.startSpan(
|
|
1138
|
+
const span = this.tracer.startSpan(meta.name ?? `bitfab.${operation}`, {
|
|
1067
1139
|
attributes: {
|
|
1068
1140
|
[OPERATION_ATTRIBUTE]: operation,
|
|
1069
1141
|
[PAYLOAD_ATTRIBUTE]: body
|
|
1070
1142
|
},
|
|
1071
|
-
startTime:
|
|
1143
|
+
startTime: meta.startTime
|
|
1072
1144
|
});
|
|
1073
|
-
if (
|
|
1145
|
+
if (meta.ref !== void 0) {
|
|
1146
|
+
carrierRefs.set(span, meta.ref);
|
|
1147
|
+
}
|
|
1148
|
+
if (meta.errored === true) {
|
|
1074
1149
|
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
1075
1150
|
}
|
|
1076
|
-
endSpan(span,
|
|
1151
|
+
endSpan(span, meta.endTime);
|
|
1077
1152
|
} catch (error) {
|
|
1078
1153
|
logError("failed to queue an OpenTelemetry span", error);
|
|
1079
1154
|
}
|
|
@@ -1123,9 +1198,6 @@ function flushTraceTransports(timeoutMs) {
|
|
|
1123
1198
|
function shutdownTraceTransports(timeoutMs) {
|
|
1124
1199
|
return shutdownOtelTransports(timeoutMs);
|
|
1125
1200
|
}
|
|
1126
|
-
function takeReplaySpanCounts2(traceIds) {
|
|
1127
|
-
return takeReplaySpanCounts(traceIds);
|
|
1128
|
-
}
|
|
1129
1201
|
var init_transport = __esm({
|
|
1130
1202
|
"src/transport.ts"() {
|
|
1131
1203
|
"use strict";
|
|
@@ -1174,7 +1246,96 @@ async function waitForPromises(promises, timeoutMs) {
|
|
|
1174
1246
|
}
|
|
1175
1247
|
}
|
|
1176
1248
|
}
|
|
1177
|
-
|
|
1249
|
+
function readHeader(response, name) {
|
|
1250
|
+
try {
|
|
1251
|
+
return response.headers?.get(name) ?? null;
|
|
1252
|
+
} catch {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function parseRetryAfterMs(header) {
|
|
1257
|
+
const value = header?.trim();
|
|
1258
|
+
if (!value) {
|
|
1259
|
+
return void 0;
|
|
1260
|
+
}
|
|
1261
|
+
const seconds = Number(value);
|
|
1262
|
+
if (Number.isFinite(seconds)) {
|
|
1263
|
+
return seconds >= 0 ? seconds * 1e3 : void 0;
|
|
1264
|
+
}
|
|
1265
|
+
const at = Date.parse(value);
|
|
1266
|
+
if (Number.isNaN(at)) {
|
|
1267
|
+
return void 0;
|
|
1268
|
+
}
|
|
1269
|
+
return Math.max(0, at - Date.now());
|
|
1270
|
+
}
|
|
1271
|
+
function carrierMeta(operation, payload, ref) {
|
|
1272
|
+
return {
|
|
1273
|
+
ref,
|
|
1274
|
+
name: carrierName(operation, payload),
|
|
1275
|
+
startTime: payloadTimestamp(payload, "started_at"),
|
|
1276
|
+
endTime: payloadTimestamp(payload, "ended_at"),
|
|
1277
|
+
errored: payloadHasError(payload)
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function carrierName(operation, payload) {
|
|
1281
|
+
if (operation === "external_span") {
|
|
1282
|
+
const spanData = asPayloadRecord(
|
|
1283
|
+
asPayloadRecord(payload.rawSpan)?.span_data
|
|
1284
|
+
);
|
|
1285
|
+
if (typeof spanData?.name === "string") {
|
|
1286
|
+
return spanData.name;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (typeof payload.traceFunctionKey === "string") {
|
|
1290
|
+
return payload.traceFunctionKey;
|
|
1291
|
+
}
|
|
1292
|
+
return `bitfab.${operation}`;
|
|
1293
|
+
}
|
|
1294
|
+
function payloadTimestamp(payload, field) {
|
|
1295
|
+
const rawSpan = asPayloadRecord(payload.rawSpan);
|
|
1296
|
+
const rawTrace = asPayloadRecord(payload.externalTrace) ?? asPayloadRecord(payload.rawTrace);
|
|
1297
|
+
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
1298
|
+
if (typeof raw !== "string") {
|
|
1299
|
+
return void 0;
|
|
1300
|
+
}
|
|
1301
|
+
const parsed = Date.parse(raw);
|
|
1302
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1303
|
+
}
|
|
1304
|
+
function payloadHasError(payload) {
|
|
1305
|
+
const spanData = asPayloadRecord(asPayloadRecord(payload.rawSpan)?.span_data);
|
|
1306
|
+
if (spanData?.error != null) {
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
const errors = payload.errors;
|
|
1310
|
+
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
1311
|
+
}
|
|
1312
|
+
function asPayloadRecord(value) {
|
|
1313
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1314
|
+
}
|
|
1315
|
+
function carrierRef(payload) {
|
|
1316
|
+
const traceId = sourceTraceIdOf(payload);
|
|
1317
|
+
if (traceId === void 0) {
|
|
1318
|
+
return void 0;
|
|
1319
|
+
}
|
|
1320
|
+
const rawSpan = payload.rawSpan;
|
|
1321
|
+
if (rawSpan === void 0) {
|
|
1322
|
+
return { traceId };
|
|
1323
|
+
}
|
|
1324
|
+
const spanId = rawSpan?.id;
|
|
1325
|
+
return {
|
|
1326
|
+
traceId,
|
|
1327
|
+
spanId: typeof spanId === "string" ? spanId : `submission-${++carrierSeq}`
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function sourceTraceIdOf(payload) {
|
|
1331
|
+
if (typeof payload.sourceTraceId === "string") {
|
|
1332
|
+
return payload.sourceTraceId;
|
|
1333
|
+
}
|
|
1334
|
+
const rawTrace = payload.externalTrace ?? payload.rawTrace;
|
|
1335
|
+
const id = rawTrace?.id;
|
|
1336
|
+
return typeof id === "string" ? id : void 0;
|
|
1337
|
+
}
|
|
1338
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
|
|
1178
1339
|
var init_http = __esm({
|
|
1179
1340
|
"src/http.ts"() {
|
|
1180
1341
|
"use strict";
|
|
@@ -1184,9 +1345,12 @@ var init_http = __esm({
|
|
|
1184
1345
|
init_replayContext();
|
|
1185
1346
|
init_serializePayload();
|
|
1186
1347
|
init_transport();
|
|
1348
|
+
init_transportTypes();
|
|
1187
1349
|
init_unrefTimer();
|
|
1188
1350
|
init_warnOnce();
|
|
1189
1351
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
1352
|
+
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
1353
|
+
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1190
1354
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
1191
1355
|
DEFAULT_LIFECYCLE_TIMEOUT_MS2 = 3e4;
|
|
1192
1356
|
pendingTracePromises = /* @__PURE__ */ new Set();
|
|
@@ -1206,8 +1370,12 @@ var init_http = __esm({
|
|
|
1206
1370
|
});
|
|
1207
1371
|
});
|
|
1208
1372
|
}
|
|
1373
|
+
carrierSeq = 0;
|
|
1209
1374
|
HttpClient = class {
|
|
1210
1375
|
constructor(config) {
|
|
1376
|
+
// Only traces a caller asked about are tracked, so ordinary tracing stores
|
|
1377
|
+
// nothing here.
|
|
1378
|
+
this.traceDeliveries = /* @__PURE__ */ new Map();
|
|
1211
1379
|
// Deferred span work owned by THIS client. The module-global set backs the
|
|
1212
1380
|
// process-wide `flushTraces()` and the exit hook, but per-client lifecycle
|
|
1213
1381
|
// must not wait on another client's slow finalize: a false `close()` failure
|
|
@@ -1243,13 +1411,131 @@ var init_http = __esm({
|
|
|
1243
1411
|
}
|
|
1244
1412
|
if (!this.traceTransport) {
|
|
1245
1413
|
this.traceTransport = createTraceTransport({
|
|
1246
|
-
directSender: (
|
|
1247
|
-
|
|
1248
|
-
})
|
|
1414
|
+
directSender: (request, timeoutMs) => this.deliverCarriers(request, timeoutMs),
|
|
1415
|
+
onDelivered: (refs) => this.recordDeliveredCarriers(refs)
|
|
1249
1416
|
});
|
|
1250
1417
|
}
|
|
1251
1418
|
return this.traceTransport;
|
|
1252
1419
|
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Post one encoded batch and decide what the server's answer means, so the
|
|
1422
|
+
* transport never reads a response. Rejections and permanent statuses come
|
|
1423
|
+
* back as a non-retryable {@link DeliveryError}; anything the server might
|
|
1424
|
+
* still accept on a second try comes back retryable.
|
|
1425
|
+
*/
|
|
1426
|
+
async deliverCarriers(request, timeoutMs) {
|
|
1427
|
+
let response;
|
|
1428
|
+
try {
|
|
1429
|
+
response = await this.sendPrepared(
|
|
1430
|
+
OTLP_TRACES_ENDPOINT,
|
|
1431
|
+
request,
|
|
1432
|
+
{ timeout: timeoutMs }
|
|
1433
|
+
);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
const status = error instanceof BitfabError ? error.status : void 0;
|
|
1436
|
+
if (status === void 0) {
|
|
1437
|
+
throw new DeliveryError(`OTLP ingestion failed: ${String(error)}`, {
|
|
1438
|
+
retryable: true
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
throw new DeliveryError(`OTLP ingestion failed with HTTP ${status}`, {
|
|
1442
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
1443
|
+
oversized: status === 413,
|
|
1444
|
+
...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
const partialSuccess = asPayloadRecord(response?.partialSuccess);
|
|
1448
|
+
const rejected = partialSuccess?.rejectedSpans;
|
|
1449
|
+
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1450
|
+
throw new DeliveryError(
|
|
1451
|
+
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Start tracking delivery for `traceIds`. Nothing is recorded for a trace
|
|
1457
|
+
* that was never tracked, so ordinary tracing costs no bookkeeping at all.
|
|
1458
|
+
*/
|
|
1459
|
+
trackTraceDeliveries(traceIds) {
|
|
1460
|
+
for (const traceId of traceIds) {
|
|
1461
|
+
if (!this.traceDeliveries.has(traceId)) {
|
|
1462
|
+
this.traceDeliveries.set(traceId, {
|
|
1463
|
+
submittedSpanIds: /* @__PURE__ */ new Set(),
|
|
1464
|
+
ackedSpanIds: /* @__PURE__ */ new Set(),
|
|
1465
|
+
closed: false,
|
|
1466
|
+
closingAcked: false
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
/** Whether any tracked trace has had its closing carrier submitted. */
|
|
1472
|
+
hasClosedDeliveries(traceIds) {
|
|
1473
|
+
return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Report what each tracked trace submitted and whether the server confirmed
|
|
1477
|
+
* it, and stop tracking them. Every id passed is freed, so a caller cannot
|
|
1478
|
+
* leak a record for a trace that never closed.
|
|
1479
|
+
*
|
|
1480
|
+
* `delivered` is only meaningful once a flush has settled: acks land before
|
|
1481
|
+
* an export resolves, so a flush that reported success has already collected
|
|
1482
|
+
* every ack it is going to collect.
|
|
1483
|
+
*/
|
|
1484
|
+
takeTraceDeliveries(traceIds) {
|
|
1485
|
+
const reports = {};
|
|
1486
|
+
for (const traceId of traceIds) {
|
|
1487
|
+
const delivery = this.traceDeliveries.get(traceId);
|
|
1488
|
+
if (delivery === void 0) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
this.traceDeliveries.delete(traceId);
|
|
1492
|
+
reports[traceId] = {
|
|
1493
|
+
spanCount: delivery.submittedSpanIds.size,
|
|
1494
|
+
closed: delivery.closed,
|
|
1495
|
+
delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
|
|
1496
|
+
(spanId) => delivery.ackedSpanIds.has(spanId)
|
|
1497
|
+
)
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return reports;
|
|
1501
|
+
}
|
|
1502
|
+
/** Build a carrier's meta and record what it adds to its trace's expected set. */
|
|
1503
|
+
recordedMeta(operation, payload, ref) {
|
|
1504
|
+
this.recordSubmittedCarrier(ref);
|
|
1505
|
+
return carrierMeta(operation, payload, ref);
|
|
1506
|
+
}
|
|
1507
|
+
recordSubmittedCarrier(ref) {
|
|
1508
|
+
if (ref === void 0) {
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1512
|
+
if (delivery === void 0) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
if (ref.spanId === void 0) {
|
|
1516
|
+
delivery.closed = true;
|
|
1517
|
+
} else {
|
|
1518
|
+
delivery.submittedSpanIds.add(ref.spanId);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Ingestion commits every carrier in a request before it answers, so a
|
|
1523
|
+
* delivered ref is proof its row exists: the same fact the replay status
|
|
1524
|
+
* endpoint would report, already in hand.
|
|
1525
|
+
*/
|
|
1526
|
+
recordDeliveredCarriers(refs) {
|
|
1527
|
+
for (const ref of refs) {
|
|
1528
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1529
|
+
if (delivery === void 0) {
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
if (ref.spanId === void 0) {
|
|
1533
|
+
delivery.closingAcked = true;
|
|
1534
|
+
} else {
|
|
1535
|
+
delivery.ackedSpanIds.add(ref.spanId);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1253
1539
|
/**
|
|
1254
1540
|
* Track deferred span work so this client's own lifecycle waits for it, and
|
|
1255
1541
|
* so the process-wide flush and exit hook do too.
|
|
@@ -1330,13 +1616,16 @@ var init_http = __esm({
|
|
|
1330
1616
|
* same data twice.
|
|
1331
1617
|
*/
|
|
1332
1618
|
async sendEncoded(endpoint, body, options) {
|
|
1619
|
+
const prepared = encodeRequestBody(body);
|
|
1620
|
+
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1621
|
+
return this.sendPrepared(endpoint, encoded, options);
|
|
1622
|
+
}
|
|
1623
|
+
async sendPrepared(endpoint, encoded, options) {
|
|
1333
1624
|
const url = `${this.serviceUrl}${endpoint}`;
|
|
1334
1625
|
const timeout = options?.timeout ?? this.timeout;
|
|
1335
1626
|
const method = options?.method ?? "POST";
|
|
1336
1627
|
const controller = new AbortController();
|
|
1337
1628
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1338
|
-
const prepared = encodeRequestBody(body);
|
|
1339
|
-
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1340
1629
|
const headers = {
|
|
1341
1630
|
"Content-Type": "application/json",
|
|
1342
1631
|
Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
|
|
@@ -1356,7 +1645,8 @@ var init_http = __esm({
|
|
|
1356
1645
|
throw new BitfabError(
|
|
1357
1646
|
`HTTP ${response.status}: ${errorText.slice(0, 500)}`,
|
|
1358
1647
|
void 0,
|
|
1359
|
-
response.status
|
|
1648
|
+
response.status,
|
|
1649
|
+
parseRetryAfterMs(readHeader(response, "retry-after"))
|
|
1360
1650
|
);
|
|
1361
1651
|
}
|
|
1362
1652
|
const result = await response.json();
|
|
@@ -1442,11 +1732,12 @@ var init_http = __esm({
|
|
|
1442
1732
|
* the OTLP carrier has no path to carry it.
|
|
1443
1733
|
*/
|
|
1444
1734
|
sendInternalTrace(functionId, payload) {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1735
|
+
const body = { ...payload, functionId, sdkVersion: __version__ };
|
|
1736
|
+
this.getTraceTransport()?.submit(
|
|
1737
|
+
"internal_trace",
|
|
1738
|
+
body,
|
|
1739
|
+
carrierMeta("internal_trace", body, void 0)
|
|
1740
|
+
);
|
|
1450
1741
|
}
|
|
1451
1742
|
/**
|
|
1452
1743
|
* Queue an external span (from withSpan wrapper or OpenAI tracing) onto this
|
|
@@ -1455,10 +1746,11 @@ var init_http = __esm({
|
|
|
1455
1746
|
* promise.
|
|
1456
1747
|
*/
|
|
1457
1748
|
sendExternalSpan(payload) {
|
|
1458
|
-
this.getTraceTransport()?.submit(
|
|
1459
|
-
|
|
1460
|
-
sdkVersion: __version__
|
|
1461
|
-
|
|
1749
|
+
this.getTraceTransport()?.submit(
|
|
1750
|
+
"external_span",
|
|
1751
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1752
|
+
this.recordedMeta("external_span", payload, carrierRef(payload))
|
|
1753
|
+
);
|
|
1462
1754
|
}
|
|
1463
1755
|
/**
|
|
1464
1756
|
* Queue an external trace completion (from OpenAI tracing) onto this
|
|
@@ -1467,10 +1759,15 @@ var init_http = __esm({
|
|
|
1467
1759
|
* server-authoritative barrier in `replay.ts`, not by awaiting this call.
|
|
1468
1760
|
*/
|
|
1469
1761
|
sendExternalTrace(payload) {
|
|
1470
|
-
this.getTraceTransport()?.submit(
|
|
1471
|
-
|
|
1472
|
-
sdkVersion: __version__
|
|
1473
|
-
|
|
1762
|
+
this.getTraceTransport()?.submit(
|
|
1763
|
+
"external_trace",
|
|
1764
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1765
|
+
this.recordedMeta(
|
|
1766
|
+
"external_trace",
|
|
1767
|
+
payload,
|
|
1768
|
+
payload.completed === true ? carrierRef(payload) : void 0
|
|
1769
|
+
)
|
|
1770
|
+
);
|
|
1474
1771
|
}
|
|
1475
1772
|
/**
|
|
1476
1773
|
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
@@ -1810,8 +2107,8 @@ var init_serialize = __esm({
|
|
|
1810
2107
|
import_superjson = __toESM(require("superjson"), 1);
|
|
1811
2108
|
init_payloadBudget();
|
|
1812
2109
|
init_warnOnce();
|
|
1813
|
-
MAX_SERIALIZED_BYTES =
|
|
1814
|
-
MAX_FRAMEWORK_SERIALIZED_BYTES =
|
|
2110
|
+
MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
2111
|
+
MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
1815
2112
|
MAX_SAFE_DEPTH = 6;
|
|
1816
2113
|
}
|
|
1817
2114
|
});
|
|
@@ -2066,7 +2363,8 @@ __export(replay_exports, {
|
|
|
2066
2363
|
ReplayError: () => ReplayError,
|
|
2067
2364
|
replay: () => replay,
|
|
2068
2365
|
reportReplayProgress: () => reportReplayProgress,
|
|
2069
|
-
serializeReplayResult: () => serializeReplayResult
|
|
2366
|
+
serializeReplayResult: () => serializeReplayResult,
|
|
2367
|
+
waitForReplayPersistence: () => waitForReplayPersistence
|
|
2070
2368
|
});
|
|
2071
2369
|
function dbBranchEnabled(dbBranch) {
|
|
2072
2370
|
return dbBranch !== void 0 && dbBranch !== false;
|
|
@@ -2354,15 +2652,29 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
|
|
|
2354
2652
|
REPLAY_PERSISTENCE_TIMEOUT_MS
|
|
2355
2653
|
);
|
|
2356
2654
|
if (!deferredSettled) {
|
|
2655
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2357
2656
|
throw new BitfabError(
|
|
2358
2657
|
`Replay could not settle deferred span work before the deadline, so the expected span counts are incomplete (testRunId ${testRunId}).`
|
|
2359
2658
|
);
|
|
2360
2659
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2660
|
+
if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
|
|
2661
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2363
2662
|
return;
|
|
2364
2663
|
}
|
|
2365
2664
|
const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
|
|
2665
|
+
const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2666
|
+
const expectedSpanCounts = {};
|
|
2667
|
+
let allDelivered = true;
|
|
2668
|
+
for (const [traceId, delivery] of Object.entries(deliveries)) {
|
|
2669
|
+
if (!delivery.closed) {
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
expectedSpanCounts[traceId] = delivery.spanCount;
|
|
2673
|
+
allDelivered = allDelivered && delivery.delivered;
|
|
2674
|
+
}
|
|
2675
|
+
if (allDelivered) {
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2366
2678
|
const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
|
|
2367
2679
|
let missing = Object.keys(expectedSpanCounts).length;
|
|
2368
2680
|
while (true) {
|
|
@@ -2471,6 +2783,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
2471
2783
|
...registeredOverrides
|
|
2472
2784
|
];
|
|
2473
2785
|
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
2786
|
+
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
2474
2787
|
const tasks = serverItems.map(
|
|
2475
2788
|
(serverItem, index) => () => processItem(
|
|
2476
2789
|
httpClient,
|
|
@@ -2668,7 +2981,6 @@ var init_replay = __esm({
|
|
|
2668
2981
|
init_randomUuid();
|
|
2669
2982
|
init_replayContext();
|
|
2670
2983
|
init_serialize();
|
|
2671
|
-
init_transport();
|
|
2672
2984
|
init_unrefTimer();
|
|
2673
2985
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
2674
2986
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|