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