@hue-run/sdk 0.12.1 → 0.13.0
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/CLI.md +6 -4
- package/ENVIRONMENTS.md +18 -4
- package/README.md +29 -13
- package/dist/client.d.ts +8 -0
- package/dist/client.js +23 -2
- package/dist/environment/types.d.ts +8 -1
- package/dist/environment/world.d.ts +8 -0
- package/dist/environment/world.js +17 -0
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +1 -1
- package/dist/evals/failure.d.ts +17 -0
- package/dist/evals/failure.js +102 -0
- package/dist/evals/runner.js +21 -12
- package/dist/evals/types.d.ts +6 -0
- package/dist/privacy.d.ts +18 -0
- package/dist/privacy.js +147 -57
- package/dist/safety.d.ts +18 -0
- package/dist/safety.js +76 -23
- package/dist/transport.d.ts +5 -0
- package/dist/transport.js +277 -57
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/transport.js
CHANGED
|
@@ -9,7 +9,7 @@ import { isInsecureOrigin, MAX_BODY_BYTES, validateOptions } from "./config.js";
|
|
|
9
9
|
import { announcesLiveSpan, LIVE_SPAN_INTERVAL_MILLIS, MAX_LIVE_SPANS, pendingPlaceholder, PLACEHOLDERS_HEADER, withoutPlaceholderMarkers, } from "./live-spans.js";
|
|
10
10
|
import { estimateRecordBytes } from "./safety.js";
|
|
11
11
|
import { snapshotLog, snapshotSpan } from "./snapshot.js";
|
|
12
|
-
import { redactLog, redactSpan } from "./privacy.js";
|
|
12
|
+
import { DROPPED_RECORDS_KEY, isContentKey, redactLog, redactSpan, TRUNCATED_KEY, truncatedMarker, withTruncatedKeys, } from "./privacy.js";
|
|
13
13
|
import { sdkVersion } from "./version.js";
|
|
14
14
|
/** Per-record allowance for protobuf length prefixes that grow when records are grouped. */
|
|
15
15
|
const RECORD_FRAMING_BYTES = 64;
|
|
@@ -77,6 +77,10 @@ export class HueTransport {
|
|
|
77
77
|
logs = new Map();
|
|
78
78
|
pendingBytes = 0;
|
|
79
79
|
dropped = { traces: 0, logs: 0 };
|
|
80
|
+
/** Records of each trace the SDK never sent (dropped from the queue, refused as invalid or
|
|
81
|
+
* over the request limit), reported on the trace's root span as `hue.sdk.dropped_records` when
|
|
82
|
+
* the root is exported, so Hue reads the trace as incomplete by that many records. */
|
|
83
|
+
droppedByTrace = new Map();
|
|
80
84
|
instrumentationFailures = 0;
|
|
81
85
|
diagnosticPending = false;
|
|
82
86
|
lastDiagnosticAt = -Infinity;
|
|
@@ -89,6 +93,8 @@ export class HueTransport {
|
|
|
89
93
|
live = new Set();
|
|
90
94
|
liveTimer;
|
|
91
95
|
liveSpans;
|
|
96
|
+
// Whether this client announces live spans at all; `liveSpans` also goes off at shutdown.
|
|
97
|
+
announcesLiveSpans;
|
|
92
98
|
placeholdersRejected = false;
|
|
93
99
|
// Admitted placeholder snapshots and the marker attributes added after redaction.
|
|
94
100
|
placeholders = new WeakMap();
|
|
@@ -99,6 +105,7 @@ export class HueTransport {
|
|
|
99
105
|
this.options = validateOptions(options);
|
|
100
106
|
Object.defineProperty(this, "options", { enumerable: false });
|
|
101
107
|
this.liveSpans = this.options.enabled !== false && this.options.liveSpans;
|
|
108
|
+
this.announcesLiveSpans = this.liveSpans;
|
|
102
109
|
this.traceExporter = new ReportingExporter(this, "traces", ProtobufTraceSerializer, TraceExporterMetricsHelper, (span, cache) => redactSpan(span, this.options, cache));
|
|
103
110
|
this.logExporter = new ReportingExporter(this, "logs", ProtobufLogsSerializer, LogsExporterMetricsHelper, (log, cache) => redactLog(log, this.options, cache));
|
|
104
111
|
if (this.options.enabled === false) {
|
|
@@ -280,13 +287,18 @@ export class HueTransport {
|
|
|
280
287
|
return !this.placeholdersRejected;
|
|
281
288
|
}
|
|
282
289
|
/**
|
|
283
|
-
* @internal Exporter callback: a
|
|
284
|
-
*
|
|
290
|
+
* @internal Exporter callback: a trace acknowledgement lacked the placeholder-support header,
|
|
291
|
+
* so the receiver predates placeholders and rejects each by its zero end time; `count` is how
|
|
292
|
+
* many it rejected in that request, zero when the request carried none. Stops announcing for
|
|
293
|
+
* this transport and records one warning.
|
|
285
294
|
*/
|
|
286
295
|
rejectPlaceholders(count) {
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
// A client that never announces live spans (by option or setup credential) records no
|
|
297
|
+
// warning about placeholders; one that does records it once, even when the acknowledgement
|
|
298
|
+
// arrives during its final flush, after shutdown has already stopped announcing.
|
|
299
|
+
if (!this.announcesLiveSpans || this.placeholdersRejected)
|
|
289
300
|
return;
|
|
301
|
+
this.stopLiveSpans();
|
|
290
302
|
this.placeholdersRejected = true;
|
|
291
303
|
this.issue("traces", "warning", count, "This Hue server does not accept in-progress span placeholders; live spans are disabled");
|
|
292
304
|
}
|
|
@@ -302,6 +314,25 @@ export class HueTransport {
|
|
|
302
314
|
acceptedRecords(signal, count) {
|
|
303
315
|
this.accepted[signal] += count;
|
|
304
316
|
}
|
|
317
|
+
/** @internal Exporter callback: a trace's root span carries the count of its records the SDK
|
|
318
|
+
* never sent, read when the root is exported (a record lost after that is not counted: the
|
|
319
|
+
* root has left). The count stays until the request carrying the root is acknowledged
|
|
320
|
+
* (`consumeDroppedRecords`): a root written again under retry says the same, and a root whose
|
|
321
|
+
* request failed has not told Hue, so its count is not forgotten with it. */
|
|
322
|
+
withDroppedRecords(span) {
|
|
323
|
+
if (span.parentSpanContext)
|
|
324
|
+
return span;
|
|
325
|
+
const count = this.droppedByTrace.get(span.spanContext().traceId);
|
|
326
|
+
if (!count)
|
|
327
|
+
return span;
|
|
328
|
+
return { ...span, attributes: { ...span.attributes, [DROPPED_RECORDS_KEY]: count } };
|
|
329
|
+
}
|
|
330
|
+
/** @internal Exporter callback: these traces' counts reached Hue on their roots, so the map
|
|
331
|
+
* holds nothing for a trace that has ended and told its losses. */
|
|
332
|
+
consumeDroppedRecords(traceIds) {
|
|
333
|
+
for (const traceId of traceIds)
|
|
334
|
+
this.droppedByTrace.delete(traceId);
|
|
335
|
+
}
|
|
305
336
|
/** @internal Records a sanitized issue, updates counters and rate-limits the diagnostic callback. */
|
|
306
337
|
issue(signal, kind, count, message, status, traceIds) {
|
|
307
338
|
if (kind === "dropped")
|
|
@@ -310,6 +341,15 @@ export class HueTransport {
|
|
|
310
341
|
this.rejected[signal] += count;
|
|
311
342
|
else if (kind !== "warning")
|
|
312
343
|
this.failed[signal] += count;
|
|
344
|
+
// A record lost before any request names its one trace; a failed batch is retried or
|
|
345
|
+
// reported as delivery, not counted here.
|
|
346
|
+
if ((kind === "dropped" || kind === "invalid") && count > 0 && traceIds?.length === 1) {
|
|
347
|
+
const [traceId] = traceIds;
|
|
348
|
+
// Bounded: a trace whose root never arrives (an abandoned run) gives way to newer ones.
|
|
349
|
+
if (this.droppedByTrace.size >= 1024 && !this.droppedByTrace.has(traceId))
|
|
350
|
+
this.droppedByTrace.delete(this.droppedByTrace.keys().next().value);
|
|
351
|
+
this.droppedByTrace.set(traceId, (this.droppedByTrace.get(traceId) ?? 0) + count);
|
|
352
|
+
}
|
|
313
353
|
const issue = {
|
|
314
354
|
sequence: ++this.sequence,
|
|
315
355
|
signal,
|
|
@@ -480,6 +520,101 @@ function traceIdsOf(records) {
|
|
|
480
520
|
// concern any trace in flight.
|
|
481
521
|
return ids.size ? [...ids] : undefined;
|
|
482
522
|
}
|
|
523
|
+
/** The serialized size of one attribute value, for choosing what to shed first. */
|
|
524
|
+
function valueBytes(value) {
|
|
525
|
+
if (typeof value === "string")
|
|
526
|
+
return Buffer.byteLength(value);
|
|
527
|
+
if (value instanceof Uint8Array)
|
|
528
|
+
return value.byteLength;
|
|
529
|
+
if (value !== null && typeof value === "object") {
|
|
530
|
+
try {
|
|
531
|
+
return Buffer.byteLength(JSON.stringify(value));
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
return 0;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return 8;
|
|
538
|
+
}
|
|
539
|
+
/** Whether a value is already the receiver's marker for a value that was shed. */
|
|
540
|
+
function isTruncatedMarker(value) {
|
|
541
|
+
return (value !== null &&
|
|
542
|
+
typeof value === "object" &&
|
|
543
|
+
!Array.isArray(value) &&
|
|
544
|
+
value[TRUNCATED_KEY] === true);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* The record with its largest content value (a log's body before its attributes) replaced by
|
|
548
|
+
* the receiver's marker and listed under `hue.truncated`; undefined when no content value is
|
|
549
|
+
* left to shed. Metadata is never shed: a record too large without its content is lost whole.
|
|
550
|
+
*/
|
|
551
|
+
function shedLargestContent(record, signal) {
|
|
552
|
+
if (signal === "logs") {
|
|
553
|
+
const log = record;
|
|
554
|
+
if (log.body !== undefined && !isTruncatedMarker(log.body)) {
|
|
555
|
+
const bytes = valueBytes(log.body);
|
|
556
|
+
const attributes = {
|
|
557
|
+
...log.attributes,
|
|
558
|
+
[TRUNCATED_KEY]: withTruncatedKeys(log.attributes[TRUNCATED_KEY], ["body"]),
|
|
559
|
+
};
|
|
560
|
+
return { ...log, body: truncatedMarker(bytes), attributes };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const { attributes } = record;
|
|
564
|
+
// The record's own content values, and a span's events' (listed as the receiver lists an
|
|
565
|
+
// event's cut: `event:<name>:<key>`).
|
|
566
|
+
const events = signal === "traces" ? record.events : [];
|
|
567
|
+
const links = signal === "traces" ? record.links : [];
|
|
568
|
+
let largest;
|
|
569
|
+
const consider = (where, index, key, value) => {
|
|
570
|
+
if (!isContentKey(key) || isTruncatedMarker(value))
|
|
571
|
+
return;
|
|
572
|
+
const bytes = valueBytes(value);
|
|
573
|
+
if (!largest || bytes > largest.bytes)
|
|
574
|
+
largest = { where, index, key, bytes };
|
|
575
|
+
};
|
|
576
|
+
for (const [key, value] of Object.entries(attributes))
|
|
577
|
+
consider("own", 0, key, value);
|
|
578
|
+
events.forEach((event, index) => {
|
|
579
|
+
for (const [key, value] of Object.entries(event.attributes ?? {}))
|
|
580
|
+
consider("event", index, key, value);
|
|
581
|
+
});
|
|
582
|
+
links.forEach((link, index) => {
|
|
583
|
+
for (const [key, value] of Object.entries(link.attributes ?? {}))
|
|
584
|
+
consider("link", index, key, value);
|
|
585
|
+
});
|
|
586
|
+
if (!largest)
|
|
587
|
+
return undefined;
|
|
588
|
+
const found = largest;
|
|
589
|
+
// Listed as the receiver lists a cut of the same place: the key alone, `event:<name>:<key>`,
|
|
590
|
+
// or `link:<key>`.
|
|
591
|
+
const listed = found.where === "own"
|
|
592
|
+
? found.key
|
|
593
|
+
: found.where === "event"
|
|
594
|
+
? `event:${events[found.index].name}:${found.key}`
|
|
595
|
+
: `link:${found.key}`;
|
|
596
|
+
const shed = {
|
|
597
|
+
...attributes,
|
|
598
|
+
...(found.where === "own" ? { [found.key]: truncatedMarker(found.bytes) } : {}),
|
|
599
|
+
[TRUNCATED_KEY]: withTruncatedKeys(attributes[TRUNCATED_KEY], [listed]),
|
|
600
|
+
};
|
|
601
|
+
const marker = { [found.key]: truncatedMarker(found.bytes) };
|
|
602
|
+
if (found.where === "own")
|
|
603
|
+
return { ...record, attributes: shed };
|
|
604
|
+
if (found.where === "event")
|
|
605
|
+
return {
|
|
606
|
+
...record,
|
|
607
|
+
attributes: shed,
|
|
608
|
+
events: events.map((event, index) => index === found.index
|
|
609
|
+
? { ...event, attributes: { ...event.attributes, ...marker } }
|
|
610
|
+
: event),
|
|
611
|
+
};
|
|
612
|
+
return {
|
|
613
|
+
...record,
|
|
614
|
+
attributes: shed,
|
|
615
|
+
links: links.map((link, index) => index === found.index ? { ...link, attributes: { ...link.attributes, ...marker } } : link),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
483
618
|
class ReportingExporter {
|
|
484
619
|
transport;
|
|
485
620
|
signal;
|
|
@@ -532,11 +667,17 @@ class ReportingExporter {
|
|
|
532
667
|
return real;
|
|
533
668
|
}
|
|
534
669
|
async exportRecords(records) {
|
|
535
|
-
|
|
536
|
-
//
|
|
537
|
-
|
|
670
|
+
// Completed records and placeholders travel in separate requests, completed records first,
|
|
671
|
+
// so a rejection count is always one kind of record's. Placeholders are advisory: losing one
|
|
672
|
+
// is a warning, never an export failure.
|
|
673
|
+
const real = [];
|
|
674
|
+
const pending = [];
|
|
538
675
|
const cache = new WeakMap();
|
|
539
676
|
let failed = false;
|
|
677
|
+
// A request of completed records the receiver refused or did not answer, as opposed to a
|
|
678
|
+
// record this side could not redact or encode: placeholders are advisory, so such a receiver
|
|
679
|
+
// gets no second request.
|
|
680
|
+
let refused = false;
|
|
540
681
|
let redactedBytes = 0;
|
|
541
682
|
const invalid = (placeholder, message, record) => {
|
|
542
683
|
if (placeholder)
|
|
@@ -546,11 +687,6 @@ class ReportingExporter {
|
|
|
546
687
|
this.transport.issue(this.signal, "invalid", 1, message, undefined, record ? traceIdsOf([record]) : undefined);
|
|
547
688
|
}
|
|
548
689
|
};
|
|
549
|
-
const send = async (batch) => {
|
|
550
|
-
const count = batch.filter((record) => placeholders.has(record)).length;
|
|
551
|
-
if (!(await this.send(batch, count)))
|
|
552
|
-
failed = true;
|
|
553
|
-
};
|
|
554
690
|
const resourceDeadline = Date.now() + this.transport.options.timeoutMillis;
|
|
555
691
|
for (const { record, markers } of this.ordered(records)) {
|
|
556
692
|
try {
|
|
@@ -587,9 +723,7 @@ class ReportingExporter {
|
|
|
587
723
|
if (redactedBytes + bytes > this.transport.options.maxQueueBytes)
|
|
588
724
|
throw new RangeError("Redacted batch exceeds byte budget");
|
|
589
725
|
redactedBytes += bytes;
|
|
590
|
-
|
|
591
|
-
if (markers)
|
|
592
|
-
placeholders.add(redacted);
|
|
726
|
+
(markers ? pending : real).push(redacted);
|
|
593
727
|
}
|
|
594
728
|
catch {
|
|
595
729
|
invalid(markers !== undefined, "Telemetry record could not be redacted or exceeds supported content limits", record);
|
|
@@ -600,43 +734,123 @@ class ReportingExporter {
|
|
|
600
734
|
// encodings plus a fixed framing margin bounds the request size. Room is left for gzip
|
|
601
735
|
// headers/blocks when otherwise incompressible data is near the wire cap.
|
|
602
736
|
const limit = MAX_BODY_BYTES - 1024;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
737
|
+
const measure = (record) => this.serializer.serializeRequest([record])?.byteLength ?? Infinity;
|
|
738
|
+
/** The record, shed until it fits the request limit, and its size; undefined when no content
|
|
739
|
+
* is left to shed. */
|
|
740
|
+
const fit = (record) => {
|
|
741
|
+
let bytes = measure(record);
|
|
742
|
+
// A record over the request limit sheds its content values, largest first, each
|
|
743
|
+
// replaced by the receiver's marker and listed under `hue.truncated`, until it fits:
|
|
744
|
+
// the span and what it still holds reach Hue, and a reader sees what was shed. Only a
|
|
745
|
+
// record too large without any content value is lost, and counted on its root.
|
|
746
|
+
while (bytes > limit) {
|
|
747
|
+
const shed = shedLargestContent(record, this.signal);
|
|
748
|
+
if (!shed)
|
|
749
|
+
return undefined;
|
|
750
|
+
record = shed;
|
|
751
|
+
bytes = measure(record);
|
|
609
752
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
753
|
+
return { record, bytes };
|
|
754
|
+
};
|
|
755
|
+
/** Sends one kind of record in requests up to the limit: completed records, whose roots carry
|
|
756
|
+
* their traces' dropped-record counts, or placeholders alone (`advisory`). */
|
|
757
|
+
const pack = async (accepted, advisory) => {
|
|
758
|
+
let batch = [];
|
|
759
|
+
let batchBytes = 0;
|
|
760
|
+
// The traces whose root in the batch carries its dropped-record count, consumed once the
|
|
761
|
+
// request that carries it is acknowledged; a failed request keeps the count.
|
|
762
|
+
let counted = [];
|
|
763
|
+
const send = async () => {
|
|
764
|
+
const records = batch;
|
|
765
|
+
const traces = counted;
|
|
617
766
|
batch = [];
|
|
618
767
|
batchBytes = 0;
|
|
768
|
+
counted = [];
|
|
769
|
+
// A request of placeholders never fails the export, and none follows an acknowledgement
|
|
770
|
+
// that turned live spans off.
|
|
771
|
+
if (advisory && !this.transport.sendsPlaceholders())
|
|
772
|
+
return;
|
|
773
|
+
if (await this.send(records, advisory ? records.length : 0)) {
|
|
774
|
+
this.transport.consumeDroppedRecords(traces);
|
|
775
|
+
}
|
|
776
|
+
else if (!advisory) {
|
|
777
|
+
failed = true;
|
|
778
|
+
refused = true;
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
// Every record is measured, shed and, where it cannot be sent, counted lost before any root
|
|
782
|
+
// is written, so a root carries the losses of children that ended after it.
|
|
783
|
+
const prepared = [];
|
|
784
|
+
for (const record of accepted) {
|
|
785
|
+
let fitted;
|
|
786
|
+
try {
|
|
787
|
+
fitted = fit(record);
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
invalid(advisory, "Telemetry record could not be serialized", record);
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (!fitted) {
|
|
794
|
+
invalid(advisory, "Telemetry record exceeds the 1 MiB request limit", record);
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
prepared.push(fitted);
|
|
619
798
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
799
|
+
for (const entry of prepared) {
|
|
800
|
+
let { record, bytes: recordBytes } = entry;
|
|
801
|
+
let carriesCount = false;
|
|
802
|
+
if (this.signal === "traces" && !advisory) {
|
|
803
|
+
const withCount = this.transport.withDroppedRecords(record);
|
|
804
|
+
if (withCount !== record) {
|
|
805
|
+
carriesCount = true;
|
|
806
|
+
// The count may put a root shed to just under the limit over it again: shed on.
|
|
807
|
+
let fitted;
|
|
808
|
+
try {
|
|
809
|
+
fitted = fit(withCount);
|
|
810
|
+
}
|
|
811
|
+
catch {
|
|
812
|
+
invalid(false, "Telemetry record could not be serialized", withCount);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (!fitted) {
|
|
816
|
+
invalid(false, "Telemetry record exceeds the 1 MiB request limit", withCount);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
({ record, bytes: recordBytes } = fitted);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const framedBytes = recordBytes + RECORD_FRAMING_BYTES;
|
|
823
|
+
if (batch.length && batchBytes + framedBytes > limit)
|
|
824
|
+
await send();
|
|
825
|
+
batch.push(record);
|
|
826
|
+
batchBytes += framedBytes;
|
|
827
|
+
if (carriesCount)
|
|
828
|
+
counted.push(record.spanContext().traceId);
|
|
623
829
|
}
|
|
624
|
-
batch.
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
830
|
+
if (batch.length)
|
|
831
|
+
await send();
|
|
832
|
+
};
|
|
833
|
+
await pack(real, false);
|
|
834
|
+
// None is sent once a receiver answered without placeholder support, which the completed
|
|
835
|
+
// records' acknowledgements may have just shown, nor after this export's completed records were
|
|
836
|
+
// refused or unanswered: a receiver that just refused or timed out gets no second request with
|
|
837
|
+
// its own retries. A record this side could not send keeps the placeholders going out.
|
|
838
|
+
if (pending.length && !refused && this.transport.sendsPlaceholders())
|
|
839
|
+
await pack(pending, true);
|
|
629
840
|
if (failed)
|
|
630
841
|
throw new Error("Hue telemetry export failed");
|
|
631
842
|
}
|
|
632
|
-
/**
|
|
843
|
+
/**
|
|
844
|
+
* Sends one request: of completed records, or of placeholders alone when `placeholders` is
|
|
845
|
+
* `records.length`. Placeholders are advisory and never count as lost.
|
|
846
|
+
*/
|
|
633
847
|
async send(records, placeholders = 0) {
|
|
634
848
|
const options = this.transport.options;
|
|
635
849
|
const real = records.length - placeholders;
|
|
636
850
|
// The traces this batch carries, spans or the log records emitted in their spans, so a loss
|
|
637
851
|
// or rejection names whose telemetry it was.
|
|
638
852
|
const traceIds = traceIdsOf(records);
|
|
639
|
-
//
|
|
853
|
+
// Losing a request of placeholders is a warning; losing one of completed records counts them.
|
|
640
854
|
const lose = (message, status) => {
|
|
641
855
|
this.transport.issue(this.signal, real ? "failed" : "warning", real || placeholders, real ? message : `${message} (in-progress span placeholders only)`, status, traceIds);
|
|
642
856
|
return !real;
|
|
@@ -660,26 +874,32 @@ class ReportingExporter {
|
|
|
660
874
|
const count = Number(partial?.[this.signal === "traces" ? "rejectedSpans" : "rejectedLogRecords"] ?? 0);
|
|
661
875
|
if (!Number.isSafeInteger(count) || count < 0 || count > records.length)
|
|
662
876
|
throw new Error("Invalid rejection count");
|
|
663
|
-
// A
|
|
664
|
-
//
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
877
|
+
// A trace acknowledgement without the header comes from a receiver that predates
|
|
878
|
+
// placeholders, whatever the request carried: live spans stop, before any placeholder
|
|
879
|
+
// reaches a receiver that acknowledged completed spans first.
|
|
880
|
+
if (this.signal === "traces" && !acceptsPlaceholders)
|
|
881
|
+
this.transport.rejectPlaceholders(placeholders ? count : 0);
|
|
882
|
+
if (placeholders) {
|
|
883
|
+
// Placeholders travel alone, so the count is theirs: a warning, never a loss. A
|
|
884
|
+
// receiver with the header never rejects a placeholder for being one.
|
|
885
|
+
if (acceptsPlaceholders && (count || partial?.errorMessage))
|
|
886
|
+
this.transport.issue(this.signal, "warning", count, count
|
|
887
|
+
? "Hue rejected in-progress span placeholders"
|
|
888
|
+
: "Hue returned an ingestion warning");
|
|
889
|
+
return {};
|
|
890
|
+
}
|
|
891
|
+
// Completed records travel alone too, so the count is theirs in full. Rejections are
|
|
892
|
+
// not matched to records: a rejection of spans in a batch of one trace is that trace's;
|
|
893
|
+
// in a batch of several it names none, since the innocent traces' spans may have been
|
|
894
|
+
// accepted, and each case's receipt decides. A rejection of log records names every
|
|
895
|
+
// trace in the batch: the receipt counts spans, not logs, so it could not tell whose
|
|
896
|
+
// logs were lost.
|
|
897
|
+
rejected = count;
|
|
676
898
|
const rejectedTraceIds = this.signal === "logs" || (traceIds && traceIds.length === 1) ? traceIds : undefined;
|
|
677
|
-
if (
|
|
678
|
-
this.transport.issue(this.signal,
|
|
899
|
+
if (count || partial?.errorMessage)
|
|
900
|
+
this.transport.issue(this.signal, count ? "rejected" : "warning", count, count
|
|
679
901
|
? "Hue rejected telemetry records; inspect the project ingestion settings and supported limits"
|
|
680
|
-
:
|
|
681
|
-
? "Hue rejected in-progress span placeholders"
|
|
682
|
-
: "Hue returned an ingestion warning", undefined, rejectedTraceIds);
|
|
902
|
+
: "Hue returned an ingestion warning", undefined, rejectedTraceIds);
|
|
683
903
|
// Do not pass backend error text or raw response bytes into the global OTel diagnostic logger.
|
|
684
904
|
return {};
|
|
685
905
|
}
|
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.
|
|
2
|
+
export declare const sdkVersion = "0.13.0";
|
package/dist/version.js
CHANGED