@hue-run/sdk 0.18.0 → 0.18.2

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/transport.js CHANGED
@@ -6,7 +6,8 @@ import { LogsExporterMetricsHelper, ProtobufLogsSerializer, ProtobufTraceSeriali
6
6
  import { BatchSpanProcessor, } from "@opentelemetry/sdk-trace";
7
7
  import { BatchLogRecordProcessor, } from "@opentelemetry/sdk-logs";
8
8
  import { isInsecureOrigin, REDACTION_CONTEXT_UNITS, validateOptions } from "./config.js";
9
- import { BLOBS_KEY, MAX_BLOB_ENTRIES, blobEntry, countFallback, BlobUploader, FALLBACK_MESSAGES, fallbackAttributes, fallbackRecord, MAX_HELD_BLOB_BYTES, newTally, mediaKey, offloadAttributes, offloadRecord, uploadBudgetMillis, } from "./blobs.js";
9
+ import { BLOBS_KEY, MAX_BLOB_ENTRIES, blobEntry, countFallback, BlobUploader, FALLBACK_MESSAGES, fallbackAttributes, fallbackRecord, heldBytesOf, MAX_HELD_BLOB_BYTES, newTally, mediaKey, offloadAttributes, offloadRecord, sizeOf, uploadBudgetMillis, } from "./blobs.js";
10
+ import { isByteSource } from "./large-media.js";
10
11
  import { advertisedLimits, BATCH_TARGET_BYTES, DEFAULT_RECEIVER_LIMITS, fitsWithoutCompression, } from "./limits.js";
11
12
  import { announcesLiveSpan, LIVE_SPAN_INTERVAL_MILLIS, MAX_LIVE_SPANS, pendingPlaceholder, PLACEHOLDERS_HEADER, withoutPlaceholderMarkers, } from "./live-spans.js";
12
13
  import { estimateRecordBytes, spanSource } from "./safety.js";
@@ -184,6 +185,9 @@ export class HueTransport {
184
185
  * over the request limit), reported on the trace's root span as `hue.sdk.dropped_records` when
185
186
  * the root is exported, so Hue reads the trace as incomplete by that many records. */
186
187
  droppedByTrace = new Map();
188
+ /** Whether a trace's root carries `hue.sdk.dropped_records`; off for a replay of recorded
189
+ * spans, which must send the same root on every run. */
190
+ marksDroppedRecords = true;
187
191
  instrumentationFailures = 0;
188
192
  diagnosticPending = false;
189
193
  lastDiagnosticAt = -Infinity;
@@ -215,6 +219,8 @@ export class HueTransport {
215
219
  admissionDigests = new WeakMap();
216
220
  // The span's own messages each admitted span inspected, or left to export to inspect.
217
221
  admissionInspected = new WeakMap();
222
+ // The span's own values each admitted span held whole past the inspection bounds.
223
+ admissionLarge = new WeakMap();
218
224
  /** Uploads values over the inline limit apart from their spans; absent when disabled. */
219
225
  uploader;
220
226
  /** Bytes of values over the inline limit queued spans hold whole for upload, and each span's. */
@@ -242,9 +248,12 @@ export class HueTransport {
242
248
  this.liveSpans = this.options.enabled !== false && this.options.liveSpans;
243
249
  this.announcesLiveSpans = this.liveSpans;
244
250
  this.traceExporter = new ReportingExporter(this, "traces", ProtobufTraceSerializer, TraceExporterMetricsHelper, (span, cache, offload) => {
245
- // Export inspects only the messages admission inspected within the record's allowance.
246
- if (offload)
251
+ // Export inspects only the messages admission inspected within the record's allowance,
252
+ // and reads in place the values it held whole past the inspection bounds.
253
+ if (offload) {
247
254
  offload.inspected = this.admissionInspected.get(span);
255
+ offload.large = this.admissionLarge.get(span);
256
+ }
248
257
  return redactSpan(span, this.options, cache, {
249
258
  valueBytes: this.limits.valueBytes,
250
259
  cut: this.admissionCuts.get(span),
@@ -305,7 +314,7 @@ export class HueTransport {
305
314
  if (!queued) {
306
315
  const tally = newTally();
307
316
  for (const file of this.mediaAttachments(span))
308
- if (file.bytes.byteLength)
317
+ if (sizeOf(file.bytes))
309
318
  countFallback(tally, "budget", span.spanContext().traceId);
310
319
  this.reportOffload(tally);
311
320
  this.releaseMedia(span);
@@ -404,6 +413,8 @@ export class HueTransport {
404
413
  this.admissionDigests.set(snapshot.record, snapshot.digested);
405
414
  if (signal === "traces")
406
415
  this.admissionInspected.set(snapshot.record, snapshot.inspected);
416
+ if (snapshot.large.size)
417
+ this.admissionLarge.set(snapshot.record, snapshot.large);
407
418
  if (snapshot.held) {
408
419
  this.heldBytes += snapshot.held;
409
420
  this.held.set(snapshot.record, snapshot.held);
@@ -531,7 +542,7 @@ export class HueTransport {
531
542
  }
532
543
  /** @internal Retain a defensive copy within the existing held-value budget. */
533
544
  stageMedia(span, file) {
534
- if (this.closed || !this.offloadsValues() || file?.bytes.byteLength === 0)
545
+ if (this.closed || !this.offloadsValues() || (file && sizeOf(file.bytes) === 0))
535
546
  return true;
536
547
  if (!file) {
537
548
  const tally = newTally();
@@ -549,16 +560,18 @@ export class HueTransport {
549
560
  const files = this.media.get(identity) ?? [];
550
561
  if (files.length >= 64 ||
551
562
  (!this.media.has(identity) && this.media.size >= 2048) ||
552
- this.heldBytes + file.bytes.byteLength > MAX_HELD_BLOB_BYTES) {
563
+ this.heldBytes + heldBytesOf(file.bytes) > MAX_HELD_BLOB_BYTES) {
553
564
  this.uploadFallbacks++;
554
565
  this.issue("traces", "warning", 1, "File bytes did not fit the SDK's upload budget; their hue.file metadata was recorded", undefined, [span.spanContext().traceId]);
555
566
  return false;
556
567
  }
557
- const bytes = Uint8Array.from(file.bytes);
568
+ // A file read from disk as it is uploaded holds no bytes; bytes are copied, so a later change
569
+ // to the caller's buffer cannot alter what was recorded.
570
+ const bytes = isByteSource(file.bytes) ? file.bytes : Uint8Array.from(file.bytes);
558
571
  files.push({ ...file, bytes });
559
572
  this.media.set(identity, files);
560
573
  this.ownMedia(span, identity);
561
- this.heldBytes += bytes.byteLength;
574
+ this.heldBytes += heldBytesOf(bytes);
562
575
  return true;
563
576
  }
564
577
  /**
@@ -576,7 +589,7 @@ export class HueTransport {
576
589
  const kept = previous.filter((file) => {
577
590
  if (file.attribute !== attribute)
578
591
  return true;
579
- this.heldBytes -= file.bytes.byteLength;
592
+ this.heldBytes -= heldBytesOf(file.bytes);
580
593
  return false;
581
594
  });
582
595
  if (kept.length)
@@ -586,7 +599,7 @@ export class HueTransport {
586
599
  }
587
600
  const tally = newTally();
588
601
  for (const file of files) {
589
- if (!file.bytes.byteLength)
602
+ if (!sizeOf(file.bytes))
590
603
  continue;
591
604
  const refused = this.uploader?.refusal();
592
605
  if (!this.holdsValues() || refused) {
@@ -597,14 +610,14 @@ export class HueTransport {
597
610
  const staged = this.media.get(identity) ?? [];
598
611
  if (staged.length >= MAX_BLOB_ENTRIES ||
599
612
  (!this.media.has(identity) && this.media.size >= 2048) ||
600
- this.heldBytes + file.bytes.byteLength > MAX_HELD_BLOB_BYTES) {
613
+ this.heldBytes + heldBytesOf(file.bytes) > MAX_HELD_BLOB_BYTES) {
601
614
  countFallback(tally, "budget", traceId);
602
615
  continue;
603
616
  }
604
617
  // Attachment bytes are the attachment's own copy: held as they are.
605
618
  staged.push({ ...file, attribute });
606
619
  this.media.set(identity, staged);
607
- this.heldBytes += file.bytes.byteLength;
620
+ this.heldBytes += heldBytesOf(file.bytes);
608
621
  this.ownMedia(span, identity);
609
622
  }
610
623
  if (tally.fallbacks.size)
@@ -621,7 +634,7 @@ export class HueTransport {
621
634
  const tally = newTally();
622
635
  const staged = [];
623
636
  for (const file of files) {
624
- if (!file.bytes.byteLength)
637
+ if (!sizeOf(file.bytes))
625
638
  continue;
626
639
  const refused = this.uploader?.refusal();
627
640
  if (!this.holdsValues() || refused)
@@ -648,7 +661,7 @@ export class HueTransport {
648
661
  if (!pending)
649
662
  return;
650
663
  this.logParts.delete(body);
651
- const size = pending.files.reduce((total, file) => total + file.bytes.byteLength, 0);
664
+ const size = pending.files.reduce((total, file) => total + heldBytesOf(file.bytes), 0);
652
665
  if (this.heldBytes + size > MAX_HELD_BLOB_BYTES) {
653
666
  const tally = newTally();
654
667
  for (const _file of pending.files)
@@ -711,7 +724,7 @@ export class HueTransport {
711
724
  if (!files)
712
725
  return;
713
726
  for (const file of files)
714
- this.heldBytes -= file.bytes.byteLength;
727
+ this.heldBytes -= heldBytesOf(file.bytes);
715
728
  this.media.delete(identity);
716
729
  }
717
730
  sweepMedia() {
@@ -822,13 +835,20 @@ export class HueTransport {
822
835
  * (`consumeDroppedRecords`): a root written again under retry says the same, and a root whose
823
836
  * request failed has not told Hue, so its count is not forgotten with it. */
824
837
  withDroppedRecords(span) {
825
- if (span.parentSpanContext)
838
+ if (span.parentSpanContext || !this.marksDroppedRecords)
826
839
  return span;
827
840
  const count = this.droppedByTrace.get(span.spanContext().traceId);
828
841
  if (!count)
829
842
  return span;
830
843
  return { ...span, attributes: { ...span.attributes, [DROPPED_RECORDS_KEY]: count } };
831
844
  }
845
+ /** @internal For an import that replays recorded spans: roots never carry the count of
846
+ * records this run did not deliver, so a later run that delivers them sends each root as it
847
+ * was first sent, which Hue keeps as an exact replay. The losses are still counted and
848
+ * reported. */
849
+ omitDroppedRecords() {
850
+ this.marksDroppedRecords = false;
851
+ }
832
852
  /** @internal Exporter callback: these counts reached Hue on their traces' roots. Only what a
833
853
  * root carried is forgotten: a record lost while its request was in flight is still counted. */
834
854
  consumeDroppedRecords(counts) {
package/dist/types.d.ts CHANGED
@@ -256,11 +256,19 @@ export interface FileRecord {
256
256
  mediaType: string;
257
257
  /** Hex SHA-256 of the file's bytes; computed from `data` when omitted. */
258
258
  sha256?: string;
259
- /** Upload supplied bytes on this span when content capture is enabled. Defaults to false. */
259
+ /** Upload the file's bytes (`data`, or the file at `path`) on this span when content capture is
260
+ * enabled. Defaults to false. */
260
261
  upload?: boolean;
261
262
  /** The file's bytes, hashed and measured locally by default; a string is hashed as UTF-8.
262
- * Data larger than 25 MiB is omitted and counted as an instrumentation failure. */
263
+ * Data larger than 25 MiB, which would be held in memory until it is uploaded, is omitted and
264
+ * counted as an instrumentation failure. Give `data` or `path`, not both. */
263
265
  data?: Uint8Array | string;
266
+ /** A file to hash and measure locally (Node.js file system) instead of `data`. Up to 25 MiB it is
267
+ * read now, as `data` is. A larger regular file, up to Hue's 1 GB upload limit, is hashed now a
268
+ * slice at a time and, with `upload: true`, read again from disk when it is uploaded: it must
269
+ * stay unchanged until the client flushes, and is uploaded only if it still has the size and
270
+ * SHA-256 it was recorded with (otherwise it counts as an upload fallback). */
271
+ path?: string;
264
272
  /** Size in bytes; computed from `data` when omitted. */
265
273
  byteSize?: number;
266
274
  /** File name, recorded as `hue.file.name` only when `captureContent` is true. */
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Package version shared by the instrumentation scope and the export User-Agent. */
2
- export declare const sdkVersion = "0.18.0";
2
+ export declare const sdkVersion = "0.18.2";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/write-version.mjs from package.json; do not edit by hand.
2
2
  /** Package version shared by the instrumentation scope and the export User-Agent. */
3
- export const sdkVersion = "0.18.0";
3
+ export const sdkVersion = "0.18.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {