@bitfab/sdk 0.36.7 → 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-EDOZ3DP3.js → chunk-B4HK3BPE.js} +3 -3
- package/dist/{chunk-CFECTUDU.js → chunk-QUX6N7ON.js} +373 -156
- package/dist/chunk-QUX6N7ON.js.map +1 -0
- package/dist/index.cjs +383 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +2 -2
- package/dist/node.cjs +383 -159
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +2 -2
- package/dist/{replay-RAKYO5UI.js → replay-7PUB6ZOG.js} +6 -4
- package/package.json +1 -1
- package/dist/chunk-CFECTUDU.js.map +0 -1
- /package/dist/{chunk-EDOZ3DP3.js.map → chunk-B4HK3BPE.js.map} +0 -0
- /package/dist/{replay-RAKYO5UI.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
|
|
|
@@ -214,10 +214,11 @@ var init_errors = __esm({
|
|
|
214
214
|
"src/errors.ts"() {
|
|
215
215
|
"use strict";
|
|
216
216
|
BitfabError = class extends Error {
|
|
217
|
-
constructor(message, url, status) {
|
|
217
|
+
constructor(message, url, status, retryAfterMs) {
|
|
218
218
|
super(message);
|
|
219
219
|
this.url = url;
|
|
220
220
|
this.status = status;
|
|
221
|
+
this.retryAfterMs = retryAfterMs;
|
|
221
222
|
this.name = "BitfabError";
|
|
222
223
|
}
|
|
223
224
|
};
|
|
@@ -533,6 +534,23 @@ var init_serializePayload = __esm({
|
|
|
533
534
|
}
|
|
534
535
|
});
|
|
535
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
|
+
|
|
536
554
|
// src/unrefTimer.ts
|
|
537
555
|
function unrefTimer(timer) {
|
|
538
556
|
const handle = timer;
|
|
@@ -572,59 +590,6 @@ function logError(message, error) {
|
|
|
572
590
|
} catch {
|
|
573
591
|
}
|
|
574
592
|
}
|
|
575
|
-
function recordTraceSubmission(operation, payload) {
|
|
576
|
-
const sourceTraceId = resolveSourceTraceId(payload);
|
|
577
|
-
if (sourceTraceId === void 0) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
if (operation === "external_span") {
|
|
581
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
582
|
-
if (typeof rawSpan?.id !== "string") {
|
|
583
|
-
submissionCounter += 1;
|
|
584
|
-
}
|
|
585
|
-
const sourceSpanId = typeof rawSpan?.id === "string" ? rawSpan.id : `submission-${submissionCounter}`;
|
|
586
|
-
const existing = traceSubmissionSpanIds.get(sourceTraceId);
|
|
587
|
-
if (existing) {
|
|
588
|
-
existing.add(sourceSpanId);
|
|
589
|
-
} else {
|
|
590
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set([sourceSpanId]));
|
|
591
|
-
}
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
if (payload.completed !== true) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
if (typeof payload.testRunId === "string") {
|
|
598
|
-
replayTraceSubmissions.add(sourceTraceId);
|
|
599
|
-
if (!traceSubmissionSpanIds.has(sourceTraceId)) {
|
|
600
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set());
|
|
601
|
-
}
|
|
602
|
-
} else {
|
|
603
|
-
traceSubmissionSpanIds.delete(sourceTraceId);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
function takeReplaySpanCounts(traceIds) {
|
|
607
|
-
const counts = {};
|
|
608
|
-
for (const traceId of traceIds) {
|
|
609
|
-
if (!replayTraceSubmissions.has(traceId)) {
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
counts[traceId] = traceSubmissionSpanIds.get(traceId)?.size ?? 0;
|
|
613
|
-
traceSubmissionSpanIds.delete(traceId);
|
|
614
|
-
replayTraceSubmissions.delete(traceId);
|
|
615
|
-
}
|
|
616
|
-
return counts;
|
|
617
|
-
}
|
|
618
|
-
function asRecord2(value) {
|
|
619
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
620
|
-
}
|
|
621
|
-
function resolveSourceTraceId(payload) {
|
|
622
|
-
if (typeof payload.sourceTraceId === "string") {
|
|
623
|
-
return payload.sourceTraceId;
|
|
624
|
-
}
|
|
625
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
626
|
-
return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
|
|
627
|
-
}
|
|
628
593
|
function otlpValue(value) {
|
|
629
594
|
if (typeof value === "boolean") {
|
|
630
595
|
return { boolValue: value };
|
|
@@ -682,7 +647,11 @@ function spanToOtlp(span) {
|
|
|
682
647
|
}
|
|
683
648
|
function encodeSpan(span) {
|
|
684
649
|
const json = JSON.stringify(spanToOtlp(span));
|
|
685
|
-
return {
|
|
650
|
+
return {
|
|
651
|
+
json,
|
|
652
|
+
size: byteLength(json),
|
|
653
|
+
ref: carrierRefs.get(span)
|
|
654
|
+
};
|
|
686
655
|
}
|
|
687
656
|
function trimEncodedSpan(span) {
|
|
688
657
|
try {
|
|
@@ -765,48 +734,27 @@ async function mapWithConcurrency(items, limit, task) {
|
|
|
765
734
|
await Promise.all(workers);
|
|
766
735
|
return results;
|
|
767
736
|
}
|
|
768
|
-
function responseStatus(error) {
|
|
769
|
-
return error instanceof BitfabError ? error.status : void 0;
|
|
770
|
-
}
|
|
771
737
|
function isRetryable(error) {
|
|
772
|
-
|
|
773
|
-
if (status === void 0) {
|
|
774
|
-
return true;
|
|
775
|
-
}
|
|
776
|
-
return RETRYABLE_STATUSES.has(status) || status >= 500;
|
|
777
|
-
}
|
|
778
|
-
function endSpan(span, endTime) {
|
|
779
|
-
span.end(endTime);
|
|
738
|
+
return error instanceof DeliveryError && error.retryable;
|
|
780
739
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
|
|
784
|
-
if (typeof spanData?.name === "string") {
|
|
785
|
-
return spanData.name;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
if (typeof payload.traceFunctionKey === "string") {
|
|
789
|
-
return payload.traceFunctionKey;
|
|
790
|
-
}
|
|
791
|
-
return `bitfab.${operation}`;
|
|
740
|
+
function isOversized(error) {
|
|
741
|
+
return error instanceof DeliveryError && error.oversized;
|
|
792
742
|
}
|
|
793
|
-
function
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
return void 0;
|
|
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;
|
|
799
748
|
}
|
|
800
|
-
const
|
|
801
|
-
|
|
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;
|
|
802
755
|
}
|
|
803
|
-
function
|
|
804
|
-
|
|
805
|
-
if (spanData?.error != null) {
|
|
806
|
-
return true;
|
|
807
|
-
}
|
|
808
|
-
const errors = payload.errors;
|
|
809
|
-
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
756
|
+
function endSpan(span, endTime) {
|
|
757
|
+
span.end(endTime);
|
|
810
758
|
}
|
|
811
759
|
function createOtelTransport(options) {
|
|
812
760
|
return new OtelBatchTransport({
|
|
@@ -845,7 +793,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
|
|
|
845
793
|
(transport, remaining) => transport.shutdown(remaining)
|
|
846
794
|
);
|
|
847
795
|
}
|
|
848
|
-
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;
|
|
849
797
|
var init_otel = __esm({
|
|
850
798
|
"src/otel.ts"() {
|
|
851
799
|
"use strict";
|
|
@@ -859,11 +807,11 @@ var init_otel = __esm({
|
|
|
859
807
|
init_payloadBudget();
|
|
860
808
|
init_readEnv();
|
|
861
809
|
init_serializePayload();
|
|
810
|
+
init_transportTypes();
|
|
862
811
|
init_unrefTimer();
|
|
863
812
|
init_warnOnce();
|
|
864
813
|
OPERATION_ATTRIBUTE = "bitfab.operation";
|
|
865
814
|
PAYLOAD_ATTRIBUTE = "bitfab.payload";
|
|
866
|
-
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
867
815
|
MAX_EXPORT_REQUEST_BYTES = 3e6;
|
|
868
816
|
MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
|
|
869
817
|
MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
|
|
@@ -875,25 +823,23 @@ var init_otel = __esm({
|
|
|
875
823
|
MAX_EXPORT_CONCURRENCY = 64;
|
|
876
824
|
SCHEDULE_DELAY_MILLIS = 5e3;
|
|
877
825
|
EXPORT_TIMEOUT_MILLIS = 3e4;
|
|
878
|
-
|
|
826
|
+
RETRY_BASE_DELAY_MILLIS = 100;
|
|
827
|
+
RETRY_BACKOFF_CEILING_MILLIS = 5e3;
|
|
879
828
|
MAX_SEND_ATTEMPTS = 3;
|
|
880
829
|
DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
881
|
-
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
882
830
|
liveTransports = /* @__PURE__ */ new Set();
|
|
883
|
-
|
|
884
|
-
replayTraceSubmissions = /* @__PURE__ */ new Set();
|
|
885
|
-
submissionCounter = 0;
|
|
831
|
+
carrierRefs = /* @__PURE__ */ new WeakMap();
|
|
886
832
|
SPAN_SEPARATOR_BYTES = 1;
|
|
887
|
-
OtlpPayloadTooLargeError = class extends Error {
|
|
888
|
-
};
|
|
889
|
-
OtlpPartialSuccessError = class extends Error {
|
|
890
|
-
};
|
|
891
833
|
BitfabSpanExporter = class {
|
|
892
|
-
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency) {
|
|
834
|
+
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency, onDelivered, exportTimeoutMillis = EXPORT_TIMEOUT_MILLIS) {
|
|
893
835
|
this.directSender = directSender;
|
|
894
836
|
this.maxRequestBytes = maxRequestBytes;
|
|
895
837
|
this.maxRequestBatchSize = maxRequestBatchSize;
|
|
896
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;
|
|
897
843
|
}
|
|
898
844
|
export(spans, resultCallback) {
|
|
899
845
|
void this.exportAsync(spans).then(
|
|
@@ -959,6 +905,7 @@ var init_otel = __esm({
|
|
|
959
905
|
);
|
|
960
906
|
if (prepared.wireBytes <= this.maxRequestBytes) {
|
|
961
907
|
await this.sendWithRetries(prepared);
|
|
908
|
+
this.reportDelivered(batch.spans);
|
|
962
909
|
return true;
|
|
963
910
|
}
|
|
964
911
|
}
|
|
@@ -986,15 +933,12 @@ var init_otel = __esm({
|
|
|
986
933
|
alreadyTrimmed = true;
|
|
987
934
|
}
|
|
988
935
|
} catch (error) {
|
|
989
|
-
if (error
|
|
936
|
+
if (isOversized(error)) {
|
|
990
937
|
logError(
|
|
991
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"
|
|
992
939
|
);
|
|
993
940
|
return false;
|
|
994
941
|
}
|
|
995
|
-
if (error instanceof OtlpPartialSuccessError) {
|
|
996
|
-
return false;
|
|
997
|
-
}
|
|
998
942
|
logError("failed to export an OpenTelemetry span batch", error);
|
|
999
943
|
return false;
|
|
1000
944
|
}
|
|
@@ -1012,37 +956,79 @@ var init_otel = __esm({
|
|
|
1012
956
|
* the server does not yet understand. The fix is a client-supplied
|
|
1013
957
|
* idempotency key that ingestion dedupes on.
|
|
1014
958
|
*/
|
|
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
|
+
}
|
|
1015
991
|
async sendWithRetries(request) {
|
|
992
|
+
const deadline = Date.now() + this.exportTimeoutMillis;
|
|
1016
993
|
for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
|
|
1017
994
|
try {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
request,
|
|
1021
|
-
EXPORT_TIMEOUT_MILLIS
|
|
1022
|
-
);
|
|
1023
|
-
const partialSuccess = asRecord2(response?.partialSuccess);
|
|
1024
|
-
const rejected = partialSuccess?.rejectedSpans;
|
|
1025
|
-
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1026
|
-
logError(
|
|
1027
|
-
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1028
|
-
);
|
|
1029
|
-
throw new OtlpPartialSuccessError();
|
|
1030
|
-
}
|
|
995
|
+
await this.awaitThrottle(deadline);
|
|
996
|
+
await this.directSender(request, Math.max(0, deadline - Date.now()));
|
|
1031
997
|
return;
|
|
1032
998
|
} catch (error) {
|
|
1033
|
-
if (error
|
|
999
|
+
if (isOversized(error)) {
|
|
1034
1000
|
throw error;
|
|
1035
1001
|
}
|
|
1036
|
-
|
|
1037
|
-
throw new OtlpPayloadTooLargeError();
|
|
1038
|
-
}
|
|
1002
|
+
this.recordThrottle(error);
|
|
1039
1003
|
if (attempt === MAX_SEND_ATTEMPTS - 1 || !isRetryable(error)) {
|
|
1040
1004
|
throw error;
|
|
1041
1005
|
}
|
|
1042
|
-
|
|
1006
|
+
const wait = retryWaitMillis(error, attempt, deadline - Date.now());
|
|
1007
|
+
if (wait === null) {
|
|
1008
|
+
throw error;
|
|
1009
|
+
}
|
|
1010
|
+
await delay(wait);
|
|
1043
1011
|
}
|
|
1044
1012
|
}
|
|
1045
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
|
+
}
|
|
1046
1032
|
async shutdown() {
|
|
1047
1033
|
}
|
|
1048
1034
|
async forceFlush() {
|
|
@@ -1100,7 +1086,9 @@ var init_otel = __esm({
|
|
|
1100
1086
|
options.directSender,
|
|
1101
1087
|
maxRequestBytes,
|
|
1102
1088
|
maxRequestBatchSize,
|
|
1103
|
-
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
|
|
1089
|
+
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY,
|
|
1090
|
+
options.onDelivered,
|
|
1091
|
+
options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
|
|
1104
1092
|
)
|
|
1105
1093
|
);
|
|
1106
1094
|
this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
|
|
@@ -1124,8 +1112,7 @@ var init_otel = __esm({
|
|
|
1124
1112
|
this.tracer = this.provider.getTracer("bitfab", __version__);
|
|
1125
1113
|
liveTransports.add(this);
|
|
1126
1114
|
}
|
|
1127
|
-
submit(operation, payload) {
|
|
1128
|
-
recordTraceSubmission(operation, payload);
|
|
1115
|
+
submit(operation, payload, meta = {}) {
|
|
1129
1116
|
if (this.closed) {
|
|
1130
1117
|
warnOnce(
|
|
1131
1118
|
"otel-submit-after-shutdown",
|
|
@@ -1146,17 +1133,20 @@ var init_otel = __esm({
|
|
|
1146
1133
|
].join(", ")}); they were stubbed so the span still ships, but the trace may be incomplete or not replayable.`
|
|
1147
1134
|
);
|
|
1148
1135
|
}
|
|
1149
|
-
const span = this.tracer.startSpan(
|
|
1136
|
+
const span = this.tracer.startSpan(meta.name ?? `bitfab.${operation}`, {
|
|
1150
1137
|
attributes: {
|
|
1151
1138
|
[OPERATION_ATTRIBUTE]: operation,
|
|
1152
1139
|
[PAYLOAD_ATTRIBUTE]: body
|
|
1153
1140
|
},
|
|
1154
|
-
startTime:
|
|
1141
|
+
startTime: meta.startTime
|
|
1155
1142
|
});
|
|
1156
|
-
if (
|
|
1143
|
+
if (meta.ref !== void 0) {
|
|
1144
|
+
carrierRefs.set(span, meta.ref);
|
|
1145
|
+
}
|
|
1146
|
+
if (meta.errored === true) {
|
|
1157
1147
|
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
1158
1148
|
}
|
|
1159
|
-
endSpan(span,
|
|
1149
|
+
endSpan(span, meta.endTime);
|
|
1160
1150
|
} catch (error) {
|
|
1161
1151
|
logError("failed to queue an OpenTelemetry span", error);
|
|
1162
1152
|
}
|
|
@@ -1206,9 +1196,6 @@ function flushTraceTransports(timeoutMs) {
|
|
|
1206
1196
|
function shutdownTraceTransports(timeoutMs) {
|
|
1207
1197
|
return shutdownOtelTransports(timeoutMs);
|
|
1208
1198
|
}
|
|
1209
|
-
function takeReplaySpanCounts2(traceIds) {
|
|
1210
|
-
return takeReplaySpanCounts(traceIds);
|
|
1211
|
-
}
|
|
1212
1199
|
var init_transport = __esm({
|
|
1213
1200
|
"src/transport.ts"() {
|
|
1214
1201
|
"use strict";
|
|
@@ -1257,7 +1244,96 @@ async function waitForPromises(promises, timeoutMs) {
|
|
|
1257
1244
|
}
|
|
1258
1245
|
}
|
|
1259
1246
|
}
|
|
1260
|
-
|
|
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;
|
|
1261
1337
|
var init_http = __esm({
|
|
1262
1338
|
"src/http.ts"() {
|
|
1263
1339
|
"use strict";
|
|
@@ -1267,9 +1343,12 @@ var init_http = __esm({
|
|
|
1267
1343
|
init_replayContext();
|
|
1268
1344
|
init_serializePayload();
|
|
1269
1345
|
init_transport();
|
|
1346
|
+
init_transportTypes();
|
|
1270
1347
|
init_unrefTimer();
|
|
1271
1348
|
init_warnOnce();
|
|
1272
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]);
|
|
1273
1352
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
1274
1353
|
DEFAULT_LIFECYCLE_TIMEOUT_MS2 = 3e4;
|
|
1275
1354
|
pendingTracePromises = /* @__PURE__ */ new Set();
|
|
@@ -1289,8 +1368,12 @@ var init_http = __esm({
|
|
|
1289
1368
|
});
|
|
1290
1369
|
});
|
|
1291
1370
|
}
|
|
1371
|
+
carrierSeq = 0;
|
|
1292
1372
|
HttpClient = class {
|
|
1293
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();
|
|
1294
1377
|
// Deferred span work owned by THIS client. The module-global set backs the
|
|
1295
1378
|
// process-wide `flushTraces()` and the exit hook, but per-client lifecycle
|
|
1296
1379
|
// must not wait on another client's slow finalize: a false `close()` failure
|
|
@@ -1326,13 +1409,131 @@ var init_http = __esm({
|
|
|
1326
1409
|
}
|
|
1327
1410
|
if (!this.traceTransport) {
|
|
1328
1411
|
this.traceTransport = createTraceTransport({
|
|
1329
|
-
directSender: (
|
|
1330
|
-
|
|
1331
|
-
})
|
|
1412
|
+
directSender: (request, timeoutMs) => this.deliverCarriers(request, timeoutMs),
|
|
1413
|
+
onDelivered: (refs) => this.recordDeliveredCarriers(refs)
|
|
1332
1414
|
});
|
|
1333
1415
|
}
|
|
1334
1416
|
return this.traceTransport;
|
|
1335
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
|
+
}
|
|
1336
1537
|
/**
|
|
1337
1538
|
* Track deferred span work so this client's own lifecycle waits for it, and
|
|
1338
1539
|
* so the process-wide flush and exit hook do too.
|
|
@@ -1442,7 +1643,8 @@ var init_http = __esm({
|
|
|
1442
1643
|
throw new BitfabError(
|
|
1443
1644
|
`HTTP ${response.status}: ${errorText.slice(0, 500)}`,
|
|
1444
1645
|
void 0,
|
|
1445
|
-
response.status
|
|
1646
|
+
response.status,
|
|
1647
|
+
parseRetryAfterMs(readHeader(response, "retry-after"))
|
|
1446
1648
|
);
|
|
1447
1649
|
}
|
|
1448
1650
|
const result = await response.json();
|
|
@@ -1528,11 +1730,12 @@ var init_http = __esm({
|
|
|
1528
1730
|
* the OTLP carrier has no path to carry it.
|
|
1529
1731
|
*/
|
|
1530
1732
|
sendInternalTrace(functionId, payload) {
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1733
|
+
const body = { ...payload, functionId, sdkVersion: __version__ };
|
|
1734
|
+
this.getTraceTransport()?.submit(
|
|
1735
|
+
"internal_trace",
|
|
1736
|
+
body,
|
|
1737
|
+
carrierMeta("internal_trace", body, void 0)
|
|
1738
|
+
);
|
|
1536
1739
|
}
|
|
1537
1740
|
/**
|
|
1538
1741
|
* Queue an external span (from withSpan wrapper or OpenAI tracing) onto this
|
|
@@ -1541,10 +1744,11 @@ var init_http = __esm({
|
|
|
1541
1744
|
* promise.
|
|
1542
1745
|
*/
|
|
1543
1746
|
sendExternalSpan(payload) {
|
|
1544
|
-
this.getTraceTransport()?.submit(
|
|
1545
|
-
|
|
1546
|
-
sdkVersion: __version__
|
|
1547
|
-
|
|
1747
|
+
this.getTraceTransport()?.submit(
|
|
1748
|
+
"external_span",
|
|
1749
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1750
|
+
this.recordedMeta("external_span", payload, carrierRef(payload))
|
|
1751
|
+
);
|
|
1548
1752
|
}
|
|
1549
1753
|
/**
|
|
1550
1754
|
* Queue an external trace completion (from OpenAI tracing) onto this
|
|
@@ -1553,10 +1757,15 @@ var init_http = __esm({
|
|
|
1553
1757
|
* server-authoritative barrier in `replay.ts`, not by awaiting this call.
|
|
1554
1758
|
*/
|
|
1555
1759
|
sendExternalTrace(payload) {
|
|
1556
|
-
this.getTraceTransport()?.submit(
|
|
1557
|
-
|
|
1558
|
-
sdkVersion: __version__
|
|
1559
|
-
|
|
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
|
+
);
|
|
1560
1769
|
}
|
|
1561
1770
|
/**
|
|
1562
1771
|
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
@@ -2152,7 +2361,8 @@ __export(replay_exports, {
|
|
|
2152
2361
|
ReplayError: () => ReplayError,
|
|
2153
2362
|
replay: () => replay,
|
|
2154
2363
|
reportReplayProgress: () => reportReplayProgress,
|
|
2155
|
-
serializeReplayResult: () => serializeReplayResult
|
|
2364
|
+
serializeReplayResult: () => serializeReplayResult,
|
|
2365
|
+
waitForReplayPersistence: () => waitForReplayPersistence
|
|
2156
2366
|
});
|
|
2157
2367
|
function dbBranchEnabled(dbBranch) {
|
|
2158
2368
|
return dbBranch !== void 0 && dbBranch !== false;
|
|
@@ -2440,15 +2650,29 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
|
|
|
2440
2650
|
REPLAY_PERSISTENCE_TIMEOUT_MS
|
|
2441
2651
|
);
|
|
2442
2652
|
if (!deferredSettled) {
|
|
2653
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2443
2654
|
throw new BitfabError(
|
|
2444
2655
|
`Replay could not settle deferred span work before the deadline, so the expected span counts are incomplete (testRunId ${testRunId}).`
|
|
2445
2656
|
);
|
|
2446
2657
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2658
|
+
if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
|
|
2659
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2449
2660
|
return;
|
|
2450
2661
|
}
|
|
2451
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
|
+
}
|
|
2452
2676
|
const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
|
|
2453
2677
|
let missing = Object.keys(expectedSpanCounts).length;
|
|
2454
2678
|
while (true) {
|
|
@@ -2557,6 +2781,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
2557
2781
|
...registeredOverrides
|
|
2558
2782
|
];
|
|
2559
2783
|
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
2784
|
+
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
2560
2785
|
const tasks = serverItems.map(
|
|
2561
2786
|
(serverItem, index) => () => processItem(
|
|
2562
2787
|
httpClient,
|
|
@@ -2754,7 +2979,6 @@ var init_replay = __esm({
|
|
|
2754
2979
|
init_randomUuid();
|
|
2755
2980
|
init_replayContext();
|
|
2756
2981
|
init_serialize();
|
|
2757
|
-
init_transport();
|
|
2758
2982
|
init_unrefTimer();
|
|
2759
2983
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
2760
2984
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|