@frontmcp/observability 1.9.1-rc.3 → 1.9.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/esm/index.mjs CHANGED
@@ -332,7 +332,7 @@ import {
332
332
  AgentCallHook,
333
333
  DynamicPlugin,
334
334
  FlowHooksOf,
335
- FRONTMCP_CONTEXT,
335
+ FRONTMCP_CONTEXT as FRONTMCP_CONTEXT2,
336
336
  HttpHook,
337
337
  ListResourcesHook,
338
338
  ListResourceTemplatesHook,
@@ -772,15 +772,13 @@ var RequestLogCollector = class {
772
772
  }
773
773
  };
774
774
 
775
- // libs/observability/src/request-log/request-log.tokens.ts
776
- var REQUEST_LOG_COLLECTOR = /* @__PURE__ */ Symbol.for("frontmcp:observability:request-log-collector");
777
-
778
- // libs/observability/src/telemetry/telemetry.accessor.ts
775
+ // libs/observability/src/plugin/observability.hooks.ts
779
776
  import {
780
- SpanKind as SpanKind12,
781
- SpanStatusCode as SpanStatusCode2,
782
- trace as trace4
777
+ context as otelContext3,
778
+ SpanKind as SpanKind11,
779
+ trace as trace3
783
780
  } from "@opentelemetry/api";
781
+ import { sha256Hex } from "@frontmcp/utils";
784
782
 
785
783
  // libs/observability/src/otel/otel.types.ts
786
784
  var McpAttributes = {
@@ -895,43 +893,6 @@ var EnduserAttributes = {
895
893
  SCOPE: "enduser.scope"
896
894
  };
897
895
 
898
- // libs/observability/src/otel/trace-context-bridge.ts
899
- import {
900
- TraceFlags,
901
- trace,
902
- context as otelContext
903
- } from "@opentelemetry/api";
904
- function frontmcpToOTelSpanContext(tc) {
905
- return {
906
- traceId: tc.traceId,
907
- spanId: tc.parentId,
908
- traceFlags: tc.traceFlags & TraceFlags.SAMPLED ? TraceFlags.SAMPLED : TraceFlags.NONE,
909
- isRemote: true
910
- };
911
- }
912
- function otelToFrontmcpContext(sc) {
913
- const flags = sc.traceFlags.toString(16).padStart(2, "0");
914
- return {
915
- traceId: sc.traceId,
916
- parentId: sc.spanId,
917
- traceFlags: sc.traceFlags,
918
- raw: `00-${sc.traceId}-${sc.spanId}-${flags}`
919
- };
920
- }
921
- function createOTelContextFromTrace(tc) {
922
- const spanContext = frontmcpToOTelSpanContext(tc);
923
- const span = trace.wrapSpanContext(spanContext);
924
- return trace.setSpan(otelContext.active(), span);
925
- }
926
-
927
- // libs/observability/src/plugin/observability.hooks.ts
928
- import {
929
- context as otelContext3,
930
- SpanKind as SpanKind11,
931
- trace as trace3
932
- } from "@opentelemetry/api";
933
- import { sha256Hex } from "@frontmcp/utils";
934
-
935
896
  // libs/observability/src/otel/spans/auth.span.ts
936
897
  import { SpanKind as SpanKind2 } from "@opentelemetry/api";
937
898
 
@@ -939,11 +900,11 @@ import { SpanKind as SpanKind2 } from "@opentelemetry/api";
939
900
  import {
940
901
  SpanKind,
941
902
  SpanStatusCode,
942
- context as otelContext2,
943
- trace as trace2
903
+ context as otelContext,
904
+ trace
944
905
  } from "@opentelemetry/api";
945
906
  function startSpan(tracer, options) {
946
- const parentCtx = options.parentContext ?? otelContext2.active();
907
+ const parentCtx = options.parentContext ?? otelContext.active();
947
908
  const span = tracer.startSpan(
948
909
  options.name,
949
910
  {
@@ -952,7 +913,7 @@ function startSpan(tracer, options) {
952
913
  },
953
914
  parentCtx
954
915
  );
955
- const spanContext = trace2.setSpan(parentCtx, span);
916
+ const spanContext = trace.setSpan(parentCtx, span);
956
917
  return { span, context: spanContext };
957
918
  }
958
919
  function endSpanOk(span) {
@@ -969,7 +930,7 @@ function endSpanError(span, error) {
969
930
  }
970
931
  async function withSpan(tracer, options, fn) {
971
932
  const { span, context: spanContext } = startSpan(tracer, options);
972
- return otelContext2.with(spanContext, async () => {
933
+ return otelContext.with(spanContext, async () => {
973
934
  try {
974
935
  const result = await fn(span);
975
936
  endSpanOk(span);
@@ -1184,6 +1145,35 @@ function setTransportRequestType(span, requestType) {
1184
1145
  span.setAttribute(FrontMcpAttributes.TRANSPORT_REQUEST_TYPE, requestType);
1185
1146
  }
1186
1147
 
1148
+ // libs/observability/src/otel/trace-context-bridge.ts
1149
+ import {
1150
+ TraceFlags,
1151
+ trace as trace2,
1152
+ context as otelContext2
1153
+ } from "@opentelemetry/api";
1154
+ function frontmcpToOTelSpanContext(tc) {
1155
+ return {
1156
+ traceId: tc.traceId,
1157
+ spanId: tc.parentId,
1158
+ traceFlags: tc.traceFlags & TraceFlags.SAMPLED ? TraceFlags.SAMPLED : TraceFlags.NONE,
1159
+ isRemote: true
1160
+ };
1161
+ }
1162
+ function otelToFrontmcpContext(sc) {
1163
+ const flags = sc.traceFlags.toString(16).padStart(2, "0");
1164
+ return {
1165
+ traceId: sc.traceId,
1166
+ parentId: sc.spanId,
1167
+ traceFlags: sc.traceFlags,
1168
+ raw: `00-${sc.traceId}-${sc.spanId}-${flags}`
1169
+ };
1170
+ }
1171
+ function createOTelContextFromTrace(tc) {
1172
+ const spanContext = frontmcpToOTelSpanContext(tc);
1173
+ const span = trace2.wrapSpanContext(spanContext);
1174
+ return trace2.setSpan(otelContext2.active(), span);
1175
+ }
1176
+
1187
1177
  // libs/observability/src/plugin/observability.hooks.ts
1188
1178
  var SPAN_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:span");
1189
1179
  var SPAN_CTX_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:span-ctx");
@@ -1375,7 +1365,7 @@ function onToolWillApplyUI(flowCtx) {
1375
1365
  function onToolDidFinalize(flowCtx) {
1376
1366
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1377
1367
  const toolSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1378
- const error = flowCtx.state?.error;
1368
+ const error = flowCtx.state?.flowError;
1379
1369
  if (error) {
1380
1370
  if (toolSpan) endSpanError(toolSpan, error instanceof Error ? error : String(error));
1381
1371
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1428,7 +1418,7 @@ function onResourceDidExecute(flowCtx) {
1428
1418
  function onResourceDidFinalize(flowCtx) {
1429
1419
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1430
1420
  const resSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1431
- const error = flowCtx.state?.error;
1421
+ const error = flowCtx.state?.flowError;
1432
1422
  if (error) {
1433
1423
  if (resSpan) endSpanError(resSpan, error instanceof Error ? error : String(error));
1434
1424
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1484,7 +1474,7 @@ function onPromptDidExecute(flowCtx) {
1484
1474
  function onPromptDidFinalize(flowCtx) {
1485
1475
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1486
1476
  const promptSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1487
- const error = flowCtx.state?.error;
1477
+ const error = flowCtx.state?.flowError;
1488
1478
  if (error) {
1489
1479
  if (promptSpan) endSpanError(promptSpan, error instanceof Error ? error : String(error));
1490
1480
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1547,7 +1537,7 @@ function onAgentDidExecute(flowCtx) {
1547
1537
  function onAgentDidFinalize(flowCtx) {
1548
1538
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1549
1539
  const agentSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1550
- const error = flowCtx.state?.error;
1540
+ const error = flowCtx.state?.flowError;
1551
1541
  if (error) {
1552
1542
  if (agentSpan) endSpanError(agentSpan, error instanceof Error ? error : String(error));
1553
1543
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1586,7 +1576,7 @@ function onGenericFlowStage(stageName, flowCtx) {
1586
1576
  function onGenericFlowDidFinalize(flowCtx) {
1587
1577
  const span = flowCtx.state?.[SPAN_KEY];
1588
1578
  if (!span) return;
1589
- const error = flowCtx.state?.error;
1579
+ const error = flowCtx.state?.flowError;
1590
1580
  if (error) {
1591
1581
  endSpanError(span, error instanceof Error ? error : String(error));
1592
1582
  } else {
@@ -1625,7 +1615,7 @@ function onTransportDidFinalize(flowCtx) {
1625
1615
  const span = flowCtx.state?.[SPAN_KEY];
1626
1616
  if (!span) return;
1627
1617
  recordStageEvent(span, "cleanup", flowCtx.state);
1628
- const error = flowCtx.state?.error;
1618
+ const error = flowCtx.state?.flowError;
1629
1619
  if (error) {
1630
1620
  endSpanError(span, error instanceof Error ? error : String(error));
1631
1621
  } else {
@@ -1661,7 +1651,7 @@ function onAuthStage(stageName, flowCtx) {
1661
1651
  function onAuthDidFinalize(flowCtx) {
1662
1652
  const span = flowCtx.state?.[SPAN_KEY];
1663
1653
  if (!span) return;
1664
- const error = flowCtx.state?.error;
1654
+ const error = flowCtx.state?.flowError;
1665
1655
  if (error) {
1666
1656
  setAuthResult(span, "unauthorized");
1667
1657
  endSpanError(span, error instanceof Error ? error : String(error));
@@ -1710,6 +1700,64 @@ function reportStartup(data) {
1710
1700
  emitStartupReport(tracer, data);
1711
1701
  }
1712
1702
 
1703
+ // libs/observability/src/request-log/request-log.tokens.ts
1704
+ var REQUEST_LOG_COLLECTOR = /* @__PURE__ */ Symbol.for("frontmcp:observability:request-log-collector");
1705
+
1706
+ // libs/observability/src/request-log/request-log.hooks.ts
1707
+ var FRONTMCP_CONTEXT = /* @__PURE__ */ Symbol.for("frontmcp:CONTEXT");
1708
+ function resolve(flowCtx, token) {
1709
+ try {
1710
+ return flowCtx.get?.(token);
1711
+ } catch {
1712
+ return void 0;
1713
+ }
1714
+ }
1715
+ function currentRequestLog(flowCtx) {
1716
+ return resolve(flowCtx, FRONTMCP_CONTEXT)?.get(REQUEST_LOG_COLLECTOR);
1717
+ }
1718
+ function startRequestLog(flowCtx, options) {
1719
+ const context = resolve(flowCtx, FRONTMCP_CONTEXT);
1720
+ if (!context) return;
1721
+ const collector = new RequestLogCollector(
1722
+ {
1723
+ requestId: context.requestId,
1724
+ traceId: context.traceContext.traceId,
1725
+ sessionIdHash: sessionTracingId(context.sessionId).slice(0, 12),
1726
+ scopeId: context.scopeId
1727
+ },
1728
+ options
1729
+ );
1730
+ context.set(REQUEST_LOG_COLLECTOR, collector);
1731
+ const request = flowCtx.rawInput?.request;
1732
+ if (request?.method && request.path) collector.setHttpInfo(request.method, request.path);
1733
+ const rpcMethod = request?.body?.method;
1734
+ if (typeof rpcMethod === "string") collector.setRpcMethod(rpcMethod);
1735
+ }
1736
+ function recordRequestLogFailure(flowCtx) {
1737
+ const failure = flowCtx.state?.["flowError"];
1738
+ if (!failure) return;
1739
+ const error = failure instanceof Error ? failure : new Error(String(failure));
1740
+ currentRequestLog(flowCtx)?.setError({
1741
+ type: error.name,
1742
+ message: error.message,
1743
+ code: error.code
1744
+ });
1745
+ }
1746
+ async function completeRequestLog(flowCtx) {
1747
+ const collector = currentRequestLog(flowCtx);
1748
+ if (!collector || collector.isFinalized()) return;
1749
+ const statusCode = flowCtx.state?.["statusCode"] ?? flowCtx.rawInput?.response?.statusCode;
1750
+ if (typeof statusCode === "number" && statusCode >= 400) collector.setStatus("error", statusCode);
1751
+ await collector.finalize();
1752
+ }
1753
+
1754
+ // libs/observability/src/telemetry/telemetry.accessor.ts
1755
+ import {
1756
+ SpanKind as SpanKind12,
1757
+ SpanStatusCode as SpanStatusCode2,
1758
+ trace as trace4
1759
+ } from "@opentelemetry/api";
1760
+
1713
1761
  // libs/observability/src/telemetry/telemetry.counters.ts
1714
1762
  import { metrics } from "@opentelemetry/api";
1715
1763
  function snapshotKey(name, attributes) {
@@ -2085,8 +2133,19 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2085
2133
  get tracingEnabled() {
2086
2134
  return this.options.tracing !== false;
2087
2135
  }
2136
+ get requestLogsEnabled() {
2137
+ return this.options.requestLogs !== false;
2138
+ }
2139
+ /** Names the entry a request serves on its request log, and records the entry flow's failure. */
2140
+ annotateRequestLog(ctx, set) {
2141
+ if (!this.requestLogsEnabled) return;
2142
+ const flowCtx = ctx;
2143
+ const log = currentRequestLog(ctx);
2144
+ if (log && flowCtx.state?.input) set(log, flowCtx.state.input);
2145
+ }
2088
2146
  _httpWillTrace(ctx) {
2089
2147
  if (this.tracingEnabled) onHttpWillTrace(this.tracingOpts, ctx);
2148
+ if (this.options.requestLogs !== false) startRequestLog(ctx, this.options.requestLogs);
2090
2149
  }
2091
2150
  _httpWillQuota(ctx) {
2092
2151
  if (this.tracingEnabled) onHttpWillAcquireQuota(ctx);
@@ -2108,12 +2167,14 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2108
2167
  }
2109
2168
  _httpDidFinalize(ctx) {
2110
2169
  if (this.tracingEnabled) onHttpDidFinalize(ctx);
2170
+ if (this.requestLogsEnabled) void completeRequestLog(ctx);
2111
2171
  }
2112
2172
  _toolWillParse(ctx) {
2113
2173
  if (this.tracingEnabled) onToolWillParse(this.tracingOpts, ctx);
2114
2174
  }
2115
2175
  _toolWillFind(ctx) {
2116
2176
  if (this.tracingEnabled) onToolWillFindTool(ctx);
2177
+ this.annotateRequestLog(ctx, (log, input) => log.setToolName(String(input["name"])));
2117
2178
  }
2118
2179
  _toolWillCheckAuth(ctx) {
2119
2180
  if (this.tracingEnabled) onToolWillCheckAuth(ctx);
@@ -2144,12 +2205,14 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2144
2205
  }
2145
2206
  _toolDidFinalize(ctx) {
2146
2207
  if (this.tracingEnabled) onToolDidFinalize(ctx);
2208
+ if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2147
2209
  }
2148
2210
  _resourceWillParse(ctx) {
2149
2211
  if (this.tracingEnabled) onResourceWillParse(this.tracingOpts, ctx);
2150
2212
  }
2151
2213
  _resourceWillFind(ctx) {
2152
2214
  if (this.tracingEnabled) onResourceWillFind(ctx);
2215
+ this.annotateRequestLog(ctx, (log, input) => log.setResourceUri(String(input["uri"])));
2153
2216
  }
2154
2217
  _resourceWillCheckAuthorities(ctx) {
2155
2218
  if (this.tracingEnabled) onEntryWillCheckAuthorities(ctx);
@@ -2165,12 +2228,14 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2165
2228
  }
2166
2229
  _resourceDidFinalize(ctx) {
2167
2230
  if (this.tracingEnabled) onResourceDidFinalize(ctx);
2231
+ if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2168
2232
  }
2169
2233
  _promptWillParse(ctx) {
2170
2234
  if (this.tracingEnabled) onPromptWillParse(this.tracingOpts, ctx);
2171
2235
  }
2172
2236
  _promptWillFind(ctx) {
2173
2237
  if (this.tracingEnabled) onPromptWillFind(ctx);
2238
+ this.annotateRequestLog(ctx, (log, input) => log.setPromptName(String(input["name"])));
2174
2239
  }
2175
2240
  _promptWillCheckAuthorities(ctx) {
2176
2241
  if (this.tracingEnabled) onEntryWillCheckAuthorities(ctx);
@@ -2186,6 +2251,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2186
2251
  }
2187
2252
  _promptDidFinalize(ctx) {
2188
2253
  if (this.tracingEnabled) onPromptDidFinalize(ctx);
2254
+ if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2189
2255
  }
2190
2256
  _agentWillParse(ctx) {
2191
2257
  if (this.tracingEnabled) onAgentWillParse(this.tracingOpts, ctx);
@@ -2480,7 +2546,7 @@ ObservabilityPlugin.dynamicProviders = (input) => {
2480
2546
  name: "observability:telemetry-accessor",
2481
2547
  provide: TELEMETRY_ACCESSOR,
2482
2548
  scope: ProviderScope.CONTEXT,
2483
- inject: () => [FRONTMCP_CONTEXT],
2549
+ inject: () => [FRONTMCP_CONTEXT2],
2484
2550
  useFactory: (ctx) => new TelemetryAccessor(ctx)
2485
2551
  });
2486
2552
  providers.push({
@@ -2513,7 +2579,7 @@ ObservabilityPlugin.dynamicProviders = (input) => {
2513
2579
  name: "observability:request-log-collector",
2514
2580
  provide: REQUEST_LOG_COLLECTOR,
2515
2581
  scope: ProviderScope.CONTEXT,
2516
- inject: () => [FRONTMCP_CONTEXT],
2582
+ inject: () => [FRONTMCP_CONTEXT2],
2517
2583
  useFactory: (ctx) => {
2518
2584
  return new RequestLogCollector(
2519
2585
  {
@@ -3212,6 +3278,13 @@ function renderJsonExposition(counters, gauges = []) {
3212
3278
  import { readdirSync } from "@frontmcp/utils";
3213
3279
  var NS_PER_SECOND = 1e9;
3214
3280
  var MICROS_PER_SECOND = 1e6;
3281
+ function probeSafely(probe) {
3282
+ try {
3283
+ return probe();
3284
+ } catch {
3285
+ return void 0;
3286
+ }
3287
+ }
3215
3288
  function defaultMonitorEventLoopDelay() {
3216
3289
  try {
3217
3290
  const { monitorEventLoopDelay } = __require("node:perf_hooks");
@@ -3257,54 +3330,70 @@ var ProcessStatsCollector = class {
3257
3330
  this.getActiveHandles = init.getActiveHandles ?? defaultGetActiveHandles;
3258
3331
  this.getActiveRequests = init.getActiveRequests ?? defaultGetActiveRequests;
3259
3332
  this.readFdCount = init.readFdCount ?? defaultReadFdCount;
3260
- this.cpuStart = this.cpuUsage();
3333
+ this.cpuStart = probeSafely(() => this.cpuUsage());
3261
3334
  if (this.options.eventLoopLag !== false) {
3262
3335
  this.histogram = init.monitorEventLoopDelay ? init.monitorEventLoopDelay() : defaultMonitorEventLoopDelay();
3263
3336
  }
3264
3337
  }
3265
3338
  collect() {
3266
3339
  const entries = [];
3267
- const cpu = this.cpuUsage(this.cpuStart);
3268
- entries.push({
3269
- name: "frontmcp_process_cpu_seconds_total",
3270
- value: cpu.user / MICROS_PER_SECOND,
3271
- attributes: { mode: "user" },
3272
- help: "CPU time consumed since collector start, by mode (seconds)"
3273
- });
3274
- entries.push({
3275
- name: "frontmcp_process_cpu_seconds_total",
3276
- value: cpu.system / MICROS_PER_SECOND,
3277
- attributes: { mode: "system" }
3278
- });
3279
- const mem = this.memoryUsage();
3280
- entries.push({
3281
- name: "frontmcp_process_resident_memory_bytes",
3282
- value: mem.rss,
3283
- help: "Resident memory size in bytes"
3284
- });
3285
- entries.push({
3286
- name: "frontmcp_process_heap_bytes",
3287
- value: mem.heapTotal,
3288
- help: "Total V8 heap size in bytes"
3289
- });
3290
- entries.push({
3291
- name: "frontmcp_process_heap_used_bytes",
3292
- value: mem.heapUsed,
3293
- help: "Used V8 heap size in bytes"
3294
- });
3295
- entries.push({
3296
- name: "frontmcp_process_external_bytes",
3297
- value: mem.external,
3298
- help: "Memory used by C++ objects bound to JS in bytes"
3299
- });
3300
- entries.push({
3301
- name: "frontmcp_process_uptime_seconds",
3302
- value: this.uptime(),
3303
- help: "Time since process start in seconds"
3340
+ this.cpuStart ??= probeSafely(() => this.cpuUsage());
3341
+ const cpu = this.cpuStart && probeSafely(() => this.cpuUsage(this.cpuStart));
3342
+ if (cpu) {
3343
+ entries.push({
3344
+ name: "frontmcp_process_cpu_seconds_total",
3345
+ value: cpu.user / MICROS_PER_SECOND,
3346
+ attributes: { mode: "user" },
3347
+ help: "CPU time consumed since collector start, by mode (seconds)"
3348
+ });
3349
+ entries.push({
3350
+ name: "frontmcp_process_cpu_seconds_total",
3351
+ value: cpu.system / MICROS_PER_SECOND,
3352
+ attributes: { mode: "system" }
3353
+ });
3354
+ }
3355
+ const mem = probeSafely(() => this.memoryUsage());
3356
+ if (mem) {
3357
+ entries.push({
3358
+ name: "frontmcp_process_resident_memory_bytes",
3359
+ value: mem.rss,
3360
+ help: "Resident memory size in bytes"
3361
+ });
3362
+ entries.push({
3363
+ name: "frontmcp_process_heap_bytes",
3364
+ value: mem.heapTotal,
3365
+ help: "Total V8 heap size in bytes"
3366
+ });
3367
+ entries.push({
3368
+ name: "frontmcp_process_heap_used_bytes",
3369
+ value: mem.heapUsed,
3370
+ help: "Used V8 heap size in bytes"
3371
+ });
3372
+ entries.push({
3373
+ name: "frontmcp_process_external_bytes",
3374
+ value: mem.external,
3375
+ help: "Memory used by C++ objects bound to JS in bytes"
3376
+ });
3377
+ }
3378
+ const uptime = probeSafely(() => this.uptime());
3379
+ if (uptime !== void 0) {
3380
+ entries.push({
3381
+ name: "frontmcp_process_uptime_seconds",
3382
+ value: uptime,
3383
+ help: "Time since process start in seconds"
3384
+ });
3385
+ }
3386
+ const histogram = this.options.eventLoopLag !== false ? this.histogram : void 0;
3387
+ const lag = histogram && probeSafely(() => {
3388
+ const sample = {
3389
+ meanSeconds: histogram.mean / NS_PER_SECOND,
3390
+ p99Seconds: histogram.percentile(99) / NS_PER_SECOND
3391
+ };
3392
+ histogram.reset();
3393
+ return sample;
3304
3394
  });
3305
- if (this.options.eventLoopLag !== false && this.histogram) {
3306
- const meanSeconds = this.histogram.mean / NS_PER_SECOND;
3307
- const p99Seconds = this.histogram.percentile(99) / NS_PER_SECOND;
3395
+ if (lag) {
3396
+ const { meanSeconds, p99Seconds } = lag;
3308
3397
  if (Number.isFinite(meanSeconds)) {
3309
3398
  entries.push({
3310
3399
  name: "frontmcp_nodejs_eventloop_lag_seconds",
@@ -3320,7 +3409,6 @@ var ProcessStatsCollector = class {
3320
3409
  attributes: { quantile: "p99" }
3321
3410
  });
3322
3411
  }
3323
- this.histogram.reset();
3324
3412
  }
3325
3413
  if (this.options.activeHandles !== false) {
3326
3414
  const handles = this.getActiveHandles?.();
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/observability",
3
- "version": "1.9.1-rc.3",
3
+ "version": "1.9.2",
4
4
  "description": "OpenTelemetry instrumentation, structured JSON logging, and request log objects for FrontMCP",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -51,8 +51,8 @@
51
51
  "@opentelemetry/api": "^1.9.0"
52
52
  },
53
53
  "peerDependencies": {
54
- "@frontmcp/sdk": "1.9.1-rc.3",
55
- "@frontmcp/utils": "1.9.1-rc.3",
54
+ "@frontmcp/sdk": "1.9.2",
55
+ "@frontmcp/utils": "1.9.2",
56
56
  "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
57
57
  "@opentelemetry/sdk-node": "^0.219.0",
58
58
  "@opentelemetry/sdk-trace-base": "^2.8.0",
package/index.js CHANGED
@@ -831,11 +831,9 @@ var RequestLogCollector = class {
831
831
  }
832
832
  };
833
833
 
834
- // libs/observability/src/request-log/request-log.tokens.ts
835
- var REQUEST_LOG_COLLECTOR = /* @__PURE__ */ Symbol.for("frontmcp:observability:request-log-collector");
836
-
837
- // libs/observability/src/telemetry/telemetry.accessor.ts
838
- var import_api14 = require("@opentelemetry/api");
834
+ // libs/observability/src/plugin/observability.hooks.ts
835
+ var import_api12 = require("@opentelemetry/api");
836
+ var import_utils = require("@frontmcp/utils");
839
837
 
840
838
  // libs/observability/src/otel/otel.types.ts
841
839
  var McpAttributes = {
@@ -950,60 +948,31 @@ var EnduserAttributes = {
950
948
  SCOPE: "enduser.scope"
951
949
  };
952
950
 
953
- // libs/observability/src/otel/trace-context-bridge.ts
954
- var import_api = require("@opentelemetry/api");
955
- function frontmcpToOTelSpanContext(tc) {
956
- return {
957
- traceId: tc.traceId,
958
- spanId: tc.parentId,
959
- traceFlags: tc.traceFlags & import_api.TraceFlags.SAMPLED ? import_api.TraceFlags.SAMPLED : import_api.TraceFlags.NONE,
960
- isRemote: true
961
- };
962
- }
963
- function otelToFrontmcpContext(sc) {
964
- const flags = sc.traceFlags.toString(16).padStart(2, "0");
965
- return {
966
- traceId: sc.traceId,
967
- parentId: sc.spanId,
968
- traceFlags: sc.traceFlags,
969
- raw: `00-${sc.traceId}-${sc.spanId}-${flags}`
970
- };
971
- }
972
- function createOTelContextFromTrace(tc) {
973
- const spanContext = frontmcpToOTelSpanContext(tc);
974
- const span = import_api.trace.wrapSpanContext(spanContext);
975
- return import_api.trace.setSpan(import_api.context.active(), span);
976
- }
977
-
978
- // libs/observability/src/plugin/observability.hooks.ts
979
- var import_api12 = require("@opentelemetry/api");
980
- var import_utils = require("@frontmcp/utils");
981
-
982
951
  // libs/observability/src/otel/spans/auth.span.ts
983
- var import_api3 = require("@opentelemetry/api");
952
+ var import_api2 = require("@opentelemetry/api");
984
953
 
985
954
  // libs/observability/src/otel/spans/span.utils.ts
986
- var import_api2 = require("@opentelemetry/api");
955
+ var import_api = require("@opentelemetry/api");
987
956
  function startSpan(tracer, options) {
988
- const parentCtx = options.parentContext ?? import_api2.context.active();
957
+ const parentCtx = options.parentContext ?? import_api.context.active();
989
958
  const span = tracer.startSpan(
990
959
  options.name,
991
960
  {
992
- kind: options.kind ?? import_api2.SpanKind.INTERNAL,
961
+ kind: options.kind ?? import_api.SpanKind.INTERNAL,
993
962
  attributes: options.attributes
994
963
  },
995
964
  parentCtx
996
965
  );
997
- const spanContext = import_api2.trace.setSpan(parentCtx, span);
966
+ const spanContext = import_api.trace.setSpan(parentCtx, span);
998
967
  return { span, context: spanContext };
999
968
  }
1000
969
  function endSpanOk(span) {
1001
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
970
+ span.setStatus({ code: import_api.SpanStatusCode.OK });
1002
971
  span.end();
1003
972
  }
1004
973
  function endSpanError(span, error) {
1005
974
  const message = typeof error === "string" ? error : error.message;
1006
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
975
+ span.setStatus({ code: import_api.SpanStatusCode.ERROR, message });
1007
976
  if (error instanceof Error) {
1008
977
  span.recordException(error);
1009
978
  }
@@ -1011,7 +980,7 @@ function endSpanError(span, error) {
1011
980
  }
1012
981
  async function withSpan(tracer, options, fn) {
1013
982
  const { span, context: spanContext } = startSpan(tracer, options);
1014
- return import_api2.context.with(spanContext, async () => {
983
+ return import_api.context.with(spanContext, async () => {
1015
984
  try {
1016
985
  const result = await fn(span);
1017
986
  endSpanOk(span);
@@ -1027,7 +996,7 @@ async function withSpan(tracer, options, fn) {
1027
996
  function startAuthSpan(tracer, options) {
1028
997
  return startSpan(tracer, {
1029
998
  name: `auth ${options.flowName}`,
1030
- kind: import_api3.SpanKind.INTERNAL,
999
+ kind: import_api2.SpanKind.INTERNAL,
1031
1000
  attributes: {
1032
1001
  [FrontMcpAttributes.FLOW_NAME]: options.flowName
1033
1002
  },
@@ -1042,11 +1011,11 @@ function setAuthResult(span, result) {
1042
1011
  }
1043
1012
 
1044
1013
  // libs/observability/src/otel/spans/fetch.span.ts
1045
- var import_api4 = require("@opentelemetry/api");
1014
+ var import_api3 = require("@opentelemetry/api");
1046
1015
  function startFetchSpan(tracer, options) {
1047
1016
  return startSpan(tracer, {
1048
1017
  name: options.method.toUpperCase(),
1049
- kind: import_api4.SpanKind.CLIENT,
1018
+ kind: import_api3.SpanKind.CLIENT,
1050
1019
  attributes: {
1051
1020
  [HttpAttributes.METHOD]: options.method.toUpperCase(),
1052
1021
  [HttpAttributes.URL_FULL]: options.url
@@ -1059,7 +1028,7 @@ function setFetchResponseStatus(span, statusCode) {
1059
1028
  }
1060
1029
 
1061
1030
  // libs/observability/src/otel/spans/http-server.span.ts
1062
- var import_api5 = require("@opentelemetry/api");
1031
+ var import_api4 = require("@opentelemetry/api");
1063
1032
  function startHttpServerSpan(tracer, options) {
1064
1033
  const attributes = {
1065
1034
  [HttpAttributes.METHOD]: options.method.toUpperCase(),
@@ -1082,7 +1051,7 @@ function startHttpServerSpan(tracer, options) {
1082
1051
  }
1083
1052
  return startSpan(tracer, {
1084
1053
  name: `${options.method.toUpperCase()} ${options.path}`,
1085
- kind: import_api5.SpanKind.SERVER,
1054
+ kind: import_api4.SpanKind.SERVER,
1086
1055
  attributes,
1087
1056
  parentContext: options.parentContext
1088
1057
  });
@@ -1092,11 +1061,11 @@ function setHttpResponseStatus(span, statusCode) {
1092
1061
  }
1093
1062
 
1094
1063
  // libs/observability/src/otel/spans/prompt.span.ts
1095
- var import_api6 = require("@opentelemetry/api");
1064
+ var import_api5 = require("@opentelemetry/api");
1096
1065
  function startPromptSpan(tracer, options) {
1097
1066
  return startSpan(tracer, {
1098
1067
  name: `prompt ${options.name}`,
1099
- kind: import_api6.SpanKind.INTERNAL,
1068
+ kind: import_api5.SpanKind.INTERNAL,
1100
1069
  attributes: {
1101
1070
  [FrontMcpAttributes.PROMPT_NAME]: options.name,
1102
1071
  [McpAttributes.COMPONENT_TYPE]: "prompt",
@@ -1107,7 +1076,7 @@ function startPromptSpan(tracer, options) {
1107
1076
  }
1108
1077
 
1109
1078
  // libs/observability/src/otel/spans/resource.span.ts
1110
- var import_api7 = require("@opentelemetry/api");
1079
+ var import_api6 = require("@opentelemetry/api");
1111
1080
  function startResourceSpan(tracer, options) {
1112
1081
  const attributes = {
1113
1082
  [FrontMcpAttributes.RESOURCE_URI]: options.uri,
@@ -1120,14 +1089,14 @@ function startResourceSpan(tracer, options) {
1120
1089
  }
1121
1090
  return startSpan(tracer, {
1122
1091
  name: `resource ${options.uri}`,
1123
- kind: import_api7.SpanKind.INTERNAL,
1092
+ kind: import_api6.SpanKind.INTERNAL,
1124
1093
  attributes,
1125
1094
  parentContext: options.parentContext
1126
1095
  });
1127
1096
  }
1128
1097
 
1129
1098
  // libs/observability/src/otel/spans/rpc.span.ts
1130
- var import_api8 = require("@opentelemetry/api");
1099
+ var import_api7 = require("@opentelemetry/api");
1131
1100
  function startRpcSpan(tracer, options) {
1132
1101
  const attributes = {
1133
1102
  [RpcAttributes.SYSTEM]: "mcp",
@@ -1151,14 +1120,14 @@ function startRpcSpan(tracer, options) {
1151
1120
  }
1152
1121
  return startSpan(tracer, {
1153
1122
  name: options.method,
1154
- kind: import_api8.SpanKind.SERVER,
1123
+ kind: import_api7.SpanKind.SERVER,
1155
1124
  attributes,
1156
1125
  parentContext: options.parentContext
1157
1126
  });
1158
1127
  }
1159
1128
 
1160
1129
  // libs/observability/src/otel/spans/startup.span.ts
1161
- var import_api9 = require("@opentelemetry/api");
1130
+ var import_api8 = require("@opentelemetry/api");
1162
1131
  function emitStartupReport(tracer, data) {
1163
1132
  const attributes = {
1164
1133
  [FrontMcpAttributes.STARTUP_TOOLS_COUNT]: data.toolsCount,
@@ -1172,14 +1141,14 @@ function emitStartupReport(tracer, data) {
1172
1141
  }
1173
1142
  const { span } = startSpan(tracer, {
1174
1143
  name: "frontmcp.startup",
1175
- kind: import_api9.SpanKind.INTERNAL,
1144
+ kind: import_api8.SpanKind.INTERNAL,
1176
1145
  attributes
1177
1146
  });
1178
1147
  endSpanOk(span);
1179
1148
  }
1180
1149
 
1181
1150
  // libs/observability/src/otel/spans/tool.span.ts
1182
- var import_api10 = require("@opentelemetry/api");
1151
+ var import_api9 = require("@opentelemetry/api");
1183
1152
  function startToolSpan(tracer, options) {
1184
1153
  const attributes = {
1185
1154
  [FrontMcpAttributes.TOOL_NAME]: options.name,
@@ -1197,14 +1166,14 @@ function startToolSpan(tracer, options) {
1197
1166
  }
1198
1167
  return startSpan(tracer, {
1199
1168
  name: `tool ${options.name}`,
1200
- kind: import_api10.SpanKind.INTERNAL,
1169
+ kind: import_api9.SpanKind.INTERNAL,
1201
1170
  attributes,
1202
1171
  parentContext: options.parentContext
1203
1172
  });
1204
1173
  }
1205
1174
 
1206
1175
  // libs/observability/src/otel/spans/transport.span.ts
1207
- var import_api11 = require("@opentelemetry/api");
1176
+ var import_api10 = require("@opentelemetry/api");
1208
1177
  function startTransportSpan(tracer, options) {
1209
1178
  const attributes = {
1210
1179
  [FrontMcpAttributes.TRANSPORT_TYPE]: options.type
@@ -1217,7 +1186,7 @@ function startTransportSpan(tracer, options) {
1217
1186
  }
1218
1187
  return startSpan(tracer, {
1219
1188
  name: `transport ${options.type}`,
1220
- kind: import_api11.SpanKind.INTERNAL,
1189
+ kind: import_api10.SpanKind.INTERNAL,
1221
1190
  attributes,
1222
1191
  parentContext: options.parentContext
1223
1192
  });
@@ -1226,6 +1195,31 @@ function setTransportRequestType(span, requestType) {
1226
1195
  span.setAttribute(FrontMcpAttributes.TRANSPORT_REQUEST_TYPE, requestType);
1227
1196
  }
1228
1197
 
1198
+ // libs/observability/src/otel/trace-context-bridge.ts
1199
+ var import_api11 = require("@opentelemetry/api");
1200
+ function frontmcpToOTelSpanContext(tc) {
1201
+ return {
1202
+ traceId: tc.traceId,
1203
+ spanId: tc.parentId,
1204
+ traceFlags: tc.traceFlags & import_api11.TraceFlags.SAMPLED ? import_api11.TraceFlags.SAMPLED : import_api11.TraceFlags.NONE,
1205
+ isRemote: true
1206
+ };
1207
+ }
1208
+ function otelToFrontmcpContext(sc) {
1209
+ const flags = sc.traceFlags.toString(16).padStart(2, "0");
1210
+ return {
1211
+ traceId: sc.traceId,
1212
+ parentId: sc.spanId,
1213
+ traceFlags: sc.traceFlags,
1214
+ raw: `00-${sc.traceId}-${sc.spanId}-${flags}`
1215
+ };
1216
+ }
1217
+ function createOTelContextFromTrace(tc) {
1218
+ const spanContext = frontmcpToOTelSpanContext(tc);
1219
+ const span = import_api11.trace.wrapSpanContext(spanContext);
1220
+ return import_api11.trace.setSpan(import_api11.context.active(), span);
1221
+ }
1222
+
1229
1223
  // libs/observability/src/plugin/observability.hooks.ts
1230
1224
  var SPAN_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:span");
1231
1225
  var SPAN_CTX_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:span-ctx");
@@ -1417,7 +1411,7 @@ function onToolWillApplyUI(flowCtx) {
1417
1411
  function onToolDidFinalize(flowCtx) {
1418
1412
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1419
1413
  const toolSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1420
- const error = flowCtx.state?.error;
1414
+ const error = flowCtx.state?.flowError;
1421
1415
  if (error) {
1422
1416
  if (toolSpan) endSpanError(toolSpan, error instanceof Error ? error : String(error));
1423
1417
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1470,7 +1464,7 @@ function onResourceDidExecute(flowCtx) {
1470
1464
  function onResourceDidFinalize(flowCtx) {
1471
1465
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1472
1466
  const resSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1473
- const error = flowCtx.state?.error;
1467
+ const error = flowCtx.state?.flowError;
1474
1468
  if (error) {
1475
1469
  if (resSpan) endSpanError(resSpan, error instanceof Error ? error : String(error));
1476
1470
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1526,7 +1520,7 @@ function onPromptDidExecute(flowCtx) {
1526
1520
  function onPromptDidFinalize(flowCtx) {
1527
1521
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1528
1522
  const promptSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1529
- const error = flowCtx.state?.error;
1523
+ const error = flowCtx.state?.flowError;
1530
1524
  if (error) {
1531
1525
  if (promptSpan) endSpanError(promptSpan, error instanceof Error ? error : String(error));
1532
1526
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1589,7 +1583,7 @@ function onAgentDidExecute(flowCtx) {
1589
1583
  function onAgentDidFinalize(flowCtx) {
1590
1584
  const rpcSpan = flowCtx.state?.[SPAN_KEY];
1591
1585
  const agentSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1592
- const error = flowCtx.state?.error;
1586
+ const error = flowCtx.state?.flowError;
1593
1587
  if (error) {
1594
1588
  if (agentSpan) endSpanError(agentSpan, error instanceof Error ? error : String(error));
1595
1589
  if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
@@ -1628,7 +1622,7 @@ function onGenericFlowStage(stageName, flowCtx) {
1628
1622
  function onGenericFlowDidFinalize(flowCtx) {
1629
1623
  const span = flowCtx.state?.[SPAN_KEY];
1630
1624
  if (!span) return;
1631
- const error = flowCtx.state?.error;
1625
+ const error = flowCtx.state?.flowError;
1632
1626
  if (error) {
1633
1627
  endSpanError(span, error instanceof Error ? error : String(error));
1634
1628
  } else {
@@ -1667,7 +1661,7 @@ function onTransportDidFinalize(flowCtx) {
1667
1661
  const span = flowCtx.state?.[SPAN_KEY];
1668
1662
  if (!span) return;
1669
1663
  recordStageEvent(span, "cleanup", flowCtx.state);
1670
- const error = flowCtx.state?.error;
1664
+ const error = flowCtx.state?.flowError;
1671
1665
  if (error) {
1672
1666
  endSpanError(span, error instanceof Error ? error : String(error));
1673
1667
  } else {
@@ -1703,7 +1697,7 @@ function onAuthStage(stageName, flowCtx) {
1703
1697
  function onAuthDidFinalize(flowCtx) {
1704
1698
  const span = flowCtx.state?.[SPAN_KEY];
1705
1699
  if (!span) return;
1706
- const error = flowCtx.state?.error;
1700
+ const error = flowCtx.state?.flowError;
1707
1701
  if (error) {
1708
1702
  setAuthResult(span, "unauthorized");
1709
1703
  endSpanError(span, error instanceof Error ? error : String(error));
@@ -1752,6 +1746,60 @@ function reportStartup(data) {
1752
1746
  emitStartupReport(tracer, data);
1753
1747
  }
1754
1748
 
1749
+ // libs/observability/src/request-log/request-log.tokens.ts
1750
+ var REQUEST_LOG_COLLECTOR = /* @__PURE__ */ Symbol.for("frontmcp:observability:request-log-collector");
1751
+
1752
+ // libs/observability/src/request-log/request-log.hooks.ts
1753
+ var FRONTMCP_CONTEXT = /* @__PURE__ */ Symbol.for("frontmcp:CONTEXT");
1754
+ function resolve(flowCtx, token) {
1755
+ try {
1756
+ return flowCtx.get?.(token);
1757
+ } catch {
1758
+ return void 0;
1759
+ }
1760
+ }
1761
+ function currentRequestLog(flowCtx) {
1762
+ return resolve(flowCtx, FRONTMCP_CONTEXT)?.get(REQUEST_LOG_COLLECTOR);
1763
+ }
1764
+ function startRequestLog(flowCtx, options) {
1765
+ const context = resolve(flowCtx, FRONTMCP_CONTEXT);
1766
+ if (!context) return;
1767
+ const collector = new RequestLogCollector(
1768
+ {
1769
+ requestId: context.requestId,
1770
+ traceId: context.traceContext.traceId,
1771
+ sessionIdHash: sessionTracingId(context.sessionId).slice(0, 12),
1772
+ scopeId: context.scopeId
1773
+ },
1774
+ options
1775
+ );
1776
+ context.set(REQUEST_LOG_COLLECTOR, collector);
1777
+ const request = flowCtx.rawInput?.request;
1778
+ if (request?.method && request.path) collector.setHttpInfo(request.method, request.path);
1779
+ const rpcMethod = request?.body?.method;
1780
+ if (typeof rpcMethod === "string") collector.setRpcMethod(rpcMethod);
1781
+ }
1782
+ function recordRequestLogFailure(flowCtx) {
1783
+ const failure = flowCtx.state?.["flowError"];
1784
+ if (!failure) return;
1785
+ const error = failure instanceof Error ? failure : new Error(String(failure));
1786
+ currentRequestLog(flowCtx)?.setError({
1787
+ type: error.name,
1788
+ message: error.message,
1789
+ code: error.code
1790
+ });
1791
+ }
1792
+ async function completeRequestLog(flowCtx) {
1793
+ const collector = currentRequestLog(flowCtx);
1794
+ if (!collector || collector.isFinalized()) return;
1795
+ const statusCode = flowCtx.state?.["statusCode"] ?? flowCtx.rawInput?.response?.statusCode;
1796
+ if (typeof statusCode === "number" && statusCode >= 400) collector.setStatus("error", statusCode);
1797
+ await collector.finalize();
1798
+ }
1799
+
1800
+ // libs/observability/src/telemetry/telemetry.accessor.ts
1801
+ var import_api14 = require("@opentelemetry/api");
1802
+
1755
1803
  // libs/observability/src/telemetry/telemetry.counters.ts
1756
1804
  var import_api13 = require("@opentelemetry/api");
1757
1805
  function snapshotKey(name, attributes) {
@@ -2127,8 +2175,19 @@ var ObservabilityPlugin = class extends import_sdk2.DynamicPlugin {
2127
2175
  get tracingEnabled() {
2128
2176
  return this.options.tracing !== false;
2129
2177
  }
2178
+ get requestLogsEnabled() {
2179
+ return this.options.requestLogs !== false;
2180
+ }
2181
+ /** Names the entry a request serves on its request log, and records the entry flow's failure. */
2182
+ annotateRequestLog(ctx, set) {
2183
+ if (!this.requestLogsEnabled) return;
2184
+ const flowCtx = ctx;
2185
+ const log = currentRequestLog(ctx);
2186
+ if (log && flowCtx.state?.input) set(log, flowCtx.state.input);
2187
+ }
2130
2188
  _httpWillTrace(ctx) {
2131
2189
  if (this.tracingEnabled) onHttpWillTrace(this.tracingOpts, ctx);
2190
+ if (this.options.requestLogs !== false) startRequestLog(ctx, this.options.requestLogs);
2132
2191
  }
2133
2192
  _httpWillQuota(ctx) {
2134
2193
  if (this.tracingEnabled) onHttpWillAcquireQuota(ctx);
@@ -2150,12 +2209,14 @@ var ObservabilityPlugin = class extends import_sdk2.DynamicPlugin {
2150
2209
  }
2151
2210
  _httpDidFinalize(ctx) {
2152
2211
  if (this.tracingEnabled) onHttpDidFinalize(ctx);
2212
+ if (this.requestLogsEnabled) void completeRequestLog(ctx);
2153
2213
  }
2154
2214
  _toolWillParse(ctx) {
2155
2215
  if (this.tracingEnabled) onToolWillParse(this.tracingOpts, ctx);
2156
2216
  }
2157
2217
  _toolWillFind(ctx) {
2158
2218
  if (this.tracingEnabled) onToolWillFindTool(ctx);
2219
+ this.annotateRequestLog(ctx, (log, input) => log.setToolName(String(input["name"])));
2159
2220
  }
2160
2221
  _toolWillCheckAuth(ctx) {
2161
2222
  if (this.tracingEnabled) onToolWillCheckAuth(ctx);
@@ -2186,12 +2247,14 @@ var ObservabilityPlugin = class extends import_sdk2.DynamicPlugin {
2186
2247
  }
2187
2248
  _toolDidFinalize(ctx) {
2188
2249
  if (this.tracingEnabled) onToolDidFinalize(ctx);
2250
+ if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2189
2251
  }
2190
2252
  _resourceWillParse(ctx) {
2191
2253
  if (this.tracingEnabled) onResourceWillParse(this.tracingOpts, ctx);
2192
2254
  }
2193
2255
  _resourceWillFind(ctx) {
2194
2256
  if (this.tracingEnabled) onResourceWillFind(ctx);
2257
+ this.annotateRequestLog(ctx, (log, input) => log.setResourceUri(String(input["uri"])));
2195
2258
  }
2196
2259
  _resourceWillCheckAuthorities(ctx) {
2197
2260
  if (this.tracingEnabled) onEntryWillCheckAuthorities(ctx);
@@ -2207,12 +2270,14 @@ var ObservabilityPlugin = class extends import_sdk2.DynamicPlugin {
2207
2270
  }
2208
2271
  _resourceDidFinalize(ctx) {
2209
2272
  if (this.tracingEnabled) onResourceDidFinalize(ctx);
2273
+ if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2210
2274
  }
2211
2275
  _promptWillParse(ctx) {
2212
2276
  if (this.tracingEnabled) onPromptWillParse(this.tracingOpts, ctx);
2213
2277
  }
2214
2278
  _promptWillFind(ctx) {
2215
2279
  if (this.tracingEnabled) onPromptWillFind(ctx);
2280
+ this.annotateRequestLog(ctx, (log, input) => log.setPromptName(String(input["name"])));
2216
2281
  }
2217
2282
  _promptWillCheckAuthorities(ctx) {
2218
2283
  if (this.tracingEnabled) onEntryWillCheckAuthorities(ctx);
@@ -2228,6 +2293,7 @@ var ObservabilityPlugin = class extends import_sdk2.DynamicPlugin {
2228
2293
  }
2229
2294
  _promptDidFinalize(ctx) {
2230
2295
  if (this.tracingEnabled) onPromptDidFinalize(ctx);
2296
+ if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2231
2297
  }
2232
2298
  _agentWillParse(ctx) {
2233
2299
  if (this.tracingEnabled) onAgentWillParse(this.tracingOpts, ctx);
@@ -3252,6 +3318,13 @@ function renderJsonExposition(counters, gauges = []) {
3252
3318
  var import_utils2 = require("@frontmcp/utils");
3253
3319
  var NS_PER_SECOND = 1e9;
3254
3320
  var MICROS_PER_SECOND = 1e6;
3321
+ function probeSafely(probe) {
3322
+ try {
3323
+ return probe();
3324
+ } catch {
3325
+ return void 0;
3326
+ }
3327
+ }
3255
3328
  function defaultMonitorEventLoopDelay() {
3256
3329
  try {
3257
3330
  const { monitorEventLoopDelay } = require("node:perf_hooks");
@@ -3297,54 +3370,70 @@ var ProcessStatsCollector = class {
3297
3370
  this.getActiveHandles = init.getActiveHandles ?? defaultGetActiveHandles;
3298
3371
  this.getActiveRequests = init.getActiveRequests ?? defaultGetActiveRequests;
3299
3372
  this.readFdCount = init.readFdCount ?? defaultReadFdCount;
3300
- this.cpuStart = this.cpuUsage();
3373
+ this.cpuStart = probeSafely(() => this.cpuUsage());
3301
3374
  if (this.options.eventLoopLag !== false) {
3302
3375
  this.histogram = init.monitorEventLoopDelay ? init.monitorEventLoopDelay() : defaultMonitorEventLoopDelay();
3303
3376
  }
3304
3377
  }
3305
3378
  collect() {
3306
3379
  const entries = [];
3307
- const cpu = this.cpuUsage(this.cpuStart);
3308
- entries.push({
3309
- name: "frontmcp_process_cpu_seconds_total",
3310
- value: cpu.user / MICROS_PER_SECOND,
3311
- attributes: { mode: "user" },
3312
- help: "CPU time consumed since collector start, by mode (seconds)"
3313
- });
3314
- entries.push({
3315
- name: "frontmcp_process_cpu_seconds_total",
3316
- value: cpu.system / MICROS_PER_SECOND,
3317
- attributes: { mode: "system" }
3318
- });
3319
- const mem = this.memoryUsage();
3320
- entries.push({
3321
- name: "frontmcp_process_resident_memory_bytes",
3322
- value: mem.rss,
3323
- help: "Resident memory size in bytes"
3324
- });
3325
- entries.push({
3326
- name: "frontmcp_process_heap_bytes",
3327
- value: mem.heapTotal,
3328
- help: "Total V8 heap size in bytes"
3329
- });
3330
- entries.push({
3331
- name: "frontmcp_process_heap_used_bytes",
3332
- value: mem.heapUsed,
3333
- help: "Used V8 heap size in bytes"
3334
- });
3335
- entries.push({
3336
- name: "frontmcp_process_external_bytes",
3337
- value: mem.external,
3338
- help: "Memory used by C++ objects bound to JS in bytes"
3339
- });
3340
- entries.push({
3341
- name: "frontmcp_process_uptime_seconds",
3342
- value: this.uptime(),
3343
- help: "Time since process start in seconds"
3380
+ this.cpuStart ??= probeSafely(() => this.cpuUsage());
3381
+ const cpu = this.cpuStart && probeSafely(() => this.cpuUsage(this.cpuStart));
3382
+ if (cpu) {
3383
+ entries.push({
3384
+ name: "frontmcp_process_cpu_seconds_total",
3385
+ value: cpu.user / MICROS_PER_SECOND,
3386
+ attributes: { mode: "user" },
3387
+ help: "CPU time consumed since collector start, by mode (seconds)"
3388
+ });
3389
+ entries.push({
3390
+ name: "frontmcp_process_cpu_seconds_total",
3391
+ value: cpu.system / MICROS_PER_SECOND,
3392
+ attributes: { mode: "system" }
3393
+ });
3394
+ }
3395
+ const mem = probeSafely(() => this.memoryUsage());
3396
+ if (mem) {
3397
+ entries.push({
3398
+ name: "frontmcp_process_resident_memory_bytes",
3399
+ value: mem.rss,
3400
+ help: "Resident memory size in bytes"
3401
+ });
3402
+ entries.push({
3403
+ name: "frontmcp_process_heap_bytes",
3404
+ value: mem.heapTotal,
3405
+ help: "Total V8 heap size in bytes"
3406
+ });
3407
+ entries.push({
3408
+ name: "frontmcp_process_heap_used_bytes",
3409
+ value: mem.heapUsed,
3410
+ help: "Used V8 heap size in bytes"
3411
+ });
3412
+ entries.push({
3413
+ name: "frontmcp_process_external_bytes",
3414
+ value: mem.external,
3415
+ help: "Memory used by C++ objects bound to JS in bytes"
3416
+ });
3417
+ }
3418
+ const uptime = probeSafely(() => this.uptime());
3419
+ if (uptime !== void 0) {
3420
+ entries.push({
3421
+ name: "frontmcp_process_uptime_seconds",
3422
+ value: uptime,
3423
+ help: "Time since process start in seconds"
3424
+ });
3425
+ }
3426
+ const histogram = this.options.eventLoopLag !== false ? this.histogram : void 0;
3427
+ const lag = histogram && probeSafely(() => {
3428
+ const sample = {
3429
+ meanSeconds: histogram.mean / NS_PER_SECOND,
3430
+ p99Seconds: histogram.percentile(99) / NS_PER_SECOND
3431
+ };
3432
+ histogram.reset();
3433
+ return sample;
3344
3434
  });
3345
- if (this.options.eventLoopLag !== false && this.histogram) {
3346
- const meanSeconds = this.histogram.mean / NS_PER_SECOND;
3347
- const p99Seconds = this.histogram.percentile(99) / NS_PER_SECOND;
3435
+ if (lag) {
3436
+ const { meanSeconds, p99Seconds } = lag;
3348
3437
  if (Number.isFinite(meanSeconds)) {
3349
3438
  entries.push({
3350
3439
  name: "frontmcp_nodejs_eventloop_lag_seconds",
@@ -3360,7 +3449,6 @@ var ProcessStatsCollector = class {
3360
3449
  attributes: { quantile: "p99" }
3361
3450
  });
3362
3451
  }
3363
- this.histogram.reset();
3364
3452
  }
3365
3453
  if (this.options.activeHandles !== false) {
3366
3454
  const handles = this.getActiveHandles?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/observability",
3
- "version": "1.9.1-rc.3",
3
+ "version": "1.9.2",
4
4
  "description": "OpenTelemetry instrumentation, structured JSON logging, and request log objects for FrontMCP",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -51,8 +51,8 @@
51
51
  "@opentelemetry/api": "^1.9.0"
52
52
  },
53
53
  "peerDependencies": {
54
- "@frontmcp/sdk": "1.9.1-rc.3",
55
- "@frontmcp/utils": "1.9.1-rc.3",
54
+ "@frontmcp/sdk": "1.9.2",
55
+ "@frontmcp/utils": "1.9.2",
56
56
  "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
57
57
  "@opentelemetry/sdk-node": "^0.219.0",
58
58
  "@opentelemetry/sdk-trace-base": "^2.8.0",
@@ -25,6 +25,9 @@ export default class ObservabilityPlugin extends DynamicPlugin<ObservabilityPlug
25
25
  private tracingOpts;
26
26
  constructor(options?: ObservabilityPluginOptionsInput);
27
27
  private get tracingEnabled();
28
+ private get requestLogsEnabled();
29
+ /** Names the entry a request serves on its request log, and records the entry flow's failure. */
30
+ private annotateRequestLog;
28
31
  _httpWillTrace(ctx: unknown): void;
29
32
  _httpWillQuota(ctx: unknown): void;
30
33
  _httpDidSemaphore(ctx: unknown): void;
@@ -53,7 +53,7 @@ export declare class ProcessStatsCollector {
53
53
  private readonly getActiveHandles?;
54
54
  private readonly getActiveRequests?;
55
55
  private readonly readFdCount;
56
- private cpuStart;
56
+ private cpuStart?;
57
57
  constructor(init?: ProcessStatsCollectorOptions);
58
58
  collect(): GaugeSnapshotEntry[];
59
59
  /**
@@ -0,0 +1,28 @@
1
+ import { RequestLogCollector } from './request-log.collector';
2
+ import type { RequestLogCollectorOptions } from './request-log.types';
3
+ interface RequestLogFlowContext {
4
+ get?(token: unknown): unknown;
5
+ rawInput?: {
6
+ request?: {
7
+ method?: string;
8
+ path?: string;
9
+ body?: unknown;
10
+ };
11
+ response?: {
12
+ statusCode?: unknown;
13
+ };
14
+ };
15
+ state?: Record<string | symbol, unknown>;
16
+ }
17
+ /** The open request log of the request `flowCtx` serves; entry flows annotate it. */
18
+ export declare function currentRequestLog(flowCtx: RequestLogFlowContext): RequestLogCollector | undefined;
19
+ /** Opens the request's log, kept on the request context so the entry flows and log entries reach it. */
20
+ export declare function startRequestLog(flowCtx: RequestLogFlowContext, options: RequestLogCollectorOptions): void;
21
+ /** Records why an entry flow failed (its `state.flowError`) on the request's log. */
22
+ export declare function recordRequestLogFailure(flowCtx: RequestLogFlowContext): void;
23
+ /**
24
+ * Closes the request's log with its HTTP status, which fires `requestLogs.onRequestComplete`: the status the
25
+ * flow responded with (`state.statusCode`), else the one a transport wrote to the response itself.
26
+ */
27
+ export declare function completeRequestLog(flowCtx: RequestLogFlowContext): Promise<void>;
28
+ export {};
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Module augmentation for TypeScript — adds `this.telemetry` to every
3
3
  * execution context: ExecutionContextBase (ToolContext, ResourceContext,
4
- * AgentContext, JobContext, SkillContext, ...) and PromptContext.
4
+ * AgentContext, JobContext, SkillContext, PromptContext, ...).
5
5
  *
6
6
  * This file provides type information only. The runtime getter is
7
7
  * installed by the SDK's context extension mechanism when the
8
- * ObservabilityPlugin registers its `contextExtensions`, on the same two
9
- * prototypes: ExecutionContextBase and PromptContext.
8
+ * ObservabilityPlugin registers its `contextExtensions`, on
9
+ * ExecutionContextBase.prototype, which PromptContext inherits.
10
10
  */
11
11
  import type { TelemetryAccessor } from './telemetry.accessor';
12
12
  declare module '@frontmcp/sdk' {