@juspay/neurolink 12.14.0 → 12.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -3
- package/dist/browser/neurolink.min.js +366 -366
- package/dist/cli/commands/proxy.d.ts +1 -0
- package/dist/cli/commands/proxy.js +41 -20
- package/dist/neurolink.js +16 -0
- package/dist/providers/anthropic/client.js +73 -4
- package/dist/proxy/bodyCaptureProcessing.d.ts +1 -1
- package/dist/proxy/bodyCaptureProcessing.js +10 -0
- package/dist/proxy/bodyCaptureWorker.d.ts +1 -1
- package/dist/proxy/logCleanupScheduler.js +4 -0
- package/dist/proxy/otelLogSink.d.ts +34 -0
- package/dist/proxy/otelLogSink.js +254 -0
- package/dist/proxy/proxyLifecycle.js +31 -1
- package/dist/proxy/requestLogger.js +45 -10
- package/dist/proxy/workerLog.js +4 -0
- package/dist/types/aiCompat.d.ts +1 -0
- package/dist/types/proxy.d.ts +4 -0
- package/docs-site/static/search-index.json +1 -1
- package/package.json +6 -5
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { emitProxyOtelEvent, isProxyOtelOnly, initializeProxyOtelLogs, } from "./otelLogSink.js";
|
|
1
2
|
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
2
3
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { appendFile } from "node:fs/promises";
|
|
@@ -297,6 +298,25 @@ export function configureProxyLifecycleLogger(options) {
|
|
|
297
298
|
batchSize = positiveInteger(options.batchSize, DEFAULT_BATCH_SIZE);
|
|
298
299
|
maxWriteRetries = positiveInteger(options.maxWriteRetries, DEFAULT_MAX_WRITE_RETRIES);
|
|
299
300
|
flushIntervalMs = positiveInteger(options.flushIntervalMs, DEFAULT_FLUSH_INTERVAL_MS);
|
|
301
|
+
if (options.enabled && isProxyOtelOnly()) {
|
|
302
|
+
initializeProxyOtelLogs(options.filePrefix === "proxy-supervisor" ? "supervisor" : "worker");
|
|
303
|
+
loggerEnabled = true;
|
|
304
|
+
sessionHashKey = process.env.NEUROLINK_PROXY_SESSION_SECRET
|
|
305
|
+
? createHash("sha256")
|
|
306
|
+
.update(process.env.NEUROLINK_PROXY_SESSION_SECRET)
|
|
307
|
+
.digest()
|
|
308
|
+
: sessionHashKey;
|
|
309
|
+
stopRuntimeMetrics = startProxyRuntimeMetrics((runtimeSample) => {
|
|
310
|
+
logProxyLifecycleEvent({
|
|
311
|
+
event: "runtime_sample",
|
|
312
|
+
requestId: "-",
|
|
313
|
+
method: "-",
|
|
314
|
+
path: "-",
|
|
315
|
+
runtimeSample,
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
300
320
|
if (options.enabled && options.logDir) {
|
|
301
321
|
try {
|
|
302
322
|
mkdirSync(options.logDir, { recursive: true, mode: 0o700 });
|
|
@@ -336,7 +356,7 @@ export function logProxyLifecycleEvent(input) {
|
|
|
336
356
|
* capacity is unavailable.
|
|
337
357
|
*/
|
|
338
358
|
function enqueueLifecycleEvent(input, onPersisted) {
|
|
339
|
-
if (!loggerEnabled || !lifecycleLogDir) {
|
|
359
|
+
if (!loggerEnabled || (!lifecycleLogDir && !isProxyOtelOnly())) {
|
|
340
360
|
onPersisted?.(false);
|
|
341
361
|
return;
|
|
342
362
|
}
|
|
@@ -406,6 +426,10 @@ function enqueueLifecycleEvent(input, onPersisted) {
|
|
|
406
426
|
: {}),
|
|
407
427
|
...(input.runtimeSample ? { runtimeSample: input.runtimeSample } : {}),
|
|
408
428
|
};
|
|
429
|
+
if (isProxyOtelOnly()) {
|
|
430
|
+
emitProxyOtelEvent(filePrefix === "proxy-supervisor" ? "supervisor" : "lifecycle", record);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
409
433
|
queue.push({
|
|
410
434
|
filePrefix,
|
|
411
435
|
logDir: lifecycleLogDir,
|
|
@@ -431,6 +455,10 @@ export async function persistProxyLifecycleAcceptance(input, timeoutMs = LIFECYC
|
|
|
431
455
|
if (!loggerRequired) {
|
|
432
456
|
return;
|
|
433
457
|
}
|
|
458
|
+
if (isProxyOtelOnly()) {
|
|
459
|
+
enqueueLifecycleEvent({ ...input, event: "request_accepted" });
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
434
462
|
const confirmed = new Promise((resolve) => {
|
|
435
463
|
enqueueLifecycleEvent({ ...input, event: "request_accepted" }, resolve);
|
|
436
464
|
});
|
|
@@ -462,6 +490,8 @@ export async function flushProxyLifecycleEvents(timeoutMs = 5_000) {
|
|
|
462
490
|
export function getProxyLifecycleLoggerSnapshot() {
|
|
463
491
|
return {
|
|
464
492
|
enabled: loggerEnabled,
|
|
493
|
+
sink: isProxyOtelOnly() ? "otel" : "file",
|
|
494
|
+
admissionPolicy: isProxyOtelOnly() ? "best-effort" : "durable-file",
|
|
465
495
|
schemaVersion: SCHEMA_VERSION,
|
|
466
496
|
processInstanceId,
|
|
467
497
|
nextSequence,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* when a LoggerProvider is configured via OpenTelemetry instrumentation.
|
|
6
6
|
* Useful for debugging and auditing proxy traffic.
|
|
7
7
|
*/
|
|
8
|
+
import { emitProxyOtelEvent, getProxyOtelLogSnapshot, initializeProxyOtelLogs, isProxyOtelOnly, } from "./otelLogSink.js";
|
|
8
9
|
import { join } from "path";
|
|
9
10
|
import { homedir } from "os";
|
|
10
11
|
import { logger } from "../utils/logger.js";
|
|
@@ -46,6 +47,8 @@ const metadataSinks = {
|
|
|
46
47
|
export function getRequestLoggerSnapshot() {
|
|
47
48
|
return {
|
|
48
49
|
enabled: logEnabled,
|
|
50
|
+
diskEnabled: logEnabled && !isProxyOtelOnly(),
|
|
51
|
+
otel: getProxyOtelLogSnapshot(),
|
|
49
52
|
requests: { ...metadataSinks.requests },
|
|
50
53
|
attempts: { ...metadataSinks.attempts },
|
|
51
54
|
debug: { ...metadataSinks.debug },
|
|
@@ -155,6 +158,12 @@ export function initRequestLogger(enabled = true, customLogsDir) {
|
|
|
155
158
|
configureProxyLifecycleLogger({ enabled: false });
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
161
|
+
if (isProxyOtelOnly()) {
|
|
162
|
+
initializeProxyOtelLogs();
|
|
163
|
+
logDir = null;
|
|
164
|
+
configureProxyLifecycleLogger({ enabled: true });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
158
167
|
try {
|
|
159
168
|
logDir = customLogsDir ?? join(homedir(), ".neurolink", "logs");
|
|
160
169
|
if (!existsSync(logDir)) {
|
|
@@ -183,7 +192,7 @@ export async function logRequest(entry) {
|
|
|
183
192
|
? "handler_error"
|
|
184
193
|
: "completed";
|
|
185
194
|
notifyProxyFinalLog(entry);
|
|
186
|
-
if (!logEnabled || !logDir) {
|
|
195
|
+
if (!logEnabled || (!logDir && !isProxyOtelOnly())) {
|
|
187
196
|
return;
|
|
188
197
|
}
|
|
189
198
|
// Only use OtelBridge if traceId not already provided by caller.
|
|
@@ -197,6 +206,10 @@ export async function logRequest(entry) {
|
|
|
197
206
|
entry.spanId = traceCtx.spanId;
|
|
198
207
|
}
|
|
199
208
|
}
|
|
209
|
+
if (isProxyOtelOnly()) {
|
|
210
|
+
await emitOtlpLogRecord(entry);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
200
213
|
const logFile = join(logDir, `proxy-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
201
214
|
const line = JSON.stringify(entry) + "\n";
|
|
202
215
|
try {
|
|
@@ -206,7 +219,7 @@ export async function logRequest(entry) {
|
|
|
206
219
|
// Non-fatal — don't crash proxy for logging failures
|
|
207
220
|
}
|
|
208
221
|
// Emit OTLP log record (additive — file logging is the primary sink)
|
|
209
|
-
emitOtlpLogRecord(entry);
|
|
222
|
+
void emitOtlpLogRecord(entry);
|
|
210
223
|
}
|
|
211
224
|
/**
|
|
212
225
|
* Log an upstream attempt separately from the final request outcome.
|
|
@@ -214,7 +227,7 @@ export async function logRequest(entry) {
|
|
|
214
227
|
* or OTLP-derived dashboard panels.
|
|
215
228
|
*/
|
|
216
229
|
export async function logRequestAttempt(entry) {
|
|
217
|
-
if (!logEnabled || !logDir) {
|
|
230
|
+
if (!logEnabled || (!logDir && !isProxyOtelOnly())) {
|
|
218
231
|
return;
|
|
219
232
|
}
|
|
220
233
|
if (!entry.traceId) {
|
|
@@ -225,6 +238,10 @@ export async function logRequestAttempt(entry) {
|
|
|
225
238
|
entry.spanId = traceCtx.spanId;
|
|
226
239
|
}
|
|
227
240
|
}
|
|
241
|
+
if (isProxyOtelOnly()) {
|
|
242
|
+
emitProxyOtelEvent("attempt", entry);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
228
245
|
const logFile = join(logDir, `proxy-attempts-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
229
246
|
const line = JSON.stringify(entry) + "\n";
|
|
230
247
|
try {
|
|
@@ -242,6 +259,9 @@ export async function logRequestAttempt(entry) {
|
|
|
242
259
|
* where OTel initialization completes after the first log request.
|
|
243
260
|
*/
|
|
244
261
|
async function resolveLoggerProvider() {
|
|
262
|
+
if (isProxyOtelOnly()) {
|
|
263
|
+
return initializeProxyOtelLogs();
|
|
264
|
+
}
|
|
245
265
|
if (otelLoggerProvider === false) {
|
|
246
266
|
return undefined;
|
|
247
267
|
} // permanently unavailable
|
|
@@ -275,7 +295,7 @@ async function resolveLoggerProvider() {
|
|
|
275
295
|
* Non-blocking, non-fatal — failures are silently swallowed.
|
|
276
296
|
*/
|
|
277
297
|
function emitOtlpLogRecord(entry) {
|
|
278
|
-
resolveLoggerProvider()
|
|
298
|
+
return resolveLoggerProvider()
|
|
279
299
|
.then((provider) => {
|
|
280
300
|
if (!provider) {
|
|
281
301
|
return;
|
|
@@ -293,8 +313,11 @@ function emitOtlpLogRecord(entry) {
|
|
|
293
313
|
otelLogger.emit({
|
|
294
314
|
severityNumber,
|
|
295
315
|
severityText,
|
|
296
|
-
body:
|
|
316
|
+
body: isProxyOtelOnly()
|
|
317
|
+
? JSON.stringify(entry)
|
|
318
|
+
: `${entry.method} ${entry.path} → ${entry.responseStatus} (${entry.responseTimeMs}ms)`,
|
|
297
319
|
attributes: {
|
|
320
|
+
"proxy.record_kind": "request_final",
|
|
298
321
|
// Core request fields
|
|
299
322
|
"request.id": entry.requestId,
|
|
300
323
|
"http.method": entry.method,
|
|
@@ -440,6 +463,7 @@ function emitOtlpBodyLogRecord(entry, stored) {
|
|
|
440
463
|
body: chunk,
|
|
441
464
|
attributes: {
|
|
442
465
|
"event.name": "proxy.body_capture",
|
|
466
|
+
"proxy.record_kind": "body",
|
|
443
467
|
"request.id": entry.requestId,
|
|
444
468
|
"body.phase": entry.phase,
|
|
445
469
|
"body.chunk_index": chunkIndex,
|
|
@@ -484,7 +508,7 @@ function emitOtlpBodyLogRecord(entry, stored) {
|
|
|
484
508
|
}
|
|
485
509
|
/** Capture an owned request body with bounded processing and tracked index/export publication. */
|
|
486
510
|
export async function logBodyCapture(entry) {
|
|
487
|
-
if (!logEnabled || !logDir) {
|
|
511
|
+
if (!logEnabled || (!logDir && !isProxyOtelOnly())) {
|
|
488
512
|
return;
|
|
489
513
|
}
|
|
490
514
|
// Borrowed traffic is somebody else's conversation. Capturing it would leave
|
|
@@ -507,7 +531,9 @@ export async function logBodyCapture(entry) {
|
|
|
507
531
|
const redactedHeaders = processed.headers;
|
|
508
532
|
const stored = processed.stored;
|
|
509
533
|
const dateStr = new Date(metadata.timestamp).toISOString().split("T")[0];
|
|
510
|
-
const logFile =
|
|
534
|
+
const logFile = destination
|
|
535
|
+
? join(destination, `proxy-debug-${dateStr}.jsonl`)
|
|
536
|
+
: undefined;
|
|
511
537
|
const indexEntry = {
|
|
512
538
|
timestamp: metadata.timestamp,
|
|
513
539
|
type: "body_capture",
|
|
@@ -538,8 +564,13 @@ export async function logBodyCapture(entry) {
|
|
|
538
564
|
indexEntry.traceId = traceCtx.traceId;
|
|
539
565
|
indexEntry.spanId = traceCtx.spanId;
|
|
540
566
|
}
|
|
567
|
+
if (isProxyOtelOnly()) {
|
|
568
|
+
emitProxyOtelEvent("body_capture_index", indexEntry);
|
|
569
|
+
}
|
|
541
570
|
try {
|
|
542
|
-
|
|
571
|
+
if (logFile) {
|
|
572
|
+
await appendMetadataRecord(logFile, JSON.stringify(indexEntry) + "\n", "debug", { waitForPersistence: true });
|
|
573
|
+
}
|
|
543
574
|
}
|
|
544
575
|
catch {
|
|
545
576
|
// Non-fatal
|
|
@@ -596,12 +627,11 @@ export async function logFullRequestResponse(entry) {
|
|
|
596
627
|
* These are invisible in normal request logs since the 200 was already recorded.
|
|
597
628
|
*/
|
|
598
629
|
export async function logStreamError(entry) {
|
|
599
|
-
if (!logEnabled || !logDir) {
|
|
630
|
+
if (!logEnabled || (!logDir && !isProxyOtelOnly())) {
|
|
600
631
|
return;
|
|
601
632
|
}
|
|
602
633
|
const bridge = new OtelBridge();
|
|
603
634
|
const traceCtx = bridge.getCurrentTraceContext();
|
|
604
|
-
const logFile = join(logDir, `proxy-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
605
635
|
const logEntry = {
|
|
606
636
|
...entry,
|
|
607
637
|
responseStatus: 200,
|
|
@@ -614,6 +644,11 @@ export async function logStreamError(entry) {
|
|
|
614
644
|
logEntry.traceId = traceCtx.traceId;
|
|
615
645
|
logEntry.spanId = traceCtx.spanId;
|
|
616
646
|
}
|
|
647
|
+
if (isProxyOtelOnly()) {
|
|
648
|
+
emitProxyOtelEvent("stream_error", logEntry);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const logFile = join(logDir, `proxy-${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
617
652
|
try {
|
|
618
653
|
await appendMetadataRecord(logFile, JSON.stringify(logEntry) + "\n", "requests");
|
|
619
654
|
}
|
package/dist/proxy/workerLog.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { isProxyOtelOnly } from "./otelLogSink.js";
|
|
1
2
|
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, statSync, } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { basename, join } from "node:path";
|
|
4
5
|
/** Open a restrictive worker log without making proxy startup depend on it. */
|
|
5
6
|
export function openProxyWorkerLog(filename, logDir = join(homedir(), ".neurolink", "logs")) {
|
|
7
|
+
if (isProxyOtelOnly()) {
|
|
8
|
+
return { stdio: "ignore", close: () => undefined };
|
|
9
|
+
}
|
|
6
10
|
let fd;
|
|
7
11
|
try {
|
|
8
12
|
if (basename(filename) !== filename) {
|
package/dist/types/aiCompat.d.ts
CHANGED
package/dist/types/proxy.d.ts
CHANGED
|
@@ -644,6 +644,8 @@ export type ProxyBodyCaptureWorkerSnapshot = {
|
|
|
644
644
|
lastError?: string;
|
|
645
645
|
};
|
|
646
646
|
export type ProxyRequestLoggerSnapshot = {
|
|
647
|
+
diskEnabled?: boolean;
|
|
648
|
+
otel?: ReturnType<typeof import("../proxy/otelLogSink.js").getProxyOtelLogSnapshot>;
|
|
647
649
|
bodyCapture?: ProxyBodyCaptureWorkerSnapshot;
|
|
648
650
|
enabled: boolean;
|
|
649
651
|
requests: ProxyRequestLogSinkSnapshot;
|
|
@@ -1685,6 +1687,8 @@ export type ProxyRuntimeSample = {
|
|
|
1685
1687
|
};
|
|
1686
1688
|
/** Data-quality counters for the bounded lifecycle metadata sink. */
|
|
1687
1689
|
export type ProxyLifecycleLoggerSnapshot = {
|
|
1690
|
+
sink?: "otel" | "file";
|
|
1691
|
+
admissionPolicy?: "best-effort" | "durable-file";
|
|
1688
1692
|
enabled: boolean;
|
|
1689
1693
|
schemaVersion: number;
|
|
1690
1694
|
processInstanceId: string;
|