@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/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var __version__;
|
|
|
42
42
|
var init_version_generated = __esm({
|
|
43
43
|
"src/version.generated.ts"() {
|
|
44
44
|
"use strict";
|
|
45
|
-
__version__ = "0.36.
|
|
45
|
+
__version__ = "0.36.8";
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
@@ -168,10 +168,11 @@ var init_errors = __esm({
|
|
|
168
168
|
"src/errors.ts"() {
|
|
169
169
|
"use strict";
|
|
170
170
|
BitfabError = class extends Error {
|
|
171
|
-
constructor(message, url, status) {
|
|
171
|
+
constructor(message, url, status, retryAfterMs) {
|
|
172
172
|
super(message);
|
|
173
173
|
this.url = url;
|
|
174
174
|
this.status = status;
|
|
175
|
+
this.retryAfterMs = retryAfterMs;
|
|
175
176
|
this.name = "BitfabError";
|
|
176
177
|
}
|
|
177
178
|
};
|
|
@@ -526,6 +527,23 @@ var init_serializePayload = __esm({
|
|
|
526
527
|
}
|
|
527
528
|
});
|
|
528
529
|
|
|
530
|
+
// src/transportTypes.ts
|
|
531
|
+
var DeliveryError;
|
|
532
|
+
var init_transportTypes = __esm({
|
|
533
|
+
"src/transportTypes.ts"() {
|
|
534
|
+
"use strict";
|
|
535
|
+
DeliveryError = class extends Error {
|
|
536
|
+
constructor(message, options = {}) {
|
|
537
|
+
super(message);
|
|
538
|
+
this.name = "DeliveryError";
|
|
539
|
+
this.retryable = options.retryable ?? false;
|
|
540
|
+
this.oversized = options.oversized ?? false;
|
|
541
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
529
547
|
// src/unrefTimer.ts
|
|
530
548
|
function unrefTimer(timer) {
|
|
531
549
|
const handle = timer;
|
|
@@ -565,59 +583,6 @@ function logError(message, error) {
|
|
|
565
583
|
} catch {
|
|
566
584
|
}
|
|
567
585
|
}
|
|
568
|
-
function recordTraceSubmission(operation, payload) {
|
|
569
|
-
const sourceTraceId = resolveSourceTraceId(payload);
|
|
570
|
-
if (sourceTraceId === void 0) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
if (operation === "external_span") {
|
|
574
|
-
const rawSpan = asRecord2(payload.rawSpan);
|
|
575
|
-
if (typeof rawSpan?.id !== "string") {
|
|
576
|
-
submissionCounter += 1;
|
|
577
|
-
}
|
|
578
|
-
const sourceSpanId = typeof rawSpan?.id === "string" ? rawSpan.id : `submission-${submissionCounter}`;
|
|
579
|
-
const existing = traceSubmissionSpanIds.get(sourceTraceId);
|
|
580
|
-
if (existing) {
|
|
581
|
-
existing.add(sourceSpanId);
|
|
582
|
-
} else {
|
|
583
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set([sourceSpanId]));
|
|
584
|
-
}
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
if (payload.completed !== true) {
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
if (typeof payload.testRunId === "string") {
|
|
591
|
-
replayTraceSubmissions.add(sourceTraceId);
|
|
592
|
-
if (!traceSubmissionSpanIds.has(sourceTraceId)) {
|
|
593
|
-
traceSubmissionSpanIds.set(sourceTraceId, /* @__PURE__ */ new Set());
|
|
594
|
-
}
|
|
595
|
-
} else {
|
|
596
|
-
traceSubmissionSpanIds.delete(sourceTraceId);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
function takeReplaySpanCounts(traceIds) {
|
|
600
|
-
const counts = {};
|
|
601
|
-
for (const traceId of traceIds) {
|
|
602
|
-
if (!replayTraceSubmissions.has(traceId)) {
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
counts[traceId] = traceSubmissionSpanIds.get(traceId)?.size ?? 0;
|
|
606
|
-
traceSubmissionSpanIds.delete(traceId);
|
|
607
|
-
replayTraceSubmissions.delete(traceId);
|
|
608
|
-
}
|
|
609
|
-
return counts;
|
|
610
|
-
}
|
|
611
|
-
function asRecord2(value) {
|
|
612
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
613
|
-
}
|
|
614
|
-
function resolveSourceTraceId(payload) {
|
|
615
|
-
if (typeof payload.sourceTraceId === "string") {
|
|
616
|
-
return payload.sourceTraceId;
|
|
617
|
-
}
|
|
618
|
-
const rawTrace = asRecord2(payload.externalTrace) ?? asRecord2(payload.rawTrace);
|
|
619
|
-
return typeof rawTrace?.id === "string" ? rawTrace.id : void 0;
|
|
620
|
-
}
|
|
621
586
|
function otlpValue(value) {
|
|
622
587
|
if (typeof value === "boolean") {
|
|
623
588
|
return { boolValue: value };
|
|
@@ -675,7 +640,11 @@ function spanToOtlp(span) {
|
|
|
675
640
|
}
|
|
676
641
|
function encodeSpan(span) {
|
|
677
642
|
const json = JSON.stringify(spanToOtlp(span));
|
|
678
|
-
return {
|
|
643
|
+
return {
|
|
644
|
+
json,
|
|
645
|
+
size: byteLength(json),
|
|
646
|
+
ref: carrierRefs.get(span)
|
|
647
|
+
};
|
|
679
648
|
}
|
|
680
649
|
function trimEncodedSpan(span) {
|
|
681
650
|
try {
|
|
@@ -758,48 +727,27 @@ async function mapWithConcurrency(items, limit, task) {
|
|
|
758
727
|
await Promise.all(workers);
|
|
759
728
|
return results;
|
|
760
729
|
}
|
|
761
|
-
function responseStatus(error) {
|
|
762
|
-
return error instanceof BitfabError ? error.status : void 0;
|
|
763
|
-
}
|
|
764
730
|
function isRetryable(error) {
|
|
765
|
-
|
|
766
|
-
if (status === void 0) {
|
|
767
|
-
return true;
|
|
768
|
-
}
|
|
769
|
-
return RETRYABLE_STATUSES.has(status) || status >= 500;
|
|
770
|
-
}
|
|
771
|
-
function endSpan(span, endTime) {
|
|
772
|
-
span.end(endTime);
|
|
731
|
+
return error instanceof DeliveryError && error.retryable;
|
|
773
732
|
}
|
|
774
|
-
function
|
|
775
|
-
|
|
776
|
-
const spanData = asRecord2(asRecord2(payload.rawSpan)?.span_data);
|
|
777
|
-
if (typeof spanData?.name === "string") {
|
|
778
|
-
return spanData.name;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if (typeof payload.traceFunctionKey === "string") {
|
|
782
|
-
return payload.traceFunctionKey;
|
|
783
|
-
}
|
|
784
|
-
return `bitfab.${operation}`;
|
|
733
|
+
function isOversized(error) {
|
|
734
|
+
return error instanceof DeliveryError && error.oversized;
|
|
785
735
|
}
|
|
786
|
-
function
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
return void 0;
|
|
736
|
+
function retryWaitMillis(error, attempt, remainingMillis) {
|
|
737
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
738
|
+
const affordable = remainingMillis / 2;
|
|
739
|
+
if (requested !== void 0) {
|
|
740
|
+
return requested < affordable ? requested : null;
|
|
792
741
|
}
|
|
793
|
-
const
|
|
794
|
-
|
|
742
|
+
const backoff = Math.min(
|
|
743
|
+
RETRY_BASE_DELAY_MILLIS * 2 ** attempt,
|
|
744
|
+
RETRY_BACKOFF_CEILING_MILLIS
|
|
745
|
+
);
|
|
746
|
+
const jittered = backoff / 2 + Math.random() * (backoff / 2);
|
|
747
|
+
return jittered < affordable ? jittered : null;
|
|
795
748
|
}
|
|
796
|
-
function
|
|
797
|
-
|
|
798
|
-
if (spanData?.error != null) {
|
|
799
|
-
return true;
|
|
800
|
-
}
|
|
801
|
-
const errors = payload.errors;
|
|
802
|
-
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
749
|
+
function endSpan(span, endTime) {
|
|
750
|
+
span.end(endTime);
|
|
803
751
|
}
|
|
804
752
|
function createOtelTransport(options) {
|
|
805
753
|
return new OtelBatchTransport({
|
|
@@ -838,7 +786,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
|
|
|
838
786
|
(transport, remaining) => transport.shutdown(remaining)
|
|
839
787
|
);
|
|
840
788
|
}
|
|
841
|
-
var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE,
|
|
789
|
+
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;
|
|
842
790
|
var init_otel = __esm({
|
|
843
791
|
"src/otel.ts"() {
|
|
844
792
|
"use strict";
|
|
@@ -852,11 +800,11 @@ var init_otel = __esm({
|
|
|
852
800
|
init_payloadBudget();
|
|
853
801
|
init_readEnv();
|
|
854
802
|
init_serializePayload();
|
|
803
|
+
init_transportTypes();
|
|
855
804
|
init_unrefTimer();
|
|
856
805
|
init_warnOnce();
|
|
857
806
|
OPERATION_ATTRIBUTE = "bitfab.operation";
|
|
858
807
|
PAYLOAD_ATTRIBUTE = "bitfab.payload";
|
|
859
|
-
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
860
808
|
MAX_EXPORT_REQUEST_BYTES = 3e6;
|
|
861
809
|
MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
|
|
862
810
|
MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
|
|
@@ -868,25 +816,23 @@ var init_otel = __esm({
|
|
|
868
816
|
MAX_EXPORT_CONCURRENCY = 64;
|
|
869
817
|
SCHEDULE_DELAY_MILLIS = 5e3;
|
|
870
818
|
EXPORT_TIMEOUT_MILLIS = 3e4;
|
|
871
|
-
|
|
819
|
+
RETRY_BASE_DELAY_MILLIS = 100;
|
|
820
|
+
RETRY_BACKOFF_CEILING_MILLIS = 5e3;
|
|
872
821
|
MAX_SEND_ATTEMPTS = 3;
|
|
873
822
|
DEFAULT_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
874
|
-
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
875
823
|
liveTransports = /* @__PURE__ */ new Set();
|
|
876
|
-
|
|
877
|
-
replayTraceSubmissions = /* @__PURE__ */ new Set();
|
|
878
|
-
submissionCounter = 0;
|
|
824
|
+
carrierRefs = /* @__PURE__ */ new WeakMap();
|
|
879
825
|
SPAN_SEPARATOR_BYTES = 1;
|
|
880
|
-
OtlpPayloadTooLargeError = class extends Error {
|
|
881
|
-
};
|
|
882
|
-
OtlpPartialSuccessError = class extends Error {
|
|
883
|
-
};
|
|
884
826
|
BitfabSpanExporter = class {
|
|
885
|
-
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency) {
|
|
827
|
+
constructor(directSender, maxRequestBytes, maxRequestBatchSize, exportConcurrency, onDelivered, exportTimeoutMillis = EXPORT_TIMEOUT_MILLIS) {
|
|
886
828
|
this.directSender = directSender;
|
|
887
829
|
this.maxRequestBytes = maxRequestBytes;
|
|
888
830
|
this.maxRequestBatchSize = maxRequestBatchSize;
|
|
889
831
|
this.exportConcurrency = exportConcurrency;
|
|
832
|
+
this.onDelivered = onDelivered;
|
|
833
|
+
this.exportTimeoutMillis = exportTimeoutMillis;
|
|
834
|
+
/** Epoch ms until which the server has asked this exporter to stay away. */
|
|
835
|
+
this.throttledUntil = 0;
|
|
890
836
|
}
|
|
891
837
|
export(spans, resultCallback) {
|
|
892
838
|
void this.exportAsync(spans).then(
|
|
@@ -952,6 +898,7 @@ var init_otel = __esm({
|
|
|
952
898
|
);
|
|
953
899
|
if (prepared.wireBytes <= this.maxRequestBytes) {
|
|
954
900
|
await this.sendWithRetries(prepared);
|
|
901
|
+
this.reportDelivered(batch.spans);
|
|
955
902
|
return true;
|
|
956
903
|
}
|
|
957
904
|
}
|
|
@@ -979,15 +926,12 @@ var init_otel = __esm({
|
|
|
979
926
|
alreadyTrimmed = true;
|
|
980
927
|
}
|
|
981
928
|
} catch (error) {
|
|
982
|
-
if (error
|
|
929
|
+
if (isOversized(error)) {
|
|
983
930
|
logError(
|
|
984
931
|
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"
|
|
985
932
|
);
|
|
986
933
|
return false;
|
|
987
934
|
}
|
|
988
|
-
if (error instanceof OtlpPartialSuccessError) {
|
|
989
|
-
return false;
|
|
990
|
-
}
|
|
991
935
|
logError("failed to export an OpenTelemetry span batch", error);
|
|
992
936
|
return false;
|
|
993
937
|
}
|
|
@@ -1005,37 +949,79 @@ var init_otel = __esm({
|
|
|
1005
949
|
* the server does not yet understand. The fix is a client-supplied
|
|
1006
950
|
* idempotency key that ingestion dedupes on.
|
|
1007
951
|
*/
|
|
952
|
+
/**
|
|
953
|
+
* Remember a throttle the server asked for, so the requests fanned out
|
|
954
|
+
* alongside this one respect it too. Delaying only the request that was
|
|
955
|
+
* refused leaves the other seven in the window hitting a server that just
|
|
956
|
+
* asked for room.
|
|
957
|
+
*/
|
|
958
|
+
recordThrottle(error) {
|
|
959
|
+
const requested = error instanceof DeliveryError ? error.retryAfterMs : void 0;
|
|
960
|
+
if (requested !== void 0) {
|
|
961
|
+
this.throttledUntil = Math.max(
|
|
962
|
+
this.throttledUntil,
|
|
963
|
+
Date.now() + requested
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Waits out an active throttle, or reports the batch undeliverable when the
|
|
969
|
+
* throttle outlasts what we are willing to hold it for. Either way nothing is
|
|
970
|
+
* sent while the server has asked us to stay away.
|
|
971
|
+
*/
|
|
972
|
+
async awaitThrottle(deadline) {
|
|
973
|
+
const remaining = this.throttledUntil - Date.now();
|
|
974
|
+
if (remaining <= 0) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (remaining >= (deadline - Date.now()) / 2) {
|
|
978
|
+
throw new DeliveryError(
|
|
979
|
+
`OTLP ingestion is throttled for another ${remaining}ms, longer than the export budget`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
await delay(remaining);
|
|
983
|
+
}
|
|
1008
984
|
async sendWithRetries(request) {
|
|
985
|
+
const deadline = Date.now() + this.exportTimeoutMillis;
|
|
1009
986
|
for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
|
|
1010
987
|
try {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
request,
|
|
1014
|
-
EXPORT_TIMEOUT_MILLIS
|
|
1015
|
-
);
|
|
1016
|
-
const partialSuccess = asRecord2(response?.partialSuccess);
|
|
1017
|
-
const rejected = partialSuccess?.rejectedSpans;
|
|
1018
|
-
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1019
|
-
logError(
|
|
1020
|
-
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1021
|
-
);
|
|
1022
|
-
throw new OtlpPartialSuccessError();
|
|
1023
|
-
}
|
|
988
|
+
await this.awaitThrottle(deadline);
|
|
989
|
+
await this.directSender(request, Math.max(0, deadline - Date.now()));
|
|
1024
990
|
return;
|
|
1025
991
|
} catch (error) {
|
|
1026
|
-
if (error
|
|
992
|
+
if (isOversized(error)) {
|
|
1027
993
|
throw error;
|
|
1028
994
|
}
|
|
1029
|
-
|
|
1030
|
-
throw new OtlpPayloadTooLargeError();
|
|
1031
|
-
}
|
|
995
|
+
this.recordThrottle(error);
|
|
1032
996
|
if (attempt === MAX_SEND_ATTEMPTS - 1 || !isRetryable(error)) {
|
|
1033
997
|
throw error;
|
|
1034
998
|
}
|
|
1035
|
-
|
|
999
|
+
const wait = retryWaitMillis(error, attempt, deadline - Date.now());
|
|
1000
|
+
if (wait === null) {
|
|
1001
|
+
throw error;
|
|
1002
|
+
}
|
|
1003
|
+
await delay(wait);
|
|
1036
1004
|
}
|
|
1037
1005
|
}
|
|
1038
1006
|
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Announce the carriers a request delivered. Wrapped because a listener that
|
|
1009
|
+
* throws must never turn a delivered batch into a failed export.
|
|
1010
|
+
*/
|
|
1011
|
+
reportDelivered(spans) {
|
|
1012
|
+
if (this.onDelivered === void 0) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const refs = spans.map((span) => span.ref).filter((ref) => ref !== void 0);
|
|
1016
|
+
if (refs.length === 0) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
this.onDelivered(refs);
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
logError("a delivery listener threw", error);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1039
1025
|
async shutdown() {
|
|
1040
1026
|
}
|
|
1041
1027
|
async forceFlush() {
|
|
@@ -1093,7 +1079,9 @@ var init_otel = __esm({
|
|
|
1093
1079
|
options.directSender,
|
|
1094
1080
|
maxRequestBytes,
|
|
1095
1081
|
maxRequestBatchSize,
|
|
1096
|
-
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY
|
|
1082
|
+
options.exportConcurrency ?? DEFAULT_EXPORT_CONCURRENCY,
|
|
1083
|
+
options.onDelivered,
|
|
1084
|
+
options.exportTimeoutMillis ?? EXPORT_TIMEOUT_MILLIS
|
|
1097
1085
|
)
|
|
1098
1086
|
);
|
|
1099
1087
|
this.processor = new import_sdk_trace_base.BatchSpanProcessor(this.deliveryTracker, {
|
|
@@ -1117,8 +1105,7 @@ var init_otel = __esm({
|
|
|
1117
1105
|
this.tracer = this.provider.getTracer("bitfab", __version__);
|
|
1118
1106
|
liveTransports.add(this);
|
|
1119
1107
|
}
|
|
1120
|
-
submit(operation, payload) {
|
|
1121
|
-
recordTraceSubmission(operation, payload);
|
|
1108
|
+
submit(operation, payload, meta = {}) {
|
|
1122
1109
|
if (this.closed) {
|
|
1123
1110
|
warnOnce(
|
|
1124
1111
|
"otel-submit-after-shutdown",
|
|
@@ -1139,17 +1126,20 @@ var init_otel = __esm({
|
|
|
1139
1126
|
].join(", ")}); they were stubbed so the span still ships, but the trace may be incomplete or not replayable.`
|
|
1140
1127
|
);
|
|
1141
1128
|
}
|
|
1142
|
-
const span = this.tracer.startSpan(
|
|
1129
|
+
const span = this.tracer.startSpan(meta.name ?? `bitfab.${operation}`, {
|
|
1143
1130
|
attributes: {
|
|
1144
1131
|
[OPERATION_ATTRIBUTE]: operation,
|
|
1145
1132
|
[PAYLOAD_ATTRIBUTE]: body
|
|
1146
1133
|
},
|
|
1147
|
-
startTime:
|
|
1134
|
+
startTime: meta.startTime
|
|
1148
1135
|
});
|
|
1149
|
-
if (
|
|
1136
|
+
if (meta.ref !== void 0) {
|
|
1137
|
+
carrierRefs.set(span, meta.ref);
|
|
1138
|
+
}
|
|
1139
|
+
if (meta.errored === true) {
|
|
1150
1140
|
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
1151
1141
|
}
|
|
1152
|
-
endSpan(span,
|
|
1142
|
+
endSpan(span, meta.endTime);
|
|
1153
1143
|
} catch (error) {
|
|
1154
1144
|
logError("failed to queue an OpenTelemetry span", error);
|
|
1155
1145
|
}
|
|
@@ -1199,9 +1189,6 @@ function flushTraceTransports(timeoutMs) {
|
|
|
1199
1189
|
function shutdownTraceTransports(timeoutMs) {
|
|
1200
1190
|
return shutdownOtelTransports(timeoutMs);
|
|
1201
1191
|
}
|
|
1202
|
-
function takeReplaySpanCounts2(traceIds) {
|
|
1203
|
-
return takeReplaySpanCounts(traceIds);
|
|
1204
|
-
}
|
|
1205
1192
|
var init_transport = __esm({
|
|
1206
1193
|
"src/transport.ts"() {
|
|
1207
1194
|
"use strict";
|
|
@@ -1250,7 +1237,96 @@ async function waitForPromises(promises, timeoutMs) {
|
|
|
1250
1237
|
}
|
|
1251
1238
|
}
|
|
1252
1239
|
}
|
|
1253
|
-
|
|
1240
|
+
function readHeader(response, name) {
|
|
1241
|
+
try {
|
|
1242
|
+
return response.headers?.get(name) ?? null;
|
|
1243
|
+
} catch {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
function parseRetryAfterMs(header) {
|
|
1248
|
+
const value = header?.trim();
|
|
1249
|
+
if (!value) {
|
|
1250
|
+
return void 0;
|
|
1251
|
+
}
|
|
1252
|
+
const seconds = Number(value);
|
|
1253
|
+
if (Number.isFinite(seconds)) {
|
|
1254
|
+
return seconds >= 0 ? seconds * 1e3 : void 0;
|
|
1255
|
+
}
|
|
1256
|
+
const at = Date.parse(value);
|
|
1257
|
+
if (Number.isNaN(at)) {
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
return Math.max(0, at - Date.now());
|
|
1261
|
+
}
|
|
1262
|
+
function carrierMeta(operation, payload, ref) {
|
|
1263
|
+
return {
|
|
1264
|
+
ref,
|
|
1265
|
+
name: carrierName(operation, payload),
|
|
1266
|
+
startTime: payloadTimestamp(payload, "started_at"),
|
|
1267
|
+
endTime: payloadTimestamp(payload, "ended_at"),
|
|
1268
|
+
errored: payloadHasError(payload)
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
function carrierName(operation, payload) {
|
|
1272
|
+
if (operation === "external_span") {
|
|
1273
|
+
const spanData = asPayloadRecord(
|
|
1274
|
+
asPayloadRecord(payload.rawSpan)?.span_data
|
|
1275
|
+
);
|
|
1276
|
+
if (typeof spanData?.name === "string") {
|
|
1277
|
+
return spanData.name;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (typeof payload.traceFunctionKey === "string") {
|
|
1281
|
+
return payload.traceFunctionKey;
|
|
1282
|
+
}
|
|
1283
|
+
return `bitfab.${operation}`;
|
|
1284
|
+
}
|
|
1285
|
+
function payloadTimestamp(payload, field) {
|
|
1286
|
+
const rawSpan = asPayloadRecord(payload.rawSpan);
|
|
1287
|
+
const rawTrace = asPayloadRecord(payload.externalTrace) ?? asPayloadRecord(payload.rawTrace);
|
|
1288
|
+
const raw = rawSpan?.[field] ?? rawTrace?.[field];
|
|
1289
|
+
if (typeof raw !== "string") {
|
|
1290
|
+
return void 0;
|
|
1291
|
+
}
|
|
1292
|
+
const parsed = Date.parse(raw);
|
|
1293
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1294
|
+
}
|
|
1295
|
+
function payloadHasError(payload) {
|
|
1296
|
+
const spanData = asPayloadRecord(asPayloadRecord(payload.rawSpan)?.span_data);
|
|
1297
|
+
if (spanData?.error != null) {
|
|
1298
|
+
return true;
|
|
1299
|
+
}
|
|
1300
|
+
const errors = payload.errors;
|
|
1301
|
+
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
1302
|
+
}
|
|
1303
|
+
function asPayloadRecord(value) {
|
|
1304
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1305
|
+
}
|
|
1306
|
+
function carrierRef(payload) {
|
|
1307
|
+
const traceId = sourceTraceIdOf(payload);
|
|
1308
|
+
if (traceId === void 0) {
|
|
1309
|
+
return void 0;
|
|
1310
|
+
}
|
|
1311
|
+
const rawSpan = payload.rawSpan;
|
|
1312
|
+
if (rawSpan === void 0) {
|
|
1313
|
+
return { traceId };
|
|
1314
|
+
}
|
|
1315
|
+
const spanId = rawSpan?.id;
|
|
1316
|
+
return {
|
|
1317
|
+
traceId,
|
|
1318
|
+
spanId: typeof spanId === "string" ? spanId : `submission-${++carrierSeq}`
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function sourceTraceIdOf(payload) {
|
|
1322
|
+
if (typeof payload.sourceTraceId === "string") {
|
|
1323
|
+
return payload.sourceTraceId;
|
|
1324
|
+
}
|
|
1325
|
+
const rawTrace = payload.externalTrace ?? payload.rawTrace;
|
|
1326
|
+
const id = rawTrace?.id;
|
|
1327
|
+
return typeof id === "string" ? id : void 0;
|
|
1328
|
+
}
|
|
1329
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, carrierSeq, HttpClient;
|
|
1254
1330
|
var init_http = __esm({
|
|
1255
1331
|
"src/http.ts"() {
|
|
1256
1332
|
"use strict";
|
|
@@ -1260,9 +1336,12 @@ var init_http = __esm({
|
|
|
1260
1336
|
init_replayContext();
|
|
1261
1337
|
init_serializePayload();
|
|
1262
1338
|
init_transport();
|
|
1339
|
+
init_transportTypes();
|
|
1263
1340
|
init_unrefTimer();
|
|
1264
1341
|
init_warnOnce();
|
|
1265
1342
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
1343
|
+
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
1344
|
+
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
1266
1345
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
1267
1346
|
DEFAULT_LIFECYCLE_TIMEOUT_MS2 = 3e4;
|
|
1268
1347
|
pendingTracePromises = /* @__PURE__ */ new Set();
|
|
@@ -1282,8 +1361,12 @@ var init_http = __esm({
|
|
|
1282
1361
|
});
|
|
1283
1362
|
});
|
|
1284
1363
|
}
|
|
1364
|
+
carrierSeq = 0;
|
|
1285
1365
|
HttpClient = class {
|
|
1286
1366
|
constructor(config) {
|
|
1367
|
+
// Only traces a caller asked about are tracked, so ordinary tracing stores
|
|
1368
|
+
// nothing here.
|
|
1369
|
+
this.traceDeliveries = /* @__PURE__ */ new Map();
|
|
1287
1370
|
// Deferred span work owned by THIS client. The module-global set backs the
|
|
1288
1371
|
// process-wide `flushTraces()` and the exit hook, but per-client lifecycle
|
|
1289
1372
|
// must not wait on another client's slow finalize: a false `close()` failure
|
|
@@ -1319,13 +1402,131 @@ var init_http = __esm({
|
|
|
1319
1402
|
}
|
|
1320
1403
|
if (!this.traceTransport) {
|
|
1321
1404
|
this.traceTransport = createTraceTransport({
|
|
1322
|
-
directSender: (
|
|
1323
|
-
|
|
1324
|
-
})
|
|
1405
|
+
directSender: (request, timeoutMs) => this.deliverCarriers(request, timeoutMs),
|
|
1406
|
+
onDelivered: (refs) => this.recordDeliveredCarriers(refs)
|
|
1325
1407
|
});
|
|
1326
1408
|
}
|
|
1327
1409
|
return this.traceTransport;
|
|
1328
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Post one encoded batch and decide what the server's answer means, so the
|
|
1413
|
+
* transport never reads a response. Rejections and permanent statuses come
|
|
1414
|
+
* back as a non-retryable {@link DeliveryError}; anything the server might
|
|
1415
|
+
* still accept on a second try comes back retryable.
|
|
1416
|
+
*/
|
|
1417
|
+
async deliverCarriers(request, timeoutMs) {
|
|
1418
|
+
let response;
|
|
1419
|
+
try {
|
|
1420
|
+
response = await this.sendPrepared(
|
|
1421
|
+
OTLP_TRACES_ENDPOINT,
|
|
1422
|
+
request,
|
|
1423
|
+
{ timeout: timeoutMs }
|
|
1424
|
+
);
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
const status = error instanceof BitfabError ? error.status : void 0;
|
|
1427
|
+
if (status === void 0) {
|
|
1428
|
+
throw new DeliveryError(`OTLP ingestion failed: ${String(error)}`, {
|
|
1429
|
+
retryable: true
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
throw new DeliveryError(`OTLP ingestion failed with HTTP ${status}`, {
|
|
1433
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
1434
|
+
oversized: status === 413,
|
|
1435
|
+
...error instanceof BitfabError && error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
const partialSuccess = asPayloadRecord(response?.partialSuccess);
|
|
1439
|
+
const rejected = partialSuccess?.rejectedSpans;
|
|
1440
|
+
if (rejected !== void 0 && rejected !== "0" && rejected !== 0) {
|
|
1441
|
+
throw new DeliveryError(
|
|
1442
|
+
`OTLP ingestion rejected ${rejected} span(s): ${partialSuccess?.errorMessage ?? "no reason provided"}`
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Start tracking delivery for `traceIds`. Nothing is recorded for a trace
|
|
1448
|
+
* that was never tracked, so ordinary tracing costs no bookkeeping at all.
|
|
1449
|
+
*/
|
|
1450
|
+
trackTraceDeliveries(traceIds) {
|
|
1451
|
+
for (const traceId of traceIds) {
|
|
1452
|
+
if (!this.traceDeliveries.has(traceId)) {
|
|
1453
|
+
this.traceDeliveries.set(traceId, {
|
|
1454
|
+
submittedSpanIds: /* @__PURE__ */ new Set(),
|
|
1455
|
+
ackedSpanIds: /* @__PURE__ */ new Set(),
|
|
1456
|
+
closed: false,
|
|
1457
|
+
closingAcked: false
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/** Whether any tracked trace has had its closing carrier submitted. */
|
|
1463
|
+
hasClosedDeliveries(traceIds) {
|
|
1464
|
+
return traceIds.some((traceId) => this.traceDeliveries.get(traceId)?.closed);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Report what each tracked trace submitted and whether the server confirmed
|
|
1468
|
+
* it, and stop tracking them. Every id passed is freed, so a caller cannot
|
|
1469
|
+
* leak a record for a trace that never closed.
|
|
1470
|
+
*
|
|
1471
|
+
* `delivered` is only meaningful once a flush has settled: acks land before
|
|
1472
|
+
* an export resolves, so a flush that reported success has already collected
|
|
1473
|
+
* every ack it is going to collect.
|
|
1474
|
+
*/
|
|
1475
|
+
takeTraceDeliveries(traceIds) {
|
|
1476
|
+
const reports = {};
|
|
1477
|
+
for (const traceId of traceIds) {
|
|
1478
|
+
const delivery = this.traceDeliveries.get(traceId);
|
|
1479
|
+
if (delivery === void 0) {
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
this.traceDeliveries.delete(traceId);
|
|
1483
|
+
reports[traceId] = {
|
|
1484
|
+
spanCount: delivery.submittedSpanIds.size,
|
|
1485
|
+
closed: delivery.closed,
|
|
1486
|
+
delivered: delivery.closingAcked && [...delivery.submittedSpanIds].every(
|
|
1487
|
+
(spanId) => delivery.ackedSpanIds.has(spanId)
|
|
1488
|
+
)
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
return reports;
|
|
1492
|
+
}
|
|
1493
|
+
/** Build a carrier's meta and record what it adds to its trace's expected set. */
|
|
1494
|
+
recordedMeta(operation, payload, ref) {
|
|
1495
|
+
this.recordSubmittedCarrier(ref);
|
|
1496
|
+
return carrierMeta(operation, payload, ref);
|
|
1497
|
+
}
|
|
1498
|
+
recordSubmittedCarrier(ref) {
|
|
1499
|
+
if (ref === void 0) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1503
|
+
if (delivery === void 0) {
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
if (ref.spanId === void 0) {
|
|
1507
|
+
delivery.closed = true;
|
|
1508
|
+
} else {
|
|
1509
|
+
delivery.submittedSpanIds.add(ref.spanId);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Ingestion commits every carrier in a request before it answers, so a
|
|
1514
|
+
* delivered ref is proof its row exists: the same fact the replay status
|
|
1515
|
+
* endpoint would report, already in hand.
|
|
1516
|
+
*/
|
|
1517
|
+
recordDeliveredCarriers(refs) {
|
|
1518
|
+
for (const ref of refs) {
|
|
1519
|
+
const delivery = this.traceDeliveries.get(ref.traceId);
|
|
1520
|
+
if (delivery === void 0) {
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (ref.spanId === void 0) {
|
|
1524
|
+
delivery.closingAcked = true;
|
|
1525
|
+
} else {
|
|
1526
|
+
delivery.ackedSpanIds.add(ref.spanId);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1329
1530
|
/**
|
|
1330
1531
|
* Track deferred span work so this client's own lifecycle waits for it, and
|
|
1331
1532
|
* so the process-wide flush and exit hook do too.
|
|
@@ -1435,7 +1636,8 @@ var init_http = __esm({
|
|
|
1435
1636
|
throw new BitfabError(
|
|
1436
1637
|
`HTTP ${response.status}: ${errorText.slice(0, 500)}`,
|
|
1437
1638
|
void 0,
|
|
1438
|
-
response.status
|
|
1639
|
+
response.status,
|
|
1640
|
+
parseRetryAfterMs(readHeader(response, "retry-after"))
|
|
1439
1641
|
);
|
|
1440
1642
|
}
|
|
1441
1643
|
const result = await response.json();
|
|
@@ -1521,11 +1723,12 @@ var init_http = __esm({
|
|
|
1521
1723
|
* the OTLP carrier has no path to carry it.
|
|
1522
1724
|
*/
|
|
1523
1725
|
sendInternalTrace(functionId, payload) {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1726
|
+
const body = { ...payload, functionId, sdkVersion: __version__ };
|
|
1727
|
+
this.getTraceTransport()?.submit(
|
|
1728
|
+
"internal_trace",
|
|
1729
|
+
body,
|
|
1730
|
+
carrierMeta("internal_trace", body, void 0)
|
|
1731
|
+
);
|
|
1529
1732
|
}
|
|
1530
1733
|
/**
|
|
1531
1734
|
* Queue an external span (from withSpan wrapper or OpenAI tracing) onto this
|
|
@@ -1534,10 +1737,11 @@ var init_http = __esm({
|
|
|
1534
1737
|
* promise.
|
|
1535
1738
|
*/
|
|
1536
1739
|
sendExternalSpan(payload) {
|
|
1537
|
-
this.getTraceTransport()?.submit(
|
|
1538
|
-
|
|
1539
|
-
sdkVersion: __version__
|
|
1540
|
-
|
|
1740
|
+
this.getTraceTransport()?.submit(
|
|
1741
|
+
"external_span",
|
|
1742
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1743
|
+
this.recordedMeta("external_span", payload, carrierRef(payload))
|
|
1744
|
+
);
|
|
1541
1745
|
}
|
|
1542
1746
|
/**
|
|
1543
1747
|
* Queue an external trace completion (from OpenAI tracing) onto this
|
|
@@ -1546,10 +1750,15 @@ var init_http = __esm({
|
|
|
1546
1750
|
* server-authoritative barrier in `replay.ts`, not by awaiting this call.
|
|
1547
1751
|
*/
|
|
1548
1752
|
sendExternalTrace(payload) {
|
|
1549
|
-
this.getTraceTransport()?.submit(
|
|
1550
|
-
|
|
1551
|
-
sdkVersion: __version__
|
|
1552
|
-
|
|
1753
|
+
this.getTraceTransport()?.submit(
|
|
1754
|
+
"external_trace",
|
|
1755
|
+
{ ...payload, sdkVersion: __version__ },
|
|
1756
|
+
this.recordedMeta(
|
|
1757
|
+
"external_trace",
|
|
1758
|
+
payload,
|
|
1759
|
+
payload.completed === true ? carrierRef(payload) : void 0
|
|
1760
|
+
)
|
|
1761
|
+
);
|
|
1553
1762
|
}
|
|
1554
1763
|
/**
|
|
1555
1764
|
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
@@ -2145,7 +2354,8 @@ __export(replay_exports, {
|
|
|
2145
2354
|
ReplayError: () => ReplayError,
|
|
2146
2355
|
replay: () => replay,
|
|
2147
2356
|
reportReplayProgress: () => reportReplayProgress,
|
|
2148
|
-
serializeReplayResult: () => serializeReplayResult
|
|
2357
|
+
serializeReplayResult: () => serializeReplayResult,
|
|
2358
|
+
waitForReplayPersistence: () => waitForReplayPersistence
|
|
2149
2359
|
});
|
|
2150
2360
|
function dbBranchEnabled(dbBranch) {
|
|
2151
2361
|
return dbBranch !== void 0 && dbBranch !== false;
|
|
@@ -2433,15 +2643,29 @@ async function waitForReplayPersistence(httpClient, testRunId, replayedTraceIds)
|
|
|
2433
2643
|
REPLAY_PERSISTENCE_TIMEOUT_MS
|
|
2434
2644
|
);
|
|
2435
2645
|
if (!deferredSettled) {
|
|
2646
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2436
2647
|
throw new BitfabError(
|
|
2437
2648
|
`Replay could not settle deferred span work before the deadline, so the expected span counts are incomplete (testRunId ${testRunId}).`
|
|
2438
2649
|
);
|
|
2439
2650
|
}
|
|
2440
|
-
|
|
2441
|
-
|
|
2651
|
+
if (!httpClient.hasClosedDeliveries(replayedTraceIds)) {
|
|
2652
|
+
httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2442
2653
|
return;
|
|
2443
2654
|
}
|
|
2444
2655
|
const flushed = await flushTraces(REPLAY_PERSISTENCE_TIMEOUT_MS);
|
|
2656
|
+
const deliveries = httpClient.takeTraceDeliveries(replayedTraceIds);
|
|
2657
|
+
const expectedSpanCounts = {};
|
|
2658
|
+
let allDelivered = true;
|
|
2659
|
+
for (const [traceId, delivery] of Object.entries(deliveries)) {
|
|
2660
|
+
if (!delivery.closed) {
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
expectedSpanCounts[traceId] = delivery.spanCount;
|
|
2664
|
+
allDelivered = allDelivered && delivery.delivered;
|
|
2665
|
+
}
|
|
2666
|
+
if (allDelivered) {
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2445
2669
|
const deadline = Date.now() + REPLAY_PERSISTENCE_TIMEOUT_MS;
|
|
2446
2670
|
let missing = Object.keys(expectedSpanCounts).length;
|
|
2447
2671
|
while (true) {
|
|
@@ -2550,6 +2774,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
2550
2774
|
...registeredOverrides
|
|
2551
2775
|
];
|
|
2552
2776
|
const replayedTraceIds = serverItems.map(() => randomUuid());
|
|
2777
|
+
httpClient.trackTraceDeliveries(replayedTraceIds);
|
|
2553
2778
|
const tasks = serverItems.map(
|
|
2554
2779
|
(serverItem, index) => () => processItem(
|
|
2555
2780
|
httpClient,
|
|
@@ -2747,7 +2972,6 @@ var init_replay = __esm({
|
|
|
2747
2972
|
init_randomUuid();
|
|
2748
2973
|
init_replayContext();
|
|
2749
2974
|
init_serialize();
|
|
2750
|
-
init_transport();
|
|
2751
2975
|
init_unrefTimer();
|
|
2752
2976
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
2753
2977
|
BITFAB_PROGRESS_PREFIX = "@@bitfab:progress ";
|