@hue-run/sdk 0.1.4 → 0.2.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/ENVIRONMENTS.md +182 -0
- package/EVALUATIONS.md +12 -0
- package/README.md +204 -21
- package/dist/ai-sdk.d.ts +9 -1
- package/dist/ai-sdk.js +37 -2
- package/dist/client.d.ts +130 -5
- package/dist/client.js +518 -110
- package/dist/config.d.ts +11 -2
- package/dist/config.js +50 -4
- package/dist/environment/client.d.ts +73 -0
- package/dist/environment/client.js +209 -0
- package/dist/environment/tools.d.ts +30 -0
- package/dist/environment/tools.js +24 -0
- package/dist/environment/types.d.ts +429 -0
- package/dist/environment/types.js +1 -0
- package/dist/environment.d.ts +5 -0
- package/dist/environment.js +2 -0
- package/dist/evals/attempt.d.ts +454 -0
- package/dist/evals/attempt.js +687 -0
- package/dist/evals/client.d.ts +99 -5
- package/dist/evals/client.js +136 -7
- package/dist/evals/environment-evidence.d.ts +6 -0
- package/dist/evals/environment-evidence.js +123 -0
- package/dist/evals/environment-json.d.ts +3 -0
- package/dist/evals/environment-json.js +76 -0
- package/dist/evals/json.d.ts +9 -1
- package/dist/evals/json.js +14 -6
- package/dist/evals/runner.d.ts +61 -2
- package/dist/evals/runner.js +71 -9
- package/dist/evals/scorer-publication.d.ts +2 -0
- package/dist/evals/scorer-publication.js +84 -0
- package/dist/evals/scorers.d.ts +11 -0
- package/dist/evals/scorers.js +56 -5
- package/dist/evals/simulation.d.ts +184 -0
- package/dist/evals/simulation.js +603 -0
- package/dist/evals/types.d.ts +304 -0
- package/dist/evals.d.ts +5 -1
- package/dist/evals.js +3 -1
- package/dist/experimental-telemetry.d.ts +8 -0
- package/dist/experimental-telemetry.js +13 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/managed.d.ts +51 -1
- package/dist/managed.js +11 -1
- package/dist/privacy.d.ts +2 -0
- package/dist/privacy.js +54 -21
- package/dist/receipt.d.ts +12 -1
- package/dist/receipt.js +10 -1
- package/dist/safety.d.ts +7 -0
- package/dist/safety.js +179 -0
- package/dist/snapshot.d.ts +12 -0
- package/dist/snapshot.js +200 -0
- package/dist/transport.d.ts +46 -8
- package/dist/transport.js +266 -48
- package/dist/types.d.ts +167 -8
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/package.json +51 -15
package/dist/transport.js
CHANGED
|
@@ -1,25 +1,68 @@
|
|
|
1
1
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
2
2
|
import { CompressionAlgorithm, OTLPExporterBase, OTLPExporterError, } from "@opentelemetry/otlp-exporter-base";
|
|
3
|
-
import {
|
|
3
|
+
import { createOtlpHttpExportDelegate } from "@opentelemetry/otlp-exporter-base/node-http";
|
|
4
4
|
import { LogsExporterMetricsHelper, ProtobufLogsSerializer, ProtobufTraceSerializer, TraceExporterMetricsHelper, } from "@opentelemetry/otlp-transformer";
|
|
5
5
|
import { BatchSpanProcessor, } from "@opentelemetry/sdk-trace";
|
|
6
6
|
import { BatchLogRecordProcessor, } from "@opentelemetry/sdk-logs";
|
|
7
|
-
import { MAX_BODY_BYTES, validateOptions } from "./config.js";
|
|
7
|
+
import { isInsecureOrigin, MAX_BODY_BYTES, validateOptions } from "./config.js";
|
|
8
|
+
import { estimateRecordBytes } from "./safety.js";
|
|
9
|
+
import { snapshotLog, snapshotSpan } from "./snapshot.js";
|
|
8
10
|
import { redactLog, redactSpan } from "./privacy.js";
|
|
11
|
+
import { sdkVersion } from "./version.js";
|
|
12
|
+
/** Per-record allowance for protobuf length prefixes that grow when records are grouped. */
|
|
13
|
+
const RECORD_FRAMING_BYTES = 64;
|
|
14
|
+
function recordData(record, signal) {
|
|
15
|
+
if (signal === "traces") {
|
|
16
|
+
const span = record;
|
|
17
|
+
return {
|
|
18
|
+
name: span.name,
|
|
19
|
+
attributes: span.attributes,
|
|
20
|
+
events: span.events,
|
|
21
|
+
links: span.links,
|
|
22
|
+
status: span.status,
|
|
23
|
+
resource: span.resource.attributes,
|
|
24
|
+
scope: span.instrumentationScope,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const log = record;
|
|
28
|
+
return {
|
|
29
|
+
body: log.body,
|
|
30
|
+
attributes: log.attributes,
|
|
31
|
+
eventName: log.eventName,
|
|
32
|
+
severityText: log.severityText,
|
|
33
|
+
resource: log.resource.attributes,
|
|
34
|
+
scope: log.instrumentationScope,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Rejection of {@link HueClient.flush} and {@link HueClient.shutdown}: telemetry was not fully
|
|
39
|
+
* accepted. Carries sanitized counts only, never server response text or content.
|
|
40
|
+
*/
|
|
9
41
|
export class HueExportError extends Error {
|
|
10
42
|
issues;
|
|
11
43
|
report;
|
|
12
|
-
constructor(
|
|
44
|
+
constructor(
|
|
45
|
+
/** Non-warning issues observed since the failing drain began. */
|
|
46
|
+
issues,
|
|
47
|
+
/** Cumulative counters and current gauges at the time of the failure. */
|
|
48
|
+
report) {
|
|
13
49
|
super("Hue could not accept all telemetry. Inspect issues and report for sanitized counts.");
|
|
14
50
|
this.issues = issues;
|
|
15
51
|
this.report = report;
|
|
16
52
|
this.name = "HueExportError";
|
|
17
53
|
}
|
|
18
54
|
}
|
|
19
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Hue's export pipeline: OTLP/HTTP exporters behind bounded batch processors, with cumulative
|
|
57
|
+
* counters and a sanitized issue history. A client owns one; in attach mode the application attaches
|
|
58
|
+
* `spanProcessor` and `logRecordProcessor` to its own providers while constructing them.
|
|
59
|
+
*/
|
|
20
60
|
export class HueTransport {
|
|
61
|
+
/** Validated options with defaults applied; `baseUrl` is the origin. Not enumerable, so it does not leak the key when logged. */
|
|
21
62
|
options;
|
|
63
|
+
/** Span processor to attach to a tracer provider; a no-op when disabled. */
|
|
22
64
|
spanProcessor;
|
|
65
|
+
/** Log record processor to attach to a logger provider; a no-op when disabled. */
|
|
23
66
|
logRecordProcessor;
|
|
24
67
|
sequence = 0;
|
|
25
68
|
observedSequence = 0;
|
|
@@ -28,8 +71,13 @@ export class HueTransport {
|
|
|
28
71
|
accepted = { traces: 0, logs: 0 };
|
|
29
72
|
rejected = { traces: 0, logs: 0 };
|
|
30
73
|
failed = { traces: 0, logs: 0 };
|
|
31
|
-
spans = new
|
|
32
|
-
logs = new
|
|
74
|
+
spans = new Map();
|
|
75
|
+
logs = new Map();
|
|
76
|
+
pendingBytes = 0;
|
|
77
|
+
dropped = { traces: 0, logs: 0 };
|
|
78
|
+
instrumentationFailures = 0;
|
|
79
|
+
diagnosticPending = false;
|
|
80
|
+
lastDiagnosticAt = -Infinity;
|
|
33
81
|
traceExporter;
|
|
34
82
|
logExporter;
|
|
35
83
|
closed = false;
|
|
@@ -40,6 +88,13 @@ export class HueTransport {
|
|
|
40
88
|
Object.defineProperty(this, "options", { enumerable: false });
|
|
41
89
|
this.traceExporter = new ReportingExporter(this, "traces", ProtobufTraceSerializer, TraceExporterMetricsHelper, (span, cache) => redactSpan(span, this.options, cache));
|
|
42
90
|
this.logExporter = new ReportingExporter(this, "logs", ProtobufLogsSerializer, LogsExporterMetricsHelper, (log, cache) => redactLog(log, this.options, cache));
|
|
91
|
+
if (this.options.enabled === false) {
|
|
92
|
+
this.spanProcessor = { onStart() { }, onEnd() { }, async forceFlush() { }, async shutdown() { } };
|
|
93
|
+
this.logRecordProcessor = { onEmit() { }, async forceFlush() { }, async shutdown() { } };
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (isInsecureOrigin(this.options.baseUrl))
|
|
97
|
+
this.issue("traces", "warning", 0, "allowInsecureHttp is set: telemetry and the project key are sent over plain HTTP to a host that is not loopback");
|
|
43
98
|
const batching = {
|
|
44
99
|
maxQueueSize: 2048,
|
|
45
100
|
maxExportBatchSize: 128,
|
|
@@ -49,22 +104,49 @@ export class HueTransport {
|
|
|
49
104
|
const spans = new BatchSpanProcessor({ exporter: this.traceExporter, ...batching });
|
|
50
105
|
const logs = new BatchLogRecordProcessor({ exporter: this.logExporter, ...batching });
|
|
51
106
|
this.spanProcessor = {
|
|
52
|
-
onStart: (span, parent) =>
|
|
107
|
+
onStart: (span, parent) => {
|
|
108
|
+
try {
|
|
109
|
+
spans.onStart(span, parent);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
this.instrumentationFailure();
|
|
113
|
+
}
|
|
114
|
+
},
|
|
53
115
|
onEnd: (span) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
116
|
+
let admitted;
|
|
117
|
+
try {
|
|
118
|
+
if (!(span.spanContext().traceFlags & 1))
|
|
119
|
+
return;
|
|
120
|
+
const queued = this.enqueue("traces", span);
|
|
121
|
+
if (!queued)
|
|
122
|
+
return;
|
|
123
|
+
admitted = queued;
|
|
124
|
+
spans.onEnd(admitted);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
if (admitted)
|
|
128
|
+
this.finish("traces", [admitted]);
|
|
129
|
+
this.issue("traces", "invalid", 1, "Telemetry processor could not accept a record");
|
|
130
|
+
}
|
|
59
131
|
},
|
|
60
132
|
forceFlush: () => spans.forceFlush(),
|
|
61
133
|
shutdown: () => spans.shutdown(),
|
|
62
134
|
};
|
|
63
135
|
this.logRecordProcessor = {
|
|
64
136
|
onEmit: (log) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
137
|
+
let admitted;
|
|
138
|
+
try {
|
|
139
|
+
const queued = this.enqueue("logs", log);
|
|
140
|
+
if (!queued)
|
|
141
|
+
return;
|
|
142
|
+
admitted = queued;
|
|
143
|
+
logs.onEmit(queued);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
if (admitted)
|
|
147
|
+
this.finish("logs", [admitted]);
|
|
148
|
+
this.issue("logs", "invalid", 1, "Telemetry processor could not accept a record");
|
|
149
|
+
}
|
|
68
150
|
},
|
|
69
151
|
forceFlush: () => logs.forceFlush(),
|
|
70
152
|
shutdown: () => logs.shutdown(),
|
|
@@ -76,26 +158,43 @@ export class HueTransport {
|
|
|
76
158
|
this.issue(signal, "dropped", 1, this.closed
|
|
77
159
|
? "Telemetry emitted after transport shutdown"
|
|
78
160
|
: "Telemetry queue reached 2048 records");
|
|
79
|
-
return
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const remaining = this.options.maxQueueBytes - this.pendingBytes;
|
|
165
|
+
const snapshot = signal === "traces"
|
|
166
|
+
? snapshotSpan(record, remaining)
|
|
167
|
+
: snapshotLog(record, remaining);
|
|
168
|
+
this.pendingBytes += snapshot.bytes;
|
|
169
|
+
if (signal === "traces")
|
|
170
|
+
this.spans.set(snapshot.record, snapshot.bytes);
|
|
171
|
+
else
|
|
172
|
+
this.logs.set(snapshot.record, snapshot.bytes);
|
|
173
|
+
if (snapshot.unresolvedResource)
|
|
174
|
+
this.issue(signal, "warning", 0, "Unresolved resource attributes omitted from the telemetry snapshot");
|
|
175
|
+
return snapshot.record;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
this.issue(signal, "dropped", 1, "Telemetry snapshot exceeded its byte or complexity budget or contained unsupported data");
|
|
179
|
+
return undefined;
|
|
80
180
|
}
|
|
81
|
-
if (signal === "traces")
|
|
82
|
-
this.spans.add(record);
|
|
83
|
-
else
|
|
84
|
-
this.logs.add(record);
|
|
85
|
-
return true;
|
|
86
181
|
}
|
|
182
|
+
/** @internal Exporter callback: releases queued records after an export attempt settles. */
|
|
87
183
|
finish(signal, records) {
|
|
88
184
|
for (const record of records) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.logs.delete(record);
|
|
185
|
+
const pending = signal === "traces" ? this.spans : this.logs;
|
|
186
|
+
this.pendingBytes -= pending.get(record) ?? 0;
|
|
187
|
+
pending.delete(record);
|
|
93
188
|
}
|
|
94
189
|
}
|
|
190
|
+
/** @internal Exporter callback: counts records the collector acknowledged. */
|
|
95
191
|
acceptedRecords(signal, count) {
|
|
96
192
|
this.accepted[signal] += count;
|
|
97
193
|
}
|
|
194
|
+
/** @internal Records a sanitized issue, updates counters and rate-limits the diagnostic callback. */
|
|
98
195
|
issue(signal, kind, count, message, status) {
|
|
196
|
+
if (kind === "dropped")
|
|
197
|
+
this.dropped[signal] += count;
|
|
99
198
|
if (kind === "rejected")
|
|
100
199
|
this.rejected[signal] += count;
|
|
101
200
|
else if (kind !== "warning")
|
|
@@ -113,13 +212,31 @@ export class HueTransport {
|
|
|
113
212
|
this.issues.push(issue);
|
|
114
213
|
if (this.issues.length > 128)
|
|
115
214
|
this.issues.shift();
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
215
|
+
// One diagnostic task at a time, at most once a second. No unbounded promise
|
|
216
|
+
// queue if a user callback never settles; all issues remain in counts/history.
|
|
217
|
+
if (this.options.onExportIssue &&
|
|
218
|
+
!this.diagnosticPending &&
|
|
219
|
+
Date.now() - this.lastDiagnosticAt >= 1000) {
|
|
220
|
+
this.diagnosticPending = true;
|
|
221
|
+
// Warnings (for example the allowInsecureHttp notice) do not consume the slot, so the first
|
|
222
|
+
// real failure still reaches the callback promptly.
|
|
223
|
+
if (kind !== "warning")
|
|
224
|
+
this.lastDiagnosticAt = Date.now();
|
|
225
|
+
void Promise.resolve()
|
|
226
|
+
.then(() => this.options.onExportIssue?.({ ...issue }))
|
|
227
|
+
.then(() => {
|
|
228
|
+
this.diagnosticPending = false;
|
|
229
|
+
}, () => {
|
|
230
|
+
this.diagnosticPending = false;
|
|
231
|
+
});
|
|
121
232
|
}
|
|
122
233
|
}
|
|
234
|
+
/** @internal Counts a helper capture or instrumentation failure that preserved application execution. */
|
|
235
|
+
instrumentationFailure(signal = "traces", message = "Telemetry capture or instrumentation failed; application execution was preserved") {
|
|
236
|
+
this.instrumentationFailures++;
|
|
237
|
+
this.issue(signal, "invalid", 0, message);
|
|
238
|
+
}
|
|
239
|
+
/** Cumulative counters and current queue gauges. */
|
|
123
240
|
getReport() {
|
|
124
241
|
return {
|
|
125
242
|
acceptedSpans: this.accepted.traces,
|
|
@@ -130,8 +247,13 @@ export class HueTransport {
|
|
|
130
247
|
failedLogs: this.failed.logs,
|
|
131
248
|
pendingSpans: this.spans.size,
|
|
132
249
|
pendingLogs: this.logs.size,
|
|
250
|
+
droppedSpans: this.dropped.traces,
|
|
251
|
+
droppedLogs: this.dropped.logs,
|
|
252
|
+
pendingBytes: this.pendingBytes,
|
|
253
|
+
instrumentationFailures: this.instrumentationFailures,
|
|
133
254
|
};
|
|
134
255
|
}
|
|
256
|
+
/** Copies of the latest 128 sanitized issues, oldest first. */
|
|
135
257
|
getIssues() {
|
|
136
258
|
return this.issues.map((issue) => ({ ...issue }));
|
|
137
259
|
}
|
|
@@ -139,6 +261,11 @@ export class HueTransport {
|
|
|
139
261
|
getFailureSequence() {
|
|
140
262
|
return this.failureSequence;
|
|
141
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Waits for the processors' and exporters' in-flight work; drain the providers first.
|
|
266
|
+
*
|
|
267
|
+
* @throws HueExportError when a new non-warning issue was recorded since the previous observation.
|
|
268
|
+
*/
|
|
142
269
|
flush() {
|
|
143
270
|
const from = this.observedSequence;
|
|
144
271
|
const next = (this.flushPromise ?? Promise.resolve()).then(() => this.flushOnce(from), () => this.flushOnce(from));
|
|
@@ -169,6 +296,12 @@ export class HueTransport {
|
|
|
169
296
|
throw new HueExportError(issues, report);
|
|
170
297
|
return report;
|
|
171
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Flushes and stops the processors; records emitted afterwards are dropped and counted. In attach
|
|
301
|
+
* mode call it after shutting down the application's providers.
|
|
302
|
+
*
|
|
303
|
+
* @throws HueExportError when the final flush observed new failures.
|
|
304
|
+
*/
|
|
172
305
|
shutdown() {
|
|
173
306
|
this.shutdownPromise ??= (async () => {
|
|
174
307
|
this.closed = true;
|
|
@@ -208,40 +341,77 @@ class ReportingExporter {
|
|
|
208
341
|
.finally(() => {
|
|
209
342
|
this.transport.finish(this.signal, records);
|
|
210
343
|
this.pending.delete(work);
|
|
211
|
-
})
|
|
344
|
+
})
|
|
345
|
+
.catch(() => { });
|
|
212
346
|
this.pending.add(work);
|
|
213
347
|
}
|
|
214
348
|
async exportRecords(records) {
|
|
215
349
|
const accepted = [];
|
|
216
350
|
const cache = new WeakMap();
|
|
217
351
|
let failed = false;
|
|
352
|
+
let redactedBytes = 0;
|
|
353
|
+
const resourceDeadline = Date.now() + this.transport.options.timeoutMillis;
|
|
218
354
|
for (const record of records) {
|
|
219
355
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
356
|
+
const ready = record.resource.waitForAsyncAttributes?.();
|
|
357
|
+
if (ready) {
|
|
358
|
+
let timer;
|
|
359
|
+
try {
|
|
360
|
+
await Promise.race([
|
|
361
|
+
ready,
|
|
362
|
+
new Promise((_resolve, reject) => {
|
|
363
|
+
timer = setTimeout(() => reject(new Error("Resource deadline exceeded")), Math.max(1, resourceDeadline - Date.now()));
|
|
364
|
+
}),
|
|
365
|
+
]);
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
clearTimeout(timer);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const redacted = this.redact(record, cache);
|
|
372
|
+
const bytes = 512 +
|
|
373
|
+
estimateRecordBytes(recordData(redacted, this.signal), this.transport.options.maxQueueBytes - redactedBytes);
|
|
374
|
+
if (redactedBytes + bytes > this.transport.options.maxQueueBytes)
|
|
375
|
+
throw new RangeError("Redacted batch exceeds byte budget");
|
|
376
|
+
redactedBytes += bytes;
|
|
377
|
+
accepted.push(redacted);
|
|
222
378
|
}
|
|
223
379
|
catch {
|
|
224
380
|
failed = true;
|
|
225
381
|
this.transport.issue(this.signal, "invalid", 1, "Telemetry record could not be redacted or exceeds supported content limits");
|
|
226
382
|
}
|
|
227
383
|
}
|
|
384
|
+
// Each record is encoded once to measure it; a request is encoded once more when it is sent.
|
|
385
|
+
// Records sharing a resource and scope are grouped on the wire, so the sum of the individual
|
|
386
|
+
// encodings plus a fixed framing margin bounds the request size. Room is left for gzip
|
|
387
|
+
// headers/blocks when otherwise incompressible data is near the wire cap.
|
|
388
|
+
const limit = MAX_BODY_BYTES - 1024;
|
|
228
389
|
let batch = [];
|
|
390
|
+
let batchBytes = 0;
|
|
229
391
|
for (const record of accepted) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
batch = candidate;
|
|
234
|
-
continue;
|
|
392
|
+
let recordBytes;
|
|
393
|
+
try {
|
|
394
|
+
recordBytes = this.serializer.serializeRequest([record])?.byteLength ?? Infinity;
|
|
235
395
|
}
|
|
236
|
-
|
|
396
|
+
catch {
|
|
237
397
|
failed = true;
|
|
238
|
-
|
|
239
|
-
|
|
398
|
+
this.transport.issue(this.signal, "invalid", 1, "Telemetry record could not be serialized");
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const framedBytes = recordBytes + RECORD_FRAMING_BYTES;
|
|
402
|
+
if (batch.length && batchBytes + framedBytes > limit) {
|
|
403
|
+
if (!(await this.send(batch)))
|
|
404
|
+
failed = true;
|
|
405
|
+
batch = [];
|
|
406
|
+
batchBytes = 0;
|
|
407
|
+
}
|
|
408
|
+
if (recordBytes > limit) {
|
|
240
409
|
failed = true;
|
|
241
410
|
this.transport.issue(this.signal, "invalid", 1, "Telemetry record exceeds the 1 MiB request limit");
|
|
411
|
+
continue;
|
|
242
412
|
}
|
|
243
|
-
|
|
244
|
-
|
|
413
|
+
batch.push(record);
|
|
414
|
+
batchBytes += framedBytes;
|
|
245
415
|
}
|
|
246
416
|
if (batch.length && !(await this.send(batch)))
|
|
247
417
|
failed = true;
|
|
@@ -252,9 +422,17 @@ class ReportingExporter {
|
|
|
252
422
|
const options = this.transport.options;
|
|
253
423
|
let rejected = 0;
|
|
254
424
|
let validResponse = true;
|
|
425
|
+
let receivedResponse = false;
|
|
426
|
+
let expired = false;
|
|
427
|
+
const agents = new Set();
|
|
428
|
+
const deadline = Date.now() + options.timeoutMillis;
|
|
429
|
+
let timer;
|
|
255
430
|
const serializer = {
|
|
256
431
|
serializeRequest: (data) => this.serializer.serializeRequest(data),
|
|
257
432
|
deserializeResponse: (bytes) => {
|
|
433
|
+
if (expired)
|
|
434
|
+
return {};
|
|
435
|
+
receivedResponse = true;
|
|
258
436
|
try {
|
|
259
437
|
const response = this.serializer.deserializeResponse(bytes);
|
|
260
438
|
const partial = response.partialSuccess;
|
|
@@ -277,17 +455,46 @@ class ReportingExporter {
|
|
|
277
455
|
},
|
|
278
456
|
};
|
|
279
457
|
const endpoint = `${options.baseUrl}/api/v1/otlp/v1/${this.signal}`;
|
|
280
|
-
|
|
458
|
+
// Explicit configuration only. OTEL_EXPORTER_OTLP_* environment variables are meant
|
|
459
|
+
// for generic exporters; merging them here could send another vendor's headers to Hue.
|
|
460
|
+
const delegate = createOtlpHttpExportDelegate({
|
|
281
461
|
url: endpoint,
|
|
282
|
-
headers:
|
|
462
|
+
headers: async () => ({
|
|
463
|
+
"Content-Type": "application/x-protobuf",
|
|
464
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
465
|
+
}),
|
|
466
|
+
// The transport prefixes this to OpenTelemetry's own User-Agent token.
|
|
467
|
+
userAgent: `hue-sdk-typescript/${sdkVersion}`,
|
|
283
468
|
timeoutMillis: options.timeoutMillis,
|
|
284
469
|
concurrencyLimit: 1,
|
|
285
470
|
compression: CompressionAlgorithm.GZIP,
|
|
286
|
-
|
|
471
|
+
agentFactory: async (protocol) => {
|
|
472
|
+
if (expired || Date.now() >= deadline)
|
|
473
|
+
throw new Error("Hue export deadline exceeded");
|
|
474
|
+
const { Agent } = await import(protocol === "https:" ? "node:https" : "node:http");
|
|
475
|
+
const agent = new Agent({ keepAlive: false });
|
|
476
|
+
agents.add(agent);
|
|
477
|
+
if (expired)
|
|
478
|
+
agent.destroy();
|
|
479
|
+
return agent;
|
|
480
|
+
},
|
|
481
|
+
}, serializer, this.signal === "traces" ? "otlp_http_span_exporter" : "otlp_http_log_exporter", this.metrics, undefined);
|
|
287
482
|
const exporter = new OTLPExporterBase(delegate);
|
|
288
483
|
try {
|
|
289
|
-
const result = await new Promise((resolve) =>
|
|
484
|
+
const result = await new Promise((resolve) => {
|
|
485
|
+
timer = setTimeout(() => {
|
|
486
|
+
expired = true;
|
|
487
|
+
for (const agent of agents)
|
|
488
|
+
agent.destroy();
|
|
489
|
+
resolve({ code: ExportResultCode.FAILED });
|
|
490
|
+
}, Math.max(1, deadline - Date.now()));
|
|
491
|
+
exporter.export(records, resolve);
|
|
492
|
+
});
|
|
290
493
|
if (result.code === ExportResultCode.SUCCESS) {
|
|
494
|
+
if (!receivedResponse) {
|
|
495
|
+
this.transport.issue(this.signal, "failed", records.length, "Hue response ended without a complete OTLP acknowledgement; acceptance is uncertain");
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
291
498
|
if (validResponse)
|
|
292
499
|
this.transport.acceptedRecords(this.signal, records.length - rejected);
|
|
293
500
|
return validResponse;
|
|
@@ -305,7 +512,12 @@ class ReportingExporter {
|
|
|
305
512
|
return false;
|
|
306
513
|
}
|
|
307
514
|
finally {
|
|
308
|
-
|
|
515
|
+
clearTimeout(timer);
|
|
516
|
+
for (const agent of agents)
|
|
517
|
+
agent.destroy();
|
|
518
|
+
// Delegate cleanup cannot extend the hard request wait. Sockets are closed
|
|
519
|
+
// and the cleanup promise is always observed, even after a caller timeout.
|
|
520
|
+
void exporter.shutdown().catch(() => { });
|
|
309
521
|
}
|
|
310
522
|
}
|
|
311
523
|
async forceFlush() {
|
|
@@ -315,6 +527,12 @@ class ReportingExporter {
|
|
|
315
527
|
await this.forceFlush();
|
|
316
528
|
}
|
|
317
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Creates the export pipeline for attach mode; pass it with the application's providers to
|
|
532
|
+
* {@link createHue}. Validates options like an owned client.
|
|
533
|
+
*
|
|
534
|
+
* @throws TypeError for invalid options; see {@link createHue}.
|
|
535
|
+
*/
|
|
318
536
|
export function createHueTransport(options) {
|
|
319
537
|
return new HueTransport(options);
|
|
320
538
|
}
|