@autter/runtime-node 1.3.2 → 1.3.4

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/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @autter/runtime-node
2
2
 
3
+ The server tracker exports per-instance RSS, heap, memory limit, and GC
4
+ metrics by default. See [memory pressure incidents](../../docs/MEMORY-PRESSURE.md)
5
+ for ECS/Kubernetes OOM event forwarding and optional heap profiles. Set
6
+ `memoryMetrics: false` only if another exporter already emits equivalent
7
+ process metrics.
8
+
3
9
  Autter Runtime for Node.js — two halves in one package:
4
10
 
5
11
  1. **Same-origin browser relay** for `@autter/runtime-browser`
package/dist/index.cjs CHANGED
@@ -83,6 +83,7 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
83
83
  "track_event",
84
84
  "outcome",
85
85
  "request_failure",
86
+ "csp_violation",
86
87
  "timing"
87
88
  ]);
88
89
  var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
@@ -255,7 +256,7 @@ function createBrowserRelayHandler(opts) {
255
256
  // src/server.ts
256
257
  var import_node_http = require("http");
257
258
  var import_node_crypto = require("crypto");
258
- var import_api = require("@opentelemetry/api");
259
+ var import_api2 = require("@opentelemetry/api");
259
260
  var import_core2 = require("@opentelemetry/core");
260
261
  var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
261
262
  var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
@@ -707,6 +708,51 @@ function makeRedactor(options) {
707
708
  return (attributes) => redactWith(attributes, r);
708
709
  }
709
710
 
711
+ // src/memory.ts
712
+ var import_node_perf_hooks = require("perf_hooks");
713
+ var import_node_v8 = require("v8");
714
+ var import_api = require("@opentelemetry/api");
715
+ function startMemoryMetrics() {
716
+ const meter = import_api.metrics.getMeter("autter-process-memory");
717
+ const rss = meter.createObservableGauge("autter.process.memory.rss", { unit: "By" });
718
+ const heapUsed = meter.createObservableGauge("autter.process.memory.heap.used", { unit: "By" });
719
+ const heapLimit = meter.createObservableGauge("autter.process.memory.heap.limit", { unit: "By" });
720
+ const containerLimit = meter.createObservableGauge("autter.process.memory.limit", { unit: "By" });
721
+ const afterGc = meter.createObservableGauge("autter.process.memory.heap.after_gc", { unit: "By" });
722
+ const gcCount = meter.createObservableCounter("autter.process.gc.count", { unit: "{collection}" });
723
+ const gcDuration = meter.createObservableCounter("autter.process.gc.duration", { unit: "ms" });
724
+ let collections = 0;
725
+ let durationMs = 0;
726
+ let lastAfterGc = null;
727
+ const callbacks = [
728
+ [rss, (result) => result.observe(process.memoryUsage().rss)],
729
+ [heapUsed, (result) => result.observe(process.memoryUsage().heapUsed)],
730
+ [heapLimit, (result) => result.observe((0, import_node_v8.getHeapStatistics)().heap_size_limit)],
731
+ [containerLimit, (result) => {
732
+ const constrained = process.constrainedMemory?.() ?? 0;
733
+ if (constrained > 0) result.observe(constrained);
734
+ }],
735
+ [afterGc, (result) => {
736
+ if (lastAfterGc !== null) result.observe(lastAfterGc);
737
+ }],
738
+ [gcCount, (result) => result.observe(collections)],
739
+ [gcDuration, (result) => result.observe(durationMs)]
740
+ ];
741
+ for (const [instrument, callback] of callbacks) instrument.addCallback(callback);
742
+ const observer = new import_node_perf_hooks.PerformanceObserver((list) => {
743
+ for (const entry of list.getEntries()) {
744
+ collections++;
745
+ durationMs += entry.duration;
746
+ lastAfterGc = process.memoryUsage().heapUsed;
747
+ }
748
+ });
749
+ observer.observe({ entryTypes: ["gc"] });
750
+ return () => {
751
+ observer.disconnect();
752
+ for (const [instrument, callback] of callbacks) instrument.removeCallback(callback);
753
+ };
754
+ }
755
+
710
756
  // src/server.ts
711
757
  var LLM_MARKER_ATTRIBUTES = [
712
758
  "gen_ai.operation.name",
@@ -769,14 +815,14 @@ var RETENTION_MAX_BUFFERED_SPANS = 5e3;
769
815
  var RETENTION_SWEEP_INTERVAL_MS = 1e4;
770
816
  var RETENTION_TRACE_TTL_MS = 3e4;
771
817
  function spanIndicatesError(span) {
772
- if (span.status.code === import_api.SpanStatusCode.ERROR) return true;
818
+ if (span.status.code === import_api2.SpanStatusCode.ERROR) return true;
773
819
  return span.events.some((event) => event.name === "exception");
774
820
  }
775
821
  var TAIL_RETAINED_ATTR = "autter.tail_retained";
776
822
  function withSampledFlag(span) {
777
823
  const spanContext = {
778
824
  ...span.spanContext(),
779
- traceFlags: span.spanContext().traceFlags | import_api.TraceFlags.SAMPLED
825
+ traceFlags: span.spanContext().traceFlags | import_api2.TraceFlags.SAMPLED
780
826
  };
781
827
  return {
782
828
  name: span.name,
@@ -816,11 +862,11 @@ var ErrorTraceRetentionProcessor = class {
816
862
  }
817
863
  onEnd(span) {
818
864
  const ctx = span.spanContext();
819
- if ((ctx.traceFlags & import_api.TraceFlags.SAMPLED) !== 0) return;
865
+ if ((ctx.traceFlags & import_api2.TraceFlags.SAMPLED) !== 0) return;
820
866
  const entry = this.entryFor(ctx.traceId);
821
867
  if (entry.retained) {
822
868
  this.forward(span);
823
- } else if (this.retainErrors && spanIndicatesError(span) || this.slowThresholdMs > 0 && span.kind === import_api.SpanKind.SERVER && span.duration[0] * 1e3 + span.duration[1] / 1e6 >= this.slowThresholdMs) {
869
+ } else if (this.retainErrors && spanIndicatesError(span) || this.slowThresholdMs > 0 && span.kind === import_api2.SpanKind.SERVER && span.duration[0] * 1e3 + span.duration[1] / 1e6 >= this.slowThresholdMs) {
824
870
  entry.retained = true;
825
871
  this.flush(entry);
826
872
  this.forward(span);
@@ -835,8 +881,8 @@ var ErrorTraceRetentionProcessor = class {
835
881
  }
836
882
  /** Promote the active trace, if any — called on captured errors. */
837
883
  retainActiveTrace() {
838
- const active2 = import_api.trace.getActiveSpan()?.spanContext();
839
- if (!active2 || (active2.traceFlags & import_api.TraceFlags.SAMPLED) !== 0) return;
884
+ const active2 = import_api2.trace.getActiveSpan()?.spanContext();
885
+ if (!active2 || (active2.traceFlags & import_api2.TraceFlags.SAMPLED) !== 0) return;
840
886
  const entry = this.entryFor(active2.traceId);
841
887
  if (entry.retained) return;
842
888
  entry.retained = true;
@@ -925,7 +971,7 @@ function usageAttributes(usage) {
925
971
  }
926
972
  async function runLlmSpan(tracer, info, fn) {
927
973
  const span = tracer.startSpan(`${info.operation ?? "chat"} ${info.model}`, {
928
- kind: import_api.SpanKind.CLIENT,
974
+ kind: import_api2.SpanKind.CLIENT,
929
975
  attributes: llmBaseAttributes(info)
930
976
  });
931
977
  const setResponseModel = (model) => span.setAttribute("gen_ai.response.model", model);
@@ -937,11 +983,11 @@ async function runLlmSpan(tracer, info, fn) {
937
983
  setAttributes: (attributes) => span.setAttributes(attributes)
938
984
  };
939
985
  try {
940
- const result = await import_api.context.with(
941
- import_api.trace.setSpan(import_api.context.active(), span),
986
+ const result = await import_api2.context.with(
987
+ import_api2.trace.setSpan(import_api2.context.active(), span),
942
988
  () => fn(handle)
943
989
  );
944
- span.setStatus({ code: import_api.SpanStatusCode.OK });
990
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
945
991
  return result;
946
992
  } catch (err) {
947
993
  if (err instanceof Error) {
@@ -949,7 +995,7 @@ async function runLlmSpan(tracer, info, fn) {
949
995
  span.setAttribute("error.type", err.name);
950
996
  }
951
997
  span.setStatus({
952
- code: import_api.SpanStatusCode.ERROR,
998
+ code: import_api2.SpanStatusCode.ERROR,
953
999
  message: err instanceof Error ? err.message : String(err)
954
1000
  });
955
1001
  throw err;
@@ -964,7 +1010,7 @@ function recordLlmCall(tracer, call) {
964
1010
  const span = tracer.startSpan(
965
1011
  `${call.operation ?? "chat"} ${call.model}`,
966
1012
  {
967
- kind: import_api.SpanKind.CLIENT,
1013
+ kind: import_api2.SpanKind.CLIENT,
968
1014
  startTime,
969
1015
  attributes: { ...llmBaseAttributes(call), ...usageAttributes(call) }
970
1016
  }
@@ -975,27 +1021,27 @@ function recordLlmCall(tracer, call) {
975
1021
  span.setAttribute("error.type", call.error.name);
976
1022
  }
977
1023
  span.setStatus({
978
- code: import_api.SpanStatusCode.ERROR,
1024
+ code: import_api2.SpanStatusCode.ERROR,
979
1025
  message: call.error instanceof Error ? call.error.message : String(call.error)
980
1026
  });
981
1027
  } else {
982
- span.setStatus({ code: import_api.SpanStatusCode.OK });
1028
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
983
1029
  }
984
1030
  span.end(endTime);
985
1031
  }
986
1032
  async function runWithSpan(tracer, name, fn, attributes) {
987
1033
  const span = tracer.startSpan(name, { attributes });
988
1034
  try {
989
- const result = await import_api.context.with(
990
- import_api.trace.setSpan(import_api.context.active(), span),
1035
+ const result = await import_api2.context.with(
1036
+ import_api2.trace.setSpan(import_api2.context.active(), span),
991
1037
  fn
992
1038
  );
993
- span.setStatus({ code: import_api.SpanStatusCode.OK });
1039
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
994
1040
  return result;
995
1041
  } catch (err) {
996
1042
  if (err instanceof Error) span.recordException(err);
997
1043
  span.setStatus({
998
- code: import_api.SpanStatusCode.ERROR,
1044
+ code: import_api2.SpanStatusCode.ERROR,
999
1045
  message: err instanceof Error ? err.message : String(err)
1000
1046
  });
1001
1047
  throw err;
@@ -1013,7 +1059,7 @@ function expressRouteOf(req) {
1013
1059
  }
1014
1060
  function captureExpressRoute(_span, response) {
1015
1061
  if (!(response instanceof import_node_http.ServerResponse)) return;
1016
- const rpcMetadata = (0, import_core2.getRPCMetadata)(import_api.context.active());
1062
+ const rpcMetadata = (0, import_core2.getRPCMetadata)(import_api2.context.active());
1017
1063
  if (rpcMetadata?.type !== import_core2.RPCType.HTTP) return;
1018
1064
  const setRoute = () => {
1019
1065
  if (rpcMetadata.route) return;
@@ -1039,7 +1085,7 @@ function initAutterServer(options) {
1039
1085
  activeRedactor = makeRedactor(options.redactAttributes ?? true);
1040
1086
  const resource = new import_resources.Resource({
1041
1087
  [import_semantic_conventions.ATTR_SERVICE_NAME]: options.service,
1042
- "service.instance.id": (0, import_node_crypto.randomUUID)(),
1088
+ "service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || (0, import_node_crypto.randomUUID)()).slice(0, 128),
1043
1089
  ...options.release ? { [import_semantic_conventions.ATTR_SERVICE_VERSION]: options.release } : {},
1044
1090
  "deployment.environment": environment,
1045
1091
  // Tells the ingester request metrics arrive on the metrics pipe, so
@@ -1104,6 +1150,7 @@ function initAutterServer(options) {
1104
1150
  ]
1105
1151
  });
1106
1152
  sdk.start();
1153
+ const stopMemoryMetrics = options.memoryMetrics === false ? null : startMemoryMetrics();
1107
1154
  const alwaysOnProvider = new import_sdk_trace_base.BasicTracerProvider({
1108
1155
  resource,
1109
1156
  sampler: new import_sdk_trace_base.AlwaysOnSampler(),
@@ -1125,7 +1172,7 @@ function initAutterServer(options) {
1125
1172
  const isError = error instanceof Error;
1126
1173
  const message = isError ? error.message : String(error);
1127
1174
  const redacted = activeRedactor(attributes);
1128
- const activeSpan = import_api.trace.getActiveSpan();
1175
+ const activeSpan = import_api2.trace.getActiveSpan();
1129
1176
  const reuseActive = activeSpan?.isRecording() === true;
1130
1177
  const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
1131
1178
  span.setAttributes({ "autter.severity": "error", ...redacted });
@@ -1150,7 +1197,7 @@ function initAutterServer(options) {
1150
1197
  ...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {}
1151
1198
  });
1152
1199
  }
1153
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1200
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
1154
1201
  if (!reuseActive) span.end();
1155
1202
  }
1156
1203
  function captureMessage2(message, severity = "warning", attributes) {
@@ -1168,13 +1215,13 @@ function initAutterServer(options) {
1168
1215
  ...stack ? { "exception.stacktrace": stack } : {},
1169
1216
  "autter.severity": severity
1170
1217
  });
1171
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1218
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
1172
1219
  span.end();
1173
1220
  }
1174
1221
  function reportOutcome2(name, message, attributes) {
1175
1222
  errorTraceBuffer?.retainActiveTrace();
1176
1223
  telemetryStats.markCaptured();
1177
- const activeSpan = import_api.trace.getActiveSpan();
1224
+ const activeSpan = import_api2.trace.getActiveSpan();
1178
1225
  const reuseActive = activeSpan?.isRecording() === true;
1179
1226
  const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
1180
1227
  const safeOutcome = activeRedactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
@@ -1186,7 +1233,7 @@ function initAutterServer(options) {
1186
1233
  "autter.outcome.name": safeName,
1187
1234
  "autter.outcome.message": safeMessage
1188
1235
  });
1189
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: safeMessage });
1236
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: safeMessage });
1190
1237
  if (!reuseActive) span.end();
1191
1238
  }
1192
1239
  if (options.captureGlobalErrors !== false) {
@@ -1225,6 +1272,7 @@ function initAutterServer(options) {
1225
1272
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1226
1273
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1227
1274
  shutdown: async () => {
1275
+ stopMemoryMetrics?.();
1228
1276
  active = null;
1229
1277
  activeAlwaysOnProvider = null;
1230
1278
  autoFlushHandle?.dispose();
@@ -1243,35 +1291,35 @@ function captureException(error, attributes) {
1243
1291
  active.captureException(error, attributes);
1244
1292
  return;
1245
1293
  }
1246
- const span = import_api.trace.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", { attributes });
1294
+ const span = import_api2.trace.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", { attributes });
1247
1295
  if (error instanceof Error) span.recordException(error);
1248
1296
  span.setStatus({
1249
- code: import_api.SpanStatusCode.ERROR,
1297
+ code: import_api2.SpanStatusCode.ERROR,
1250
1298
  message: error instanceof Error ? error.message : String(error)
1251
1299
  });
1252
1300
  span.end();
1253
1301
  }
1254
1302
  function withProcessSpan(name, fn, attributes) {
1255
1303
  if (active) return active.withProcessSpan(name, fn, attributes);
1256
- return runWithSpan(import_api.trace.getTracer("autter-processes"), name, fn, attributes);
1304
+ return runWithSpan(import_api2.trace.getTracer("autter-processes"), name, fn, attributes);
1257
1305
  }
1258
1306
  function withLlmCall(info, fn) {
1259
1307
  if (active) return active.withLlmCall(info, fn);
1260
- return runLlmSpan(import_api.trace.getTracer("autter-llm"), info, fn);
1308
+ return runLlmSpan(import_api2.trace.getTracer("autter-llm"), info, fn);
1261
1309
  }
1262
1310
  function trackLlmCall(call) {
1263
1311
  if (active) {
1264
1312
  active.trackLlmCall(call);
1265
1313
  return;
1266
1314
  }
1267
- recordLlmCall(import_api.trace.getTracer("autter-llm"), call);
1315
+ recordLlmCall(import_api2.trace.getTracer("autter-llm"), call);
1268
1316
  }
1269
1317
  function captureMessage(message, severity = "warning", attributes) {
1270
1318
  if (active) {
1271
1319
  active.captureMessage(message, severity, attributes);
1272
1320
  return;
1273
1321
  }
1274
- const span = import_api.trace.getTracer("autter-errors").startSpan("Message", {
1322
+ const span = import_api2.trace.getTracer("autter-errors").startSpan("Message", {
1275
1323
  attributes: { "autter.severity": severity, ...attributes }
1276
1324
  });
1277
1325
  span.addEvent("exception", {
@@ -1279,12 +1327,12 @@ function captureMessage(message, severity = "warning", attributes) {
1279
1327
  "exception.message": message,
1280
1328
  "autter.severity": severity
1281
1329
  });
1282
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1330
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
1283
1331
  span.end();
1284
1332
  }
1285
1333
  function reportOutcome(name, message, attributes) {
1286
1334
  if (active) return active.reportOutcome(name, message, attributes);
1287
- const span = import_api.trace.getTracer("autter-outcomes").startSpan("OutcomeFailure");
1335
+ const span = import_api2.trace.getTracer("autter-outcomes").startSpan("OutcomeFailure");
1288
1336
  const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
1289
1337
  const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
1290
1338
  const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
@@ -1294,7 +1342,7 @@ function reportOutcome(name, message, attributes) {
1294
1342
  "autter.outcome.name": safeName,
1295
1343
  "autter.outcome.message": safeMessage
1296
1344
  });
1297
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: safeMessage });
1345
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: safeMessage });
1298
1346
  span.end();
1299
1347
  }
1300
1348
  function makeSafeCapture(options) {
@@ -1305,7 +1353,7 @@ function makeSafeCapture(options) {
1305
1353
  };
1306
1354
  }
1307
1355
  function alwaysOnTracer(name) {
1308
- return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : import_api.trace.getTracer(name);
1356
+ return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : import_api2.trace.getTracer(name);
1309
1357
  }
1310
1358
  function autterLlmTracer() {
1311
1359
  return alwaysOnTracer("autter-llm");
@@ -1325,7 +1373,7 @@ async function emitLlmSelftestTrace() {
1325
1373
  attributes: { "autter.selftest": true }
1326
1374
  },
1327
1375
  (llm) => {
1328
- traceId = import_api.trace.getActiveSpan()?.spanContext().traceId ?? "";
1376
+ traceId = import_api2.trace.getActiveSpan()?.spanContext().traceId ?? "";
1329
1377
  llm.setUsage({ inputTokens: 1, outputTokens: 1 });
1330
1378
  llm.setCost(0);
1331
1379
  }
@@ -1335,7 +1383,7 @@ async function emitLlmSelftestTrace() {
1335
1383
  }
1336
1384
 
1337
1385
  // src/llm-instrument.ts
1338
- var import_api2 = require("@opentelemetry/api");
1386
+ var import_api3 = require("@opentelemetry/api");
1339
1387
  var CALL_METHODS = /* @__PURE__ */ new Set([
1340
1388
  "create",
1341
1389
  // openai chat/completions/embeddings/responses, anthropic messages
@@ -1393,7 +1441,7 @@ function applyUsage(span, usage) {
1393
1441
  function endWithError(span, err) {
1394
1442
  if (err instanceof Error) span.recordException(err);
1395
1443
  span.setStatus({
1396
- code: import_api2.SpanStatusCode.ERROR,
1444
+ code: import_api3.SpanStatusCode.ERROR,
1397
1445
  message: err instanceof Error ? err.message : String(err)
1398
1446
  });
1399
1447
  span.end();
@@ -1413,7 +1461,7 @@ function wrapStream(stream, span) {
1413
1461
  if (err !== void 0) {
1414
1462
  endWithError(span, err);
1415
1463
  } else {
1416
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1464
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
1417
1465
  span.end();
1418
1466
  }
1419
1467
  };
@@ -1468,7 +1516,7 @@ function instrumentLlmClient(client, options) {
1468
1516
  const operation = /embed/i.test(callPath) ? "embeddings" : "chat";
1469
1517
  const userId = resolveUserId();
1470
1518
  const span = autterLlmTracer().startSpan(`${operation} ${request.model}`, {
1471
- kind: import_api2.SpanKind.CLIENT,
1519
+ kind: import_api3.SpanKind.CLIENT,
1472
1520
  attributes: {
1473
1521
  "gen_ai.operation.name": operation,
1474
1522
  "gen_ai.provider.name": provider,
@@ -1490,7 +1538,7 @@ function instrumentLlmClient(client, options) {
1490
1538
  return wrapStream(result, span);
1491
1539
  }
1492
1540
  applyUsage(span, extractUsage(result));
1493
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1541
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
1494
1542
  span.end();
1495
1543
  return result;
1496
1544
  }
@@ -1500,7 +1548,7 @@ function instrumentLlmClient(client, options) {
1500
1548
  return wrapStream(resolved, span);
1501
1549
  }
1502
1550
  applyUsage(span, extractUsage(resolved));
1503
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1551
+ span.setStatus({ code: import_api3.SpanStatusCode.OK });
1504
1552
  span.end();
1505
1553
  return resolved;
1506
1554
  },
package/dist/index.d.cts CHANGED
@@ -100,6 +100,8 @@ interface AutterServerOptions {
100
100
  /** Ingester base URL. Default: https://otlp.autter.dev */
101
101
  endpoint?: string;
102
102
  service: string;
103
+ /** Stable for this process lifetime; use an ECS task/container ID or pod UID plus restart count to correlate platform events. */
104
+ instanceId?: string;
103
105
  environment?: string;
104
106
  /** e.g. a git SHA. Maps to service.version / release. */
105
107
  release?: string;
@@ -119,6 +121,8 @@ interface AutterServerOptions {
119
121
  retainTracesAboveMs?: number;
120
122
  /** Metric export interval. Default 60_000 ms. */
121
123
  metricIntervalMs?: number;
124
+ /** Collect RSS, heap, limit, and GC signals per process instance. Default true. */
125
+ memoryMetrics?: boolean;
122
126
  /** Capture crashing exceptions via process.uncaughtExceptionMonitor (default true). */
123
127
  captureGlobalErrors?: boolean;
124
128
  /**
package/dist/index.d.ts CHANGED
@@ -100,6 +100,8 @@ interface AutterServerOptions {
100
100
  /** Ingester base URL. Default: https://otlp.autter.dev */
101
101
  endpoint?: string;
102
102
  service: string;
103
+ /** Stable for this process lifetime; use an ECS task/container ID or pod UID plus restart count to correlate platform events. */
104
+ instanceId?: string;
103
105
  environment?: string;
104
106
  /** e.g. a git SHA. Maps to service.version / release. */
105
107
  release?: string;
@@ -119,6 +121,8 @@ interface AutterServerOptions {
119
121
  retainTracesAboveMs?: number;
120
122
  /** Metric export interval. Default 60_000 ms. */
121
123
  metricIntervalMs?: number;
124
+ /** Collect RSS, heap, limit, and GC signals per process instance. Default true. */
125
+ memoryMetrics?: boolean;
122
126
  /** Capture crashing exceptions via process.uncaughtExceptionMonitor (default true). */
123
127
  captureGlobalErrors?: boolean;
124
128
  /**
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
32
32
  "track_event",
33
33
  "outcome",
34
34
  "request_failure",
35
+ "csp_violation",
35
36
  "timing"
36
37
  ]);
37
38
  var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
@@ -675,6 +676,51 @@ function makeRedactor(options) {
675
676
  return (attributes) => redactWith(attributes, r);
676
677
  }
677
678
 
679
+ // src/memory.ts
680
+ import { PerformanceObserver } from "perf_hooks";
681
+ import { getHeapStatistics } from "v8";
682
+ import { metrics } from "@opentelemetry/api";
683
+ function startMemoryMetrics() {
684
+ const meter = metrics.getMeter("autter-process-memory");
685
+ const rss = meter.createObservableGauge("autter.process.memory.rss", { unit: "By" });
686
+ const heapUsed = meter.createObservableGauge("autter.process.memory.heap.used", { unit: "By" });
687
+ const heapLimit = meter.createObservableGauge("autter.process.memory.heap.limit", { unit: "By" });
688
+ const containerLimit = meter.createObservableGauge("autter.process.memory.limit", { unit: "By" });
689
+ const afterGc = meter.createObservableGauge("autter.process.memory.heap.after_gc", { unit: "By" });
690
+ const gcCount = meter.createObservableCounter("autter.process.gc.count", { unit: "{collection}" });
691
+ const gcDuration = meter.createObservableCounter("autter.process.gc.duration", { unit: "ms" });
692
+ let collections = 0;
693
+ let durationMs = 0;
694
+ let lastAfterGc = null;
695
+ const callbacks = [
696
+ [rss, (result) => result.observe(process.memoryUsage().rss)],
697
+ [heapUsed, (result) => result.observe(process.memoryUsage().heapUsed)],
698
+ [heapLimit, (result) => result.observe(getHeapStatistics().heap_size_limit)],
699
+ [containerLimit, (result) => {
700
+ const constrained = process.constrainedMemory?.() ?? 0;
701
+ if (constrained > 0) result.observe(constrained);
702
+ }],
703
+ [afterGc, (result) => {
704
+ if (lastAfterGc !== null) result.observe(lastAfterGc);
705
+ }],
706
+ [gcCount, (result) => result.observe(collections)],
707
+ [gcDuration, (result) => result.observe(durationMs)]
708
+ ];
709
+ for (const [instrument, callback] of callbacks) instrument.addCallback(callback);
710
+ const observer = new PerformanceObserver((list) => {
711
+ for (const entry of list.getEntries()) {
712
+ collections++;
713
+ durationMs += entry.duration;
714
+ lastAfterGc = process.memoryUsage().heapUsed;
715
+ }
716
+ });
717
+ observer.observe({ entryTypes: ["gc"] });
718
+ return () => {
719
+ observer.disconnect();
720
+ for (const [instrument, callback] of callbacks) instrument.removeCallback(callback);
721
+ };
722
+ }
723
+
678
724
  // src/server.ts
679
725
  var LLM_MARKER_ATTRIBUTES = [
680
726
  "gen_ai.operation.name",
@@ -1007,7 +1053,7 @@ function initAutterServer(options) {
1007
1053
  activeRedactor = makeRedactor(options.redactAttributes ?? true);
1008
1054
  const resource = new Resource({
1009
1055
  [ATTR_SERVICE_NAME]: options.service,
1010
- "service.instance.id": randomUUID(),
1056
+ "service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || randomUUID()).slice(0, 128),
1011
1057
  ...options.release ? { [ATTR_SERVICE_VERSION]: options.release } : {},
1012
1058
  "deployment.environment": environment,
1013
1059
  // Tells the ingester request metrics arrive on the metrics pipe, so
@@ -1072,6 +1118,7 @@ function initAutterServer(options) {
1072
1118
  ]
1073
1119
  });
1074
1120
  sdk.start();
1121
+ const stopMemoryMetrics = options.memoryMetrics === false ? null : startMemoryMetrics();
1075
1122
  const alwaysOnProvider = new BasicTracerProvider({
1076
1123
  resource,
1077
1124
  sampler: new AlwaysOnSampler(),
@@ -1193,6 +1240,7 @@ function initAutterServer(options) {
1193
1240
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1194
1241
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1195
1242
  shutdown: async () => {
1243
+ stopMemoryMetrics?.();
1196
1244
  active = null;
1197
1245
  activeAlwaysOnProvider = null;
1198
1246
  autoFlushHandle?.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autter/runtime-node",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Autter Runtime for Node.js: same-origin browser relay handler + curated OpenTelemetry server tracker",
5
5
  "license": "MIT",
6
6
  "type": "module",