@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.
@@ -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: `${entry.method} ${entry.path} → ${entry.responseStatus} (${entry.responseTimeMs}ms)`,
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 = join(destination, `proxy-debug-${dateStr}.jsonl`);
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
- await appendMetadataRecord(logFile, JSON.stringify(indexEntry) + "\n", "debug", { waitForPersistence: true });
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
  }
@@ -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) {
@@ -275,6 +275,7 @@ export type LanguageModelV3Content = {
275
275
  } | {
276
276
  type: "reasoning";
277
277
  text: string;
278
+ providerOptions?: Record<string, Record<string, unknown>>;
278
279
  } | {
279
280
  type: "file";
280
281
  data: unknown;
@@ -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;