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/node.cjs
CHANGED
|
@@ -97,7 +97,7 @@ var __version__;
|
|
|
97
97
|
var init_version_generated = __esm({
|
|
98
98
|
"src/version.generated.ts"() {
|
|
99
99
|
"use strict";
|
|
100
|
-
__version__ = "0.36.
|
|
100
|
+
__version__ = "0.36.8";
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
|
|
@@ -131,33 +131,58 @@ function toArrayBuffer(view) {
|
|
|
131
131
|
view.byteOffset + view.byteLength
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
|
+
function compressedRequest(body, rawBytes, compressed) {
|
|
135
|
+
if (compressed.byteLength >= rawBytes) {
|
|
136
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
|
|
140
|
+
contentEncoding: "gzip",
|
|
141
|
+
rawBytes,
|
|
142
|
+
wireBytes: compressed.byteLength
|
|
143
|
+
};
|
|
144
|
+
}
|
|
134
145
|
async function gzipViaStream(bytes) {
|
|
135
146
|
const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
136
147
|
return await new Response(stream).arrayBuffer();
|
|
137
148
|
}
|
|
138
149
|
function encodeRequestBody(body) {
|
|
139
150
|
if (readEnv(DISABLE_COMPRESSION_ENV)) {
|
|
140
|
-
|
|
151
|
+
const rawBytes = new TextEncoder().encode(body).byteLength;
|
|
152
|
+
return { body, rawBytes, wireBytes: rawBytes };
|
|
141
153
|
}
|
|
142
154
|
const bytes = new TextEncoder().encode(body);
|
|
143
155
|
if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
|
|
144
|
-
return {
|
|
156
|
+
return {
|
|
157
|
+
body,
|
|
158
|
+
rawBytes: bytes.byteLength,
|
|
159
|
+
wireBytes: bytes.byteLength
|
|
160
|
+
};
|
|
145
161
|
}
|
|
146
162
|
if (gzipNode) {
|
|
147
163
|
return gzipNode(bytes).then(
|
|
148
|
-
(compressed) => (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
164
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
165
|
+
() => ({
|
|
166
|
+
body,
|
|
167
|
+
rawBytes: bytes.byteLength,
|
|
168
|
+
wireBytes: bytes.byteLength
|
|
169
|
+
})
|
|
153
170
|
);
|
|
154
171
|
}
|
|
155
172
|
if (typeof CompressionStream === "undefined") {
|
|
156
|
-
return {
|
|
173
|
+
return {
|
|
174
|
+
body,
|
|
175
|
+
rawBytes: bytes.byteLength,
|
|
176
|
+
wireBytes: bytes.byteLength
|
|
177
|
+
};
|
|
157
178
|
}
|
|
158
179
|
return gzipViaStream(bytes).then(
|
|
159
|
-
(compressed) => (
|
|
160
|
-
() => ({
|
|
180
|
+
(compressed) => compressedRequest(body, bytes.byteLength, compressed),
|
|
181
|
+
() => ({
|
|
182
|
+
body,
|
|
183
|
+
rawBytes: bytes.byteLength,
|
|
184
|
+
wireBytes: bytes.byteLength
|
|
185
|
+
})
|
|
161
186
|
);
|
|
162
187
|
}
|
|
163
188
|
var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
|
|
@@ -198,10 +223,11 @@ var init_errors = __esm({
|
|
|
198
223
|
"src/errors.ts"() {
|
|
199
224
|
"use strict";
|
|
200
225
|
BitfabError = class extends Error {
|
|
201
|
-
constructor(message, url, status) {
|
|
226
|
+
constructor(message, url, status, retryAfterMs) {
|
|
202
227
|
super(message);
|
|
203
228
|
this.url = url;
|
|
204
229
|
this.status = status;
|
|
230
|
+
this.retryAfterMs = retryAfterMs;
|
|
205
231
|
this.name = "BitfabError";
|
|
206
232
|
}
|
|
207
233
|
};
|
|
@@ -263,15 +289,15 @@ function carrierBytesOf(encoded, body) {
|
|
|
263
289
|
}
|
|
264
290
|
return encoded.length + extra;
|
|
265
291
|
}
|
|
266
|
-
function fitsCarrierBudget(body) {
|
|
292
|
+
function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
267
293
|
const units = body.length;
|
|
268
|
-
if (units * MAX_BYTES_PER_UNIT + 2 <=
|
|
294
|
+
if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
|
|
269
295
|
return true;
|
|
270
296
|
}
|
|
271
|
-
if (units + 2 >
|
|
297
|
+
if (units + 2 > maxBytes) {
|
|
272
298
|
return false;
|
|
273
299
|
}
|
|
274
|
-
return carrierByteLength(body) <=
|
|
300
|
+
return carrierByteLength(body) <= maxBytes;
|
|
275
301
|
}
|
|
276
302
|
function asRecord(value) {
|
|
277
303
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
@@ -315,7 +341,7 @@ function collectCandidates(containers) {
|
|
|
315
341
|
}
|
|
316
342
|
return candidates.sort((a, b) => b.size - a.size);
|
|
317
343
|
}
|
|
318
|
-
function trimPayloadToBudget(payload, encode) {
|
|
344
|
+
function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
319
345
|
const { copy, containers } = cloneTrimmable(payload);
|
|
320
346
|
const candidates = collectCandidates(containers);
|
|
321
347
|
if (candidates.length === 0) {
|
|
@@ -331,30 +357,31 @@ function trimPayloadToBudget(payload, encode) {
|
|
|
331
357
|
} catch {
|
|
332
358
|
return void 0;
|
|
333
359
|
}
|
|
334
|
-
if (fitsCarrierBudget(body)) {
|
|
360
|
+
if (fitsCarrierBudget(body, maxBytes)) {
|
|
335
361
|
return { value: copy, trimmed };
|
|
336
362
|
}
|
|
337
363
|
}
|
|
338
364
|
return void 0;
|
|
339
365
|
}
|
|
340
|
-
function markPayloadTrimmed(value, trimmed) {
|
|
366
|
+
function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
341
367
|
const existing = Array.isArray(value.errors) ? value.errors : [];
|
|
342
368
|
value.errors = [
|
|
343
369
|
...existing,
|
|
344
370
|
{
|
|
345
371
|
source: "sdk",
|
|
346
372
|
step: "payload_budget",
|
|
347
|
-
error: `trimmed oversized field(s) to fit the ${
|
|
373
|
+
error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
|
|
348
374
|
...new Set(trimmed)
|
|
349
375
|
].join(", ")}`
|
|
350
376
|
}
|
|
351
377
|
];
|
|
352
378
|
}
|
|
353
|
-
var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
|
|
379
|
+
var MAX_SPAN_CARRIER_BYTES, MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
|
|
354
380
|
var init_payloadBudget = __esm({
|
|
355
381
|
"src/payloadBudget.ts"() {
|
|
356
382
|
"use strict";
|
|
357
383
|
MAX_SPAN_CARRIER_BYTES = 28e5;
|
|
384
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
|
|
358
385
|
textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
359
386
|
MAX_BYTES_PER_UNIT = 3;
|
|
360
387
|
STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -386,30 +413,31 @@ var init_warnOnce = __esm({
|
|
|
386
413
|
});
|
|
387
414
|
|
|
388
415
|
// src/serializePayload.ts
|
|
389
|
-
function serializePayloadBody(payload) {
|
|
416
|
+
function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
|
|
390
417
|
const encoded = encodePayloadBody(payload);
|
|
391
|
-
if (fitsCarrierBudget(encoded.body)) {
|
|
418
|
+
if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
|
|
392
419
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
393
420
|
}
|
|
394
|
-
return applyPayloadBudget(encoded);
|
|
421
|
+
return applyPayloadBudget(encoded, maxCarrierBytes);
|
|
395
422
|
}
|
|
396
|
-
function applyPayloadBudget(encoded) {
|
|
423
|
+
function applyPayloadBudget(encoded, maxCarrierBytes) {
|
|
397
424
|
const result = encoded.value ? trimPayloadToBudget(
|
|
398
425
|
encoded.value,
|
|
399
|
-
(value) => encodePayloadBody(value).body
|
|
426
|
+
(value) => encodePayloadBody(value).body,
|
|
427
|
+
maxCarrierBytes
|
|
400
428
|
) : void 0;
|
|
401
429
|
if (!result) {
|
|
402
430
|
return { body: encoded.body, dropped: encoded.dropped };
|
|
403
431
|
}
|
|
404
432
|
warnOnce(
|
|
405
433
|
"payload:over-budget",
|
|
406
|
-
`a span payload exceeded the ${
|
|
434
|
+
`a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
|
|
407
435
|
...new Set(result.trimmed)
|
|
408
436
|
].join(
|
|
409
437
|
", "
|
|
410
438
|
)}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
|
|
411
439
|
);
|
|
412
|
-
markPayloadTrimmed(result.value, result.trimmed);
|
|
440
|
+
markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
|
|
413
441
|
return {
|
|
414
442
|
body: encodePayloadBody(result.value).body,
|
|
415
443
|
dropped: encoded.dropped
|
|
@@ -515,6 +543,23 @@ var init_serializePayload = __esm({
|
|
|
515
543
|
}
|
|
516
544
|
});
|
|
517
545
|
|
|
546
|
+
// src/transportTypes.ts
|
|
547
|
+
var DeliveryError;
|
|
548
|
+
var init_transportTypes = __esm({
|
|
549
|
+
"src/transportTypes.ts"() {
|
|
550
|
+
"use strict";
|
|
551
|
+
DeliveryError = class extends Error {
|
|
552
|
+
constructor(message, options = {}) {
|
|
553
|
+
super(message);
|
|
554
|
+
this.name = "DeliveryError";
|
|
555
|
+
this.retryable = options.retryable ?? false;
|
|
556
|
+
this.oversized = options.oversized ?? false;
|
|
557
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
518
563
|
// src/unrefTimer.ts
|
|
519
564
|
function unrefTimer(timer) {
|
|
520
565
|
const handle = timer;
|
|
@@ -554,59 +599,6 @@ function logError(message, error) {
|
|
|
554
599
|
} catch {
|
|
555
600
|
}
|
|
556
601
|
}
|
|
557
|
-
function recordTraceSubmission(operation, payload) {
|
|
558
|
-
const sourceTraceId = resolveSourceTraceId(payload);
|
|
559
|
-
if (sourceTraceId === void 0) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
if (operation === "external_span") {
|
|
563
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
564
|
-
if (typeof rawSpan?.id !== "string") {
|
|
565
|
-
submissionCounter += 1;
|
|
566
|
-
}
|
|
567
|
-
const sourceSpanId = typeof rawSpan?.id === "string" ? rawSpan.id : `submission-${submissionCounter}`;
|
|
568
|
-
const existing = traceSubmissionSpanIds.get(sourceTraceId);
|
|
569
|
-
if (existing) {
|
|
570
|
-
existing.add(sourceSpanId);
|
|
571
|
-
} else {
|
|
572
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set([sourceSpanId]));
|
|
573
|
-
}
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
if (payload.completed !== true) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
if (typeof payload.testRunId === "string") {
|
|
580
|
-
replayTraceSubmissions.add(sourceTraceId);
|
|
581
|
-
if (!traceSubmissionSpanIds.has(sourceTraceId)) {
|
|
582
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set());
|
|
583
|
-
}
|
|
584
|
-
} else {
|
|
585
|
-
traceSubmissionSpanIds.delete(sourceTraceId);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
function takeReplaySpanCounts(traceIds) {
|
|
589
|
-
const counts = {};
|
|
590
|
-
for (const traceId of traceIds) {
|
|
591
|
-
if (!replayTraceSubmissions.has(traceId)) {
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
counts[traceId] = traceSubmissionSpanIds.get(traceId)?.size ?? 0;
|
|
595
|
-
traceSubmissionSpanIds.delete(traceId);
|
|
596
|
-
replayTraceSubmissions.delete(traceId);
|
|
597
|
-
}
|
|
598
|
-
return counts;
|
|
599
|
-
}
|
|
600
|
-
function asRecord2(value) {
|
|
601
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
602
|
-
}
|
|
603
|
-
function resolveSourceTraceId(payload) {
|
|
604
|
-
if (typeof payload.sourceTraceId === "string") {
|
|
605
|
-
return payload.sourceTraceId;
|
|
606
|
-
}
|
|
607
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
608
|
-
return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
|
|
609
|
-
}
|
|
610
602
|
function otlpValue(value) {
|
|
611
603
|
if (typeof value === "boolean") {
|
|
612
604
|
return { boolValue: value };
|
|
@@ -664,7 +656,36 @@ function spanToOtlp(span) {
|
|
|
664
656
|
}
|
|
665
657
|
function encodeSpan(span) {
|
|
666
658
|
const json = JSON.stringify(spanToOtlp(span));
|
|
667
|
-
return {
|
|
659
|
+
return {
|
|
660
|
+
json,
|
|
661
|
+
size: byteLength(json),
|
|
662
|
+
ref: carrierRefs.get(span)
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function trimEncodedSpan(span) {
|
|
666
|
+
try {
|
|
667
|
+
const carrier = JSON.parse(span.json);
|
|
668
|
+
const attribute = carrier.attributes?.find(
|
|
669
|
+
(entry) => entry.key === PAYLOAD_ATTRIBUTE
|
|
670
|
+
);
|
|
671
|
+
const payloadBody = attribute?.value?.stringValue;
|
|
672
|
+
if (!attribute?.value || payloadBody === void 0) {
|
|
673
|
+
return void 0;
|
|
674
|
+
}
|
|
675
|
+
const payload = JSON.parse(payloadBody);
|
|
676
|
+
attribute.value.stringValue = serializePayloadBody(
|
|
677
|
+
payload,
|
|
678
|
+
MAX_SPAN_CARRIER_BYTES
|
|
679
|
+
).body;
|
|
680
|
+
const json = JSON.stringify(carrier);
|
|
681
|
+
return { json, size: byteLength(json) };
|
|
682
|
+
} catch {
|
|
683
|
+
return void 0;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function prepareRequest(body) {
|
|
687
|
+
const prepared = encodeRequestBody(body);
|
|
688
|
+
return prepared instanceof Promise ? await prepared : prepared;
|
|
668
689
|
}
|
|
669
690
|
function requestEnvelope(first) {
|
|
670
691
|
const scope = first.instrumentationScope;
|
|
@@ -722,48 +743,27 @@ async function mapWithConcurrency(items, limit, task) {
|
|
|
722
743
|
await Promise.all(workers);
|
|
723
744
|
return results;
|
|
724
745
|
}
|
|
725
|
-
function responseStatus(error) {
|
|
726
|
-
return error instanceof BitfabError ? error.status : void 0;
|
|
727
|
-
}
|
|
728
746
|
function isRetryable(error) {
|
|
729
|
-
|
|
730
|
-
if (status === void 0) {
|
|
731
|
-
return true;
|
|
732
|
-
}
|
|
733
|
-
return RETRYABLE_STATUSES.has(status) || status >= 500;
|
|
747
|
+
return error instanceof DeliveryError && error.retryable;
|
|
734
748
|
}
|
|
735
|
-
function
|
|
736
|
-
|
|
749
|
+
function isOversized(error) {
|
|
750
|
+
return error instanceof DeliveryError && error.oversized;
|
|
737
751
|
}
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
752
|
+
function retryWaitMillis(error, attempt, remainingMillis) {
|
|
753
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
754
|
+
const affordable = remainingMillis / 2;
|
|
755
|
+
if (requested !== void 0) {
|
|
756
|
+
return requested < affordable ? requested : null;
|
|
744
757
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
752
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
753
|
-
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
754
|
-
if (typeof raw !== "string") {
|
|
755
|
-
return void 0;
|
|
756
|
-
}
|
|
757
|
-
const parsed = Date.parse(raw);
|
|
758
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
758
|
+
const backoff = Math.min(
|
|
759
|
+
RETRY_BASE_DELAY_MILLIS * 2 ** attempt,
|
|
760
|
+
RETRY_BACKOFF_CEILING_MILLIS
|
|
761
|
+
);
|
|
762
|
+
const jittered = backoff / 2 + Math.random() * (backoff / 2);
|
|
763
|
+
return jittered < affordable ? jittered : null;
|
|
759
764
|
}
|
|
760
|
-
function
|
|
761
|
-
|
|
762
|
-
if (spanData?.error != null) {
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
const errors = payload.errors;
|
|
766
|
-
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
765
|
+
function endSpan(span, endTime) {
|
|
766
|
+
span.end(endTime);
|
|
767
767
|
}
|
|
768
768
|
function createOtelTransport(options) {
|
|
769
769
|
return new OtelBatchTransport({
|
|
@@ -802,7 +802,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
|
|
|
802
802
|
(transport, remaining) => transport.shutdown(remaining)
|
|
803
803
|
);
|
|
804
804
|
}
|
|
805
|
-
var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE,
|
|
805
|
+
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;
|
|
806
806
|
var init_otel = __esm({
|
|
807
807
|
"src/otel.ts"() {
|
|
808
808
|
"use strict";
|
|
@@ -810,17 +810,19 @@ var init_otel = __esm({
|
|
|
810
810
|
import_core = require("@opentelemetry/core");
|
|
811
811
|
import_resources = require("@opentelemetry/resources");
|
|
812
812
|
import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
813
|
+
init_compress();
|
|
813
814
|
init_constants();
|
|
814
815
|
init_errors();
|
|
815
816
|
init_payloadBudget();
|
|
816
817
|
init_readEnv();
|
|
817
818
|
init_serializePayload();
|
|
819
|
+
init_transportTypes();
|
|
818
820
|
init_unrefTimer();
|
|
819
821
|
init_warnOnce();
|
|
820
822
|
OPERATION_ATTRIBUTE = "bitfab.operation";
|
|
821
823
|
PAYLOAD_ATTRIBUTE = "bitfab.payload";
|
|
822
|
-
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
823
824
|
MAX_EXPORT_REQUEST_BYTES = 3e6;
|
|
825
|
+
MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
|
|
824
826
|
MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
|
|
825
827
|
EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
|
|
826
828
|
MAX_QUEUE_SIZE = 8192;
|
|
@@ -830,25 +832,23 @@ var init_otel = __esm({
|
|
|
830
832
|
MAX_EXPORT_CONCURRENCY = 64;
|
|
831
833
|
SCHEDULE_DELAY_MILLIS = 5e3;
|
|
832
834
|
EXPORT_TIMEOUT_MILLIS = 3e4;
|
|
833
|
-
|
|
835
|
+
RETRY_BASE_DELAY_MILLIS = 100;
|
|
836
|
+
RETRY_BACKOFF_CEILING_MILLIS = 5e3;
|
|
834
837
|
MAX_SEND_ATTEMPTS = 3;
|
|
835
838
|
DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
836
|
-
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
837
839
|
liveTransports = /* @__PURE__ */ new Set();
|
|
838
|
-
|
|
839
|
-
replayTraceSubmissions = /* @__PURE__ */ new Set();
|
|
840
|
-
submissionCounter = 0;
|
|
840
|
+
carrierRefs = /* @__PURE__ */ new WeakMap();
|
|
841
841
|
SPAN_SEPARATOR_BYTES = 1;
|
|
842
|
-
OtlpPayloadTooLargeError = class extends Error {
|
|
843
|
-
};
|
|
844
|
-
OtlpPartialSuccessError = class extends Error {
|
|
845
|
-
};
|
|
846
842
|
BitfabSpanExporter = class {
|
|
847
|
-
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency) {
|
|
843
|
+
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency, onDelivered, exportTimeoutMillis = EXPORT_TIMEOUT_MILLIS) {
|
|
848
844
|
this.directSender = directSender;
|
|
849
845
|
this.maxRequestBytes = maxRequestBytes;
|
|
850
846
|
this.maxRequestBatchSize = maxRequestBatchSize;
|
|
851
847
|
this.exportConcurrency = exportConcurrency;
|
|
848
|
+
this.onDelivered = onDelivered;
|
|
849
|
+
this.exportTimeoutMillis = exportTimeoutMillis;
|
|
850
|
+
/** Epoch ms until which the server has asked this exporter to stay away. */
|
|
851
|
+
this.throttledUntil = 0;
|
|
852
852
|
}
|
|
853
853
|
export(spans, resultCallback) {
|
|
854
854
|
void this.exportAsync(spans).then(
|
|
@@ -903,25 +903,51 @@ var init_otel = __esm({
|
|
|
903
903
|
return batches;
|
|
904
904
|
}
|
|
905
905
|
async send(envelope, batch) {
|
|
906
|
-
if (batch.size > this.maxRequestBytes) {
|
|
907
|
-
logError(
|
|
908
|
-
"a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
|
|
909
|
-
);
|
|
910
|
-
return false;
|
|
911
|
-
}
|
|
912
906
|
try {
|
|
913
|
-
|
|
914
|
-
|
|
907
|
+
let requestSpans = batch.spans;
|
|
908
|
+
let requestRawBytes = batch.size;
|
|
909
|
+
let alreadyTrimmed = false;
|
|
910
|
+
while (true) {
|
|
911
|
+
if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
|
|
912
|
+
const prepared = await prepareRequest(
|
|
913
|
+
encodeRequest(envelope, requestSpans)
|
|
914
|
+
);
|
|
915
|
+
if (prepared.wireBytes <= this.maxRequestBytes) {
|
|
916
|
+
await this.sendWithRetries(prepared);
|
|
917
|
+
this.reportDelivered(batch.spans);
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (batch.spans.length !== 1) {
|
|
922
|
+
logError(
|
|
923
|
+
"an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
|
|
924
|
+
);
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
if (alreadyTrimmed) {
|
|
928
|
+
logError(
|
|
929
|
+
"a single OpenTelemetry span exceeded the configured request-size target after trimming"
|
|
930
|
+
);
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
const trimmed = trimEncodedSpan(batch.spans[0]);
|
|
934
|
+
if (!trimmed) {
|
|
935
|
+
logError(
|
|
936
|
+
"a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
|
|
937
|
+
);
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
requestSpans = [trimmed];
|
|
941
|
+
requestRawBytes = envelope.size + trimmed.size;
|
|
942
|
+
alreadyTrimmed = true;
|
|
943
|
+
}
|
|
915
944
|
} catch (error) {
|
|
916
|
-
if (error
|
|
945
|
+
if (isOversized(error)) {
|
|
917
946
|
logError(
|
|
918
947
|
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"
|
|
919
948
|
);
|
|
920
949
|
return false;
|
|
921
950
|
}
|
|
922
|
-
if (error instanceof OtlpPartialSuccessError) {
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
951
|
logError("failed to export an OpenTelemetry span batch", error);
|
|
926
952
|
return false;
|
|
927
953
|
}
|
|
@@ -939,37 +965,79 @@ var init_otel = __esm({
|
|
|
939
965
|
* the server does not yet understand. The fix is a client-supplied
|
|
940
966
|
* idempotency key that ingestion dedupes on.
|
|
941
967
|
*/
|
|
942
|
-
|
|
968
|
+
/**
|
|
969
|
+
* Remember a throttle the server asked for, so the requests fanned out
|
|
970
|
+
* alongside this one respect it too. Delaying only the request that was
|
|
971
|
+
* refused leaves the other seven in the window hitting a server that just
|
|
972
|
+
* asked for room.
|
|
973
|
+
*/
|
|
974
|
+
recordThrottle(error) {
|
|
975
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
976
|
+
if (requested !== void 0) {
|
|
977
|
+
this.throttledUntil = Math.max(
|
|
978
|
+
this.throttledUntil,
|
|
979
|
+
Date.now() + requested
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Waits out an active throttle, or reports the batch undeliverable when the
|
|
985
|
+
* throttle outlasts what we are willing to hold it for. Either way nothing is
|
|
986
|
+
* sent while the server has asked us to stay away.
|
|
987
|
+
*/
|
|
988
|
+
async awaitThrottle(deadline) {
|
|
989
|
+
const remaining = this.throttledUntil - Date.now();
|
|
990
|
+
if (remaining <= 0) {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (remaining >= (deadline - Date.now()) / 2) {
|
|
994
|
+
throw new DeliveryError(
|
|
995
|
+
`OTLP ingestion is throttled for another ${remaining}ms, longer than the export budget`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
await delay(remaining);
|
|
999
|
+
}
|
|
1000
|
+
async sendWithRetries(request) {
|
|
1001
|
+
const deadline = Date.now() + this.exportTimeoutMillis;
|
|
943
1002
|
for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
|
|
944
1003
|
try {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
body,
|
|
948
|
-
EXPORT_TIMEOUT_MILLIS
|
|
949
|
-
);
|
|
950
|
-
const partialSuccess = asRecord2(response?.partialSuccess);
|
|
951
|
-
const rejected = partialSuccess?.rejectedSpans;
|
|
952
|
-
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
953
|
-
logError(
|
|
954
|
-
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
955
|
-
);
|
|
956
|
-
throw new OtlpPartialSuccessError();
|
|
957
|
-
}
|
|
1004
|
+
await this.awaitThrottle(deadline);
|
|
1005
|
+
await this.directSender(request, Math.max(0, deadline - Date.now()));
|
|
958
1006
|
return;
|
|
959
1007
|
} catch (error) {
|
|
960
|
-
if (error
|
|
1008
|
+
if (isOversized(error)) {
|
|
961
1009
|
throw error;
|
|
962
1010
|
}
|
|
963
|
-
|
|
964
|
-
throw new OtlpPayloadTooLargeError();
|
|
965
|
-
}
|
|
1011
|
+
this.recordThrottle(error);
|
|
966
1012
|
if (attempt === MAX_SEND_ATTEMPTS - 1 || !isRetryable(error)) {
|
|
967
1013
|
throw error;
|
|
968
1014
|
}
|
|
969
|
-
|
|
1015
|
+
const wait = retryWaitMillis(error, attempt, deadline - Date.now());
|
|
1016
|
+
if (wait === null) {
|
|
1017
|
+
throw error;
|
|
1018
|
+
}
|
|
1019
|
+
await delay(wait);
|
|
970
1020
|
}
|
|
971
1021
|
}
|
|
972
1022
|
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Announce the carriers a request delivered. Wrapped because a listener that
|
|
1025
|
+
* throws must never turn a delivered batch into a failed export.
|
|
1026
|
+
*/
|
|
1027
|
+
reportDelivered(spans) {
|
|
1028
|
+
if (this.onDelivered === void 0) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
const refs = spans.map((span) => span.ref).filter((ref) => ref !== void 0);
|
|
1032
|
+
if (refs.length === 0) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
this.onDelivered(refs);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
logError("a delivery listener threw", error);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
973
1041
|
async shutdown() {
|
|
974
1042
|
}
|
|
975
1043
|
async forceFlush() {
|
|
@@ -1027,7 +1095,9 @@ var init_otel = __esm({
|
|
|
1027
1095
|
options.directSender,
|
|
1028
1096
|
maxRequestBytes,
|
|
1029
1097
|
maxRequestBatchSize,
|
|
1030
|
-
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
|
|
1098
|
+
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY,
|
|
1099
|
+
options.onDelivered,
|
|
1100
|
+
options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
|
|
1031
1101
|
)
|
|
1032
1102
|
);
|
|
1033
1103
|
this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
|
|
@@ -1051,8 +1121,7 @@ var init_otel = __esm({
|
|
|
1051
1121
|
this.tracer = this.provider.getTracer("bitfab", __version__);
|
|
1052
1122
|
liveTransports.add(this);
|
|
1053
1123
|
}
|
|
1054
|
-
submit(operation, payload) {
|
|
1055
|
-
recordTraceSubmission(operation, payload);
|
|
1124
|
+
submit(operation, payload, meta = {}) {
|
|
1056
1125
|
if (this.closed) {
|
|
1057
1126
|
warnOnce(
|
|
1058
1127
|
"otel-submit-after-shutdown",
|
|
@@ -1061,7 +1130,10 @@ var init_otel = __esm({
|
|
|
1061
1130
|
return;
|
|
1062
1131
|
}
|
|
1063
1132
|
try {
|
|
1064
|
-
const { body, dropped } = serializePayloadBody(
|
|
1133
|
+
const { body, dropped } = serializePayloadBody(
|
|
1134
|
+
payload,
|
|
1135
|
+
MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
|
|
1136
|
+
);
|
|
1065
1137
|
if (dropped.length > 0) {
|
|
1066
1138
|
warnOnce(
|
|
1067
1139
|
"otel-carrier-payload-stubbed",
|
|
@@ -1070,17 +1142,20 @@ var init_otel = __esm({
|
|
|
1070
1142
|
].join(", ")}); they were stubbed so the span still ships, but the trace may be incomplete or not replayable.`
|
|
1071
1143
|
);
|
|
1072
1144
|
}
|
|
1073
|
-
const span = this.tracer.startSpan(
|
|
1145
|
+
const span = this.tracer.startSpan(meta.name ?? `bitfab.${operation}`, {
|
|
1074
1146
|
attributes: {
|
|
1075
1147
|
[OPERATION_ATTRIBUTE]: operation,
|
|
1076
1148
|
[PAYLOAD_ATTRIBUTE]: body
|
|
1077
1149
|
},
|
|
1078
|
-
startTime:
|
|
1150
|
+
startTime: meta.startTime
|
|
1079
1151
|
});
|
|
1080
|
-
if (
|
|
1152
|
+
if (meta.ref !== void 0) {
|
|
1153
|
+
carrierRefs.set(span, meta.ref);
|
|
1154
|
+
}
|
|
1155
|
+
if (meta.errored === true) {
|
|
1081
1156
|
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
1082
1157
|
}
|
|
1083
|
-
endSpan(span,
|
|
1158
|
+
endSpan(span, meta.endTime);
|
|
1084
1159
|
} catch (error) {
|
|
1085
1160
|
logError("failed to queue an OpenTelemetry span", error);
|
|
1086
1161
|
}
|
|
@@ -1130,9 +1205,6 @@ function flushTraceTransports(timeoutMs) {
|
|
|
1130
1205
|
function shutdownTraceTransports(timeoutMs) {
|
|
1131
1206
|
return shutdownOtelTransports(timeoutMs);
|
|
1132
1207
|
}
|
|
1133
|
-
function takeReplaySpanCounts2(traceIds) {
|
|
1134
|
-
return takeReplaySpanCounts(traceIds);
|
|
1135
|
-
}
|
|
1136
1208
|
var init_transport = __esm({
|
|
1137
1209
|
"src/transport.ts"() {
|
|
1138
1210
|
"use strict";
|
|
@@ -1181,7 +1253,96 @@ async function waitForPromises(promises, timeoutMs) {
|
|
|
1181
1253
|
}
|
|
1182
1254
|
}
|
|
1183
1255
|
}
|
|
1184
|
-
|
|
1256
|
+
function readHeader(response, name) {
|
|
1257
|
+
try {
|
|
1258
|
+
return response.headers?.get(name) ?? null;
|
|
1259
|
+
} catch {
|
|
1260
|
+
return null;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function parseRetryAfterMs(header) {
|
|
1264
|
+
const value = header?.trim();
|
|
1265
|
+
if (!value) {
|
|
1266
|
+
return void 0;
|
|
1267
|
+
}
|
|
1268
|
+
const seconds = Number(value);
|
|
1269
|
+
if (Number.isFinite(seconds)) {
|
|
1270
|
+
return seconds >= 0 ? seconds * 1e3 : void 0;
|
|
1271
|
+
}
|
|
1272
|
+
const at = Date.parse(value);
|
|
1273
|
+
if (Number.isNaN(at)) {
|
|
1274
|
+
return void 0;
|
|
1275
|
+
}
|
|
1276
|
+
return Math.max(0, at - Date.now());
|
|
1277
|
+
}
|
|
1278
|
+
function carrierMeta(operation, payload, ref) {
|
|
1279
|
+
return {
|
|
1280
|
+
ref,
|
|
1281
|
+
name: carrierName(operation, payload),
|
|
1282
|
+
startTime: payloadTimestamp(payload, "started_at"),
|
|
1283
|
+
endTime: payloadTimestamp(payload, "ended_at"),
|
|
1284
|
+
errored: payloadHasError(payload)
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function carrierName(operation, payload) {
|
|
1288
|
+
if (operation === "external_span") {
|
|
1289
|
+
const spanData = asPayloadRecord(
|
|
1290
|
+
asPayloadRecord(payload.rawSpan)?.span_data
|
|
1291
|
+
);
|
|
1292
|
+
if (typeof spanData?.name === "string") {
|
|
1293
|
+
return spanData.name;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (typeof payload.traceFunctionKey === "string") {
|
|
1297
|
+
return payload.traceFunctionKey;
|
|
1298
|
+
}
|
|
1299
|
+
return `bitfab.${operation}`;
|
|
1300
|
+
}
|
|
1301
|
+
function payloadTimestamp(payload, field) {
|
|
1302
|
+
const rawSpan = asPayloadRecord(payload.rawSpan);
|
|
1303
|
+
const rawTrace = asPayloadRecord(payload.externalTrace) ?? asPayloadRecord(payload.rawTrace);
|
|
1304
|
+
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
1305
|
+
if (typeof raw !== "string") {
|
|
1306
|
+
return void 0;
|
|
1307
|
+
}
|
|
1308
|
+
const parsed = Date.parse(raw);
|
|
1309
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1310
|
+
}
|
|
1311
|
+
function payloadHasError(payload) {
|
|
1312
|
+
const spanData = asPayloadRecord(asPayloadRecord(payload.rawSpan)?.span_data);
|
|
1313
|
+
if (spanData?.error != null) {
|
|
1314
|
+
return true;
|
|
1315
|
+
}
|
|
1316
|
+
const errors = payload.errors;
|
|
1317
|
+
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
1318
|
+
}
|
|
1319
|
+
function asPayloadRecord(value) {
|
|
1320
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1321
|
+
}
|
|
1322
|
+
function carrierRef(payload) {
|
|
1323
|
+
const traceId = sourceTraceIdOf(payload);
|
|
1324
|
+
if (traceId === void 0) {
|
|
1325
|
+
return void 0;
|
|
1326
|
+
}
|
|
1327
|
+
const rawSpan = payload.rawSpan;
|
|
1328
|
+
if (rawSpan === void 0) {
|
|
1329
|
+
return { traceId };
|
|
1330
|
+
}
|
|
1331
|
+
const spanId = rawSpan?.id;
|
|
1332
|
+
return {
|
|
1333
|
+
traceId,
|
|
1334
|
+
spanId: typeof spanId === "string" ? spanId : `submission-${++carrierSeq}`
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function sourceTraceIdOf(payload) {
|
|
1338
|
+
if (typeof payload.sourceTraceId === "string") {
|
|
1339
|
+
return payload.sourceTraceId;
|
|
1340
|
+
}
|
|
1341
|
+
const rawTrace = payload.externalTrace ?? payload.rawTrace;
|
|
1342
|
+
const id = rawTrace?.id;
|
|
1343
|
+
return typeof id === "string" ? id : void 0;
|
|
1344
|
+
}
|
|
1345
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
|
|
1185
1346
|
var init_http = __esm({
|
|
1186
1347
|
"src/http.ts"() {
|
|
1187
1348
|
"use strict";
|
|
@@ -1191,9 +1352,12 @@ var init_http = __esm({
|
|
|
1191
1352
|
init_replayContext();
|
|
1192
1353
|
init_serializePayload();
|
|
1193
1354
|
init_transport();
|
|
1355
|
+
init_transportTypes();
|
|
1194
1356
|
init_unrefTimer();
|
|
1195
1357
|
init_warnOnce();
|
|
1196
1358
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
1359
|
+
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
1360
|
+
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1197
1361
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
1198
1362
|
DEFAULT_LIFECYCLE_TIMEOUT_MS2 = 3e4;
|
|
1199
1363
|
pendingTracePromises = /* @__PURE__ */ new Set();
|
|
@@ -1213,8 +1377,12 @@ var init_http = __esm({
|
|
|
1213
1377
|
});
|
|
1214
1378
|
});
|
|
1215
1379
|
}
|
|
1380
|
+
carrierSeq = 0;
|
|
1216
1381
|
HttpClient = class {
|
|
1217
1382
|
constructor(config) {
|
|
1383
|
+
// Only traces a caller asked about are tracked, so ordinary tracing stores
|
|
1384
|
+
// nothing here.
|
|
1385
|
+
this.traceDeliveries = /* @__PURE__ */ new Map();
|
|
1218
1386
|
// Deferred span work owned by THIS client. The module-global set backs the
|
|
1219
1387
|
// process-wide `flushTraces()` and the exit hook, but per-client lifecycle
|
|
1220
1388
|
// must not wait on another client's slow finalize: a false `close()` failure
|
|
@@ -1250,13 +1418,131 @@ var init_http = __esm({
|
|
|
1250
1418
|
}
|
|
1251
1419
|
if (!this.traceTransport) {
|
|
1252
1420
|
this.traceTransport = createTraceTransport({
|
|
1253
|
-
directSender: (
|
|
1254
|
-
|
|
1255
|
-
})
|
|
1421
|
+
directSender: (request, timeoutMs) => this.deliverCarriers(request, timeoutMs),
|
|
1422
|
+
onDelivered: (refs) => this.recordDeliveredCarriers(refs)
|
|
1256
1423
|
});
|
|
1257
1424
|
}
|
|
1258
1425
|
return this.traceTransport;
|
|
1259
1426
|
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Post one encoded batch and decide what the server's answer means, so the
|
|
1429
|
+
* transport never reads a response. Rejections and permanent statuses come
|
|
1430
|
+
* back as a non-retryable {@link DeliveryError}; anything the server might
|
|
1431
|
+
* still accept on a second try comes back retryable.
|
|
1432
|
+
*/
|
|
1433
|
+
async deliverCarriers(request, timeoutMs) {
|
|
1434
|
+
let response;
|
|
1435
|
+
try {
|
|
1436
|
+
response = await this.sendPrepared(
|
|
1437
|
+
OTLP_TRACES_ENDPOINT,
|
|
1438
|
+
request,
|
|
1439
|
+
{ timeout: timeoutMs }
|
|
1440
|
+
);
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
const status = error instanceof BitfabError ? error.status : void 0;
|
|
1443
|
+
if (status === void 0) {
|
|
1444
|
+
throw new DeliveryError(`OTLP ingestion failed: ${String(error)}`, {
|
|
1445
|
+
retryable: true
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
throw new DeliveryError(`OTLP ingestion failed with HTTP ${status}`, {
|
|
1449
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
1450
|
+
oversized: status === 413,
|
|
1451
|
+
...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
const partialSuccess = asPayloadRecord(response?.partialSuccess);
|
|
1455
|
+
const rejected = partialSuccess?.rejectedSpans;
|
|
1456
|
+
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1457
|
+
throw new DeliveryError(
|
|
1458
|
+
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Start tracking delivery for `traceIds`. Nothing is recorded for a trace
|
|
1464
|
+
* that was never tracked, so ordinary tracing costs no bookkeeping at all.
|
|
1465
|
+
*/
|
|
1466
|
+
trackTraceDeliveries(traceIds) {
|
|
1467
|
+
for (const traceId of traceIds) {
|
|
1468
|
+
if (!this.traceDeliveries.has(traceId)) {
|
|
1469
|
+
this.traceDeliveries.set(traceId, {
|
|
1470
|
+
submittedSpanIds: /* @__PURE__ */ new Set(),
|
|
1471
|
+
ackedSpanIds: /* @__PURE__ */ new Set(),
|
|
1472
|
+
closed: false,
|
|
1473
|
+
closingAcked: false
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/** Whether any tracked trace has had its closing carrier submitted. */
|
|
1479
|
+
hasClosedDeliveries(traceIds) {
|
|
1480
|
+
return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Report what each tracked trace submitted and whether the server confirmed
|
|
1484
|
+
* it, and stop tracking them. Every id passed is freed, so a caller cannot
|
|
1485
|
+
* leak a record for a trace that never closed.
|
|
1486
|
+
*
|
|
1487
|
+
* `delivered` is only meaningful once a flush has settled: acks land before
|
|
1488
|
+
* an export resolves, so a flush that reported success has already collected
|
|
1489
|
+
* every ack it is going to collect.
|
|
1490
|
+
*/
|
|
1491
|
+
takeTraceDeliveries(traceIds) {
|
|
1492
|
+
const reports = {};
|
|
1493
|
+
for (const traceId of traceIds) {
|
|
1494
|
+
const delivery = this.traceDeliveries.get(traceId);
|
|
1495
|
+
if (delivery === void 0) {
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
this.traceDeliveries.delete(traceId);
|
|
1499
|
+
reports[traceId] = {
|
|
1500
|
+
spanCount: delivery.submittedSpanIds.size,
|
|
1501
|
+
closed: delivery.closed,
|
|
1502
|
+
delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
|
|
1503
|
+
(spanId) => delivery.ackedSpanIds.has(spanId)
|
|
1504
|
+
)
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
return reports;
|
|
1508
|
+
}
|
|
1509
|
+
/** Build a carrier's meta and record what it adds to its trace's expected set. */
|
|
1510
|
+
recordedMeta(operation, payload, ref) {
|
|
1511
|
+
this.recordSubmittedCarrier(ref);
|
|
1512
|
+
return carrierMeta(operation, payload, ref);
|
|
1513
|
+
}
|
|
1514
|
+
recordSubmittedCarrier(ref) {
|
|
1515
|
+
if (ref === void 0) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1519
|
+
if (delivery === void 0) {
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (ref.spanId === void 0) {
|
|
1523
|
+
delivery.closed = true;
|
|
1524
|
+
} else {
|
|
1525
|
+
delivery.submittedSpanIds.add(ref.spanId);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Ingestion commits every carrier in a request before it answers, so a
|
|
1530
|
+
* delivered ref is proof its row exists: the same fact the replay status
|
|
1531
|
+
* endpoint would report, already in hand.
|
|
1532
|
+
*/
|
|
1533
|
+
recordDeliveredCarriers(refs) {
|
|
1534
|
+
for (const ref of refs) {
|
|
1535
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1536
|
+
if (delivery === void 0) {
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
if (ref.spanId === void 0) {
|
|
1540
|
+
delivery.closingAcked = true;
|
|
1541
|
+
} else {
|
|
1542
|
+
delivery.ackedSpanIds.add(ref.spanId);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1260
1546
|
/**
|
|
1261
1547
|
* Track deferred span work so this client's own lifecycle waits for it, and
|
|
1262
1548
|
* so the process-wide flush and exit hook do too.
|
|
@@ -1337,13 +1623,16 @@ var init_http = __esm({
|
|
|
1337
1623
|
* same data twice.
|
|
1338
1624
|
*/
|
|
1339
1625
|
async sendEncoded(endpoint, body, options) {
|
|
1626
|
+
const prepared = encodeRequestBody(body);
|
|
1627
|
+
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1628
|
+
return this.sendPrepared(endpoint, encoded, options);
|
|
1629
|
+
}
|
|
1630
|
+
async sendPrepared(endpoint, encoded, options) {
|
|
1340
1631
|
const url = `${this.serviceUrl}${endpoint}`;
|
|
1341
1632
|
const timeout = options?.timeout ?? this.timeout;
|
|
1342
1633
|
const method = options?.method ?? "POST";
|
|
1343
1634
|
const controller = new AbortController();
|
|
1344
1635
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1345
|
-
const prepared = encodeRequestBody(body);
|
|
1346
|
-
const encoded = prepared instanceof Promise ? await prepared : prepared;
|
|
1347
1636
|
const headers = {
|
|
1348
1637
|
"Content-Type": "application/json",
|
|
1349
1638
|
Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
|
|
@@ -1363,7 +1652,8 @@ var init_http = __esm({
|
|
|
1363
1652
|
throw new BitfabError(
|
|
1364
1653
|
`HTTP ${response.status}: ${errorText.slice(0, 500)}`,
|
|
1365
1654
|
void 0,
|
|
1366
|
-
response.status
|
|
1655
|
+
response.status,
|
|
1656
|
+
parseRetryAfterMs(readHeader(response, "retry-after"))
|
|
1367
1657
|
);
|
|
1368
1658
|
}
|
|
1369
1659
|
const result = await response.json();
|
|
@@ -1449,11 +1739,12 @@ var init_http = __esm({
|
|
|
1449
1739
|
* the OTLP carrier has no path to carry it.
|
|
1450
1740
|
*/
|
|
1451
1741
|
sendInternalTrace(functionId, payload) {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1742
|
+
const body = { ...payload, functionId, sdkVersion: __version__ };
|
|
1743
|
+
this.getTraceTransport()?.submit(
|
|
1744
|
+
"internal_trace",
|
|
1745
|
+
body,
|
|
1746
|
+
carrierMeta("internal_trace", body, void 0)
|
|
1747
|
+
);
|
|
1457
1748
|
}
|
|
1458
1749
|
/**
|
|
1459
1750
|
* Queue an external span (from withSpan wrapper or OpenAI tracing) onto this
|
|
@@ -1462,10 +1753,11 @@ var init_http = __esm({
|
|
|
1462
1753
|
* promise.
|
|
1463
1754
|
*/
|
|
1464
1755
|
sendExternalSpan(payload) {
|
|
1465
|
-
this.getTraceTransport()?.submit(
|
|
1466
|
-
|
|
1467
|
-
sdkVersion: __version__
|
|
1468
|
-
|
|
1756
|
+
this.getTraceTransport()?.submit(
|
|
1757
|
+
"external_span",
|
|
1758
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1759
|
+
this.recordedMeta("external_span", payload, carrierRef(payload))
|
|
1760
|
+
);
|
|
1469
1761
|
}
|
|
1470
1762
|
/**
|
|
1471
1763
|
* Queue an external trace completion (from OpenAI tracing) onto this
|
|
@@ -1474,10 +1766,15 @@ var init_http = __esm({
|
|
|
1474
1766
|
* server-authoritative barrier in `replay.ts`, not by awaiting this call.
|
|
1475
1767
|
*/
|
|
1476
1768
|
sendExternalTrace(payload) {
|
|
1477
|
-
this.getTraceTransport()?.submit(
|
|
1478
|
-
|
|
1479
|
-
sdkVersion: __version__
|
|
1480
|
-
|
|
1769
|
+
this.getTraceTransport()?.submit(
|
|
1770
|
+
"external_trace",
|
|
1771
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1772
|
+
this.recordedMeta(
|
|
1773
|
+
"external_trace",
|
|
1774
|
+
payload,
|
|
1775
|
+
payload.completed === true ? carrierRef(payload) : void 0
|
|
1776
|
+
)
|
|
1777
|
+
);
|
|
1481
1778
|
}
|
|
1482
1779
|
/**
|
|
1483
1780
|
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
@@ -1817,8 +2114,8 @@ var init_serialize = __esm({
|
|
|
1817
2114
|
import_superjson = __toESM(require("superjson"), 1);
|
|
1818
2115
|
init_payloadBudget();
|
|
1819
2116
|
init_warnOnce();
|
|
1820
|
-
MAX_SERIALIZED_BYTES =
|
|
1821
|
-
MAX_FRAMEWORK_SERIALIZED_BYTES =
|
|
2117
|
+
MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
2118
|
+
MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
|
|
1822
2119
|
MAX_SAFE_DEPTH = 6;
|
|
1823
2120
|
}
|
|
1824
2121
|
});
|
|
@@ -2073,7 +2370,8 @@ __export(replay_exports, {
|
|
|
2073
2370
|
ReplayError: () => ReplayError,
|
|
2074
2371
|
replay: () => replay,
|
|
2075
2372
|
reportReplayProgress: () => reportReplayProgress,
|
|
2076
|
-
serializeReplayResult: () => serializeReplayResult
|
|
2373
|
+
serializeReplayResult: () => serializeReplayResult,
|
|
2374
|
+
waitForReplayPersistence: () => waitForReplayPersistence
|
|
2077
2375
|
});
|
|
2078
2376
|
function dbBranchEnabled(dbBranch) {
|
|
2079
2377
|
return dbBranch !== void 0 && dbBranch !== false;
|
|
@@ -2361,15 +2659,29 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
|
|
|
2361
2659
|
REPLAY_PERSISTENCE_TIMEOUT_MS
|
|
2362
2660
|
);
|
|
2363
2661
|
if (!deferredSettled) {
|
|
2662
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2364
2663
|
throw new BitfabError(
|
|
2365
2664
|
`Replay could not settle deferred span work before the deadline, so the expected span counts are incomplete (testRunId ${testRunId}).`
|
|
2366
2665
|
);
|
|
2367
2666
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2667
|
+
if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
|
|
2668
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2370
2669
|
return;
|
|
2371
2670
|
}
|
|
2372
2671
|
const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
|
|
2672
|
+
const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2673
|
+
const expectedSpanCounts = {};
|
|
2674
|
+
let allDelivered = true;
|
|
2675
|
+
for (const [traceId, delivery] of Object.entries(deliveries)) {
|
|
2676
|
+
if (!delivery.closed) {
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
expectedSpanCounts[traceId] = delivery.spanCount;
|
|
2680
|
+
allDelivered = allDelivered && delivery.delivered;
|
|
2681
|
+
}
|
|
2682
|
+
if (allDelivered) {
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2373
2685
|
const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
|
|
2374
2686
|
let missing = Object.keys(expectedSpanCounts).length;
|
|
2375
2687
|
while (true) {
|
|
@@ -2478,6 +2790,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
2478
2790
|
...registeredOverrides
|
|
2479
2791
|
];
|
|
2480
2792
|
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
2793
|
+
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
2481
2794
|
const tasks = serverItems.map(
|
|
2482
2795
|
(serverItem, index) => () => processItem(
|
|
2483
2796
|
httpClient,
|
|
@@ -2675,7 +2988,6 @@ var init_replay = __esm({
|
|
|
2675
2988
|
init_randomUuid();
|
|
2676
2989
|
init_replayContext();
|
|
2677
2990
|
init_serialize();
|
|
2678
|
-
init_transport();
|
|
2679
2991
|
init_unrefTimer();
|
|
2680
2992
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
2681
2993
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|