@lensmcp/node-instrumentation 1.18.4 → 1.18.7

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/index.js CHANGED
@@ -1,32 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.installLensmcpNodeInstrumentation = exports.installChildAppBridge = exports.nestTap = exports.bullmqTap = exports.redisTap = exports.pgTap = exports.tapExec = exports.tapFs = exports.tapEgress = exports.installRequireHook = exports.createEventFloodGuard = exports.maybeRotateEventFile = exports.EVENT_MAX_FILES = exports.EVENT_MAX_BYTES = exports.EVENT_FILE = exports.PROJECT = exports.isLensWriting = exports.lensEmit = void 0;
4
- var emit_1 = require("./lib/emit");
5
- Object.defineProperty(exports, "lensEmit", { enumerable: true, get: function () { return emit_1.lensEmit; } });
6
- Object.defineProperty(exports, "isLensWriting", { enumerable: true, get: function () { return emit_1.isLensWriting; } });
7
- Object.defineProperty(exports, "PROJECT", { enumerable: true, get: function () { return emit_1.PROJECT; } });
8
- Object.defineProperty(exports, "EVENT_FILE", { enumerable: true, get: function () { return emit_1.EVENT_FILE; } });
9
- Object.defineProperty(exports, "EVENT_MAX_BYTES", { enumerable: true, get: function () { return emit_1.EVENT_MAX_BYTES; } });
10
- Object.defineProperty(exports, "EVENT_MAX_FILES", { enumerable: true, get: function () { return emit_1.EVENT_MAX_FILES; } });
11
- Object.defineProperty(exports, "maybeRotateEventFile", { enumerable: true, get: function () { return emit_1.maybeRotateEventFile; } });
12
- var flood_guard_1 = require("./lib/flood-guard");
13
- Object.defineProperty(exports, "createEventFloodGuard", { enumerable: true, get: function () { return flood_guard_1.createEventFloodGuard; } });
14
- var require_hook_1 = require("./lib/require-hook");
15
- Object.defineProperty(exports, "installRequireHook", { enumerable: true, get: function () { return require_hook_1.installRequireHook; } });
16
- var tap_egress_1 = require("./lib/tap-egress");
17
- Object.defineProperty(exports, "tapEgress", { enumerable: true, get: function () { return tap_egress_1.tapEgress; } });
18
- var tap_fs_1 = require("./lib/tap-fs");
19
- Object.defineProperty(exports, "tapFs", { enumerable: true, get: function () { return tap_fs_1.tapFs; } });
20
- var tap_exec_1 = require("./lib/tap-exec");
21
- Object.defineProperty(exports, "tapExec", { enumerable: true, get: function () { return tap_exec_1.tapExec; } });
22
- var tap_pg_1 = require("./lib/tap-pg");
23
- Object.defineProperty(exports, "pgTap", { enumerable: true, get: function () { return tap_pg_1.pgTap; } });
24
- var tap_redis_1 = require("./lib/tap-redis");
25
- Object.defineProperty(exports, "redisTap", { enumerable: true, get: function () { return tap_redis_1.redisTap; } });
26
- var tap_bullmq_1 = require("./lib/tap-bullmq");
27
- Object.defineProperty(exports, "bullmqTap", { enumerable: true, get: function () { return tap_bullmq_1.bullmqTap; } });
28
- var tap_nest_1 = require("./lib/tap-nest");
29
- Object.defineProperty(exports, "nestTap", { enumerable: true, get: function () { return tap_nest_1.nestTap; } });
30
- Object.defineProperty(exports, "installChildAppBridge", { enumerable: true, get: function () { return tap_nest_1.installChildAppBridge; } });
31
- var install_1 = require("./lib/install");
32
- Object.defineProperty(exports, "installLensmcpNodeInstrumentation", { enumerable: true, get: function () { return install_1.installLensmcpNodeInstrumentation; } });
1
+ "use strict";var o=Object.defineProperty;var i=(t,r)=>o(t,"name",{value:r,configurable:!0});var n=Object.defineProperty,e=i((t,r)=>n(t,"name",{value:r,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.installLensmcpNodeInstrumentation=exports.installChildAppBridge=exports.nestTap=exports.bullmqTap=exports.redisTap=exports.pgTap=exports.tapExec=exports.tapFs=exports.tapEgress=exports.installRequireHook=exports.createEventFloodGuard=exports.maybeRotateEventFile=exports.EVENT_MAX_FILES=exports.EVENT_MAX_BYTES=exports.EVENT_FILE=exports.PROJECT=exports.isLensWriting=exports.lensEmit=void 0;var emit_1=require("./lib/emit");Object.defineProperty(exports,"lensEmit",{enumerable:!0,get:e(function(){return emit_1.lensEmit},"get")}),Object.defineProperty(exports,"isLensWriting",{enumerable:!0,get:e(function(){return emit_1.isLensWriting},"get")}),Object.defineProperty(exports,"PROJECT",{enumerable:!0,get:e(function(){return emit_1.PROJECT},"get")}),Object.defineProperty(exports,"EVENT_FILE",{enumerable:!0,get:e(function(){return emit_1.EVENT_FILE},"get")}),Object.defineProperty(exports,"EVENT_MAX_BYTES",{enumerable:!0,get:e(function(){return emit_1.EVENT_MAX_BYTES},"get")}),Object.defineProperty(exports,"EVENT_MAX_FILES",{enumerable:!0,get:e(function(){return emit_1.EVENT_MAX_FILES},"get")}),Object.defineProperty(exports,"maybeRotateEventFile",{enumerable:!0,get:e(function(){return emit_1.maybeRotateEventFile},"get")});var flood_guard_1=require("./lib/flood-guard");Object.defineProperty(exports,"createEventFloodGuard",{enumerable:!0,get:e(function(){return flood_guard_1.createEventFloodGuard},"get")});var require_hook_1=require("./lib/require-hook");Object.defineProperty(exports,"installRequireHook",{enumerable:!0,get:e(function(){return require_hook_1.installRequireHook},"get")});var tap_egress_1=require("./lib/tap-egress");Object.defineProperty(exports,"tapEgress",{enumerable:!0,get:e(function(){return tap_egress_1.tapEgress},"get")});var tap_fs_1=require("./lib/tap-fs");Object.defineProperty(exports,"tapFs",{enumerable:!0,get:e(function(){return tap_fs_1.tapFs},"get")});var tap_exec_1=require("./lib/tap-exec");Object.defineProperty(exports,"tapExec",{enumerable:!0,get:e(function(){return tap_exec_1.tapExec},"get")});var tap_pg_1=require("./lib/tap-pg");Object.defineProperty(exports,"pgTap",{enumerable:!0,get:e(function(){return tap_pg_1.pgTap},"get")});var tap_redis_1=require("./lib/tap-redis");Object.defineProperty(exports,"redisTap",{enumerable:!0,get:e(function(){return tap_redis_1.redisTap},"get")});var tap_bullmq_1=require("./lib/tap-bullmq");Object.defineProperty(exports,"bullmqTap",{enumerable:!0,get:e(function(){return tap_bullmq_1.bullmqTap},"get")});var tap_nest_1=require("./lib/tap-nest");Object.defineProperty(exports,"nestTap",{enumerable:!0,get:e(function(){return tap_nest_1.nestTap},"get")}),Object.defineProperty(exports,"installChildAppBridge",{enumerable:!0,get:e(function(){return tap_nest_1.installChildAppBridge},"get")});var install_1=require("./lib/install");Object.defineProperty(exports,"installLensmcpNodeInstrumentation",{enumerable:!0,get:e(function(){return install_1.installLensmcpNodeInstrumentation},"get")});
package/lib/emit.js CHANGED
@@ -1,187 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EVENT_FILE = exports.PROJECT = exports.EVENT_MAX_FILES = exports.EVENT_MAX_BYTES = void 0;
4
- exports.maybeRotateEventFile = maybeRotateEventFile;
5
- exports.isLensWriting = isLensWriting;
6
- exports.lensEmit = lensEmit;
7
- exports.lensLog = lensLog;
8
- const tslib_1 = require("tslib");
9
- /**
10
- * The LensMCP bus writer for zero-touch taps — file rendezvous, one
11
- * BaseEvent-shaped JSON line per event (the exact shape the cluster
12
- * dashboard reducers consume: source db/redis/queue/external/fs/exec,
13
- * category `cluster`, raw `{ project, name, kind, … }`).
14
- *
15
- * The fs originals are BOUND to local consts at module load, BEFORE the fs
16
- * tap patches the fs namespace — so the lens writing to its own bus calls the
17
- * pristine functions and can never recurse into its own instrumentation. (A
18
- * bare `import { appendFileSync }` compiles to a LIVE namespace read, which
19
- * would pick up the tap's wrapper after patching — hence the explicit bind.)
20
- * `isLensWriting()` is a second belt for anything that still re-enters.
21
- */
22
- const nodeFs = tslib_1.__importStar(require("node:fs"));
23
- const node_path_1 = require("node:path");
24
- const flood_guard_1 = require("./flood-guard");
25
- // Pristine references, frozen at load — immune to the fs tap's later patching.
26
- const appendFileSync = nodeFs.appendFileSync;
27
- const mkdirSync = nodeFs.mkdirSync;
28
- const statSync = nodeFs.statSync;
29
- const renameSync = nodeFs.renameSync;
30
- const unlinkSync = nodeFs.unlinkSync;
31
- /** Per-generation cap for the events.jsonl bus (a long-running dev session hit 9 GB in the wild, and
32
- * a 254 MB file OOM-crash-looped every ingesting consumer in the 2026-07-28 flood). Override with
33
- * `LENSMCP_EVENT_MAX_BYTES` (default 50 MB). 0/invalid ⇒ the default. */
34
- exports.EVENT_MAX_BYTES = (() => {
35
- const n = Number(process.env['LENSMCP_EVENT_MAX_BYTES']);
36
- return Number.isFinite(n) && n > 0 ? n : 50 * 1024 * 1024;
37
- })();
38
- /** Rotated generations kept as history (`events.jsonl.1` … `.N`, newest → oldest). With the live
39
- * file that bounds a workspace to (N+1) × EVENT_MAX_BYTES (~300 MB at defaults). Override with
40
- * `LENSMCP_EVENT_MAX_FILES`. */
41
- exports.EVENT_MAX_FILES = (() => {
42
- const n = Number(process.env['LENSMCP_EVENT_MAX_FILES']);
43
- return Number.isFinite(n) && n >= 1 && n <= 20 ? Math.floor(n) : 5;
44
- })();
45
- const ROTATE_CHECK_BYTES = 1024 * 1024; // statSync at most ~once per MB appended (cheap amortized)
46
- const bytesSinceCheck = new Map();
47
- /**
48
- * Round-robin rotation for a bus file: when the live file exceeds the per-generation cap, drop the
49
- * oldest generation, shift `.1` → `.2` → … → `.N`, and rename the live file to `.1` (the next append
50
- * recreates it fresh). Throttled by a per-file byte counter so we `statSync` at most ~once per MB,
51
- * not per event. Best-effort + fs-tap-safe (pristine refs, callers hold `writing`). Many processes
52
- * append to the same path — the final `renameSync` is atomic, so a concurrent rotate that loses the
53
- * race just throws ENOENT and no-ops (a racing shift can at worst mis-order HISTORY generations,
54
- * never touch the live file). Exported so every OTHER writer to the shared bus (the cluster
55
- * gateway's observability emit) enforces the same contract — the gateway was the one writer with NO
56
- * rotation, which is how foodguard's bus outgrew every consumer. `caps` exists for tests.
57
- */
58
- function maybeRotateEventFile(file, addedBytes, caps) {
59
- const sofar = (bytesSinceCheck.get(file) ?? 0) + addedBytes;
60
- if (sofar < ROTATE_CHECK_BYTES && caps === undefined) {
61
- bytesSinceCheck.set(file, sofar);
62
- return;
63
- }
64
- bytesSinceCheck.set(file, 0);
65
- const maxBytes = caps?.maxBytes ?? exports.EVENT_MAX_BYTES;
66
- const maxFiles = caps?.maxFiles ?? exports.EVENT_MAX_FILES;
67
- try {
68
- if (statSync(file).size <= maxBytes)
69
- return;
70
- }
71
- catch {
72
- return; /* missing / already rotated by another writer — fine */
73
- }
74
- try {
75
- unlinkSync(`${file}.${maxFiles}`);
76
- }
77
- catch {
78
- /* no oldest generation to drop */
79
- }
80
- for (let gen = maxFiles - 1; gen >= 1; gen--) {
81
- try {
82
- renameSync(`${file}.${gen}`, `${file}.${gen + 1}`);
83
- }
84
- catch {
85
- /* that generation doesn't exist (yet) */
86
- }
87
- }
88
- try {
89
- renameSync(file, `${file}.1`);
90
- }
91
- catch {
92
- /* lost the rotate race to a concurrent writer — its cascade already ran */
93
- }
94
- }
95
- /** Service identity stamped on every event (set by the cluster serve executor). */
96
- exports.PROJECT = process.env['LENSMCP_PROJECT'] || process.env['SERVICE_NAME'] || 'app';
97
- /** The file bus — same default the cluster gateway and dashboard use. */
98
- exports.EVENT_FILE = process.env['LENSMCP_EVENT_FILE'] || `${process.cwd()}/.lensmcp/events.jsonl`;
99
- let writing = false;
100
- let dirReady = false;
101
- // Flood guards: one hot fingerprint (a query fired in a loop, a component rendering per frame) keeps
102
- // its first burst + a trickle with `floodSuppressed` counts, instead of flooding the bus file. Logs get
103
- // a laxer bucket — boot storms of a few hundred lines are normal; sustained >20 lines/s is log-spam the
104
- // bus shouldn't mirror unboundedly.
105
- const emitGuard = (0, flood_guard_1.createEventFloodGuard)({ burst: 30, perSec: 5 });
106
- const logGuard = (0, flood_guard_1.createEventFloodGuard)({ burst: 120, perSec: 20 });
107
- /** True while the lens itself is appending — the fs tap skips self-writes. */
108
- function isLensWriting() {
109
- return writing;
110
- }
111
- function lensEmit(source, severity, title, raw, fingerprint, flowCtx) {
112
- try {
113
- writing = true;
114
- const fp = fingerprint ?? `${source}:${String(raw['name'] ?? source)}`;
115
- const admit = emitGuard.admit(fp);
116
- if (!admit.ok)
117
- return; // flood-guarded — the next admitted event carries the suppressed count
118
- if (!dirReady) {
119
- mkdirSync((0, node_path_1.dirname)(exports.EVENT_FILE), { recursive: true });
120
- dirReady = true;
121
- }
122
- const line = JSON.stringify({
123
- id: Date.now().toString(36) + Math.random().toString(36).slice(2, 10),
124
- sessionId: 'pending',
125
- timestamp: Date.now(),
126
- source,
127
- category: 'cluster',
128
- severity,
129
- context: {
130
- sessionId: 'pending',
131
- projectName: exports.PROJECT,
132
- ...(flowCtx?.flowId ? { flowId: flowCtx.flowId } : {}),
133
- ...(flowCtx?.requestId ? { requestId: flowCtx.requestId } : {}),
134
- ...(flowCtx?.originNodeId ? { originNodeId: flowCtx.originNodeId } : {}),
135
- },
136
- fingerprint: fp,
137
- title,
138
- raw: { project: exports.PROJECT, ...raw, ...(admit.suppressed > 0 ? { floodSuppressed: admit.suppressed } : {}) },
139
- }) + '\n';
140
- maybeRotateEventFile(exports.EVENT_FILE, line.length);
141
- appendFileSync(exports.EVENT_FILE, line);
142
- }
143
- catch {
144
- /* lens offline — never break the host */
145
- }
146
- finally {
147
- writing = false;
148
- }
149
- }
150
- /**
151
- * Emit a raw service log line — the ACTUAL stdout/Logger output a pod prints — as
152
- * a `runtime` event so it surfaces in the dashboard Logs view. Same fs-tap-safe
153
- * write path as {@link lensEmit}; `source: 'nestjs'` + `raw.project` carries the
154
- * concrete service name (which the dashboard prefers as the log's source).
155
- */
156
- function lensLog(severity, title) {
157
- try {
158
- writing = true;
159
- const admit = logGuard.admit(`log:${exports.PROJECT}`);
160
- if (!admit.ok)
161
- return; // log-spam guard — the next admitted line carries the suppressed count
162
- if (!dirReady) {
163
- mkdirSync((0, node_path_1.dirname)(exports.EVENT_FILE), { recursive: true });
164
- dirReady = true;
165
- }
166
- const line = JSON.stringify({
167
- id: Date.now().toString(36) + Math.random().toString(36).slice(2, 10),
168
- sessionId: 'pending',
169
- timestamp: Date.now(),
170
- source: 'nestjs',
171
- category: 'runtime',
172
- severity,
173
- context: { sessionId: 'pending', projectName: exports.PROJECT },
174
- fingerprint: `log:${exports.PROJECT}`,
175
- title,
176
- raw: { project: exports.PROJECT, kind: 'log', ...(admit.suppressed > 0 ? { floodSuppressed: admit.suppressed } : {}) },
177
- }) + '\n';
178
- maybeRotateEventFile(exports.EVENT_FILE, line.length);
179
- appendFileSync(exports.EVENT_FILE, line);
180
- }
181
- catch {
182
- /* lens offline — never break the host */
183
- }
184
- finally {
185
- writing = false;
186
- }
187
- }
1
+ "use strict";var u=Object.defineProperty;var E=(e,t)=>u(e,"name",{value:t,configurable:!0});var d=Object.defineProperty,i=E((e,t)=>d(e,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EVENT_FILE=exports.PROJECT=exports.EVENT_MAX_FILES=exports.EVENT_MAX_BYTES=void 0,exports.maybeRotateEventFile=maybeRotateEventFile,exports.isLensWriting=isLensWriting,exports.lensEmit=lensEmit,exports.lensLog=lensLog;const tslib_1=require("tslib"),nodeFs=tslib_1.__importStar(require("node:fs")),node_path_1=require("node:path"),flood_guard_1=require("./flood-guard"),appendFileSync=nodeFs.appendFileSync,mkdirSync=nodeFs.mkdirSync,statSync=nodeFs.statSync,renameSync=nodeFs.renameSync,unlinkSync=nodeFs.unlinkSync;exports.EVENT_MAX_BYTES=(()=>{const e=Number(process.env.LENSMCP_EVENT_MAX_BYTES);return Number.isFinite(e)&&e>0?e:50*1024*1024})(),exports.EVENT_MAX_FILES=(()=>{const e=Number(process.env.LENSMCP_EVENT_MAX_FILES);return Number.isFinite(e)&&e>=1&&e<=20?Math.floor(e):5})();const ROTATE_CHECK_BYTES=1024*1024,bytesSinceCheck=new Map;function maybeRotateEventFile(e,t,r){const n=(bytesSinceCheck.get(e)??0)+t;if(n<ROTATE_CHECK_BYTES&&r===void 0){bytesSinceCheck.set(e,n);return}bytesSinceCheck.set(e,0);const p=r?.maxBytes??exports.EVENT_MAX_BYTES,s=r?.maxFiles??exports.EVENT_MAX_FILES;try{if(statSync(e).size<=p)return}catch{return}try{unlinkSync(`${e}.${s}`)}catch{}for(let o=s-1;o>=1;o--)try{renameSync(`${e}.${o}`,`${e}.${o+1}`)}catch{}try{renameSync(e,`${e}.1`)}catch{}}E(maybeRotateEventFile,"maybeRotateEventFile"),i(maybeRotateEventFile,"maybeRotateEventFile"),exports.PROJECT=process.env.LENSMCP_PROJECT||process.env.SERVICE_NAME||"app",exports.EVENT_FILE=process.env.LENSMCP_EVENT_FILE||`${process.cwd()}/.lensmcp/events.jsonl`;let writing=!1,dirReady=!1;const emitGuard=(0,flood_guard_1.createEventFloodGuard)({burst:30,perSec:5}),logGuard=(0,flood_guard_1.createEventFloodGuard)({burst:120,perSec:20});function isLensWriting(){return writing}E(isLensWriting,"isLensWriting"),i(isLensWriting,"isLensWriting");function lensEmit(e,t,r,n,p,s){try{writing=!0;const o=p??`${e}:${String(n.name??e)}`,c=emitGuard.admit(o);if(!c.ok)return;dirReady||(mkdirSync((0,node_path_1.dirname)(exports.EVENT_FILE),{recursive:!0}),dirReady=!0);const a=JSON.stringify({id:Date.now().toString(36)+Math.random().toString(36).slice(2,10),sessionId:"pending",timestamp:Date.now(),source:e,category:"cluster",severity:t,context:{sessionId:"pending",projectName:exports.PROJECT,...s?.flowId?{flowId:s.flowId}:{},...s?.requestId?{requestId:s.requestId}:{},...s?.originNodeId?{originNodeId:s.originNodeId}:{}},fingerprint:o,title:r,raw:{project:exports.PROJECT,...n,...c.suppressed>0?{floodSuppressed:c.suppressed}:{}}})+`
2
+ `;maybeRotateEventFile(exports.EVENT_FILE,a.length),appendFileSync(exports.EVENT_FILE,a)}catch{}finally{writing=!1}}E(lensEmit,"lensEmit"),i(lensEmit,"lensEmit");function lensLog(e,t){try{writing=!0;const r=logGuard.admit(`log:${exports.PROJECT}`);if(!r.ok)return;dirReady||(mkdirSync((0,node_path_1.dirname)(exports.EVENT_FILE),{recursive:!0}),dirReady=!0);const n=JSON.stringify({id:Date.now().toString(36)+Math.random().toString(36).slice(2,10),sessionId:"pending",timestamp:Date.now(),source:"nestjs",category:"runtime",severity:e,context:{sessionId:"pending",projectName:exports.PROJECT},fingerprint:`log:${exports.PROJECT}`,title:t,raw:{project:exports.PROJECT,kind:"log",...r.suppressed>0?{floodSuppressed:r.suppressed}:{}}})+`
3
+ `;maybeRotateEventFile(exports.EVENT_FILE,n.length),appendFileSync(exports.EVENT_FILE,n)}catch{}finally{writing=!1}}E(lensLog,"lensLog"),i(lensLog,"lensLog");
@@ -1,70 +1 @@
1
- "use strict";
2
- /**
3
- * Per-fingerprint event flood guard — a token bucket per fingerprint, shared by
4
- * every bus WRITER (lensEmit, the cluster gateway's observability emit, the
5
- * bridge env sink mirrors it). One hot fingerprint (an app retrying a fetch in
6
- * a loop, a valtio store churning per frame) must not be able to write hundreds
7
- * of lines/sec into events.jsonl: the 2026-07-28 incident sustained ~680 ev/s,
8
- * grew the bus file to 254 MB in two hours, and OOM-crash-looped every consumer
9
- * that ingests it. The guard keeps the SIGNAL (the first burst, then a trickle
10
- * with a `floodSuppressed` count) while dropping the redundant repetition.
11
- */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.createEventFloodGuard = createEventFloodGuard;
14
- /** `LENSMCP_EVENT_MAX_EPS` overrides the sustained per-fingerprint rate; `0`/`off` disables the guard. */
15
- function envPerSec() {
16
- const raw = process.env['LENSMCP_EVENT_MAX_EPS'];
17
- if (raw === undefined || raw === '')
18
- return undefined;
19
- if (raw === 'off')
20
- return 'off';
21
- const n = Number(raw);
22
- if (!Number.isFinite(n))
23
- return undefined;
24
- return n <= 0 ? 'off' : n;
25
- }
26
- function createEventFloodGuard(opts) {
27
- const env = envPerSec();
28
- if (env === 'off')
29
- return { admit: () => ({ ok: true, suppressed: 0 }) };
30
- const perSec = env ?? opts?.perSec ?? 5;
31
- const burst = Math.max(1, opts?.burst ?? Math.max(30, perSec * 4));
32
- const maxKeys = opts?.maxKeys ?? 2048;
33
- const buckets = new Map();
34
- const evictStale = (now) => {
35
- // A full bucket carries no state worth keeping — evict anything idle >60s.
36
- for (const [k, b] of buckets) {
37
- if (now - b.lastSeenAt > 60_000)
38
- buckets.delete(k);
39
- }
40
- // Pathological cardinality (unique fingerprints per event) — reset rather than grow.
41
- if (buckets.size > maxKeys)
42
- buckets.clear();
43
- };
44
- return {
45
- admit(fingerprint) {
46
- const now = Date.now();
47
- let b = buckets.get(fingerprint);
48
- if (!b) {
49
- if (buckets.size >= maxKeys)
50
- evictStale(now);
51
- b = { tokens: burst, lastRefillAt: now, suppressed: 0, lastSeenAt: now };
52
- buckets.set(fingerprint, b);
53
- }
54
- b.lastSeenAt = now;
55
- const elapsed = now - b.lastRefillAt;
56
- if (elapsed > 0) {
57
- b.tokens = Math.min(burst, b.tokens + (elapsed / 1000) * perSec);
58
- b.lastRefillAt = now;
59
- }
60
- if (b.tokens >= 1) {
61
- b.tokens -= 1;
62
- const suppressed = b.suppressed;
63
- b.suppressed = 0;
64
- return { ok: true, suppressed };
65
- }
66
- b.suppressed += 1;
67
- return { ok: false, suppressed: 0 };
68
- },
69
- };
70
- }
1
+ "use strict";var m=Object.defineProperty;var i=(t,s)=>m(t,"name",{value:s,configurable:!0});var p=Object.defineProperty,o=i((t,s)=>p(t,"name",{value:s,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEventFloodGuard=createEventFloodGuard;function envPerSec(){const t=process.env.LENSMCP_EVENT_MAX_EPS;if(t===void 0||t==="")return;if(t==="off")return"off";const s=Number(t);if(Number.isFinite(s))return s<=0?"off":s}i(envPerSec,"envPerSec"),o(envPerSec,"envPerSec");function createEventFloodGuard(t){const s=envPerSec();if(s==="off")return{admit:o(()=>({ok:!0,suppressed:0}),"admit")};const u=s??t?.perSec??5,c=Math.max(1,t?.burst??Math.max(30,u*4)),f=t?.maxKeys??2048,n=new Map,d=o(a=>{for(const[r,e]of n)a-e.lastSeenAt>6e4&&n.delete(r);n.size>f&&n.clear()},"evictStale");return{admit(a){const r=Date.now();let e=n.get(a);e||(n.size>=f&&d(r),e={tokens:c,lastRefillAt:r,suppressed:0,lastSeenAt:r},n.set(a,e)),e.lastSeenAt=r;const l=r-e.lastRefillAt;if(l>0&&(e.tokens=Math.min(c,e.tokens+l/1e3*u),e.lastRefillAt=r),e.tokens>=1){e.tokens-=1;const v=e.suppressed;return e.suppressed=0,{ok:!0,suppressed:v}}return e.suppressed+=1,{ok:!1,suppressed:0}}}}i(createEventFloodGuard,"createEventFloodGuard"),o(createEventFloodGuard,"createEventFloodGuard");
@@ -1,57 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.primeFlowContext = primeFlowContext;
4
- exports.runInFlowContext = runInFlowContext;
5
- exports.liveFlowContext = liveFlowContext;
6
- exports.flowContext = flowContext;
7
- let getCtx = null;
8
- let runWith = null;
9
- function primeFlowContext() {
10
- import('@lensmcp/nest-instrumentation')
11
- .then((m) => {
12
- getCtx = m.currentLensmcpContext;
13
- runWith = m.runInLensmcpContext;
14
- })
15
- .catch(() => {
16
- /* nest instrumentation absent — tap events stay un-correlated */
17
- });
18
- }
19
- /**
20
- * Run `fn` inside a fresh flow context (queue workers, timers — async
21
- * entry points that aren't HTTP requests). Inner taps then inherit the
22
- * flowId exactly like they do inside a traced request.
23
- */
24
- function runInFlowContext(ctx, fn) {
25
- if (!runWith)
26
- return fn();
27
- try {
28
- return runWith({ sessionId: 'pending', requestId: ctx.requestId, ...(ctx.flowId ? { flowId: ctx.flowId } : {}) }, fn);
29
- }
30
- catch {
31
- return fn();
32
- }
33
- }
34
- /**
35
- * The LIVE (mutable) request context — taps bump its operation counters
36
- * (db/redis/external) so the span wrapper's loop detector can report
37
- * "this method issued N of these" (the N+1 / fan-out signal).
38
- */
39
- function liveFlowContext() {
40
- try {
41
- return getCtx?.();
42
- }
43
- catch {
44
- return undefined;
45
- }
46
- }
47
- /** Snapshot the live flow context — call AT the operation site, before awaits. */
48
- function flowContext() {
49
- const ctx = liveFlowContext();
50
- if (!ctx)
51
- return undefined;
52
- return {
53
- ...(ctx.flowId ? { flowId: ctx.flowId } : {}),
54
- ...(ctx.requestId ? { requestId: ctx.requestId } : {}),
55
- ...(ctx.originNodeId ? { originNodeId: ctx.originNodeId } : {}),
56
- };
57
- }
1
+ "use strict";var l=Object.defineProperty;var r=(e,t)=>l(e,"name",{value:t,configurable:!0});var o=Object.defineProperty,n=r((e,t)=>o(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.primeFlowContext=primeFlowContext,exports.runInFlowContext=runInFlowContext,exports.liveFlowContext=liveFlowContext,exports.flowContext=flowContext;let getCtx=null,runWith=null;function primeFlowContext(){import("@lensmcp/nest-instrumentation").then(e=>{getCtx=e.currentLensmcpContext,runWith=e.runInLensmcpContext}).catch(()=>{})}r(primeFlowContext,"primeFlowContext"),n(primeFlowContext,"primeFlowContext");function runInFlowContext(e,t){if(!runWith)return t();try{return runWith({sessionId:"pending",requestId:e.requestId,...e.flowId?{flowId:e.flowId}:{}},t)}catch{return t()}}r(runInFlowContext,"runInFlowContext"),n(runInFlowContext,"runInFlowContext");function liveFlowContext(){try{return getCtx?.()}catch{return}}r(liveFlowContext,"liveFlowContext"),n(liveFlowContext,"liveFlowContext");function flowContext(){const e=liveFlowContext();if(e)return{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}}}r(flowContext,"flowContext"),n(flowContext,"flowContext");
package/lib/install.js CHANGED
@@ -1,64 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.installLensmcpNodeInstrumentation = installLensmcpNodeInstrumentation;
4
- /**
5
- * Installs every zero-touch tap once (idempotent via a global marker):
6
- *
7
- * builtins/globals — fetch + http/https (egress), fs (aggregated), child_process (exec)
8
- * require hook — pg (db) · ioredis (redis) · bullmq (queue) · @nestjs/core (class
9
- * traces via LensmcpModule graft + zero-touch pod bootstrap)
10
- *
11
- * Kill switches: LENSMCP_AUTO=0 disables everything; LENSMCP_NEST=0 keeps the
12
- * taps but skips the NestJS graft.
13
- */
14
- const require_hook_1 = require("./require-hook");
15
- const flow_context_1 = require("./flow-context");
16
- const tap_egress_1 = require("./tap-egress");
17
- const tap_fs_1 = require("./tap-fs");
18
- const tap_exec_1 = require("./tap-exec");
19
- const tap_pg_1 = require("./tap-pg");
20
- const tap_redis_1 = require("./tap-redis");
21
- const tap_bullmq_1 = require("./tap-bullmq");
22
- const tap_nest_1 = require("./tap-nest");
23
- const tap_stdout_1 = require("./tap-stdout");
24
- function installLensmcpNodeInstrumentation() {
25
- const g = globalThis;
26
- if (g['__lensmcpNodeInstrumented'])
27
- return;
28
- g['__lensmcpNodeInstrumented'] = true;
29
- if (process.env['LENSMCP_AUTO'] === '0')
30
- return;
31
- try {
32
- (0, flow_context_1.primeFlowContext)();
33
- }
34
- catch { /* never break the host */ }
35
- try {
36
- (0, tap_egress_1.tapEgress)();
37
- }
38
- catch { /* never break the host */ }
39
- try {
40
- (0, tap_fs_1.tapFs)();
41
- }
42
- catch { /* never break the host */ }
43
- try {
44
- (0, tap_exec_1.tapExec)();
45
- }
46
- catch { /* never break the host */ }
47
- try {
48
- (0, tap_stdout_1.tapStdout)();
49
- }
50
- catch { /* never break the host */ }
51
- try {
52
- (0, require_hook_1.installRequireHook)({
53
- pg: tap_pg_1.pgTap,
54
- ioredis: tap_redis_1.redisTap,
55
- bullmq: tap_bullmq_1.bullmqTap,
56
- '@nestjs/core': tap_nest_1.nestTap,
57
- });
58
- }
59
- catch { /* never break the host */ }
60
- try {
61
- (0, tap_nest_1.installChildAppBridge)();
62
- }
63
- catch { /* never break the host */ }
64
- }
1
+ "use strict";var p=Object.defineProperty;var a=(t,r)=>p(t,"name",{value:r,configurable:!0});var s=Object.defineProperty,e=a((t,r)=>s(t,"name",{value:r,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.installLensmcpNodeInstrumentation=installLensmcpNodeInstrumentation;const require_hook_1=require("./require-hook"),flow_context_1=require("./flow-context"),tap_egress_1=require("./tap-egress"),tap_fs_1=require("./tap-fs"),tap_exec_1=require("./tap-exec"),tap_pg_1=require("./tap-pg"),tap_redis_1=require("./tap-redis"),tap_bullmq_1=require("./tap-bullmq"),tap_nest_1=require("./tap-nest"),tap_stdout_1=require("./tap-stdout");function installLensmcpNodeInstrumentation(){const t=globalThis;if(!t.__lensmcpNodeInstrumented&&(t.__lensmcpNodeInstrumented=!0,process.env.LENSMCP_AUTO!=="0")){try{(0,flow_context_1.primeFlowContext)()}catch{}try{(0,tap_egress_1.tapEgress)()}catch{}try{(0,tap_fs_1.tapFs)()}catch{}try{(0,tap_exec_1.tapExec)()}catch{}try{(0,tap_stdout_1.tapStdout)()}catch{}try{(0,require_hook_1.installRequireHook)({pg:tap_pg_1.pgTap,ioredis:tap_redis_1.redisTap,bullmq:tap_bullmq_1.bullmqTap,"@nestjs/core":tap_nest_1.nestTap})}catch{}try{(0,tap_nest_1.installChildAppBridge)()}catch{}}}a(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation"),e(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation");
@@ -1,52 +1 @@
1
- "use strict";
2
- /**
3
- * CJS require hook: the cluster's dev webpack config externalizes every real
4
- * node_modules package (`commonjs <request>`), so at pod runtime `pg`,
5
- * `ioredis`, `bullmq` and `@nestjs/core` arrive through `Module._load`. We
6
- * intercept the EXACT package names and mutate their exports IN PLACE
7
- * (prototype/static patches, never proxies) — ESM `import` of the same CJS
8
- * package yields the same exports object, so both module systems observe the
9
- * patched behavior.
10
- */
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.installRequireHook = installRequireHook;
13
- const tapped = new Set();
14
- function runTap(name, tap, exportsObject) {
15
- try {
16
- const installed = tap(exportsObject);
17
- if (installed !== false)
18
- tapped.add(name);
19
- }
20
- catch {
21
- tapped.add(name); // a THROWING tap must not retry forever
22
- }
23
- }
24
- function installRequireHook(taps) {
25
- // eslint-disable-next-line @typescript-eslint/no-require-imports
26
- const Module = require('node:module');
27
- const origLoad = Module._load;
28
- Module._load = function (request, ...rest) {
29
- const exportsObject = origLoad.call(this, request, ...rest);
30
- const tap = taps[request];
31
- if (tap && !tapped.has(request))
32
- runTap(request, tap, exportsObject);
33
- return exportsObject;
34
- };
35
- // Anything already loaded before the hook installed gets tapped from cache.
36
- for (const name of Object.keys(taps)) {
37
- if (tapped.has(name))
38
- continue;
39
- const tap = taps[name];
40
- if (!tap)
41
- continue;
42
- try {
43
- const resolved = require.resolve(name);
44
- const cached = require.cache[resolved];
45
- if (cached)
46
- runTap(name, tap, cached.exports);
47
- }
48
- catch {
49
- /* package not installed in this workspace — fine */
50
- }
51
- }
52
- }
1
+ "use strict";var l=Object.defineProperty;var s=(e,n)=>l(e,"name",{value:n,configurable:!0});var a=Object.defineProperty,r=s((e,n)=>a(e,"name",{value:n,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.installRequireHook=installRequireHook;const tapped=new Set;function runTap(e,n,u){try{n(u)!==!1&&tapped.add(e)}catch{tapped.add(e)}}s(runTap,"runTap"),r(runTap,"runTap");function installRequireHook(e){const n=require("node:module"),u=n._load;n._load=function(t,...c){const i=u.call(this,t,...c),o=e[t];return o&&!tapped.has(t)&&runTap(t,o,i),i};for(const t of Object.keys(e)){if(tapped.has(t))continue;const c=e[t];if(c)try{const i=require.resolve(t),o=require.cache[i];o&&runTap(t,c,o.exports)}catch{}}}s(installRequireHook,"installRequireHook"),r(installRequireHook,"installRequireHook");