bitfab 0.36.6 → 0.36.7

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/index.cjs CHANGED
@@ -51,7 +51,7 @@ var __version__;
51
51
  var init_version_generated = __esm({
52
52
  "src/version.generated.ts"() {
53
53
  "use strict";
54
- __version__ = "0.36.6";
54
+ __version__ = "0.36.7";
55
55
  }
56
56
  });
57
57
 
@@ -85,33 +85,58 @@ function toArrayBuffer(view) {
85
85
  view.byteOffset + view.byteLength
86
86
  );
87
87
  }
88
+ function compressedRequest(body, rawBytes, compressed) {
89
+ if (compressed.byteLength >= rawBytes) {
90
+ return { body, rawBytes, wireBytes: rawBytes };
91
+ }
92
+ return {
93
+ body: compressed instanceof Uint8Array ? toArrayBuffer(compressed) : compressed,
94
+ contentEncoding: "gzip",
95
+ rawBytes,
96
+ wireBytes: compressed.byteLength
97
+ };
98
+ }
88
99
  async function gzipViaStream(bytes) {
89
100
  const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
90
101
  return await new Response(stream).arrayBuffer();
91
102
  }
92
103
  function encodeRequestBody(body) {
93
104
  if (readEnv(DISABLE_COMPRESSION_ENV)) {
94
- return { body };
105
+ const rawBytes = new TextEncoder().encode(body).byteLength;
106
+ return { body, rawBytes, wireBytes: rawBytes };
95
107
  }
96
108
  const bytes = new TextEncoder().encode(body);
97
109
  if (bytes.byteLength < MIN_COMPRESSED_BYTES) {
98
- return { body };
110
+ return {
111
+ body,
112
+ rawBytes: bytes.byteLength,
113
+ wireBytes: bytes.byteLength
114
+ };
99
115
  }
100
116
  if (gzipNode) {
101
117
  return gzipNode(bytes).then(
102
- (compressed) => ({
103
- body: toArrayBuffer(compressed),
104
- contentEncoding: "gzip"
105
- }),
106
- () => ({ body })
118
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
119
+ () => ({
120
+ body,
121
+ rawBytes: bytes.byteLength,
122
+ wireBytes: bytes.byteLength
123
+ })
107
124
  );
108
125
  }
109
126
  if (typeof CompressionStream === "undefined") {
110
- return { body };
127
+ return {
128
+ body,
129
+ rawBytes: bytes.byteLength,
130
+ wireBytes: bytes.byteLength
131
+ };
111
132
  }
112
133
  return gzipViaStream(bytes).then(
113
- (compressed) => ({ body: compressed, contentEncoding: "gzip" }),
114
- () => ({ body })
134
+ (compressed) => compressedRequest(body, bytes.byteLength, compressed),
135
+ () => ({
136
+ body,
137
+ rawBytes: bytes.byteLength,
138
+ wireBytes: bytes.byteLength
139
+ })
115
140
  );
116
141
  }
117
142
  var DISABLE_COMPRESSION_ENV, MIN_COMPRESSED_BYTES, gzipNode, _nodeGzipReady;
@@ -256,15 +281,15 @@ function carrierBytesOf(encoded, body) {
256
281
  }
257
282
  return encoded.length + extra;
258
283
  }
259
- function fitsCarrierBudget(body) {
284
+ function fitsCarrierBudget(body, maxBytes = MAX_SPAN_CARRIER_BYTES) {
260
285
  const units = body.length;
261
- if (units * MAX_BYTES_PER_UNIT + 2 <= MAX_SPAN_CARRIER_BYTES) {
286
+ if (units * MAX_BYTES_PER_UNIT + 2 <= maxBytes) {
262
287
  return true;
263
288
  }
264
- if (units + 2 > MAX_SPAN_CARRIER_BYTES) {
289
+ if (units + 2 > maxBytes) {
265
290
  return false;
266
291
  }
267
- return carrierByteLength(body) <= MAX_SPAN_CARRIER_BYTES;
292
+ return carrierByteLength(body) <= maxBytes;
268
293
  }
269
294
  function asRecord(value) {
270
295
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -308,7 +333,7 @@ function collectCandidates(containers) {
308
333
  }
309
334
  return candidates.sort((a, b) => b.size - a.size);
310
335
  }
311
- function trimPayloadToBudget(payload, encode) {
336
+ function trimPayloadToBudget(payload, encode, maxBytes = MAX_SPAN_CARRIER_BYTES) {
312
337
  const { copy, containers } = cloneTrimmable(payload);
313
338
  const candidates = collectCandidates(containers);
314
339
  if (candidates.length === 0) {
@@ -324,30 +349,31 @@ function trimPayloadToBudget(payload, encode) {
324
349
  } catch {
325
350
  return void 0;
326
351
  }
327
- if (fitsCarrierBudget(body)) {
352
+ if (fitsCarrierBudget(body, maxBytes)) {
328
353
  return { value: copy, trimmed };
329
354
  }
330
355
  }
331
356
  return void 0;
332
357
  }
333
- function markPayloadTrimmed(value, trimmed) {
358
+ function markPayloadTrimmed(value, trimmed, maxBytes = MAX_SPAN_CARRIER_BYTES) {
334
359
  const existing = Array.isArray(value.errors) ? value.errors : [];
335
360
  value.errors = [
336
361
  ...existing,
337
362
  {
338
363
  source: "sdk",
339
364
  step: "payload_budget",
340
- error: `trimmed oversized field(s) to fit the ${MAX_SPAN_CARRIER_BYTES}-byte span carrier budget: ${[
365
+ error: `trimmed oversized field(s) to fit the ${maxBytes}-byte span carrier budget: ${[
341
366
  ...new Set(trimmed)
342
367
  ].join(", ")}`
343
368
  }
344
369
  ];
345
370
  }
346
- var MAX_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
371
+ var MAX_SPAN_CARRIER_BYTES, MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES, textEncoder, MAX_BYTES_PER_UNIT, STRUCTURAL_SPAN_KEYS;
347
372
  var init_payloadBudget = __esm({
348
373
  "src/payloadBudget.ts"() {
349
374
  "use strict";
350
375
  MAX_SPAN_CARRIER_BYTES = 28e5;
376
+ MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES = 78e5;
351
377
  textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
352
378
  MAX_BYTES_PER_UNIT = 3;
353
379
  STRUCTURAL_SPAN_KEYS = /* @__PURE__ */ new Set([
@@ -379,30 +405,31 @@ var init_warnOnce = __esm({
379
405
  });
380
406
 
381
407
  // src/serializePayload.ts
382
- function serializePayloadBody(payload) {
408
+ function serializePayloadBody(payload, maxCarrierBytes = MAX_SPAN_CARRIER_BYTES) {
383
409
  const encoded = encodePayloadBody(payload);
384
- if (fitsCarrierBudget(encoded.body)) {
410
+ if (fitsCarrierBudget(encoded.body, maxCarrierBytes)) {
385
411
  return { body: encoded.body, dropped: encoded.dropped };
386
412
  }
387
- return applyPayloadBudget(encoded);
413
+ return applyPayloadBudget(encoded, maxCarrierBytes);
388
414
  }
389
- function applyPayloadBudget(encoded) {
415
+ function applyPayloadBudget(encoded, maxCarrierBytes) {
390
416
  const result = encoded.value ? trimPayloadToBudget(
391
417
  encoded.value,
392
- (value) => encodePayloadBody(value).body
418
+ (value) => encodePayloadBody(value).body,
419
+ maxCarrierBytes
393
420
  ) : void 0;
394
421
  if (!result) {
395
422
  return { body: encoded.body, dropped: encoded.dropped };
396
423
  }
397
424
  warnOnce(
398
425
  "payload:over-budget",
399
- `a span payload exceeded the ${MAX_SPAN_CARRIER_BYTES}-byte carrier budget; its largest field(s) (${[
426
+ `a span payload exceeded the ${maxCarrierBytes}-byte carrier budget; its largest field(s) (${[
400
427
  ...new Set(result.trimmed)
401
428
  ].join(
402
429
  ", "
403
430
  )}) were replaced with placeholders so the span still ships. The span is incomplete and may not be replayable.`
404
431
  );
405
- markPayloadTrimmed(result.value, result.trimmed);
432
+ markPayloadTrimmed(result.value, result.trimmed, maxCarrierBytes);
406
433
  return {
407
434
  body: encodePayloadBody(result.value).body,
408
435
  dropped: encoded.dropped
@@ -659,6 +686,31 @@ function encodeSpan(span) {
659
686
  const json = JSON.stringify(spanToOtlp(span));
660
687
  return { json, size: byteLength(json) };
661
688
  }
689
+ function trimEncodedSpan(span) {
690
+ try {
691
+ const carrier = JSON.parse(span.json);
692
+ const attribute = carrier.attributes?.find(
693
+ (entry) => entry.key === PAYLOAD_ATTRIBUTE
694
+ );
695
+ const payloadBody = attribute?.value?.stringValue;
696
+ if (!attribute?.value || payloadBody === void 0) {
697
+ return void 0;
698
+ }
699
+ const payload = JSON.parse(payloadBody);
700
+ attribute.value.stringValue = serializePayloadBody(
701
+ payload,
702
+ MAX_SPAN_CARRIER_BYTES
703
+ ).body;
704
+ const json = JSON.stringify(carrier);
705
+ return { json, size: byteLength(json) };
706
+ } catch {
707
+ return void 0;
708
+ }
709
+ }
710
+ async function prepareRequest(body) {
711
+ const prepared = encodeRequestBody(body);
712
+ return prepared instanceof Promise ? await prepared : prepared;
713
+ }
662
714
  function requestEnvelope(first) {
663
715
  const scope = first.instrumentationScope;
664
716
  const resource = JSON.stringify({
@@ -795,7 +847,7 @@ function shutdownOtelTransports(timeoutMs = DEFAULT_LIFECYCLE_TIMEOUT_MS) {
795
847
  (transport, remaining) => transport.shutdown(remaining)
796
848
  );
797
849
  }
798
- var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, OTLP_TRACES_ENDPOINT, MAX_EXPORT_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_DELAY_MILLIS, MAX_SEND_ATTEMPTS, DEFAULT_LIFECYCLE_TIMEOUT_MS, RETRYABLE_STATUSES, liveTransports, traceSubmissionSpanIds, replayTraceSubmissions, submissionCounter, SPAN_SEPARATOR_BYTES, OtlpPayloadTooLargeError, OtlpPartialSuccessError, BitfabSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
850
+ var import_api, import_core, import_resources, import_sdk_trace_base, OPERATION_ATTRIBUTE, PAYLOAD_ATTRIBUTE, OTLP_TRACES_ENDPOINT, 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_DELAY_MILLIS, MAX_SEND_ATTEMPTS, DEFAULT_LIFECYCLE_TIMEOUT_MS, RETRYABLE_STATUSES, liveTransports, traceSubmissionSpanIds, replayTraceSubmissions, submissionCounter, SPAN_SEPARATOR_BYTES, OtlpPayloadTooLargeError, OtlpPartialSuccessError, BitfabSpanExporter, DeliveryTrackingExporter, OtelBatchTransport;
799
851
  var init_otel = __esm({
800
852
  "src/otel.ts"() {
801
853
  "use strict";
@@ -803,6 +855,7 @@ var init_otel = __esm({
803
855
  import_core = require("@opentelemetry/core");
804
856
  import_resources = require("@opentelemetry/resources");
805
857
  import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
858
+ init_compress();
806
859
  init_constants();
807
860
  init_errors();
808
861
  init_payloadBudget();
@@ -814,6 +867,7 @@ var init_otel = __esm({
814
867
  PAYLOAD_ATTRIBUTE = "bitfab.payload";
815
868
  OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
816
869
  MAX_EXPORT_REQUEST_BYTES = 3e6;
870
+ MAX_DECOMPRESSED_REQUEST_BYTES = 8e6;
817
871
  MAX_REQUEST_BYTES_ENV = "BITFAB_OTEL_MAX_REQUEST_BYTES";
818
872
  EXPORT_CONCURRENCY_ENV = "BITFAB_OTEL_EXPORT_CONCURRENCY";
819
873
  MAX_QUEUE_SIZE = 8192;
@@ -896,15 +950,43 @@ var init_otel = __esm({
896
950
  return batches;
897
951
  }
898
952
  async send(envelope, batch) {
899
- if (batch.size > this.maxRequestBytes) {
900
- logError(
901
- "a single OpenTelemetry span exceeded the configured request-size target and could not be exported"
902
- );
903
- return false;
904
- }
905
953
  try {
906
- await this.sendWithRetries(encodeRequest(envelope, batch.spans));
907
- return true;
954
+ let requestSpans = batch.spans;
955
+ let requestRawBytes = batch.size;
956
+ let alreadyTrimmed = false;
957
+ while (true) {
958
+ if (requestRawBytes <= MAX_DECOMPRESSED_REQUEST_BYTES) {
959
+ const prepared = await prepareRequest(
960
+ encodeRequest(envelope, requestSpans)
961
+ );
962
+ if (prepared.wireBytes <= this.maxRequestBytes) {
963
+ await this.sendWithRetries(prepared);
964
+ return true;
965
+ }
966
+ }
967
+ if (batch.spans.length !== 1) {
968
+ logError(
969
+ "an OpenTelemetry span batch exceeded the configured request-size target and could not be exported"
970
+ );
971
+ return false;
972
+ }
973
+ if (alreadyTrimmed) {
974
+ logError(
975
+ "a single OpenTelemetry span exceeded the configured request-size target after trimming"
976
+ );
977
+ return false;
978
+ }
979
+ const trimmed = trimEncodedSpan(batch.spans[0]);
980
+ if (!trimmed) {
981
+ logError(
982
+ "a single OpenTelemetry span exceeded the configured request-size target and could not be trimmed"
983
+ );
984
+ return false;
985
+ }
986
+ requestSpans = [trimmed];
987
+ requestRawBytes = envelope.size + trimmed.size;
988
+ alreadyTrimmed = true;
989
+ }
908
990
  } catch (error) {
909
991
  if (error instanceof OtlpPayloadTooLargeError) {
910
992
  logError(
@@ -932,12 +1014,12 @@ var init_otel = __esm({
932
1014
  * the server does not yet understand. The fix is a client-supplied
933
1015
  * idempotency key that ingestion dedupes on.
934
1016
  */
935
- async sendWithRetries(body) {
1017
+ async sendWithRetries(request) {
936
1018
  for (let attempt = 0; attempt < MAX_SEND_ATTEMPTS; attempt += 1) {
937
1019
  try {
938
1020
  const response = await this.directSender(
939
1021
  OTLP_TRACES_ENDPOINT,
940
- body,
1022
+ request,
941
1023
  EXPORT_TIMEOUT_MILLIS
942
1024
  );
943
1025
  const partialSuccess = asRecord2(response?.partialSuccess);
@@ -1054,7 +1136,10 @@ var init_otel = __esm({
1054
1136
  return;
1055
1137
  }
1056
1138
  try {
1057
- const { body, dropped } = serializePayloadBody(payload);
1139
+ const { body, dropped } = serializePayloadBody(
1140
+ payload,
1141
+ MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES
1142
+ );
1058
1143
  if (dropped.length > 0) {
1059
1144
  warnOnce(
1060
1145
  "otel-carrier-payload-stubbed",
@@ -1243,7 +1328,7 @@ var init_http = __esm({
1243
1328
  }
1244
1329
  if (!this.traceTransport) {
1245
1330
  this.traceTransport = createTraceTransport({
1246
- directSender: (endpoint, body, timeoutMs) => this.sendEncoded(endpoint, body, {
1331
+ directSender: (endpoint, request, timeoutMs) => this.sendPrepared(endpoint, request, {
1247
1332
  timeout: timeoutMs
1248
1333
  })
1249
1334
  });
@@ -1330,13 +1415,16 @@ var init_http = __esm({
1330
1415
  * same data twice.
1331
1416
  */
1332
1417
  async sendEncoded(endpoint, body, options) {
1418
+ const prepared = encodeRequestBody(body);
1419
+ const encoded = prepared instanceof Promise ? await prepared : prepared;
1420
+ return this.sendPrepared(endpoint, encoded, options);
1421
+ }
1422
+ async sendPrepared(endpoint, encoded, options) {
1333
1423
  const url = `${this.serviceUrl}${endpoint}`;
1334
1424
  const timeout = options?.timeout ?? this.timeout;
1335
1425
  const method = options?.method ?? "POST";
1336
1426
  const controller = new AbortController();
1337
1427
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1338
- const prepared = encodeRequestBody(body);
1339
- const encoded = prepared instanceof Promise ? await prepared : prepared;
1340
1428
  const headers = {
1341
1429
  "Content-Type": "application/json",
1342
1430
  Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
@@ -1810,8 +1898,8 @@ var init_serialize = __esm({
1810
1898
  import_superjson = __toESM(require("superjson"), 1);
1811
1899
  init_payloadBudget();
1812
1900
  init_warnOnce();
1813
- MAX_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1814
- MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_SPAN_CARRIER_BYTES;
1901
+ MAX_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1902
+ MAX_FRAMEWORK_SERIALIZED_BYTES = MAX_COMPRESSIBLE_SPAN_CARRIER_BYTES;
1815
1903
  MAX_SAFE_DEPTH = 6;
1816
1904
  }
1817
1905
  });