@lensmcp/nest-instrumentation 1.25.33 → 1.25.35

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.
@@ -7,6 +7,8 @@ export interface TraceWrapOptions {
7
7
  db?: boolean;
8
8
  /** Capture this method's arguments and/or what it returned on its span (capture.ts). Off unless set here or named in `LENSMCP_CAPTURE`. */
9
9
  capture?: CaptureWhat;
10
+ /** Named by `@TraceMethod`: its span is emitted however short, in a flow or not (`trace.flowlessMinMs`). */
11
+ explicit?: boolean;
10
12
  }
11
13
  /** `@LensmcpIgnore()` — skip a class (all methods) or a single method. */
12
14
  export declare function LensmcpIgnore(): ClassDecorator & MethodDecorator;
package/lib/instrument.js CHANGED
@@ -1 +1 @@
1
- "use strict";var Q=Object.defineProperty;var m=(t,r)=>Q(t,"name",{value:r,configurable:!0});var V=Object.defineProperty,l=m((t,r)=>V(t,"name",{value:r,configurable:!0}),"l");import{fingerprint as b,ulid as w}from"@lensmcp/core";import{redactText as $}from"@lensmcp/core/redact";import{captureFor as z,capturedValue as O}from"./capture.js";import{currentLensmcpContext as H,currentSpanId as X,LENSMCP_SPAN_STORAGE as J}from"./context.js";import{activeOptions as K}from"./state.js";import{performance as R}from"node:perf_hooks";export const LENSMCP_TRACED=Symbol.for("@lensmcp/nest-instrumentation/traced"),LENSMCP_IGNORE=Symbol.for("@lensmcp/nest-instrumentation/ignore");const h=300;function T(t){if(t instanceof Error){const d=t.code,c=$(t.message??"");return{name:t.name||t.constructor?.name||"Error",...c?{message:c.length>h?`${c.slice(0,h)}\u2026`:c}:{},...typeof d=="string"&&d.length<=64?{code:d}:{}}}const r=typeof t=="string"?$(t):void 0;return{name:t==null?"nothing":typeof t=="object"?"non-Error object":`non-Error ${typeof t}`,...r?{message:r.slice(0,h)}:{}}}m(T,"z"),l(T,"thrownOf");export function LensmcpIgnore(){return((t,r)=>{if(r===void 0)t[LENSMCP_IGNORE]=!0;else{const d=t[r];typeof d=="function"&&(d[LENSMCP_IGNORE]=!0)}})}m(LensmcpIgnore,"LensmcpIgnore"),l(LensmcpIgnore,"LensmcpIgnore");export function isTraced(t){return typeof t=="function"&&!!t[LENSMCP_TRACED]}m(isTraced,"isTraced"),l(isTraced,"isTraced");export function isIgnored(t){return!!t&&(typeof t=="function"||typeof t=="object")&&!!t[LENSMCP_IGNORE]}m(isIgnored,"isIgnored"),l(isIgnored,"isIgnored");export function traceWrap(t,r,d={}){if(isTraced(t))return t;const c=d.db??!1,x=l(function(...I){let n;try{n=K()}catch{return t.apply(this,I)}if(!n.trace.services)return t.apply(this,I);const u=Date.now();let D=u,j=0;const e=H(),g=w(),E=X(),A=e?.dbCallCount??0,P=e?.redisCallCount??0,_=e?.externalCallCount??0,B=e?.loopDbCalls??0,G=e?.loopRedisCalls??0,W=e?.loopExternalCalls??0,f=d.capture??z(r),v=f==="args"||f==="both"?O(I):void 0,q=l((o,s,C)=>{const a=Date.now()-u,i={id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:o==="error"?"error":"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"method-call",identity:r}),title:`${r} (${a}ms)${o==="error"?" \u2717":""}`,message:s instanceof Error?s.message:void 0,raw:{kind:"span",...n.projectName?{project:n.projectName}:{},span:{name:r,spanId:g,...E?{parentSpanId:E}:{},flowId:e?.flowId,requestId:e?.requestId,startTime:u,endTime:Date.now(),durationMs:a,syncStartedAt:D,syncDurationMs:j,status:o,outcome:o==="error"?"threw":"returned",...o==="error"?{error:T(s)}:{},...v!==void 0?{args:v}:{},...o==="ok"&&(f==="result"||f==="both")?{result:O(C)}:{}}}};n.emit(i)},"emitSpan"),M=l(()=>{e&&(e.dbCallCount=(e.dbCallCount??0)+1);const o=Date.now();n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"db",category:"db",severity:"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"db-query",identity:r}),title:`query ${r}`,raw:{kind:"db-query",...n.projectName?{project:n.projectName}:{},query:{signature:r},startedAt:u,durationMs:o-u,span:{parentSpanId:g}}})},"emitDbQuery"),N=l(()=>{if(c||!e)return;const o=(e.dbCallCount??0)-A,s=(e.redisCallCount??0)-P,C=(e.externalCallCount??0)-_,a=Math.max(0,o-((e.loopDbCalls??0)-B)),i=Math.max(0,s-((e.loopRedisCalls??0)-G)),p=Math.max(0,C-((e.loopExternalCalls??0)-W));if(a<3&&i<3&&p<3)return;const F=Date.now()-u,y=[];a>=3&&y.push(`${a} DB calls`),i>=3&&y.push(`${i} redis calls`),p>=3&&y.push(`${p} external calls`),a>=3&&(e.loopDbCalls=(e.loopDbCalls??0)+a),i>=3&&(e.loopRedisCalls=(e.loopRedisCalls??0)+i),p>=3&&(e.loopExternalCalls=(e.loopExternalCalls??0)+p);const L=o-a+(s-i)+(C-p);n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:"warning",context:{sessionId:n.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{}},fingerprint:b({kind:"loop",identity:r}),title:`loop in ${r} (${y.join(", ")})`,raw:{kind:"loop",...n.projectName?{project:n.projectName}:{},loop:{iterations:Math.max(a,i,p),durationMs:F,startedAt:u,dbCallsInsideLoop:a,redisCallsInsideLoop:i,externalCallsInsideLoop:p,awaitedOperationsInsideLoop:a+i+p,...L>0?{reportedBeneath:L}:{}}}})},"emitLoopIfNeeded"),S=l(o=>{c&&M(),q("ok",void 0,o),N()},"onOk"),k=l(o=>{c&&M(),q("error",o),N()},"onErr");try{const o=J.run(g,()=>{D=Date.now();const s=R.now();try{return t.apply(this,I)}finally{j=Math.round((R.now()-s)*100)/100}});return o instanceof Promise?o.then(s=>(S(s),s),s=>{throw k(s),s}):(S(o),o)}catch(o){throw k(o),o}},"wrapped");return x[LENSMCP_TRACED]=!0,x}m(traceWrap,"traceWrap"),l(traceWrap,"traceWrap");
1
+ "use strict";var Q=Object.defineProperty;var f=(t,r)=>Q(t,"name",{value:r,configurable:!0});var V=Object.defineProperty,l=f((t,r)=>V(t,"name",{value:r,configurable:!0}),"l");import{fingerprint as b,ulid as w}from"@lensmcp/core";import{redactText as $}from"@lensmcp/core/redact";import{captureFor as z,capturedValue as O}from"./capture.js";import{currentLensmcpContext as H,currentSpanId as X,LENSMCP_SPAN_STORAGE as J}from"./context.js";import{activeOptions as K}from"./state.js";import{performance as R}from"node:perf_hooks";export const LENSMCP_TRACED=Symbol.for("@lensmcp/nest-instrumentation/traced"),LENSMCP_IGNORE=Symbol.for("@lensmcp/nest-instrumentation/ignore");const h=300;function T(t){if(t instanceof Error){const d=t.code,p=$(t.message??"");return{name:t.name||t.constructor?.name||"Error",...p?{message:p.length>h?`${p.slice(0,h)}\u2026`:p}:{},...typeof d=="string"&&d.length<=64?{code:d}:{}}}const r=typeof t=="string"?$(t):void 0;return{name:t==null?"nothing":typeof t=="object"?"non-Error object":`non-Error ${typeof t}`,...r?{message:r.slice(0,h)}:{}}}f(T,"z"),l(T,"thrownOf");export function LensmcpIgnore(){return((t,r)=>{if(r===void 0)t[LENSMCP_IGNORE]=!0;else{const d=t[r];typeof d=="function"&&(d[LENSMCP_IGNORE]=!0)}})}f(LensmcpIgnore,"LensmcpIgnore"),l(LensmcpIgnore,"LensmcpIgnore");export function isTraced(t){return typeof t=="function"&&!!t[LENSMCP_TRACED]}f(isTraced,"isTraced"),l(isTraced,"isTraced");export function isIgnored(t){return!!t&&(typeof t=="function"||typeof t=="object")&&!!t[LENSMCP_IGNORE]}f(isIgnored,"isIgnored"),l(isIgnored,"isIgnored");export function traceWrap(t,r,d={}){if(isTraced(t))return t;const p=d.db??!1,x=l(function(...I){let n;try{n=K()}catch{return t.apply(this,I)}if(!n.trace.services)return t.apply(this,I);const u=Date.now();let D=u,j=0;const e=H(),g=w(),E=X(),A=e?.dbCallCount??0,P=e?.redisCallCount??0,_=e?.externalCallCount??0,B=e?.loopDbCalls??0,G=e?.loopRedisCalls??0,W=e?.loopExternalCalls??0,m=d.capture??z(r),v=m==="args"||m==="both"?O(I):void 0,M=l((o,s,C)=>{const a=Date.now()-u;if(o==="ok"&&!d.explicit&&m===void 0&&!e?.flowId&&!e?.requestId&&!e?.runId&&a<n.trace.flowlessMinMs)return;const i={id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:o==="error"?"error":"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"method-call",identity:r}),title:`${r} (${a}ms)${o==="error"?" \u2717":""}`,message:s instanceof Error?s.message:void 0,raw:{kind:"span",...n.projectName?{project:n.projectName}:{},span:{name:r,spanId:g,...E?{parentSpanId:E}:{},flowId:e?.flowId,requestId:e?.requestId,startTime:u,endTime:Date.now(),durationMs:a,syncStartedAt:D,syncDurationMs:j,status:o,outcome:o==="error"?"threw":"returned",...o==="error"?{error:T(s)}:{},...v!==void 0?{args:v}:{},...o==="ok"&&(m==="result"||m==="both")?{result:O(C)}:{}}}};n.emit(i)},"emitSpan"),q=l(()=>{e&&(e.dbCallCount=(e.dbCallCount??0)+1);const o=Date.now();n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"db",category:"db",severity:"info",context:{sessionId:n.sessionId,flowId:e?.flowId,requestId:e?.requestId,...e?.runId?{runId:e.runId}:{}},fingerprint:b({kind:"db-query",identity:r}),title:`query ${r}`,raw:{kind:"db-query",...n.projectName?{project:n.projectName}:{},query:{signature:r},startedAt:u,durationMs:o-u,span:{parentSpanId:g}}})},"emitDbQuery"),k=l(()=>{if(p||!e)return;const o=(e.dbCallCount??0)-A,s=(e.redisCallCount??0)-P,C=(e.externalCallCount??0)-_,a=Math.max(0,o-((e.loopDbCalls??0)-B)),i=Math.max(0,s-((e.loopRedisCalls??0)-G)),c=Math.max(0,C-((e.loopExternalCalls??0)-W));if(a<3&&i<3&&c<3)return;const F=Date.now()-u,y=[];a>=3&&y.push(`${a} DB calls`),i>=3&&y.push(`${i} redis calls`),c>=3&&y.push(`${c} external calls`),a>=3&&(e.loopDbCalls=(e.loopDbCalls??0)+a),i>=3&&(e.loopRedisCalls=(e.loopRedisCalls??0)+i),c>=3&&(e.loopExternalCalls=(e.loopExternalCalls??0)+c);const L=o-a+(s-i)+(C-c);n.emit({id:w(),sessionId:n.sessionId,timestamp:Date.now(),source:"nestjs",category:"backend",severity:"warning",context:{sessionId:n.sessionId,flowId:e.flowId,requestId:e.requestId,...e.runId?{runId:e.runId}:{}},fingerprint:b({kind:"loop",identity:r}),title:`loop in ${r} (${y.join(", ")})`,raw:{kind:"loop",...n.projectName?{project:n.projectName}:{},loop:{iterations:Math.max(a,i,c),durationMs:F,startedAt:u,dbCallsInsideLoop:a,redisCallsInsideLoop:i,externalCallsInsideLoop:c,awaitedOperationsInsideLoop:a+i+c,...L>0?{reportedBeneath:L}:{}}}})},"emitLoopIfNeeded"),N=l(o=>{p&&q(),M("ok",void 0,o),k()},"onOk"),S=l(o=>{p&&q(),M("error",o),k()},"onErr");try{const o=J.run(g,()=>{D=Date.now();const s=R.now();try{return t.apply(this,I)}finally{j=Math.round((R.now()-s)*100)/100}});return o instanceof Promise?o.then(s=>(N(s),s),s=>{throw S(s),s}):(N(o),o)}catch(o){throw S(o),o}},"wrapped");return x[LENSMCP_TRACED]=!0,x}f(traceWrap,"traceWrap"),l(traceWrap,"traceWrap");
package/lib/state.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { LensmcpModuleOptions, ResolvedOptions } from './types.js';
2
2
  export declare function setActiveOptions(opts: LensmcpModuleOptions): ResolvedOptions;
3
+ /** The floor a call outside any flow must reach to emit its span: the environment's, else the option's, else 10 ms. */
4
+ export declare function flowlessMinMs(option: number | undefined, env?: NodeJS.ProcessEnv): number;
3
5
  export declare function activeOptions(): ResolvedOptions;
4
6
  export declare function currentGeneration(): number;
package/lib/state.js CHANGED
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var r=(e,t)=>u(e,"name",{value:t,configurable:!0});var d=Object.defineProperty,o=r((e,t)=>d(e,"name",{value:t,configurable:!0}),"r");import{ulid as m}from"@lensmcp/core";import{defaultEventSink as p}from"./event-sink.js";import{performance as f}from"node:perf_hooks";import{threadId as l}from"node:worker_threads";let s,a=0;export function setActiveOptions(e){const t=e.emit??p(),c=`node:${process.pid}:${l}:${f.timeOrigin}`,n={projectName:e.projectName,sessionId:e.sessionId??process.env.LENSMCP_SESSION_ID??m(),emit:o(i=>t({...i,raw:{...i.raw,loopId:c}}),"emit"),trace:{requests:e.trace?.requests??!0,guards:e.trace?.guards??!0,controllers:e.trace?.controllers??!0,services:e.trace?.services??!0,db:e.trace?.db??!1,redis:e.trace?.redis??!1,queues:e.trace?.queues??!1,autoInstrumentMethods:e.trace?.autoInstrumentMethods??!1,suppressNoiseMethods:e.trace?.suppressNoiseMethods??!0},memory:{mode:e.memory?.mode??"off",scope:e.memory?.scope??"tagged"}};return s=n,a+=1,n}r(setActiveOptions,"setActiveOptions"),o(setActiveOptions,"setActiveOptions");export function activeOptions(){if(!s)throw new Error("@lensmcp/nest-instrumentation: LensmcpModule.forRoot(...) has not been registered. Did you forget to add it to your AppModule?");return s}r(activeOptions,"activeOptions"),o(activeOptions,"activeOptions");export function currentGeneration(){return a}r(currentGeneration,"currentGeneration"),o(currentGeneration,"currentGeneration");
1
+ "use strict";var u=Object.defineProperty;var s=(e,r)=>u(e,"name",{value:r,configurable:!0});var m=Object.defineProperty,n=s((e,r)=>m(e,"name",{value:r,configurable:!0}),"t");import{ulid as d}from"@lensmcp/core";import{defaultEventSink as p}from"./event-sink.js";import{performance as f}from"node:perf_hooks";import{threadId as l}from"node:worker_threads";let i,a=0;export function setActiveOptions(e){const r=e.emit??p(),o=`node:${process.pid}:${l}:${f.timeOrigin}`,t={projectName:e.projectName,sessionId:e.sessionId??process.env.LENSMCP_SESSION_ID??d(),emit:n(c=>r({...c,raw:{...c.raw,loopId:o}}),"emit"),trace:{requests:e.trace?.requests??!0,guards:e.trace?.guards??!0,controllers:e.trace?.controllers??!0,services:e.trace?.services??!0,db:e.trace?.db??!1,redis:e.trace?.redis??!1,queues:e.trace?.queues??!1,autoInstrumentMethods:e.trace?.autoInstrumentMethods??!1,suppressNoiseMethods:e.trace?.suppressNoiseMethods??!0,flowlessMinMs:flowlessMinMs(e.trace?.flowlessMinMs)},memory:{mode:e.memory?.mode??"off",scope:e.memory?.scope??"tagged"}};return i=t,a+=1,t}s(setActiveOptions,"setActiveOptions"),n(setActiveOptions,"setActiveOptions");export function flowlessMinMs(e,r=process.env){const o=r.LENSMCP_TRACE_FLOWLESS_MIN_MS,t=o!==void 0&&o.trim()!==""?Number(o):e;return t!==void 0&&Number.isFinite(t)&&t>=0?t:10}s(flowlessMinMs,"flowlessMinMs"),n(flowlessMinMs,"flowlessMinMs");export function activeOptions(){if(!i)throw new Error("@lensmcp/nest-instrumentation: LensmcpModule.forRoot(...) has not been registered. Did you forget to add it to your AppModule?");return i}s(activeOptions,"activeOptions"),n(activeOptions,"activeOptions");export function currentGeneration(){return a}s(currentGeneration,"currentGeneration"),n(currentGeneration,"currentGeneration");
@@ -1 +1 @@
1
- "use strict";var i=Object.defineProperty;var n=(r,e)=>i(r,"name",{value:e,configurable:!0});var s=Object.defineProperty,p=n((r,e)=>s(r,"name",{value:e,configurable:!0}),"u");import{traceWrap as m}from"./instrument.js";export function TraceMethod(r){const e=typeof r=="string"?{name:r}:r??{};return function(c,u,t){const a=t.value;if(typeof a!="function")return t;const o=c.constructor?.name??"Anonymous",f=e.name??`${o}.${String(u)}`;return t.value=m(a,f,{db:e.db,...e.capture?{capture:e.capture}:{}}),t}}n(TraceMethod,"TraceMethod"),p(TraceMethod,"TraceMethod");
1
+ "use strict";var p=Object.defineProperty;var n=(e,t)=>p(e,"name",{value:t,configurable:!0});var f=Object.defineProperty,s=n((e,t)=>f(e,"name",{value:t,configurable:!0}),"a");import{traceWrap as m}from"./instrument.js";export function TraceMethod(e){const t=typeof e=="string"?{name:e}:e??{};return function(c,u,r){const a=r.value;if(typeof a!="function")return r;const o=c.constructor?.name??"Anonymous",i=t.name??`${o}.${String(u)}`;return r.value=m(a,i,{db:t.db,explicit:!0,...t.capture?{capture:t.capture}:{}}),r}}n(TraceMethod,"TraceMethod"),s(TraceMethod,"TraceMethod");
package/lib/types.d.ts CHANGED
@@ -42,6 +42,14 @@ export interface LensmcpModuleOptions {
42
42
  * Default: true. Set false to trace absolutely everything.
43
43
  */
44
44
  suppressNoiseMethods?: boolean;
45
+ /**
46
+ * A traced call outside any request, flow or job run that returned in under this many ms emits no span (default
47
+ * 10; `LENSMCP_TRACE_FLOWLESS_MIN_MS` overrides it, `0` keeps them all). No reader asks for one: on FoodGuard
48
+ * (2026-10-29) a queue's poll loop and 0 ms getters made 69% of all spans, none in a flow, and at the event
49
+ * history's size cap they pushed everything else out after 2.5 days. A call that threw, a `@TraceMethod`, a
50
+ * captured method, and every call in a flow still emit theirs; the loop (N+1) check runs either way.
51
+ */
52
+ flowlessMinMs?: number;
45
53
  };
46
54
  /** Memory tracking (Phase 7). */
47
55
  memory?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/nest-instrumentation",
3
- "version": "1.25.33",
3
+ "version": "1.25.35",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -14,9 +14,9 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@lensmcp/core": "1.25.33",
18
- "@lensmcp/memory-tracker": "1.25.33",
19
- "@lensmcp/protocol-types": "1.25.33",
17
+ "@lensmcp/core": "1.25.35",
18
+ "@lensmcp/memory-tracker": "1.25.35",
19
+ "@lensmcp/protocol-types": "1.25.35",
20
20
  "tslib": "^2.3.0"
21
21
  },
22
22
  "peerDependencies": {