agentfootprint 11.1.0 → 11.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/CAPABILITIES.md +1 -0
- package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js +35 -4
- package/dist/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/transport.js +48 -22
- package/dist/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/adapters/observability/cloudwatch.js +101 -110
- package/dist/adapters/observability/cloudwatch.js.map +1 -1
- package/dist/adapters/observability/file.js +48 -71
- package/dist/adapters/observability/file.js.map +1 -1
- package/dist/adapters/observability/outbox.js +217 -0
- package/dist/adapters/observability/outbox.js.map +1 -0
- package/dist/adapters/observability/xray.js +56 -70
- package/dist/adapters/observability/xray.js.map +1 -1
- package/dist/core/RunnerBase.js +20 -1
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +6 -2
- package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js +35 -4
- package/dist/esm/adapters/mcp/agentcoreRuntime/sessionBusy.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.d.ts +38 -12
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js +47 -21
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/esm/adapters/observability/cloudwatch.d.ts +6 -2
- package/dist/esm/adapters/observability/cloudwatch.js +101 -110
- package/dist/esm/adapters/observability/cloudwatch.js.map +1 -1
- package/dist/esm/adapters/observability/file.d.ts +8 -4
- package/dist/esm/adapters/observability/file.js +48 -71
- package/dist/esm/adapters/observability/file.js.map +1 -1
- package/dist/esm/adapters/observability/outbox.d.ts +104 -0
- package/dist/esm/adapters/observability/outbox.js +213 -0
- package/dist/esm/adapters/observability/outbox.js.map +1 -0
- package/dist/esm/adapters/observability/xray.js +56 -70
- package/dist/esm/adapters/observability/xray.js.map +1 -1
- package/dist/esm/core/RunnerBase.d.ts +18 -0
- package/dist/esm/core/RunnerBase.js +20 -1
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/runner.d.ts +1 -0
- package/dist/esm/lib/longTimeout.d.ts +34 -0
- package/dist/esm/lib/longTimeout.js +56 -0
- package/dist/esm/lib/longTimeout.js.map +1 -0
- package/dist/esm/lib/sleep.d.ts +2 -0
- package/dist/esm/lib/sleep.js +2 -0
- package/dist/esm/lib/sleep.js.map +1 -1
- package/dist/esm/mcp/backoff.d.ts +81 -0
- package/dist/esm/mcp/backoff.js +158 -0
- package/dist/esm/mcp/backoff.js.map +1 -0
- package/dist/esm/mcp/mcpClient.js +3 -1
- package/dist/esm/mcp/mcpClient.js.map +1 -1
- package/dist/esm/mcp/throttleRetry.d.ts +198 -28
- package/dist/esm/mcp/throttleRetry.js +428 -83
- package/dist/esm/mcp/throttleRetry.js.map +1 -1
- package/dist/esm/mcp/types.d.ts +16 -7
- package/dist/esm/strategies/attach.js +3 -0
- package/dist/esm/strategies/attach.js.map +1 -1
- package/dist/esm/strategies/flushDeadline.d.ts +47 -0
- package/dist/esm/strategies/flushDeadline.js +96 -0
- package/dist/esm/strategies/flushDeadline.js.map +1 -0
- package/dist/lib/longTimeout.js +60 -0
- package/dist/lib/longTimeout.js.map +1 -0
- package/dist/lib/sleep.js +2 -0
- package/dist/lib/sleep.js.map +1 -1
- package/dist/mcp/backoff.js +163 -0
- package/dist/mcp/backoff.js.map +1 -0
- package/dist/mcp/mcpClient.js +3 -1
- package/dist/mcp/mcpClient.js.map +1 -1
- package/dist/mcp/throttleRetry.js +432 -85
- package/dist/mcp/throttleRetry.js.map +1 -1
- package/dist/strategies/attach.js +3 -0
- package/dist/strategies/attach.js.map +1 -1
- package/dist/strategies/flushDeadline.js +102 -0
- package/dist/strategies/flushDeadline.js.map +1 -0
- package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts +6 -2
- package/dist/types/adapters/mcp/agentcoreRuntime/sessionBusy.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts +38 -12
- package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts.map +1 -1
- package/dist/types/adapters/observability/cloudwatch.d.ts +6 -2
- package/dist/types/adapters/observability/cloudwatch.d.ts.map +1 -1
- package/dist/types/adapters/observability/file.d.ts +8 -4
- package/dist/types/adapters/observability/file.d.ts.map +1 -1
- package/dist/types/adapters/observability/outbox.d.ts +105 -0
- package/dist/types/adapters/observability/outbox.d.ts.map +1 -0
- package/dist/types/adapters/observability/xray.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +18 -0
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/runner.d.ts +1 -0
- package/dist/types/core/runner.d.ts.map +1 -1
- package/dist/types/lib/longTimeout.d.ts +35 -0
- package/dist/types/lib/longTimeout.d.ts.map +1 -0
- package/dist/types/lib/sleep.d.ts +2 -0
- package/dist/types/lib/sleep.d.ts.map +1 -1
- package/dist/types/mcp/backoff.d.ts +82 -0
- package/dist/types/mcp/backoff.d.ts.map +1 -0
- package/dist/types/mcp/throttleRetry.d.ts +198 -28
- package/dist/types/mcp/throttleRetry.d.ts.map +1 -1
- package/dist/types/mcp/types.d.ts +16 -7
- package/dist/types/mcp/types.d.ts.map +1 -1
- package/dist/types/strategies/attach.d.ts.map +1 -1
- package/dist/types/strategies/flushDeadline.d.ts +48 -0
- package/dist/types/strategies/flushDeadline.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -37,10 +37,14 @@
|
|
|
37
37
|
* `exportEvent` is sync and never touches the disk: it serializes, buffers, and
|
|
38
38
|
* returns. Batches are appended asynchronously on a size trigger
|
|
39
39
|
* (`maxBufferEvents` / `maxBufferBytes`), on a timer (`flushIntervalMs`), and on
|
|
40
|
-
* `flush()
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* `flush()` — all through ONE queue (`outbox.ts`), so one append is in flight
|
|
41
|
+
* at a time: this sink's appends never interleave, and lines land in dispatch
|
|
42
|
+
* order (#223). That is not atomicity — a disk that fails partway through an
|
|
43
|
+
* append (ENOSPC) can still leave part of a line behind, and that batch is
|
|
44
|
+
* reported dropped. A hard kill loses the buffer and can interrupt the append
|
|
45
|
+
* in flight — the price of not making telemetry a term in agent-loop latency.
|
|
46
|
+
* Call `flush()` (or `agent.shutdown()`, which does) at process end; see the
|
|
47
|
+
* 8.12.0 lifecycle laws on {@link BaseStrategy.flush}.
|
|
44
48
|
*
|
|
45
49
|
* ## Rotation is ONE generation, and that is deliberate
|
|
46
50
|
*
|
|
@@ -75,6 +79,7 @@ exports.fileObservability = void 0;
|
|
|
75
79
|
const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
|
|
76
80
|
const wireJson_js_1 = require("../../lib/wireJson.js");
|
|
77
81
|
const deliveryErrors_js_1 = require("./deliveryErrors.js");
|
|
82
|
+
const outbox_js_1 = require("./outbox.js");
|
|
78
83
|
// ─── Public factory ──────────────────────────────────────────────────
|
|
79
84
|
/**
|
|
80
85
|
* NDJSON-to-a-local-file observability strategy. See
|
|
@@ -120,27 +125,13 @@ function fileObservability(opts) {
|
|
|
120
125
|
`Fix the path or the permission — this sink refuses to start rather than ` +
|
|
121
126
|
`silently drop every event.`);
|
|
122
127
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
let lastFlushPromise = Promise.resolve();
|
|
127
|
-
let timer;
|
|
128
|
+
// Set by `stop()`: this sink accepts no more events. It is NOT a guard on
|
|
129
|
+
// writing — `stop()` does not authorise throwing away what was already
|
|
130
|
+
// accepted, and `flush()` after it still writes (the 8.11.1 stance).
|
|
128
131
|
let stopped = false;
|
|
129
132
|
// The fallback when the consumer wires nothing. Rate-limited; a
|
|
130
133
|
// consumer-supplied sink is not.
|
|
131
134
|
const consoleSink = (0, deliveryErrors_js_1.rateLimitedConsoleSink)(strategyName);
|
|
132
|
-
function scheduleTimedFlush() {
|
|
133
|
-
if (timer || flushIntervalMs <= 0 || stopped)
|
|
134
|
-
return;
|
|
135
|
-
timer = setTimeout(() => {
|
|
136
|
-
timer = undefined;
|
|
137
|
-
void doFlush();
|
|
138
|
-
}, flushIntervalMs);
|
|
139
|
-
// Never hold the process open for telemetry. `unref` exists on Node's
|
|
140
|
-
// Timeout and not on the browser's number — feature-detected, so a
|
|
141
|
-
// non-Node runtime with an injected `_fs` still works.
|
|
142
|
-
timer.unref?.();
|
|
143
|
-
}
|
|
144
135
|
/** Route a delivery failure through whatever `_onError` IS RIGHT NOW —
|
|
145
136
|
* reading it at call time, so a consumer who assigns `_onError` after
|
|
146
137
|
* construction still receives them (the 8.11.0 fix). */
|
|
@@ -162,28 +153,29 @@ function fileObservability(opts) {
|
|
|
162
153
|
await fs.rename(path, rotatedPath);
|
|
163
154
|
bytesOnDisk = 0;
|
|
164
155
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const batch = buffer.splice(0);
|
|
175
|
-
const batchBytes = bufferBytes;
|
|
176
|
-
bufferBytes = 0;
|
|
177
|
-
const data = `${batch.join('\n')}\n`;
|
|
178
|
-
try {
|
|
179
|
-
await rotateIfNeeded(batchBytes);
|
|
180
|
-
await fs.appendFile(path, data);
|
|
181
|
-
bytesOnDisk += byteLength(data);
|
|
182
|
-
}
|
|
183
|
-
catch (err) {
|
|
184
|
-
reportFailure(new Error(`${batch.length} event(s) dropped writing to '${path}': ${errorMessage(err)}`));
|
|
185
|
-
}
|
|
156
|
+
/** One batch: rotate if it would cross the ceiling, then append. Runs
|
|
157
|
+
* inside the outbox's queue, so no other batch — and no other rotation —
|
|
158
|
+
* can start until this one settles. */
|
|
159
|
+
async function writeBatch(lines) {
|
|
160
|
+
const data = `${lines.join('\n')}\n`;
|
|
161
|
+
const bytes = byteLength(data);
|
|
162
|
+
await rotateIfNeeded(bytes);
|
|
163
|
+
await fs.appendFile(path, data);
|
|
164
|
+
bytesOnDisk += bytes;
|
|
186
165
|
}
|
|
166
|
+
// Buffered lines, and the one queue every write goes through — size
|
|
167
|
+
// trigger, timer, `flush()`, shutdown (outbox.ts; #223).
|
|
168
|
+
const outbox = (0, outbox_js_1.createOutbox)({
|
|
169
|
+
name: strategyName,
|
|
170
|
+
maxItems: maxBufferEvents,
|
|
171
|
+
maxBytes: maxBufferBytes,
|
|
172
|
+
flushIntervalMs,
|
|
173
|
+
// Never hold the process open for a local write.
|
|
174
|
+
timerHoldsProcess: false,
|
|
175
|
+
deliver: writeBatch,
|
|
176
|
+
dropped: (lines, cause) => new Error(`${lines.length} event(s) dropped writing to '${path}': ${errorMessage(cause)}`),
|
|
177
|
+
report: reportFailure,
|
|
178
|
+
});
|
|
187
179
|
function enqueue(event) {
|
|
188
180
|
if (stopped)
|
|
189
181
|
return;
|
|
@@ -205,17 +197,11 @@ function fileObservability(opts) {
|
|
|
205
197
|
// write, and a bare "undefined" line would corrupt the NDJSON stream.
|
|
206
198
|
if (line === undefined)
|
|
207
199
|
return;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// offline reader can rely on.
|
|
214
|
-
lastFlushPromise = lastFlushPromise.then(doFlush, doFlush);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
scheduleTimedFlush();
|
|
218
|
-
}
|
|
200
|
+
// + the newline this line will carry. The outbox decides when it is
|
|
201
|
+
// written, and writes it after every line accepted before it — the file's
|
|
202
|
+
// line order IS the dispatch order, the one thing an offline reader can
|
|
203
|
+
// rely on.
|
|
204
|
+
outbox.add(line, byteLength(line) + 1);
|
|
219
205
|
}
|
|
220
206
|
const strategy = {
|
|
221
207
|
name: strategyName,
|
|
@@ -226,31 +212,22 @@ function fileObservability(opts) {
|
|
|
226
212
|
* Write what is buffered. Called for you on shutdown (8.12.0) — by the
|
|
227
213
|
* handle `enable.observability()` returns, by `agent.shutdown()`, and by a
|
|
228
214
|
* `standingAgent` closing. Safe at any time, including after `stop()`.
|
|
215
|
+
*
|
|
216
|
+
* Resolves once every event accepted before the call is written (or
|
|
217
|
+
* dropped and reported) — one a timed write already had in flight
|
|
218
|
+
* included — after one more sweep of what arrived while it waited. It
|
|
219
|
+
* does not chase a producer that keeps emitting. A write that never
|
|
220
|
+
* settles holds it: nothing can cancel an in-flight write.
|
|
229
221
|
*/
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// line; a pass that removes none ends the drain instead of retrying. The
|
|
233
|
-
// shape the CloudWatch adapter arrived at in 8.11.1, for the same reason:
|
|
234
|
-
// a drain that cannot finish must return, never spin.
|
|
235
|
-
for (;;) {
|
|
236
|
-
const pending = buffer.length;
|
|
237
|
-
lastFlushPromise = lastFlushPromise.then(doFlush, doFlush);
|
|
238
|
-
await lastFlushPromise;
|
|
239
|
-
if (buffer.length === 0)
|
|
240
|
-
return;
|
|
241
|
-
if (buffer.length >= pending)
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
222
|
+
flush() {
|
|
223
|
+
return outbox.drain();
|
|
244
224
|
},
|
|
245
225
|
/** Clear the timer and stop accepting events. Terminal: there is no
|
|
246
226
|
* restart. What it does NOT do is discard the buffer — `flush()` after
|
|
247
227
|
* `stop()` still writes it. */
|
|
248
228
|
stop() {
|
|
249
229
|
stopped = true;
|
|
250
|
-
|
|
251
|
-
clearTimeout(timer);
|
|
252
|
-
timer = undefined;
|
|
253
|
-
}
|
|
230
|
+
outbox.stop();
|
|
254
231
|
},
|
|
255
232
|
/** Where failures go. Two callers: the dispatch layer (when `exportEvent`
|
|
256
233
|
* itself throws) and this adapter's own write path. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/adapters/observability/file.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/adapters/observability/file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* outbox — the one owner of buffered, timed, serialized delivery for the
|
|
4
|
+
* batching sinks in this folder: `file`, `cloudwatch` (and `agentcore`, which
|
|
5
|
+
* is built on it) and `xray`.
|
|
6
|
+
*
|
|
7
|
+
* A sink accepts items on its hot path (sync, no I/O) and hands them here. The
|
|
8
|
+
* outbox decides WHEN a batch leaves and makes sure only one leaves at a time:
|
|
9
|
+
*
|
|
10
|
+
* - **One queue, every trigger.** The size trigger, the timer and `drain()`
|
|
11
|
+
* (the sink's `flush()`, and so every shutdown) all queue a delivery on
|
|
12
|
+
* the same promise chain. Nothing calls `deliver` any other way.
|
|
13
|
+
* - **One delivery in flight.** A delivery starts only after the previous
|
|
14
|
+
* one settled. A large `fs.promises.appendFile` is several `write()`s; two
|
|
15
|
+
* of them interleaved tear NDJSON lines (reported in #223, where the timer
|
|
16
|
+
* bypassed the chain). Whatever a sink does around its write — the file
|
|
17
|
+
* sink's rotation — runs inside its delivery, so it can never be skipped
|
|
18
|
+
* or reordered either. This keeps ONE sink's deliveries from interleaving;
|
|
19
|
+
* it does not make a delivery atomic (a disk that fails partway through an
|
|
20
|
+
* append can still leave part of it behind).
|
|
21
|
+
* - **Oldest first, within the receiver's limits.** A delivery takes the
|
|
22
|
+
* oldest waiting items, so items leave in the order they were accepted.
|
|
23
|
+
* When a receiving service limits one request (`maxItemsPerDelivery`,
|
|
24
|
+
* `maxBytesPerDelivery` — CloudWatch's PutLogEvents), a delivery takes
|
|
25
|
+
* only the oldest items that fit, and the rest go in the next delivery,
|
|
26
|
+
* right after. That matters BECAUSE of the queue: a slow delivery lets a
|
|
27
|
+
* backlog build behind it, which one request may not be allowed to carry.
|
|
28
|
+
* Each part settles on its own — shipped, or dropped and reported.
|
|
29
|
+
* - **`drain()` is bounded.** It resolves once every item accepted BEFORE
|
|
30
|
+
* the call has settled (delivered, or dropped and reported), one the timer
|
|
31
|
+
* already had in flight included — then makes one sweep of what arrived
|
|
32
|
+
* while it waited. It never chases a producer that keeps emitting: under
|
|
33
|
+
* steady traffic it ends after about two deliveries.
|
|
34
|
+
* - **A failed batch is dropped and reported once**, then the next batch
|
|
35
|
+
* goes. It is never requeued, so a full disk or an outage cannot grow the
|
|
36
|
+
* outbox. The queue never rejects: a failure report that itself throws is
|
|
37
|
+
* said on the console instead of becoming an unhandled rejection.
|
|
38
|
+
*
|
|
39
|
+
* What it does NOT do: bound a delivery that never settles. A hung disk holds
|
|
40
|
+
* the queue, and `drain()` with it; nothing can cancel an in-flight write.
|
|
41
|
+
*
|
|
42
|
+
* Role: leaf helper for `adapters/observability/*`. Composed as a field,
|
|
43
|
+
* never inherited.
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.createOutbox = void 0;
|
|
47
|
+
const deliveryErrors_js_1 = require("./deliveryErrors.js");
|
|
48
|
+
/** Build the outbox one sink owns. A per-delivery cap that could take
|
|
49
|
+
* nothing is refused: such a delivery could never finish a drain. */
|
|
50
|
+
function createOutbox(opts) {
|
|
51
|
+
if (opts.maxItemsPerDelivery !== undefined) {
|
|
52
|
+
requireDeliveryCap(opts.name, opts.maxItemsPerDelivery);
|
|
53
|
+
}
|
|
54
|
+
const waiting = [];
|
|
55
|
+
let waitingBytes = 0;
|
|
56
|
+
// Running counts. Deliveries take the oldest items, one delivery at a time,
|
|
57
|
+
// so once `settled` reaches n the first n accepted items are done.
|
|
58
|
+
let accepted = 0; // handed to `add`
|
|
59
|
+
let taken = 0; // taken by a delivery (in flight or settled)
|
|
60
|
+
let settled = 0; // delivered, or dropped and reported
|
|
61
|
+
// The ONE queue. Every delivery is chained here; `deliverNext` never
|
|
62
|
+
// rejects, so the chain never does either.
|
|
63
|
+
let queue = Promise.resolve();
|
|
64
|
+
// A delivery is queued and has not started yet. It will take everything
|
|
65
|
+
// waiting when it starts, so another trigger meanwhile has nothing to add —
|
|
66
|
+
// and the chain cannot grow while a delivery hangs.
|
|
67
|
+
let deliveryQueued = false;
|
|
68
|
+
let timer;
|
|
69
|
+
let stopped = false;
|
|
70
|
+
const consoleFallback = (0, deliveryErrors_js_1.rateLimitedConsoleSink)(opts.name);
|
|
71
|
+
function add(item, bytes = 0) {
|
|
72
|
+
waiting.push({ item, bytes });
|
|
73
|
+
waitingBytes += bytes;
|
|
74
|
+
accepted++;
|
|
75
|
+
if (atSizeTrigger())
|
|
76
|
+
queueDelivery();
|
|
77
|
+
else
|
|
78
|
+
armTimer();
|
|
79
|
+
}
|
|
80
|
+
function atSizeTrigger() {
|
|
81
|
+
if (waiting.length >= opts.maxItems)
|
|
82
|
+
return true;
|
|
83
|
+
return opts.maxBytes !== undefined && waitingBytes >= opts.maxBytes;
|
|
84
|
+
}
|
|
85
|
+
function armTimer() {
|
|
86
|
+
if (timer !== undefined || stopped || opts.flushIntervalMs <= 0)
|
|
87
|
+
return;
|
|
88
|
+
timer = setTimeout(onTimer, opts.flushIntervalMs);
|
|
89
|
+
// `unref` exists on Node's Timeout and not on a browser's number, so it
|
|
90
|
+
// is feature-detected.
|
|
91
|
+
if (!opts.timerHoldsProcess)
|
|
92
|
+
timer.unref?.();
|
|
93
|
+
}
|
|
94
|
+
function onTimer() {
|
|
95
|
+
timer = undefined;
|
|
96
|
+
queueDelivery();
|
|
97
|
+
}
|
|
98
|
+
function queueDelivery() {
|
|
99
|
+
if (deliveryQueued)
|
|
100
|
+
return;
|
|
101
|
+
deliveryQueued = true;
|
|
102
|
+
queue = queue.then(deliverNext);
|
|
103
|
+
}
|
|
104
|
+
async function deliverNext() {
|
|
105
|
+
deliveryQueued = false;
|
|
106
|
+
const { batch, leftBehind } = takeOldest();
|
|
107
|
+
if (batch.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
await opts.deliver(batch);
|
|
111
|
+
}
|
|
112
|
+
catch (cause) {
|
|
113
|
+
reportDropped(batch, cause);
|
|
114
|
+
}
|
|
115
|
+
// Settled exactly once, whatever happened to the deliveries around it.
|
|
116
|
+
settled += batch.length;
|
|
117
|
+
// Items a per-delivery limit left behind were already due when this
|
|
118
|
+
// delivery started — deliver them now rather than on the next timer.
|
|
119
|
+
if (leftBehind)
|
|
120
|
+
queueDelivery();
|
|
121
|
+
}
|
|
122
|
+
/** Take the oldest waiting items one delivery may carry. `leftBehind` says
|
|
123
|
+
* a per-delivery limit stopped the take with items still waiting. */
|
|
124
|
+
function takeOldest() {
|
|
125
|
+
const entries = waiting.splice(0, countForOneDelivery());
|
|
126
|
+
for (const entry of entries)
|
|
127
|
+
waitingBytes -= entry.bytes;
|
|
128
|
+
if (waiting.length === 0)
|
|
129
|
+
waitingBytes = 0;
|
|
130
|
+
taken += entries.length;
|
|
131
|
+
return { batch: entries.map((entry) => entry.item), leftBehind: waiting.length > 0 };
|
|
132
|
+
}
|
|
133
|
+
/** How many of the oldest waiting items fit one delivery: at least one,
|
|
134
|
+
* then as many as the item and byte limits allow, in order. */
|
|
135
|
+
function countForOneDelivery() {
|
|
136
|
+
const most = Math.min(waiting.length, opts.maxItemsPerDelivery ?? waiting.length);
|
|
137
|
+
const byteLimit = opts.maxBytesPerDelivery;
|
|
138
|
+
if (byteLimit === undefined)
|
|
139
|
+
return most;
|
|
140
|
+
const overhead = opts.overheadBytesPerItem ?? 0;
|
|
141
|
+
let count = 0;
|
|
142
|
+
let bytes = 0;
|
|
143
|
+
while (count < most) {
|
|
144
|
+
const entry = waiting[count];
|
|
145
|
+
if (entry === undefined)
|
|
146
|
+
break;
|
|
147
|
+
const cost = entry.bytes + overhead;
|
|
148
|
+
if (count > 0 && bytes + cost > byteLimit)
|
|
149
|
+
break;
|
|
150
|
+
bytes += cost;
|
|
151
|
+
count++;
|
|
152
|
+
}
|
|
153
|
+
return count;
|
|
154
|
+
}
|
|
155
|
+
function reportDropped(batch, cause) {
|
|
156
|
+
let error;
|
|
157
|
+
try {
|
|
158
|
+
error = opts.dropped(batch, cause);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
error = new Error(`${batch.length} item(s) dropped: ${messageOf(cause)}`);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
opts.report(error);
|
|
165
|
+
}
|
|
166
|
+
catch (hookError) {
|
|
167
|
+
// The consumer's own `onError` threw. Say both things on the console
|
|
168
|
+
// rather than letting the queue reject (an unhandled rejection, which
|
|
169
|
+
// ends a Node process by default).
|
|
170
|
+
consoleFallback(new Error(`${error.message} — and the onError hook threw: ${messageOf(hookError)}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function drain() {
|
|
174
|
+
// Everything accepted before this call, in flight or waiting…
|
|
175
|
+
await settleThrough(accepted);
|
|
176
|
+
// …then ONE sweep of what arrived while it waited, so a `stop()` right
|
|
177
|
+
// after `flush()` strands nothing that was already here. The target is
|
|
178
|
+
// fixed when the sweep starts, so steady traffic cannot keep it going.
|
|
179
|
+
await settleThrough(accepted);
|
|
180
|
+
}
|
|
181
|
+
/** Wait until the first `target` accepted items have settled. */
|
|
182
|
+
async function settleThrough(target) {
|
|
183
|
+
while (settled < target) {
|
|
184
|
+
const before = settled;
|
|
185
|
+
// Some of them still wait: make sure a delivery will take them.
|
|
186
|
+
if (taken < target)
|
|
187
|
+
queueDelivery();
|
|
188
|
+
await queue;
|
|
189
|
+
// A pass that settled nothing would repeat forever. With the cap
|
|
190
|
+
// refused above it cannot happen; if it ever does, end — never spin.
|
|
191
|
+
if (settled === before)
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function stop() {
|
|
196
|
+
stopped = true;
|
|
197
|
+
if (timer !== undefined) {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
timer = undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { add, drain, stop };
|
|
203
|
+
}
|
|
204
|
+
exports.createOutbox = createOutbox;
|
|
205
|
+
/** The sink-to-outbox contract: a cap is a whole number above zero. Sinks
|
|
206
|
+
* map and refuse their own options first (X-Ray: `segmentsPerPut`), so this
|
|
207
|
+
* guards the contract rather than a caller's input. */
|
|
208
|
+
function requireDeliveryCap(name, cap) {
|
|
209
|
+
if (Number.isInteger(cap) && cap > 0)
|
|
210
|
+
return;
|
|
211
|
+
throw new TypeError(`[${name}Observability] the outbox was given a per-delivery cap of ${String(cap)}; ` +
|
|
212
|
+
`a delivery that may take no items could never finish a drain.`);
|
|
213
|
+
}
|
|
214
|
+
function messageOf(err) {
|
|
215
|
+
return err instanceof Error ? err.message : String(err);
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../src/adapters/observability/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -73,6 +73,7 @@ exports.xrayObservability = void 0;
|
|
|
73
73
|
const lazyRequire_js_1 = require("../../lib/lazyRequire.js");
|
|
74
74
|
const wireJson_js_1 = require("../../lib/wireJson.js");
|
|
75
75
|
const deliveryErrors_js_1 = require("./deliveryErrors.js");
|
|
76
|
+
const outbox_js_1 = require("./outbox.js");
|
|
76
77
|
// ─── Strategy factory ────────────────────────────────────────────────
|
|
77
78
|
function xrayObservability(opts) {
|
|
78
79
|
if (!opts.serviceName) {
|
|
@@ -86,11 +87,8 @@ function xrayObservability(opts) {
|
|
|
86
87
|
// multiple in-flight turns; we key the active stack by the run
|
|
87
88
|
// anchor (`meta.runId` — see anchorRunId).
|
|
88
89
|
const activeTurns = new Map();
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
const outbox = [];
|
|
92
|
-
let lastFlushPromise = Promise.resolve();
|
|
93
|
-
let timer;
|
|
90
|
+
// Set by `stop()`: no more events accepted. NOT a guard on delivery (8.11.1):
|
|
91
|
+
// segments already closed and queued are still owed to X-Ray.
|
|
94
92
|
let stopped = false;
|
|
95
93
|
// The fallback when the consumer wires nothing. Rate-limited; a
|
|
96
94
|
// consumer-supplied sink is not.
|
|
@@ -103,44 +101,32 @@ function xrayObservability(opts) {
|
|
|
103
101
|
client = createXRayClient(opts.region, opts._sdk);
|
|
104
102
|
return client;
|
|
105
103
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}, flushIntervalMs);
|
|
113
|
-
}
|
|
114
|
-
async function doFlush() {
|
|
115
|
-
// `stopped` is deliberately NOT a guard here (8.11.1). `stop()` stops this
|
|
116
|
-
// strategy ACCEPTING events and cancels its timer; segments already closed
|
|
117
|
-
// and queued are still owed to X-Ray. Guarding here made `flush()` after
|
|
118
|
-
// `stop()` loop forever waiting for a drain that could no longer happen —
|
|
119
|
-
// an infinite microtask spin that starved the event loop of the process
|
|
120
|
-
// trying to shut down.
|
|
121
|
-
if (outbox.length === 0)
|
|
122
|
-
return;
|
|
123
|
-
const batch = outbox.splice(0, maxBatchSegments);
|
|
124
|
-
try {
|
|
125
|
-
await ensureClient().putTraceSegments({
|
|
126
|
-
TraceSegmentDocuments: batch.map((s) => (0, wireJson_js_1.toWireJson)(s)),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
catch (err) {
|
|
130
|
-
// Routed through the strategy's CURRENT `_onError` (not a hook captured
|
|
131
|
-
// at construction), so a consumer who assigns it — or passes `onError`
|
|
132
|
-
// — actually receives delivery failures. Before 8.11.0 the hook was
|
|
133
|
-
// installed lazily inside `_onError` itself and was `undefined` here,
|
|
134
|
-
// so every failed put was silent.
|
|
135
|
-
strategy._onError?.(new Error(`${batch.length} segment(s) dropped shipping to X-Ray: ` +
|
|
136
|
-
(err instanceof Error ? err.message : String(err))));
|
|
137
|
-
}
|
|
138
|
-
// If outbox grew during the put (size > maxBatchSegments emits
|
|
139
|
-
// arrived), chain another flush.
|
|
140
|
-
if (outbox.length > 0 && !stopped) {
|
|
141
|
-
lastFlushPromise = lastFlushPromise.then(doFlush, doFlush);
|
|
142
|
-
}
|
|
104
|
+
/** Ship one batch of closed segments. Throws when it could not — the
|
|
105
|
+
* outbox drops the batch and reports it once. */
|
|
106
|
+
async function shipSegments(batch) {
|
|
107
|
+
await ensureClient().putTraceSegments({
|
|
108
|
+
TraceSegmentDocuments: batch.map((s) => (0, wireJson_js_1.toWireJson)(s)),
|
|
109
|
+
});
|
|
143
110
|
}
|
|
111
|
+
// Closed segments ready for PutTraceSegments, and the one queue every put
|
|
112
|
+
// goes through — size trigger, timer, `flush()`, shutdown (outbox.ts;
|
|
113
|
+
// #223). A put takes at most `maxBatchSegments` (its whole part); what the
|
|
114
|
+
// cap leaves behind ships right after it.
|
|
115
|
+
const outbox = (0, outbox_js_1.createOutbox)({
|
|
116
|
+
name: 'xray',
|
|
117
|
+
// The trigger compares the value as given, as before the outbox.
|
|
118
|
+
maxItems: maxBatchSegments,
|
|
119
|
+
maxItemsPerDelivery: segmentsPerPut(maxBatchSegments),
|
|
120
|
+
flushIntervalMs,
|
|
121
|
+
timerHoldsProcess: true,
|
|
122
|
+
deliver: shipSegments,
|
|
123
|
+
dropped: (batch, cause) => new Error(`${batch.length} segment(s) dropped shipping to X-Ray: ` +
|
|
124
|
+
(cause instanceof Error ? cause.message : String(cause))),
|
|
125
|
+
// Routed through the strategy's CURRENT `_onError` (not a hook captured
|
|
126
|
+
// at construction), so a consumer who assigns it — or passes `onError`
|
|
127
|
+
// — actually receives delivery failures (8.11.0).
|
|
128
|
+
report: (err) => strategy._onError?.(err),
|
|
129
|
+
});
|
|
144
130
|
function pushSegment(turnState, name) {
|
|
145
131
|
const parent = turnState.stack[turnState.stack.length - 1];
|
|
146
132
|
const seg = {
|
|
@@ -199,15 +185,10 @@ function xrayObservability(opts) {
|
|
|
199
185
|
seg.metadata = { default: { ...(seg.metadata?.default ?? {}), ...extra.metadata } };
|
|
200
186
|
if (turnState.sampled) {
|
|
201
187
|
turnState.closed.push(seg);
|
|
202
|
-
// Once the root closes, the whole turn graduates to outbox.
|
|
188
|
+
// Once the root closes, the whole turn graduates to the outbox.
|
|
203
189
|
if (turnState.stack.length === 0) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
lastFlushPromise = lastFlushPromise.then(doFlush, doFlush);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
scheduleTimedFlush();
|
|
210
|
-
}
|
|
190
|
+
for (const closed of turnState.closed)
|
|
191
|
+
outbox.add(closed);
|
|
211
192
|
}
|
|
212
193
|
}
|
|
213
194
|
}
|
|
@@ -415,22 +396,11 @@ function xrayObservability(opts) {
|
|
|
415
396
|
while (t.stack.length > 0)
|
|
416
397
|
closeSegment(t, undefined);
|
|
417
398
|
}
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
// microtask spin that starved the event loop during shutdown.
|
|
424
|
-
for (;;) {
|
|
425
|
-
const pending = outbox.length;
|
|
426
|
-
// Chain, never replace: segments ship in the order they closed.
|
|
427
|
-
lastFlushPromise = lastFlushPromise.then(doFlush, doFlush);
|
|
428
|
-
await lastFlushPromise;
|
|
429
|
-
if (outbox.length === 0)
|
|
430
|
-
return;
|
|
431
|
-
if (outbox.length >= pending)
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
399
|
+
// Segments ship in the order they closed, one put at a time, behind the
|
|
400
|
+
// put in flight — one the timer started included. Resolves once every
|
|
401
|
+
// segment closed before this call is shipped (or dropped and reported),
|
|
402
|
+
// after one more sweep; a put that never settles holds it.
|
|
403
|
+
await outbox.drain();
|
|
434
404
|
},
|
|
435
405
|
/** Stop the flush timer and stop accepting events. **The framework does
|
|
436
406
|
* not call this for you** — an un-stopped strategy keeps a `setTimeout`
|
|
@@ -441,10 +411,7 @@ function xrayObservability(opts) {
|
|
|
441
411
|
* (8.11.1). */
|
|
442
412
|
stop() {
|
|
443
413
|
stopped = true;
|
|
444
|
-
|
|
445
|
-
clearTimeout(timer);
|
|
446
|
-
timer = undefined;
|
|
447
|
-
}
|
|
414
|
+
outbox.stop();
|
|
448
415
|
},
|
|
449
416
|
/**
|
|
450
417
|
* Where errors go. Two callers: the dispatch layer (when `exportEvent`
|
|
@@ -460,6 +427,25 @@ function xrayObservability(opts) {
|
|
|
460
427
|
return strategy;
|
|
461
428
|
}
|
|
462
429
|
exports.xrayObservability = xrayObservability;
|
|
430
|
+
// ─── Batch size ──────────────────────────────────────────────────────
|
|
431
|
+
/**
|
|
432
|
+
* How many segments one put takes — exactly what `outbox.splice(0,
|
|
433
|
+
* maxBatchSegments)` took before the shared queue: the whole part of the
|
|
434
|
+
* value, and every segment for `Infinity` (returned as no cap).
|
|
435
|
+
*
|
|
436
|
+
* A value whose whole part is below 1 — `0`, a fraction under 1, a negative
|
|
437
|
+
* number, `NaN` — took NO segments, so every put was empty and the queue
|
|
438
|
+
* never emptied: puts in a loop, forever. That never worked, so it is refused
|
|
439
|
+
* here, by the option's name; every value that did work keeps its meaning.
|
|
440
|
+
*/
|
|
441
|
+
function segmentsPerPut(maxBatchSegments) {
|
|
442
|
+
const whole = Math.trunc(maxBatchSegments);
|
|
443
|
+
if (whole === Infinity)
|
|
444
|
+
return undefined;
|
|
445
|
+
if (whole >= 1)
|
|
446
|
+
return whole;
|
|
447
|
+
throw new TypeError(`[xrayObservability] \`maxBatchSegments\` must be at least 1 (got ${String(maxBatchSegments)}): ` + `it is how many segments one put takes, and a put of none never empties the queue.`);
|
|
448
|
+
}
|
|
463
449
|
// ─── ID + time helpers ───────────────────────────────────────────────
|
|
464
450
|
/**
|
|
465
451
|
* Generate an X-Ray trace ID. Format:
|