@juspay/neurolink 12.17.1 → 12.17.3
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/CHANGELOG.md +6 -2
- package/dist/browser/neurolink.min.js +430 -421
- package/dist/cli/commands/proxy.js +455 -259
- package/dist/constants/contextWindows.d.ts +2 -0
- package/dist/constants/contextWindows.js +4 -0
- package/dist/neurolink.js +8 -3
- package/dist/providers/openaiChatCompletionsBase.js +60 -51
- package/dist/proxy/anthropicBudgetResponse.d.ts +3 -0
- package/dist/proxy/anthropicBudgetResponse.js +153 -0
- package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
- package/dist/proxy/bodyCaptureProcessing.js +23 -4
- package/dist/proxy/bodyCaptureWorker.js +20 -2
- package/dist/proxy/codexFallback.d.ts +10 -0
- package/dist/proxy/codexFallback.js +108 -24
- package/dist/proxy/codexUsage.js +2 -0
- package/dist/proxy/globalInstaller.d.ts +32 -1
- package/dist/proxy/globalInstaller.js +386 -3
- package/dist/proxy/otelLogSink.d.ts +3 -3
- package/dist/proxy/otelLogSink.js +137 -144
- package/dist/proxy/otlpLogTransport.d.ts +3 -0
- package/dist/proxy/otlpLogTransport.js +334 -0
- package/dist/proxy/proxyActivity.d.ts +22 -2
- package/dist/proxy/proxyActivity.js +114 -9
- package/dist/proxy/proxyAnalysis.js +82 -52
- package/dist/proxy/proxyClientCancellation.d.ts +7 -0
- package/dist/proxy/proxyClientCancellation.js +23 -0
- package/dist/proxy/proxyContextPreflight.d.ts +25 -0
- package/dist/proxy/proxyContextPreflight.js +337 -0
- package/dist/proxy/proxyFailureDetails.d.ts +19 -0
- package/dist/proxy/proxyFailureDetails.js +96 -0
- package/dist/proxy/proxyLifecycle.js +7 -0
- package/dist/proxy/proxyRouteBodyCapture.d.ts +13 -0
- package/dist/proxy/proxyRouteBodyCapture.js +112 -0
- package/dist/proxy/proxyTokenBudget.d.ts +40 -0
- package/dist/proxy/proxyTokenBudget.js +523 -0
- package/dist/proxy/proxyTracer.d.ts +3 -2
- package/dist/proxy/proxyTracer.js +46 -20
- package/dist/proxy/proxyTranslationEngine.d.ts +8 -10
- package/dist/proxy/proxyTranslationEngine.js +736 -376
- package/dist/proxy/requestLogger.d.ts +1 -0
- package/dist/proxy/requestLogger.js +107 -5
- package/dist/proxy/restartControl.js +9 -4
- package/dist/proxy/rollingWorkerProcess.js +65 -0
- package/dist/proxy/rollingWorkerSupervisor.js +7 -0
- package/dist/proxy/updateCoordinator.d.ts +14 -1
- package/dist/proxy/updateCoordinator.js +57 -2
- package/dist/server/routes/claudeProxyRoutes.d.ts +4 -5
- package/dist/server/routes/claudeProxyRoutes.js +809 -577
- package/dist/server/routes/codexProxyRoutes.js +191 -9
- package/dist/server/routes/openaiProxyRoutes.d.ts +1 -1
- package/dist/server/routes/openaiProxyRoutes.js +261 -69
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/codex.d.ts +5 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/proxy.d.ts +148 -3
- package/dist/types/proxy.js +0 -15
- package/dist/types/proxyBudget.d.ts +74 -0
- package/dist/types/proxyBudget.js +1 -0
- package/dist/types/proxyContext.d.ts +35 -0
- package/dist/types/proxyContext.js +1 -0
- package/dist/utils/fileDetector.js +28 -7
- package/dist/utils/messageBuilder.js +51 -26
- package/dist/utils/pdfProcessor.js +26 -4
- package/docs/assets/dashboards/README.md +37 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +197 -89
- package/docs-site/static/search-index.json +1 -1
- package/package.json +7 -2
- package/scripts/observability/NATIVE-DURABLE.md +63 -0
- package/scripts/observability/otel-collector.proxy-native-durable.yaml +93 -0
- package/scripts/observability/proxy-telemetry-backend.mjs +32 -5
- package/scripts/observability/proxy-telemetry-check.mjs +396 -61
- package/scripts/observability/query-proxy-history.mjs +34 -5
|
@@ -5,7 +5,7 @@ import { getProxyRequestTraceContext, proxyLogContext, } from "./proxyTraceConte
|
|
|
5
5
|
import { setImmediate as yieldToRequests } from "node:timers/promises";
|
|
6
6
|
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
7
7
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
8
|
-
import {
|
|
8
|
+
import { createProxyOtlpLogTransport } from "./otlpLogTransport.js";
|
|
9
9
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
10
10
|
import { BatchLogRecordProcessor, LoggerProvider, } from "@opentelemetry/sdk-logs";
|
|
11
11
|
import { sanitizeForLog } from "../utils/logSanitize.js";
|
|
@@ -15,7 +15,7 @@ let restoreConsole;
|
|
|
15
15
|
const queues = [];
|
|
16
16
|
const bodyPublications = new Map();
|
|
17
17
|
const bodyPublicationDeadlines = new WeakMap();
|
|
18
|
-
|
|
18
|
+
const bodyPublicationOperations = new Set();
|
|
19
19
|
let shuttingDown = false;
|
|
20
20
|
const BODY_OTLP_CHUNK_SIZE = 128 * 1024;
|
|
21
21
|
const OTLP_EXPORT_TIMEOUT_MS = 30_000;
|
|
@@ -41,6 +41,8 @@ export function isProxyOtelOnly() {
|
|
|
41
41
|
/** Reserve capacity including exports in flight, independently for metadata and bodies. */
|
|
42
42
|
function createTrackedProcessor(url, capacity, kind) {
|
|
43
43
|
const unsettled = new Set();
|
|
44
|
+
const enqueuedAt = new WeakMap();
|
|
45
|
+
const capacityWaiters = new Set();
|
|
44
46
|
const flushWaiters = new Set();
|
|
45
47
|
const state = {
|
|
46
48
|
attempted: 0,
|
|
@@ -99,12 +101,17 @@ function createTrackedProcessor(url, capacity, kind) {
|
|
|
99
101
|
}
|
|
100
102
|
});
|
|
101
103
|
};
|
|
102
|
-
const transport =
|
|
103
|
-
url,
|
|
104
|
-
timeoutMillis: OTLP_EXPORT_TIMEOUT_MS,
|
|
105
|
-
});
|
|
104
|
+
const transport = createProxyOtlpLogTransport(url, OTLP_EXPORT_TIMEOUT_MS);
|
|
106
105
|
const exporter = {
|
|
107
106
|
export(records, callback) {
|
|
107
|
+
const exportStartedAt = performance.now();
|
|
108
|
+
for (const record of records) {
|
|
109
|
+
const id = record.attributes["body.capture_id"];
|
|
110
|
+
const publication = typeof id === "string" ? bodyPublications.get(id) : undefined;
|
|
111
|
+
if (publication) {
|
|
112
|
+
publication.maxChunkQueueWaitMs = Math.max(publication.maxChunkQueueWaitMs ?? 0, exportStartedAt - (enqueuedAt.get(record) ?? exportStartedAt));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
108
115
|
let settled = false;
|
|
109
116
|
const deadline = setTimeout(() => settle({
|
|
110
117
|
code: ExportResultCode.FAILED,
|
|
@@ -133,6 +140,7 @@ function createTrackedProcessor(url, capacity, kind) {
|
|
|
133
140
|
const id = record.attributes?.["body.capture_id"];
|
|
134
141
|
const publication = typeof id === "string" ? bodyPublications.get(id) : undefined;
|
|
135
142
|
if (publication) {
|
|
143
|
+
publication.maxChunkExportMs = Math.max(publication.maxChunkExportMs ?? 0, performance.now() - exportStartedAt);
|
|
136
144
|
const activeDeadline = bodyPublicationDeadlines.get(publication);
|
|
137
145
|
if (result.code === ExportResultCode.SUCCESS &&
|
|
138
146
|
(activeDeadline === undefined || Date.now() < activeDeadline)) {
|
|
@@ -144,6 +152,9 @@ function createTrackedProcessor(url, capacity, kind) {
|
|
|
144
152
|
publication.notify?.();
|
|
145
153
|
}
|
|
146
154
|
}
|
|
155
|
+
for (const notify of capacityWaiters) {
|
|
156
|
+
notify();
|
|
157
|
+
}
|
|
147
158
|
for (const notify of flushWaiters) {
|
|
148
159
|
notify();
|
|
149
160
|
}
|
|
@@ -164,7 +175,7 @@ function createTrackedProcessor(url, capacity, kind) {
|
|
|
164
175
|
const batch = new BatchLogRecordProcessor(exporter, {
|
|
165
176
|
maxQueueSize: capacity,
|
|
166
177
|
maxExportBatchSize: 64,
|
|
167
|
-
scheduledDelayMillis: 1000,
|
|
178
|
+
scheduledDelayMillis: kind === "bodies" ? 25 : 1000,
|
|
168
179
|
exportTimeoutMillis: OTLP_EXPORT_CALLBACK_DEADLINE_MS,
|
|
169
180
|
});
|
|
170
181
|
const processor = {
|
|
@@ -188,6 +199,7 @@ function createTrackedProcessor(url, capacity, kind) {
|
|
|
188
199
|
state.submitted++;
|
|
189
200
|
state.outstanding++;
|
|
190
201
|
unsettled.add(record);
|
|
202
|
+
enqueuedAt.set(record, performance.now());
|
|
191
203
|
state.highWaterOutstanding = Math.max(state.highWaterOutstanding, state.outstanding);
|
|
192
204
|
batch.onEmit(record);
|
|
193
205
|
},
|
|
@@ -218,12 +230,32 @@ function createTrackedProcessor(url, capacity, kind) {
|
|
|
218
230
|
}
|
|
219
231
|
},
|
|
220
232
|
};
|
|
221
|
-
|
|
233
|
+
/** Wait for a shared queue slot without force-flushing or owning another capture. */
|
|
234
|
+
const waitForCapacity = (deadline) => new Promise((resolve) => {
|
|
235
|
+
const timer = setTimeout(() => finish(false), Math.max(0, deadline - Date.now()));
|
|
236
|
+
timer.unref?.();
|
|
237
|
+
const finish = (available) => {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
capacityWaiters.delete(check);
|
|
240
|
+
resolve(available);
|
|
241
|
+
};
|
|
242
|
+
const check = () => {
|
|
243
|
+
if (Date.now() >= deadline) {
|
|
244
|
+
finish(false);
|
|
245
|
+
}
|
|
246
|
+
else if (state.outstanding < capacity) {
|
|
247
|
+
finish(true);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
capacityWaiters.add(check);
|
|
251
|
+
check();
|
|
252
|
+
});
|
|
253
|
+
return { state, processor, capacity, kind, waitForCapacity };
|
|
222
254
|
}
|
|
223
255
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
256
|
+
* Publish bounded captures concurrently into a shared batch queue. Transport
|
|
257
|
+
* callbacks settle each chunk once; a flush is a process fence, not a per-body
|
|
258
|
+
* network round trip. Metadata has a separate queue and transport.
|
|
227
259
|
*/
|
|
228
260
|
export async function publishProxyOtelBody(captureId, body, emit) {
|
|
229
261
|
bodyDelivery.attempted++;
|
|
@@ -258,7 +290,7 @@ export async function publishProxyOtelBody(captureId, body, emit) {
|
|
|
258
290
|
};
|
|
259
291
|
bodyPublications.set(captureId, progress);
|
|
260
292
|
let deferredRelease;
|
|
261
|
-
const operation =
|
|
293
|
+
const operation = (async () => {
|
|
262
294
|
const queue = queues.find((candidate) => candidate.kind === "bodies");
|
|
263
295
|
if (!queue) {
|
|
264
296
|
return {
|
|
@@ -269,14 +301,39 @@ export async function publishProxyOtelBody(captureId, body, emit) {
|
|
|
269
301
|
reason: "body_exporter_unavailable",
|
|
270
302
|
};
|
|
271
303
|
}
|
|
272
|
-
|
|
273
|
-
// captures. Start its active deadline only when it reaches the head of
|
|
274
|
-
// the chain; healthy queued captures must not expire solely because an
|
|
275
|
-
// earlier export used its transport timeout.
|
|
304
|
+
const startedAt = performance.now();
|
|
276
305
|
const deadline = Date.now() + BODY_PUBLICATION_ACTIVE_DEADLINE_MS;
|
|
277
306
|
bodyPublicationDeadlines.set(progress, deadline);
|
|
278
307
|
const chunks = splitUtf8StringByBytes(body, BODY_OTLP_CHUNK_SIZE);
|
|
279
|
-
|
|
308
|
+
let capacityWaitMs = 0;
|
|
309
|
+
let reason;
|
|
310
|
+
try {
|
|
311
|
+
for (let index = 0; index < chunks.length; index++) {
|
|
312
|
+
while (queue.state.outstanding >= queue.capacity) {
|
|
313
|
+
const waitingAt = performance.now();
|
|
314
|
+
const available = await queue.waitForCapacity(deadline);
|
|
315
|
+
capacityWaitMs += performance.now() - waitingAt;
|
|
316
|
+
if (!available) {
|
|
317
|
+
reason = "body_publication_deadline";
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (Date.now() >= deadline) {
|
|
322
|
+
reason = "body_publication_deadline";
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
emit(chunks[index], index, chunks.length);
|
|
326
|
+
// Multiple waiters can wake together; the next iteration checks the
|
|
327
|
+
// shared capacity synchronously before emitting its next chunk.
|
|
328
|
+
if (index % 4 === 3) {
|
|
329
|
+
await yieldToRequests();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
reason = "body_publication_failed";
|
|
335
|
+
}
|
|
336
|
+
const settlement = new Promise((resolve) => {
|
|
280
337
|
const check = () => {
|
|
281
338
|
if (progress.acknowledged + progress.unconfirmed + progress.dropped >=
|
|
282
339
|
progress.emitted) {
|
|
@@ -287,123 +344,55 @@ export async function publishProxyOtelBody(captureId, body, emit) {
|
|
|
287
344
|
progress.notify = check;
|
|
288
345
|
check();
|
|
289
346
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
: "transport_acknowledged";
|
|
303
|
-
return {
|
|
304
|
-
status,
|
|
305
|
-
expectedChunks: chunks.length,
|
|
306
|
-
acknowledgedChunks: snapshot.acknowledged,
|
|
307
|
-
unconfirmedChunks,
|
|
308
|
-
droppedChunks: snapshot.dropped,
|
|
309
|
-
notSubmittedChunks: chunks.length - snapshot.emitted,
|
|
310
|
-
...(reason ? { reason } : {}),
|
|
311
|
-
};
|
|
312
|
-
};
|
|
313
|
-
const settleCurrentBatch = async () => {
|
|
314
|
-
const currentProgress = () => ({
|
|
315
|
-
acknowledged: progress.acknowledged,
|
|
316
|
-
unconfirmed: progress.unconfirmed,
|
|
317
|
-
dropped: progress.dropped,
|
|
318
|
-
emitted: progress.emitted,
|
|
319
|
-
});
|
|
320
|
-
const settlement = queue.processor.forceFlush().then(awaitSettlement);
|
|
321
|
-
const remainingMs = deadline - Date.now();
|
|
322
|
-
if (remainingMs <= 0) {
|
|
323
|
-
deferredRelease = settlement.catch(() => undefined);
|
|
324
|
-
return resultFromProgress(currentProgress(), "body_publication_deadline");
|
|
325
|
-
}
|
|
326
|
-
let timer;
|
|
327
|
-
const completed = await Promise.race([
|
|
328
|
-
settlement.then(() => true),
|
|
329
|
-
new Promise((resolve) => {
|
|
330
|
-
timer = setTimeout(() => resolve(false), remainingMs);
|
|
331
|
-
timer.unref?.();
|
|
332
|
-
}),
|
|
333
|
-
]).finally(() => clearTimeout(timer));
|
|
334
|
-
if (!completed || Date.now() >= deadline) {
|
|
335
|
-
if (!completed) {
|
|
336
|
-
// The exporter callback owns the submitted records until it settles.
|
|
337
|
-
// Keep the capture identity and memory reservation alive meanwhile.
|
|
338
|
-
deferredRelease = settlement.catch(() => undefined);
|
|
339
|
-
}
|
|
340
|
-
return resultFromProgress(currentProgress(), "body_publication_deadline");
|
|
341
|
-
}
|
|
342
|
-
return undefined;
|
|
343
|
-
};
|
|
344
|
-
let reason;
|
|
345
|
-
try {
|
|
346
|
-
for (let offset = 0; offset < chunks.length; offset += 64) {
|
|
347
|
-
if (Date.now() >= deadline) {
|
|
348
|
-
reason = "body_publication_deadline";
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
// Ordinary body records from an external logger may share this queue.
|
|
352
|
-
// Wait for them before admitting any part of this batch.
|
|
353
|
-
while (queue.state.outstanding > queue.capacity - 64) {
|
|
354
|
-
const deadlineResult = await settleCurrentBatch();
|
|
355
|
-
if (deadlineResult) {
|
|
356
|
-
return deadlineResult;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
for (let i = offset; i < Math.min(offset + 64, chunks.length); i++) {
|
|
360
|
-
emit(chunks[i], i, chunks.length);
|
|
361
|
-
}
|
|
362
|
-
const deadlineResult = await settleCurrentBatch();
|
|
363
|
-
if (deadlineResult) {
|
|
364
|
-
return deadlineResult;
|
|
365
|
-
}
|
|
366
|
-
if (progress.unconfirmed || progress.dropped) {
|
|
367
|
-
reason = "body_export_unconfirmed";
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
await yieldToRequests();
|
|
371
|
-
}
|
|
347
|
+
let timer;
|
|
348
|
+
const completed = await Promise.race([
|
|
349
|
+
settlement.then(() => true),
|
|
350
|
+
new Promise((resolve) => {
|
|
351
|
+
timer = setTimeout(() => resolve(false), Math.max(0, deadline - Date.now()));
|
|
352
|
+
timer.unref?.();
|
|
353
|
+
}),
|
|
354
|
+
]).finally(() => clearTimeout(timer));
|
|
355
|
+
if (!completed) {
|
|
356
|
+
// Keep identity/bytes until the real callback or callback deadline settles
|
|
357
|
+
// submitted chunks. Late success cannot rewrite an uncertainty result.
|
|
358
|
+
deferredRelease = settlement;
|
|
372
359
|
}
|
|
373
|
-
|
|
374
|
-
reason
|
|
375
|
-
error instanceof Error &&
|
|
376
|
-
error.message === "body_publication_deadline"
|
|
377
|
-
? "body_publication_deadline"
|
|
378
|
-
: "body_publication_failed";
|
|
379
|
-
// Do not let error cleanup hold the serial publication chain past this
|
|
380
|
-
// capture's deadline. Exporter callbacks still own submitted chunks, so
|
|
381
|
-
// retain the capture identity and memory reservation until they settle.
|
|
382
|
-
const settlement = queue.processor
|
|
383
|
-
.forceFlush()
|
|
384
|
-
.catch(() => undefined)
|
|
385
|
-
.then(awaitSettlement);
|
|
386
|
-
const remainingMs = deadline - Date.now();
|
|
387
|
-
if (remainingMs <= 0) {
|
|
388
|
-
deferredRelease = settlement;
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
let timer;
|
|
392
|
-
const completed = await Promise.race([
|
|
393
|
-
settlement.then(() => true),
|
|
394
|
-
new Promise((resolve) => {
|
|
395
|
-
timer = setTimeout(() => resolve(false), remainingMs);
|
|
396
|
-
timer.unref?.();
|
|
397
|
-
}),
|
|
398
|
-
]).finally(() => clearTimeout(timer));
|
|
399
|
-
if (!completed) {
|
|
400
|
-
deferredRelease = settlement;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
360
|
+
if (!completed || Date.now() >= deadline) {
|
|
361
|
+
reason ??= "body_publication_deadline";
|
|
403
362
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
363
|
+
if (!reason && progress.unconfirmed) {
|
|
364
|
+
reason = "body_export_unconfirmed";
|
|
365
|
+
}
|
|
366
|
+
else if (!reason && progress.dropped) {
|
|
367
|
+
reason = "body_export_queue_full";
|
|
368
|
+
}
|
|
369
|
+
const pending = Math.max(0, progress.emitted -
|
|
370
|
+
progress.acknowledged -
|
|
371
|
+
progress.unconfirmed -
|
|
372
|
+
progress.dropped);
|
|
373
|
+
const unconfirmedChunks = progress.unconfirmed + pending;
|
|
374
|
+
const status = progress.emitted === 0
|
|
375
|
+
? "rejected"
|
|
376
|
+
: progress.dropped || progress.emitted !== chunks.length
|
|
377
|
+
? "partial"
|
|
378
|
+
: unconfirmedChunks
|
|
379
|
+
? "export_unconfirmed"
|
|
380
|
+
: "transport_acknowledged";
|
|
381
|
+
return {
|
|
382
|
+
status,
|
|
383
|
+
expectedChunks: chunks.length,
|
|
384
|
+
acknowledgedChunks: progress.acknowledged,
|
|
385
|
+
unconfirmedChunks,
|
|
386
|
+
droppedChunks: progress.dropped,
|
|
387
|
+
notSubmittedChunks: chunks.length - progress.emitted,
|
|
388
|
+
publicationMs: performance.now() - startedAt,
|
|
389
|
+
capacityWaitMs,
|
|
390
|
+
maxChunkQueueWaitMs: progress.maxChunkQueueWaitMs ?? 0,
|
|
391
|
+
maxChunkExportMs: progress.maxChunkExportMs ?? 0,
|
|
392
|
+
...(reason ? { reason } : {}),
|
|
393
|
+
};
|
|
394
|
+
})();
|
|
395
|
+
bodyPublicationOperations.add(operation);
|
|
407
396
|
try {
|
|
408
397
|
const result = await operation;
|
|
409
398
|
if (result.status === "transport_acknowledged") {
|
|
@@ -421,13 +410,14 @@ export async function publishProxyOtelBody(captureId, body, emit) {
|
|
|
421
410
|
return result;
|
|
422
411
|
}
|
|
423
412
|
finally {
|
|
413
|
+
bodyPublicationOperations.delete(operation);
|
|
424
414
|
const release = () => {
|
|
425
415
|
bodyPublications.delete(captureId);
|
|
426
416
|
bodyDelivery.pending--;
|
|
427
417
|
bodyDelivery.pendingBytes -= bytes;
|
|
428
418
|
};
|
|
429
419
|
if (deferredRelease) {
|
|
430
|
-
void deferredRelease.
|
|
420
|
+
void deferredRelease.then(release, release);
|
|
431
421
|
}
|
|
432
422
|
else {
|
|
433
423
|
release();
|
|
@@ -449,16 +439,19 @@ export function initializeProxyOtelLogs(role = "worker") {
|
|
|
449
439
|
if (!endpoint) {
|
|
450
440
|
throw new Error("OTel-only proxy logging requires an OTLP endpoint");
|
|
451
441
|
}
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
442
|
+
const bodyEndpoint = process.env.NEUROLINK_PROXY_OTLP_BODIES_ENDPOINT ?? endpoint;
|
|
443
|
+
for (const target of [endpoint, bodyEndpoint]) {
|
|
444
|
+
const url = new URL(target);
|
|
445
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
446
|
+
throw new Error("Proxy OTLP logs endpoint must use HTTP or HTTPS");
|
|
447
|
+
}
|
|
448
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
449
|
+
if (url.protocol === "http:" && !loopback) {
|
|
450
|
+
throw new Error("Proxy OTLP logs require HTTPS for non-loopback collectors");
|
|
451
|
+
}
|
|
459
452
|
}
|
|
460
453
|
const metadata = createTrackedProcessor(endpoint, 2048, "metadata");
|
|
461
|
-
const bodies = createTrackedProcessor(
|
|
454
|
+
const bodies = createTrackedProcessor(bodyEndpoint, 256, "bodies");
|
|
462
455
|
queues.push(metadata, bodies);
|
|
463
456
|
provider = new LoggerProvider({
|
|
464
457
|
resource: resourceFromAttributes({
|
|
@@ -594,13 +587,13 @@ export function getProxyOtelLogSnapshot() {
|
|
|
594
587
|
}
|
|
595
588
|
/** Bounded provider flush belongs after final request and lifecycle publication. */
|
|
596
589
|
export async function flushProxyOtelLogs() {
|
|
597
|
-
await
|
|
590
|
+
await Promise.allSettled([...bodyPublicationOperations]);
|
|
598
591
|
await provider?.forceFlush();
|
|
599
592
|
}
|
|
600
593
|
/** Release this process's exporter and restore console ownership. */
|
|
601
594
|
export async function shutdownProxyOtelLogs() {
|
|
602
595
|
shuttingDown = true;
|
|
603
|
-
await
|
|
596
|
+
await Promise.allSettled([...bodyPublicationOperations]);
|
|
604
597
|
restoreConsole?.();
|
|
605
598
|
restoreConsole = undefined;
|
|
606
599
|
await provider?.shutdown();
|
|
@@ -608,7 +601,7 @@ export async function shutdownProxyOtelLogs() {
|
|
|
608
601
|
queues.length = 0;
|
|
609
602
|
invalidRecords = 0;
|
|
610
603
|
bodyPublications.clear();
|
|
611
|
-
|
|
604
|
+
bodyPublicationOperations.clear();
|
|
612
605
|
shuttingDown = false;
|
|
613
606
|
for (const key of [
|
|
614
607
|
"attempted",
|