@lensmcp/node-instrumentation 1.25.2 → 1.25.5

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.
@@ -0,0 +1,47 @@
1
+ export interface SiteFrame {
2
+ /** Relative to the service's working directory when inside it, else absolute. */
3
+ file: string;
4
+ line: number;
5
+ column?: number;
6
+ /** `UsersService.findAll` — the function the frame is in, when it has a name. */
7
+ fn?: string;
8
+ }
9
+ export interface CallSite {
10
+ /** The service's own frames, nearest the call first — at most {@link FRAMES_KEPT}. */
11
+ frames: SiteFrame[];
12
+ /** True when the process maps stack frames through source maps: `line` is then the source's line. */
13
+ mapped: boolean;
14
+ }
15
+ /** A stack captured at a call, not formatted yet. */
16
+ export interface SiteHolder {
17
+ stack?: string;
18
+ }
19
+ export declare const FRAMES_KEPT = 3;
20
+ /** The stack now, unformatted; undefined when turned off or when the engine refuses. */
21
+ export declare function captureSite(): SiteHolder | undefined;
22
+ /** One stack line as a frame, or undefined when it names no file the service could own. */
23
+ export declare function parseFrame(text: string, cwd?: string, own?: string): SiteFrame | undefined;
24
+ /** The service's frames of a captured stack, nearest the call first; undefined when none is the service's. */
25
+ export declare function appSite(holder: SiteHolder | undefined, cwd?: string, own?: string): CallSite | undefined;
26
+ export declare const siteKey: (site: CallSite) => string;
27
+ /**
28
+ * Which runs of a key (a statement's shape, a command) carry their site. The stack is read — formatted and parsed —
29
+ * in a key's first {@link FIRST_RUNS} runs, then one in {@link EVERY}, and always when `force` (a failure, a slow run,
30
+ * the event that carries the whole statement). A site goes on the bus the first time this process sees it for the key,
31
+ * again after {@link RESEND_MS}, and whenever `force` — so the catalog learns every code path a key runs from, and the
32
+ * bus carries a site per path, not per run.
33
+ */
34
+ export declare class SiteSampler {
35
+ private readonly opts;
36
+ private readonly keys;
37
+ /** `cwd` and `own` are the defaults' (the process's working directory, this directory) — a spec moves them. */
38
+ constructor(opts?: {
39
+ now?: () => number;
40
+ cwd?: string;
41
+ own?: string;
42
+ });
43
+ site(key: string, holder: SiteHolder | undefined, opts?: {
44
+ force?: boolean;
45
+ fallback?: SiteHolder;
46
+ }): CallSite | undefined;
47
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var l=Object.defineProperty;var a=(s,e)=>l(s,"name",{value:e,configurable:!0});var f=Object.defineProperty,c=a((s,e)=>f(s,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SiteSampler=exports.siteKey=exports.FRAMES_KEPT=void 0,exports.captureSite=captureSite,exports.parseFrame=parseFrame,exports.appSite=appSite;const node_path_1=require("node:path"),node_url_1=require("node:url"),CAPTURE_LIMIT=40;exports.FRAMES_KEPT=3;const OFF=process.env.LENSMCP_CALLSITES==="0",OWN=__dirname,CWD=process.cwd();function captureSite(){if(OFF)return;const s={},e=Error.stackTraceLimit;try{Error.stackTraceLimit=CAPTURE_LIMIT,Error.captureStackTrace(s,captureSite)}catch{return}finally{try{Error.stackTraceLimit=e}catch{}}return s}a(captureSite,"captureSite"),c(captureSite,"captureSite");const FRAME=/^\s*at (?:async )?(?:(.+?) \((.+)\)|(.+))$/,LOCATION=/^(.*):(\d+):(\d+)$/;function parseFrame(s,e=CWD,n=OWN){const i=FRAME.exec(s);if(!i)return;const t=i[2]??i[3]??"";if(t.startsWith("eval at ")||t.includes("<anonymous>"))return;const o=LOCATION.exec(t);if(!o)return;let r=o[1];if(r.startsWith("file://"))try{r=(0,node_url_1.fileURLToPath)(r)}catch{return}if(!(0,node_path_1.isAbsolute)(r)||r.includes(`${node_path_1.sep}node_modules${node_path_1.sep}`)||r===n||r.startsWith(n+node_path_1.sep))return;const u=functionName(i[1]);return{file:r.startsWith(e+node_path_1.sep)?(0,node_path_1.relative)(e,r):r,line:Number(o[2]),column:Number(o[3]),...u?{fn:u}:{}}}a(parseFrame,"parseFrame"),c(parseFrame,"parseFrame");function functionName(s){if(!s)return;let e=s.replace(/^new /,"");const n=/ \[as ([^\]]+)\]$/.exec(e);if(n&&(e=/(?:^|\.)(?:eval|<anonymous>)$/.test(e.slice(0,n.index))?n[1]:e.slice(0,n.index)),!(e==="eval"||e.includes("<anonymous>")||e==="Object.eval"))return e}a(functionName,"functionName"),c(functionName,"functionName");function appSite(s,e=CWD,n=OWN){const i=s?.stack;if(!i)return;const t=[];for(const o of i.split(`
2
+ `).slice(1)){const r=parseFrame(o,e,n);if(r&&(t.push(r),t.length>=exports.FRAMES_KEPT))break}if(t.length)return{frames:t,mapped:process.sourceMapsEnabled===!0}}a(appSite,"appSite"),c(appSite,"appSite");const siteKey=c(s=>`${s.frames[0].file}:${s.frames[0].line}`,"siteKey");exports.siteKey=siteKey;const FIRST_RUNS=5,EVERY=20,RESEND_MS=10*6e4,MAX_KEYS=2e3,MAX_SITES_PER_KEY=20;class SiteSampler{static{a(this,"SiteSampler")}static{c(this,"SiteSampler")}constructor(e={}){this.opts=e,this.keys=new Map}site(e,n,i={}){if(!n&&!i.fallback)return;let t=this.keys.get(e);if(t||(t={runs:0,sent:new Map},this.keys.set(e,t),this.keys.size>MAX_KEYS&&this.keys.delete(this.keys.keys().next().value)),t.runs+=1,!i.force&&t.runs>FIRST_RUNS&&t.runs%EVERY!==0)return;const o=appSite(n,this.opts.cwd,this.opts.own)??appSite(i.fallback,this.opts.cwd,this.opts.own);if(!o)return;const r=(this.opts.now??Date.now)(),u=(0,exports.siteKey)(o),p=t.sent.get(u);if(!(!i.force&&p!==void 0&&r-p<RESEND_MS))return t.sent.delete(u),t.sent.set(u,r),t.sent.size>MAX_SITES_PER_KEY&&t.sent.delete(t.sent.keys().next().value),o}}exports.SiteSampler=SiteSampler;
@@ -19,5 +19,17 @@ export declare function redactQuery(search: string): Array<[string, string]>;
19
19
  * secret shapes — or, binary, its size (and digest). `bytes` is what was read; `size` every byte it had.
20
20
  */
21
21
  export declare function keepBody(contentType: string, bytes: Uint8Array, size: number, paths: string[], sha256?: string): HttpExchangeBody;
22
+ /**
23
+ * How much of `.lensmcp/http/` is kept (issues/closed/foodguard-79). It was 500 files — about a minute at FoodGuard's
24
+ * ten exchanges a second, while the events that point at them stay on the bus for many minutes: a quarter of the
25
+ * exchange ids in the live event file were already gone. Now 10,000 files (~14 KB each there, so ~140 MB — the bus's
26
+ * own budget is 300 MB) and a day at most, `LENSMCP_HTTP_MAX_FILES` / `LENSMCP_HTTP_MAX_AGE_MS` to move them.
27
+ */
28
+ export declare const DEFAULT_HTTP_MAX_FILES = 10000;
29
+ export declare const DEFAULT_HTTP_MAX_AGE_MS: number;
30
+ /** When an exchange was written — its id starts with the time (`newExchangeId`), so no file needs a stat. */
31
+ export declare const exchangeTime: (file: string) => number | undefined;
32
+ /** Oldest first, by name (= by time): beyond the count, and older than the age. */
33
+ export declare function pruneHttpExchanges(dir: string, now?: number): number;
22
34
  /** Persist one exchange (temp name, then rename). Best-effort: false on any failure — the call goes on regardless. */
23
35
  export declare function writeHttpExchange(x: HttpExchange): boolean;
package/lib/http-store.js CHANGED
@@ -1 +1 @@
1
- "use strict";var x=Object.defineProperty;var u=(e,t)=>x(e,"name",{value:t,configurable:!0});var f=Object.defineProperty,s=u((e,t)=>f(e,"name",{value:t,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.newExchangeId=exports.MAX_KEPT_BYTES=exports.REMOVED=void 0,exports.httpCaptureMode=httpCaptureMode,exports.redactHeaders=redactHeaders,exports.redactQuery=redactQuery,exports.keepBody=keepBody,exports.writeHttpExchange=writeHttpExchange;const tslib_1=require("tslib"),nodeFs=tslib_1.__importStar(require("node:fs")),node_crypto_1=require("node:crypto"),http_exchange_1=require("@lensmcp/core/http-exchange"),redact_1=require("@lensmcp/core/redact"),ai_store_1=require("./ai-store"),emit_1=require("./emit"),mkdirSync=nodeFs.mkdirSync,writeFileSync=nodeFs.writeFileSync,renameSync=nodeFs.renameSync,readdirSync=nodeFs.readdirSync,unlinkSync=nodeFs.unlinkSync;function httpCaptureMode(e=process.env){const t=(e.LENSMCP_HTTP_CAPTURE??"").trim().toLowerCase();return t==="off"||t==="0"||t==="false"||t==="none"?"off":t==="headers"?"headers":"bodies"}u(httpCaptureMode,"httpCaptureMode"),s(httpCaptureMode,"httpCaptureMode"),exports.REMOVED="\u2022\u2022\u2022\u2022 redacted",exports.MAX_KEPT_BYTES=64*1024;const MAX_PARSE_BYTES=2*1024*1024,newExchangeId=s(()=>`hx_${Date.now().toString(36)}${(0,node_crypto_1.randomBytes)(5).toString("hex")}`,"newExchangeId");exports.newExchangeId=newExchangeId;const CREDENTIAL_HEADERS=/^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|api-key|openai-organization|openai-project|x-internal-token|x-amz-security-token)$|-api-key$|-token$|-secret$/i,CREDENTIAL_FIELD=/pass(word|wd)?|secret|token|api[-_]?key|authorization|credential|cookie|signature/i;function jwtClaims(e){const t=e.split(".");if(t.length===3)try{const r=JSON.parse(Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return`JWT \xB7 ${["iss","aud","sub","exp"].filter(o=>r[o]!==void 0).map(o=>`${o}=${o==="exp"&&typeof r[o]=="number"?new Date(r[o]*1e3).toISOString():String(r[o]).slice(0,80)}`).join(" \xB7 ")||"no claims"} \xB7 ${exports.REMOVED.split(" ")[0]} signature`}catch{return}}u(jwtClaims,"jwtClaims"),s(jwtClaims,"jwtClaims");function redactHeaders(e){const t=[],r=[];for(const[o,p]of e){const n=o.toLowerCase();if(CREDENTIAL_HEADERS.test(n)||(0,redact_1.isSecretParam)(n)){r.push(n);const i=/^bearer\s+(\S+)$/i.exec(p.trim()),c=i?jwtClaims(i[1]):void 0,a=/^(\w+)\s+\S/.exec(p.trim())?.[1];t.push([n,c?`Bearer ${c}`:a&&n.endsWith("authorization")?`${a} ${exports.REMOVED}`:exports.REMOVED])}else t.push([n,(0,ai_store_1.redactAiText)(p).slice(0,2048)])}return{headers:t,redacted:r}}u(redactHeaders,"redactHeaders"),s(redactHeaders,"redactHeaders");function redactQuery(e){const t=[];try{for(const[r,o]of new URLSearchParams(e))t.push([r,(0,redact_1.isSecretParam)(r)||CREDENTIAL_FIELD.test(r)?exports.REMOVED:(0,ai_store_1.redactAiText)(o).slice(0,1024)])}catch{}return t.slice(0,100)}u(redactQuery,"redactQuery"),s(redactQuery,"redactQuery");function redactJson(e,t,r,o=0){if(o>32||e===null||typeof e!="object")return typeof e=="string"?(0,ai_store_1.redactAiText)(e):e;if(Array.isArray(e))return e.map((n,i)=>redactJson(n,`${t}[${i}]`,r,o+1));const p={};for(const[n,i]of Object.entries(e)){const c=t?`${t}.${n}`:n;CREDENTIAL_FIELD.test(n)&&i!==null&&typeof i!="object"?(p[n]=exports.REMOVED,r.length<50&&r.push(c)):p[n]=redactJson(i,c,r,o+1)}return p}u(redactJson,"redactJson"),s(redactJson,"redactJson");const textual=s(e=>/json|^text\/|x-www-form-urlencoded|xml|javascript|graphql|event-stream/i.test(e)||e==="","textual");function keepBody(e,t,r,o,p){const n=e.split(";")[0].trim().toLowerCase();if(!textual(n))return{contentType:n||"application/octet-stream",size:r,kept:0,truncated:!1,omitted:"binary",...p?{sha256:p}:{}};const i=t.byteLength>=r&&r<=MAX_PARSE_BYTES,c=Buffer.from(t.buffer,t.byteOffset,t.byteLength).toString("utf8");let a=c;if(i&&/json/.test(n))try{a=JSON.stringify(redactJson(JSON.parse(c),"",o))}catch{a=(0,ai_store_1.redactAiText)(c)}else i&&/x-www-form-urlencoded/.test(n)?a=new URLSearchParams(redactQuery(c)).toString():a=(0,ai_store_1.redactAiText)(c);const d=Buffer.byteLength(a)>exports.MAX_KEPT_BYTES?Buffer.from(a).subarray(0,exports.MAX_KEPT_BYTES).toString("utf8"):a;return{contentType:n,size:r,kept:Buffer.byteLength(d),text:d,truncated:r>t.byteLength||d.length<a.length}}u(keepBody,"keepBody"),s(keepBody,"keepBody");const maxFiles=s(()=>{const e=Number(process.env.LENSMCP_HTTP_MAX_FILES);return Number.isInteger(e)&&e>=10?e:500},"maxFiles");let writes=0;function prune(e){try{const t=readdirSync(e).filter(r=>/^hx_[0-9a-z]+\.json$/.test(r)).sort();for(let r=0;r<t.length-maxFiles();r+=1)try{unlinkSync(`${e}/${t[r]}`)}catch{}}catch{}}u(prune,"prune"),s(prune,"prune");function writeHttpExchange(e){try{const t=(0,http_exchange_1.httpExchangeDir)(emit_1.EVENT_FILE);mkdirSync(t,{recursive:!0,mode:448});const r=(0,http_exchange_1.httpExchangeFile)(emit_1.EVENT_FILE,e.id);return writeFileSync(`${r}.tmp`,JSON.stringify(e),{mode:384}),renameSync(`${r}.tmp`,r),writes+=1,(writes===1||writes%25===0)&&prune(t),!0}catch{return!1}}u(writeHttpExchange,"writeHttpExchange"),s(writeHttpExchange,"writeHttpExchange");
1
+ "use strict";var d=Object.defineProperty;var u=(e,t)=>d(e,"name",{value:t,configurable:!0});var f=Object.defineProperty,i=u((e,t)=>f(e,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.exchangeTime=exports.DEFAULT_HTTP_MAX_AGE_MS=exports.DEFAULT_HTTP_MAX_FILES=exports.newExchangeId=exports.MAX_KEPT_BYTES=exports.REMOVED=void 0,exports.httpCaptureMode=httpCaptureMode,exports.redactHeaders=redactHeaders,exports.redactQuery=redactQuery,exports.keepBody=keepBody,exports.pruneHttpExchanges=pruneHttpExchanges,exports.writeHttpExchange=writeHttpExchange;const tslib_1=require("tslib"),nodeFs=tslib_1.__importStar(require("node:fs")),node_crypto_1=require("node:crypto"),http_exchange_1=require("@lensmcp/core/http-exchange"),redact_1=require("@lensmcp/core/redact"),ai_store_1=require("./ai-store"),emit_1=require("./emit"),mkdirSync=nodeFs.mkdirSync,writeFileSync=nodeFs.writeFileSync,renameSync=nodeFs.renameSync,readdirSync=nodeFs.readdirSync,unlinkSync=nodeFs.unlinkSync;function httpCaptureMode(e=process.env){const t=(e.LENSMCP_HTTP_CAPTURE??"").trim().toLowerCase();return t==="off"||t==="0"||t==="false"||t==="none"?"off":t==="headers"?"headers":"bodies"}u(httpCaptureMode,"httpCaptureMode"),i(httpCaptureMode,"httpCaptureMode"),exports.REMOVED="\u2022\u2022\u2022\u2022 redacted",exports.MAX_KEPT_BYTES=64*1024;const MAX_PARSE_BYTES=2*1024*1024,newExchangeId=i(()=>`hx_${Date.now().toString(36)}${(0,node_crypto_1.randomBytes)(5).toString("hex")}`,"newExchangeId");exports.newExchangeId=newExchangeId;const CREDENTIAL_HEADERS=/^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|api-key|openai-organization|openai-project|x-internal-token|x-amz-security-token)$|-api-key$|-token$|-secret$/i,CREDENTIAL_FIELD=/pass(word|wd)?|secret|token|api[-_]?key|authorization|credential|cookie|signature/i;function jwtClaims(e){const t=e.split(".");if(t.length===3)try{const r=JSON.parse(Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8"));return`JWT \xB7 ${["iss","aud","sub","exp"].filter(n=>r[n]!==void 0).map(n=>`${n}=${n==="exp"&&typeof r[n]=="number"?new Date(r[n]*1e3).toISOString():String(r[n]).slice(0,80)}`).join(" \xB7 ")||"no claims"} \xB7 ${exports.REMOVED.split(" ")[0]} signature`}catch{return}}u(jwtClaims,"jwtClaims"),i(jwtClaims,"jwtClaims");function redactHeaders(e){const t=[],r=[];for(const[n,c]of e){const o=n.toLowerCase();if(CREDENTIAL_HEADERS.test(o)||(0,redact_1.isSecretParam)(o)){r.push(o);const s=/^bearer\s+(\S+)$/i.exec(c.trim()),a=s?jwtClaims(s[1]):void 0,p=/^(\w+)\s+\S/.exec(c.trim())?.[1];t.push([o,a?`Bearer ${a}`:p&&o.endsWith("authorization")?`${p} ${exports.REMOVED}`:exports.REMOVED])}else t.push([o,(0,ai_store_1.redactAiText)(c).slice(0,2048)])}return{headers:t,redacted:r}}u(redactHeaders,"redactHeaders"),i(redactHeaders,"redactHeaders");function redactQuery(e){const t=[];try{for(const[r,n]of new URLSearchParams(e))t.push([r,(0,redact_1.isSecretParam)(r)||CREDENTIAL_FIELD.test(r)?exports.REMOVED:(0,ai_store_1.redactAiText)(n).slice(0,1024)])}catch{}return t.slice(0,100)}u(redactQuery,"redactQuery"),i(redactQuery,"redactQuery");function redactJson(e,t,r,n=0){if(n>32||e===null||typeof e!="object")return typeof e=="string"?(0,ai_store_1.redactAiText)(e):e;if(Array.isArray(e))return e.map((o,s)=>redactJson(o,`${t}[${s}]`,r,n+1));const c={};for(const[o,s]of Object.entries(e)){const a=t?`${t}.${o}`:o;CREDENTIAL_FIELD.test(o)&&s!==null&&typeof s!="object"?(c[o]=exports.REMOVED,r.length<50&&r.push(a)):c[o]=redactJson(s,a,r,n+1)}return c}u(redactJson,"redactJson"),i(redactJson,"redactJson");const textual=i(e=>/json|^text\/|x-www-form-urlencoded|xml|javascript|graphql|event-stream/i.test(e)||e==="","textual");function keepBody(e,t,r,n,c){const o=e.split(";")[0].trim().toLowerCase();if(!textual(o))return{contentType:o||"application/octet-stream",size:r,kept:0,truncated:!1,omitted:"binary",...c?{sha256:c}:{}};const s=t.byteLength>=r&&r<=MAX_PARSE_BYTES,a=Buffer.from(t.buffer,t.byteOffset,t.byteLength).toString("utf8");let p;if(s&&/json/.test(o))try{p=JSON.stringify(redactJson(JSON.parse(a),"",n))}catch{p=(0,ai_store_1.redactAiText)(a)}else s&&/x-www-form-urlencoded/.test(o)?p=new URLSearchParams(redactQuery(a)).toString():p=(0,ai_store_1.redactAiText)(a);const x=Buffer.byteLength(p)>exports.MAX_KEPT_BYTES?Buffer.from(p).subarray(0,exports.MAX_KEPT_BYTES).toString("utf8"):p;return{contentType:o,size:r,kept:Buffer.byteLength(x),text:x,truncated:r>t.byteLength||x.length<p.length}}u(keepBody,"keepBody"),i(keepBody,"keepBody"),exports.DEFAULT_HTTP_MAX_FILES=1e4,exports.DEFAULT_HTTP_MAX_AGE_MS=1440*6e4;const PRUNE_EVERY_MS=3e4,maxFiles=i(()=>{const e=Number(process.env.LENSMCP_HTTP_MAX_FILES);return Number.isInteger(e)&&e>=10?e:exports.DEFAULT_HTTP_MAX_FILES},"maxFiles"),maxAgeMs=i(()=>{const e=Number(process.env.LENSMCP_HTTP_MAX_AGE_MS);return Number.isFinite(e)&&e>=6e4?e:exports.DEFAULT_HTTP_MAX_AGE_MS},"maxAgeMs"),exchangeTime=i(e=>{const t=/^hx_([0-9a-z]{8})/.exec(e),r=t?parseInt(t[1],36):NaN;return Number.isFinite(r)?r:void 0},"exchangeTime");exports.exchangeTime=exchangeTime;let lastPrune=0;function pruneHttpExchanges(e,t=Date.now()){let r=0;try{const n=readdirSync(e).filter(s=>/^hx_[0-9a-z]+\.json$/.test(s)).sort(),c=n.length-maxFiles(),o=t-maxAgeMs();for(let s=0;s<n.length;s+=1){const a=(0,exports.exchangeTime)(n[s]);if(s>=c&&(a===void 0||a>=o))break;try{unlinkSync(`${e}/${n[s]}`),r+=1}catch{}}}catch{}return r}u(pruneHttpExchanges,"pruneHttpExchanges"),i(pruneHttpExchanges,"pruneHttpExchanges");function writeHttpExchange(e){try{const t=(0,http_exchange_1.httpExchangeDir)(emit_1.EVENT_FILE);mkdirSync(t,{recursive:!0,mode:448});const r=(0,http_exchange_1.httpExchangeFile)(emit_1.EVENT_FILE,e.id);writeFileSync(`${r}.tmp`,JSON.stringify(e),{mode:384}),renameSync(`${r}.tmp`,r);const n=Date.now();return n-lastPrune>=PRUNE_EVERY_MS&&(lastPrune=n,pruneHttpExchanges(t,n)),!0}catch{return!1}}u(writeHttpExchange,"writeHttpExchange"),i(writeHttpExchange,"writeHttpExchange");
@@ -1,3 +1,4 @@
1
+ export declare const checkoutSite: () => Error | undefined;
1
2
  /** Wraps the `connect` that `pg.Pool` inherits from pg-pool. True once patched (or already). */
2
3
  export declare function pgPoolTap(pg: unknown): boolean;
3
4
  /** Tests only: one sampling pass now, and a clean slate. */
@@ -1,2 +1,2 @@
1
- "use strict";var x=Object.defineProperty;var g=(e,o)=>x(e,"name",{value:o,configurable:!0});var M=Object.defineProperty,u=g((e,o)=>M(e,"name",{value:o,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.pgPoolTap=pgPoolTap,exports.samplePgPoolsForTests=samplePgPoolsForTests,exports.resetPgPoolTapForTests=resetPgPoolTapForTests;const pg_errors_1=require("@lensmcp/core/pg-errors"),active_work_1=require("./active-work"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),envMs=u((e,o,i)=>{const r=Number(process.env[e]);return Number.isFinite(r)&&r>=i?r:o},"envMs"),SAMPLE_MS=envMs("LENSMCP_PG_POOL_SAMPLE_MS",1e4,1e3),HOLD_WARN_MS=envMs("LENSMCP_PG_HOLD_WARN_MS",3e4,1e3),SLOW_WAIT_MS=250,BLOCKED_MIN_MS=1e3,WAITS_KEPT=256,SITE_FRAMES=8,stats=new WeakMap,live=[];let nextId=1,timer;const counters=u(e=>({total:e.totalCount??0,idle:e.idleCount??0,waiting:e.waitingCount??0,max:e.options?.max??10}),"counters");function whereOf(e){const o=e.options??{};let i=typeof o.host=="string"?o.host:void 0,r=typeof o.port=="number"||typeof o.port=="string"?String(o.port):void 0,t=typeof o.database=="string"?o.database:void 0;if(typeof o.connectionString=="string")try{const n=new URL(o.connectionString);i??=n.hostname||void 0,r??=n.port||void 0,t??=decodeURIComponent(n.pathname.replace(/^\//,""))||void 0}catch{}return{...t?{database:t}:{},...i?{server:`${i}:${r??5432}`}:{}}}g(whereOf,"whereOf"),u(whereOf,"whereOf");function statsOf(e){let o=stats.get(e);return o||(o={id:nextId++,gen:(0,active_work_1.activeWorkGeneration)(),ref:new WeakRef(e),createdAt:Date.now(),where:whereOf(e),out:new Map,acquires:0,waits:[],waitMax:0,peakWaiting:0,peakTotal:0,failures:0,holdMax:0},stats.set(e,o),live.push(o),startSampling()),o}g(statsOf,"statsOf"),u(statsOf,"statsOf");function siteOf(e){const o=(e.stack??"").split(`
2
- `).slice(1).map(r=>r.trim()).filter(r=>r.startsWith("at ")&&!/tap-pg-pool\.[cm]?[jt]s:/.test(r)),i=o.filter(r=>!r.includes("node_modules")&&!r.includes("node:"));return(i.length?i:o).slice(0,SITE_FRAMES)}g(siteOf,"siteOf"),u(siteOf,"siteOf");const secs=u(e=>e>=1e4?`${Math.round(e/1e3)} s`:e>=1e3?`${(e/1e3).toFixed(1)} s`:`${Math.round(e)} ms`,"secs");function failed(e,o,i,r,t){o.failures+=1;const n=(0,pg_errors_1.pgConnectReason)(i),s=counters(e),l=e.options?.connectionTimeoutMillis??0,p=n==="pool-wait-timeout"||n==="connect-timeout",d=p&&l>0?Math.max(0,r-l):0,f=d>=Math.max(BLOCKED_MIN_MS,l*.2),a=`${s.total}/${s.max} open, ${s.idle} idle, ${s.waiting} waiting`,c=n==="pool-wait-timeout"?f?`no connection in time \u2014 waited ${secs(r)} on a ${secs(l)} limit: the process was blocked ${secs(d)} (${a})`:`pool full \u2014 no free connection within ${secs(l)} (${a})`:n==="connect-timeout"?`opening a connection took over ${secs(l)}${f?` \u2014 the process was blocked ${secs(d)}`:""} (${a})`:n?`could not get a connection: ${pg_errors_1.PG_CONNECT_MEANING[n]}`:`could not get a connection (${secs(r)})`,h=i;(0,emit_1.lensEmitOutcome)("db","error",c,{name:"postgres",kind:"db-pool-error",...n?{reason:n}:{},waitedMs:r,...l?{timeoutMs:l}:{},...p?{lateMs:d}:{},...f?{blockedMs:d}:{},...s,pool:o.id,generation:o.gen,serving:(0,active_work_1.activeWorkGeneration)(),pools:live.length,...o.where,error:String(h?.message??i),...typeof h?.code=="string"?{code:h.code}:{}},`db:pool-error:${n??"other"}`,t)}g(failed,"failed"),u(failed,"failed");function checkedOut(e,o,i,r,t,n){if(e.waits.push(n),e.waits.length>WAITS_KEPT&&e.waits.splice(0,e.waits.length-WAITS_KEPT),n>e.waitMax&&(e.waitMax=n),!o||typeof o!="object")return i;const s=o;e.out.set(s,{at:Date.now(),site:r,ctx:t});const l=u(a=>{if(!a)return;e.out.delete(s);const c=Date.now()-a.at;c>e.holdMax&&(e.holdMax=c)},"back"),p=o.release,d=u(a=>typeof a=="function"?function(...c){return l(e.out.get(s)),a.apply(this??o,c)}:a,"wrap"),f=d(p);return f&&(o.release=f),i===p?f:d(i)}g(checkedOut,"checkedOut"),u(checkedOut,"checkedOut");function percentile(e,o){if(!e.length)return 0;const i=e.slice().sort((r,t)=>r-t);return i[Math.min(i.length-1,Math.floor(i.length*o))]}g(percentile,"percentile"),u(percentile,"percentile");function sample(e=Date.now()){for(let t=live.length-1;t>=0;t--){const n=live[t],s=n.ref.deref();(!s||s.ended&&n.out.size===0)&&live.splice(t,1)}const o=(0,active_work_1.activeWorkGeneration)(),i=live.reduce((t,n)=>Math.max(t,n.gen),0),r=live.map(t=>{const n=t.ref.deref(),s=counters(n);return{pool:t.id,generation:t.gen,total:s.total,idle:s.idle,waiting:s.waiting,max:s.max,...n.ending?{ending:!0}:{}}});for(const t of live){const n=t.ref.deref();if(!n)continue;const s=[];let l=0;for(const a of t.out.values()){const c=e-a.at;if(c>l&&(l=c),c<HOLD_WARN_MS||a.warned)continue;a.warned=!0;const h=siteOf(a.site);s.push({heldMs:c,site:h}),(0,emit_1.lensEmitOutcome)("db","info",`a connection held ${secs(c)} and not given back${h[0]?` \u2014 taken ${h[0].replace(/^at /,"at ")}`:""}`,{name:"postgres",kind:"db-pool-hold",heldMs:c,site:h,pool:t.id,generation:t.gen,...counters(n),...t.where},"db:pool-hold",a.ctx)}const p=counters(n),d=t.gen<i&&p.total>0,f=t.peakWaiting>0||t.waitMax>=SLOW_WAIT_MS||t.failures>0||s.length>0;(f||d)&&(0,emit_1.lensEmit)("db","info",d&&!f?`pool of generation ${t.gen} still holds ${p.total} connection(s) \u2014 the app serving now is generation ${o}`:`pool ${p.total}/${p.max} open \xB7 ${t.acquires} checkouts \xB7 wait p95 ${secs(percentile(t.waits,.95))} max ${secs(t.waitMax)}${t.peakWaiting?` \xB7 up to ${t.peakWaiting} waiting`:""}${t.failures?` \xB7 ${t.failures} failed`:""}`,{name:"postgres",kind:"db-pool",pool:t.id,generation:t.gen,serving:o,...p,acquires:t.acquires,waitP95Ms:percentile(t.waits,.95),waitMaxMs:t.waitMax,peakWaiting:t.peakWaiting,peakTotal:t.peakTotal,failures:t.failures,holdMaxMs:t.holdMax,out:t.out.size,longestHeldMs:l,...d?{leftover:!0}:{},windowMs:SAMPLE_MS,pools:r,...t.where},"db:pool",void 0),t.acquires=0,t.waits=[],t.waitMax=0,t.failures=0,t.holdMax=0,t.peakWaiting=p.waiting,t.peakTotal=p.total}}g(sample,"sample"),u(sample,"sample");function startSampling(){timer||(timer=setInterval(()=>{try{sample()}catch{}},SAMPLE_MS),timer.unref?.())}g(startSampling,"startSampling"),u(startSampling,"startSampling");function pgPoolTap(e){let o=e?.Pool?.prototype??null;for(;o&&!Object.prototype.hasOwnProperty.call(o,"connect");)o=Object.getPrototypeOf(o);const i=o?.connect;if(!o||typeof i!="function")return!1;if(i.__lensmcp)return!0;const r=u(function(t){let n;try{n=statsOf(this)}catch{return i.call(this,t)}const s=Date.now(),l=(0,flow_context_1.flowContext)(),p=new Error("pg checkout");n.acquires+=1;const d=u(()=>{try{const a=counters(this);a.waiting>n.peakWaiting&&(n.peakWaiting=a.waiting),a.total>n.peakTotal&&(n.peakTotal=a.total)}catch{}},"note");if(typeof t=="function"){const a=i.call(this,(c,h,w)=>{let m=w;try{c?failed(this,n,c,Date.now()-s,l):m=checkedOut(n,h,w,p,l,Date.now()-s)}catch{}t(c,h,m)});return d(),a}const f=i.call(this);return d(),!f||typeof f.then!="function"?f:f.then(a=>{try{checkedOut(n,a,a?.release,p,l,Date.now()-s)}catch{}return a},a=>{try{failed(this,n,a,Date.now()-s,l)}catch{}throw a})},"wrapped");return r.__lensmcp=!0,o.connect=r,!0}g(pgPoolTap,"pgPoolTap"),u(pgPoolTap,"pgPoolTap");function samplePgPoolsForTests(e){sample(e)}g(samplePgPoolsForTests,"samplePgPoolsForTests"),u(samplePgPoolsForTests,"samplePgPoolsForTests");function resetPgPoolTapForTests(){timer&&clearInterval(timer),timer=void 0,live.splice(0,live.length),nextId=1}g(resetPgPoolTapForTests,"resetPgPoolTapForTests"),u(resetPgPoolTapForTests,"resetPgPoolTapForTests");
1
+ "use strict";var k=Object.defineProperty;var h=(e,o)=>k(e,"name",{value:o,configurable:!0});var $=Object.defineProperty,p=h((e,o)=>$(e,"name",{value:o,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.checkoutSite=void 0,exports.pgPoolTap=pgPoolTap,exports.samplePgPoolsForTests=samplePgPoolsForTests,exports.resetPgPoolTapForTests=resetPgPoolTapForTests;const node_async_hooks_1=require("node:async_hooks"),pg_errors_1=require("@lensmcp/core/pg-errors"),active_work_1=require("./active-work"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),envMs=p((e,o,s)=>{const r=Number(process.env[e]);return Number.isFinite(r)&&r>=s?r:o},"envMs"),SAMPLE_MS=envMs("LENSMCP_PG_POOL_SAMPLE_MS",1e4,1e3),HOLD_WARN_MS=envMs("LENSMCP_PG_HOLD_WARN_MS",3e4,1e3),SLOW_WAIT_MS=250,BLOCKED_MIN_MS=1e3,WAITS_KEPT=256,SITE_FRAMES=8,stats=new WeakMap,live=[];let nextId=1,timer;const counters=p(e=>({total:e.totalCount??0,idle:e.idleCount??0,waiting:e.waitingCount??0,max:e.options?.max??10}),"counters");function whereOf(e){const o=e.options??{};let s=typeof o.host=="string"?o.host:void 0,r=typeof o.port=="number"||typeof o.port=="string"?String(o.port):void 0,t=typeof o.database=="string"?o.database:void 0;if(typeof o.connectionString=="string")try{const n=new URL(o.connectionString);s??=n.hostname||void 0,r??=n.port||void 0,t??=decodeURIComponent(n.pathname.replace(/^\//,""))||void 0}catch{}return{...t?{database:t}:{},...s?{server:`${s}:${r??5432}`}:{}}}h(whereOf,"whereOf"),p(whereOf,"whereOf");function statsOf(e){let o=stats.get(e);return o||(o={id:nextId++,gen:(0,active_work_1.activeWorkGeneration)(),ref:new WeakRef(e),createdAt:Date.now(),where:whereOf(e),out:new Map,acquires:0,waits:[],waitMax:0,peakWaiting:0,peakTotal:0,failures:0,holdMax:0},stats.set(e,o),live.push(o),startSampling()),o}h(statsOf,"statsOf"),p(statsOf,"statsOf");function siteOf(e){const o=(e.stack??"").split(`
2
+ `).slice(1).map(r=>r.trim()).filter(r=>r.startsWith("at ")&&!/tap-pg-pool\.[cm]?[jt]s:/.test(r)),s=o.filter(r=>!r.includes("node_modules")&&!r.includes("node:"));return(s.length?s:o).slice(0,SITE_FRAMES)}h(siteOf,"siteOf"),p(siteOf,"siteOf");const secs=p(e=>e>=1e4?`${Math.round(e/1e3)} s`:e>=1e3?`${(e/1e3).toFixed(1)} s`:`${Math.round(e)} ms`,"secs");function failed(e,o,s,r,t,n){o.failures+=1;const i=(0,pg_errors_1.pgConnectReason)(s),u=counters(e),d=n?n.total>=n.max&&n.idle===0:u.total>=u.max&&u.idle===0,l=e.options?.connectionTimeoutMillis??0,f=i==="pool-wait-timeout"||i==="connect-timeout",c=f&&l>0?Math.max(0,r-l):0,a=c>=Math.max(BLOCKED_MIN_MS,l*.2),g=`${u.total}/${u.max} open, ${u.idle} idle, ${u.waiting} waiting`,w=n?`when it asked ${n.total}/${n.max} open, ${n.idle} idle, ${n.waiting} waiting; now ${g}`:g,M=i==="pool-wait-timeout"?a?`no connection in time \u2014 waited ${secs(r)} on a ${secs(l)} limit: the process was blocked ${secs(c)} (${w})`:d?`pool full \u2014 every connection in use, none free within ${secs(l)} (${w})`:`no connection in time \u2014 the pool had room when it asked, yet the ${secs(l)} wait ran out: the process was busy or the pool's queue stalled (${w})`:i==="connect-timeout"?`opening a connection took over ${secs(l)}${a?` \u2014 the process was blocked ${secs(c)}`:""} (${g})`:i?`could not get a connection: ${pg_errors_1.PG_CONNECT_MEANING[i]}`:`could not get a connection (${secs(r)})`,m=s;(0,emit_1.lensEmitOutcome)("db","error",M,{name:"postgres",kind:"db-pool-error",...i?{reason:i}:{},waitedMs:r,...l?{timeoutMs:l}:{},...f?{lateMs:c}:{},...a?{blockedMs:c}:{},...u,...n?{asked:n}:{},fullWhenAsked:d,pool:o.id,generation:o.gen,serving:(0,active_work_1.activeWorkGeneration)(),pools:live.length,...o.where,error:String(m?.message??s),...typeof m?.code=="string"?{code:m.code}:{}},`db:pool-error:${i??"other"}`,t)}h(failed,"failed"),p(failed,"failed");function checkedOut(e,o,s,r,t,n){if(e.waits.push(n),e.waits.length>WAITS_KEPT&&e.waits.splice(0,e.waits.length-WAITS_KEPT),n>e.waitMax&&(e.waitMax=n),!o||typeof o!="object")return s;const i=o;e.out.set(i,{at:Date.now(),site:r,ctx:t});const u=p(c=>{if(!c)return;e.out.delete(i);const a=Date.now()-c.at;a>e.holdMax&&(e.holdMax=a)},"back"),d=o.release,l=p(c=>typeof c=="function"?function(...a){return u(e.out.get(i)),c.apply(this??o,a)}:c,"wrap"),f=l(d);return f&&(o.release=f),s===d?f:l(s)}h(checkedOut,"checkedOut"),p(checkedOut,"checkedOut");function percentile(e,o){if(!e.length)return 0;const s=e.slice().sort((r,t)=>r-t);return s[Math.min(s.length-1,Math.floor(s.length*o))]}h(percentile,"percentile"),p(percentile,"percentile");function sample(e=Date.now()){for(let t=live.length-1;t>=0;t--){const n=live[t],i=n.ref.deref();(!i||i.ended&&n.out.size===0)&&live.splice(t,1)}const o=(0,active_work_1.activeWorkGeneration)(),s=live.reduce((t,n)=>Math.max(t,n.gen),0),r=live.map(t=>{const n=t.ref.deref(),i=counters(n);return{pool:t.id,generation:t.gen,total:i.total,idle:i.idle,waiting:i.waiting,max:i.max,...n.ending?{ending:!0}:{}}});for(const t of live){const n=t.ref.deref();if(!n)continue;const i=[];let u=0;for(const c of t.out.values()){const a=e-c.at;if(a>u&&(u=a),a<HOLD_WARN_MS||c.warned)continue;c.warned=!0;const g=siteOf(c.site);i.push({heldMs:a,site:g}),(0,emit_1.lensEmitOutcome)("db","info",`a connection held ${secs(a)} and not given back${g[0]?` \u2014 taken ${g[0].replace(/^at /,"at ")}`:""}`,{name:"postgres",kind:"db-pool-hold",heldMs:a,site:g,pool:t.id,generation:t.gen,...counters(n),...t.where},"db:pool-hold",c.ctx)}const d=counters(n),l=t.gen<s&&d.total>0,f=t.peakWaiting>0||t.waitMax>=SLOW_WAIT_MS||t.failures>0||i.length>0;(f||l)&&(0,emit_1.lensEmit)("db","info",l&&!f?`pool of generation ${t.gen} still holds ${d.total} connection(s) \u2014 the app serving now is generation ${o}`:`pool ${d.total}/${d.max} open \xB7 ${t.acquires} checkouts \xB7 wait p95 ${secs(percentile(t.waits,.95))} max ${secs(t.waitMax)}${t.peakWaiting?` \xB7 up to ${t.peakWaiting} waiting`:""}${t.failures?` \xB7 ${t.failures} failed`:""}`,{name:"postgres",kind:"db-pool",pool:t.id,generation:t.gen,serving:o,...d,acquires:t.acquires,waitP95Ms:percentile(t.waits,.95),waitMaxMs:t.waitMax,peakWaiting:t.peakWaiting,peakTotal:t.peakTotal,failures:t.failures,holdMaxMs:t.holdMax,out:t.out.size,longestHeldMs:u,...l?{leftover:!0}:{},windowMs:SAMPLE_MS,pools:r,...t.where},"db:pool",void 0),t.acquires=0,t.waits=[],t.waitMax=0,t.failures=0,t.holdMax=0,t.peakWaiting=d.waiting,t.peakTotal=d.total}}h(sample,"sample"),p(sample,"sample");function startSampling(){timer||(timer=setInterval(()=>{try{sample()}catch{}},SAMPLE_MS),timer.unref?.())}h(startSampling,"startSampling"),p(startSampling,"startSampling");const checkoutSites=new node_async_hooks_1.AsyncLocalStorage,checkoutSite=p(()=>checkoutSites.getStore(),"checkoutSite");exports.checkoutSite=checkoutSite;function pgPoolTap(e){let o=e?.Pool?.prototype??null;for(;o&&!Object.prototype.hasOwnProperty.call(o,"connect");)o=Object.getPrototypeOf(o);const s=o?.connect;if(!o||typeof s!="function")return!1;if(s.__lensmcp)return!0;const r=p(function(t){let n;try{n=statsOf(this)}catch{return s.call(this,t)}const i=Date.now(),u=(0,flow_context_1.flowContext)();let d;try{d=counters(this)}catch{}const l=new Error("pg checkout");n.acquires+=1;const f=p(()=>{try{const a=counters(this);a.waiting>n.peakWaiting&&(n.peakWaiting=a.waiting),a.total>n.peakTotal&&(n.peakTotal=a.total)}catch{}},"note");if(typeof t=="function"){const a=s.call(this,(g,w,M)=>{let m=M;try{g?failed(this,n,g,Date.now()-i,u,d):m=checkedOut(n,w,M,l,u,Date.now()-i)}catch{}checkoutSites.run(l,()=>t(g,w,m))});return f(),a}const c=s.call(this);return f(),!c||typeof c.then!="function"?c:c.then(a=>{try{checkedOut(n,a,a?.release,l,u,Date.now()-i)}catch{}return a},a=>{try{failed(this,n,a,Date.now()-i,u,d)}catch{}throw a})},"wrapped");return r.__lensmcp=!0,o.connect=r,!0}h(pgPoolTap,"pgPoolTap"),p(pgPoolTap,"pgPoolTap");function samplePgPoolsForTests(e){sample(e)}h(samplePgPoolsForTests,"samplePgPoolsForTests"),p(samplePgPoolsForTests,"samplePgPoolsForTests");function resetPgPoolTapForTests(){timer&&clearInterval(timer),timer=void 0,live.splice(0,live.length),nextId=1}h(resetPgPoolTapForTests,"resetPgPoolTapForTests"),p(resetPgPoolTapForTests,"resetPgPoolTapForTests");
package/lib/tap-pg.js CHANGED
@@ -1 +1 @@
1
- "use strict";var D=Object.defineProperty;var c=(t,e)=>D(t,"name",{value:e,configurable:!0});var B=Object.defineProperty,o=c((t,e)=>B(t,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.taggedAppName=taggedAppName,exports.pgTap=pgTap;const redact_1=require("@lensmcp/core/redact"),sql_shape_1=require("@lensmcp/core/sql-shape"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),sql_accounting_1=require("./sql-accounting"),tap_pg_pool_1=require("./tap-pg-pool"),active_work_1=require("./active-work"),emit_2=require("./emit"),SKIP_OPS=new Set(["BEGIN","START","COMMIT","END","ROLLBACK","SAVEPOINT","RELEASE","SET","RESET","SHOW","DEALLOCATE","DISCARD","LISTEN","UNLISTEN"]),STATEMENT_EVERY_MS=10*6e4,STATEMENT_MAX=64*1024,statementSentAt=new Map,MAX_SHAPES_REMEMBERED=2e3,firstWord=o(t=>(/^\s*(?:(?:\/\*[\s\S]*?\*\/|--[^\n]*\n?)\s*)*(\w+)/.exec(t)?.[1]??"query").toUpperCase(),"firstWord");function parseSql(t){const e=(0,sql_shape_1.sqlShape)(t),r=(e.target??e.tables[0])?.split(".").pop();return{op:e.verb,...r?{table:r}:{},sqlId:e.id,tables:e.tables,...e.name?{name:e.name}:{}}}c(parseSql,"parseSql"),o(parseSql,"parseSql");const oneLine=o(t=>t.replace(/\s+/g," ").trim(),"oneLine"),clip=o((t,e)=>t.length>e?t.slice(0,e-1)+"\u2026":t,"clip");function paramPreview(t){if(!(!Array.isArray(t)||t.length===0))return t.slice(0,10).map(e=>{if(e==null)return String(e);const r=typeof e=="string"?e:(()=>{try{return JSON.stringify(e)}catch{return String(e)}})();return clip(r,80)})}c(paramPreview,"paramPreview"),o(paramPreview,"paramPreview");const rowsOf=o(t=>t?.rowCount??t?.rows?.length??0,"rowsOf");function resultPreview(t){const e=t?.rows;if(!(!Array.isArray(e)||e.length===0))try{return clip(JSON.stringify((0,redact_1.redactDeep)(e.slice(0,2))),240)}catch{return}}c(resultPreview,"resultPreview"),o(resultPreview,"resultPreview");function connectionOf(t){const e=t,r=e?.connectionParameters,n=typeof e?.database=="string"?e.database:typeof r?.database=="string"?r.database:void 0,a=typeof e?.host=="string"?e.host:typeof r?.host=="string"?r.host:void 0,i=typeof e?.port=="number"||typeof e?.port=="string"?e.port:typeof r?.port=="number"||typeof r?.port=="string"?r.port:5432;return{...n?{database:n}:{},...a?{server:`${a}:${i}`}:{}}}c(connectionOf,"connectionOf"),o(connectionOf,"connectionOf");const APP_NAME_MAX=63;function taggedAppName(t,e,r,n){const a=` lens:${e.replace(/[^\x21-\x7e]/g,"?").replace(/:/g,"_")}${r>0?`:g${r}`:""}${n?`:p${n}`:""}`.slice(0,APP_NAME_MAX);return`${(t??"node").replace(/[^\x20-\x7e]/g,"?").replace(/ lens:\S+$/,"").slice(0,Math.max(0,APP_NAME_MAX-a.length))}${a}`}c(taggedAppName,"taggedAppName"),o(taggedAppName,"taggedAppName");function tagApplicationName(t){if(process.env.LENSMCP_PG_APP_NAME==="0")return;const e=t.getStartupConf;if(typeof e!="function"||e.__lensmcp)return;const r=o(function(){const n=e.call(this);try{n&&typeof n=="object"&&emit_2.PROJECT&&(n.application_name=taggedAppName(typeof n.application_name=="string"?n.application_name:void 0,emit_2.PROJECT,(0,active_work_1.activeWorkGeneration)(),process.pid))}catch{}return n},"wrapped");r.__lensmcp=!0,t.getStartupConf=r}c(tagApplicationName,"tagApplicationName"),o(tagApplicationName,"tagApplicationName");function pgTap(t){const e=t?.Client?.prototype,r=e?.query;if(!e||typeof r!="function")return!1;try{(0,tap_pg_pool_1.pgPoolTap)(t)}catch{}try{tagApplicationName(e)}catch{}if(r.__lensmcp)return!0;const n=o(function(a,i,_){const f=a,d=typeof f=="string"?f:f?.text;if(!d||f&&typeof f=="object"&&typeof f.submit=="function")return r.call(this,a,i,_);if(SKIP_OPS.has(firstWord(d)))return r.call(this,a,i,_);const{op:L,table:p,sqlId:S,tables:v,name:P}=parseSql(d),I=connectionOf(this),m=L.toLowerCase(),R=`db:${m}:${p??"query"}`,g=Date.now(),E=(0,flow_context_1.flowContext)(),b=(0,flow_context_1.liveFlowContext)();b&&(b.dbCallCount=(b.dbCallCount??0)+1);const q=(0,sql_accounting_1.beginSqlStatement)(b,E,g),T=clip(oneLine(d),500),$=paramPreview(Array.isArray(i)?i:a?.values),h=$&&(0,redact_1.redactSqlParams)(d,$),w=o((s,u)=>{const l=Date.now()-g,M=g-(statementSentAt.get(S)??-1/0)>=STATEMENT_EVERY_MS,N={sqlId:S,...v.length?{tables:v}:{},...P?{sqlName:P}:{},...I,...M?{statement:clip(d.trim(),STATEMENT_MAX)}:{}};let A;if(s)A=(0,emit_1.lensEmitOutcome)("db","error",`${m}${p?" "+p:""} failed (${l}ms)`,{name:"postgres",kind:"db-query",op:m,...p?{table:p}:{},...N,query:T,...h?{params:h}:{},startedAt:g,error:String(s?.message??s),durationMs:l},`db:error:${m}:${p??"query"}`,E);else{const O=rowsOf(u),x=resultPreview(u);A=(0,emit_1.lensEmitOutcome)("db","info",`${m} ${p??"query"} (${O} rows, ${l}ms)`,{name:"postgres",kind:"db-query",op:m,...p?{table:p}:{},...N,query:T,...h?{params:h}:{},...x?{result:x}:{},startedAt:g,rows:O,durationMs:l},R,E)}M&&A==="written"&&(statementSentAt.delete(S),statementSentAt.set(S,g),statementSentAt.size>MAX_SHAPES_REMEMBERED&&statementSentAt.delete(statementSentAt.keys().next().value)),(0,sql_accounting_1.endSqlStatement)(q,{durationMs:l,failed:!!s,emitted:A})},"done"),C=typeof _=="function"?_:typeof i=="function"?i:void 0;if(C){const s=o((u,l)=>{try{w(u,l)}catch{}C(u,l)},"wrappedCb");try{return typeof i=="function"?r.call(this,a,s):r.call(this,a,i,s)}catch(u){throw(0,sql_accounting_1.abandonSqlStatement)(q),u}}let y;try{y=r.call(this,a,i)}catch(s){throw(0,sql_accounting_1.abandonSqlStatement)(q),s}return y&&typeof y.then=="function"?y.then(s=>w(void 0,s),s=>w(s,void 0)):(0,sql_accounting_1.abandonSqlStatement)(q),y},"wrapped");return n.__lensmcp=!0,e.query=n,!0}c(pgTap,"pgTap"),o(pgTap,"pgTap");
1
+ "use strict";var j=Object.defineProperty;var p=(t,e)=>j(t,"name",{value:e,configurable:!0});var W=Object.defineProperty,r=p((t,e)=>W(t,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.taggedAppName=taggedAppName,exports.pgTap=pgTap;const redact_1=require("@lensmcp/core/redact"),sql_shape_1=require("@lensmcp/core/sql-shape"),emit_1=require("./emit"),flow_context_1=require("./flow-context"),sql_accounting_1=require("./sql-accounting"),tap_pg_pool_1=require("./tap-pg-pool"),callsite_1=require("./callsite"),active_work_1=require("./active-work"),emit_2=require("./emit"),SKIP_OPS=new Set(["BEGIN","START","COMMIT","END","ROLLBACK","SAVEPOINT","RELEASE","SET","RESET","SHOW","DEALLOCATE","DISCARD","LISTEN","UNLISTEN"]),STATEMENT_EVERY_MS=10*6e4,STATEMENT_MAX=64*1024,SLOW_SITE_MS=250,sites=new callsite_1.SiteSampler,statementSentAt=new Map,MAX_SHAPES_REMEMBERED=2e3,firstWord=r(t=>(/^\s*(?:(?:\/\*[\s\S]*?\*\/|--[^\n]*\n?)\s*)*(\w+)/.exec(t)?.[1]??"query").toUpperCase(),"firstWord");function parseSql(t){const e=(0,sql_shape_1.sqlShape)(t),n=(e.target??e.tables[0])?.split(".").pop();return{op:e.verb,...n?{table:n}:{},sqlId:e.id,tables:e.tables,...e.name?{name:e.name}:{}}}p(parseSql,"parseSql"),r(parseSql,"parseSql");const oneLine=r(t=>t.replace(/\s+/g," ").trim(),"oneLine"),clip=r((t,e)=>t.length>e?t.slice(0,e-1)+"\u2026":t,"clip");function paramPreview(t){if(!(!Array.isArray(t)||t.length===0))return t.slice(0,10).map(e=>{if(e==null)return String(e);const n=typeof e=="string"?e:(()=>{try{return JSON.stringify(e)}catch{return String(e)}})();return clip(n,80)})}p(paramPreview,"paramPreview"),r(paramPreview,"paramPreview");const rowsOf=r(t=>t?.rowCount??t?.rows?.length??0,"rowsOf");function resultPreview(t){const e=t?.rows;if(!(!Array.isArray(e)||e.length===0))try{return clip(JSON.stringify((0,redact_1.redactDeep)(e.slice(0,2))),240)}catch{return}}p(resultPreview,"resultPreview"),r(resultPreview,"resultPreview");function connectionOf(t){const e=t,n=e?.connectionParameters,o=typeof e?.database=="string"?e.database:typeof n?.database=="string"?n.database:void 0,s=typeof e?.host=="string"?e.host:typeof n?.host=="string"?n.host:void 0,i=typeof e?.port=="number"||typeof e?.port=="string"?e.port:typeof n?.port=="number"||typeof n?.port=="string"?n.port:5432;return{...o?{database:o}:{},...s?{server:`${s}:${i}`}:{}}}p(connectionOf,"connectionOf"),r(connectionOf,"connectionOf");const APP_NAME_MAX=63;function taggedAppName(t,e,n,o){const s=` lens:${e.replace(/[^\x21-\x7e]/g,"?").replace(/:/g,"_")}${n>0?`:g${n}`:""}${o?`:p${o}`:""}`.slice(0,APP_NAME_MAX);return`${(t??"node").replace(/[^\x20-\x7e]/g,"?").replace(/ lens:\S+$/,"").slice(0,Math.max(0,APP_NAME_MAX-s.length))}${s}`}p(taggedAppName,"taggedAppName"),r(taggedAppName,"taggedAppName");function tagApplicationName(t){if(process.env.LENSMCP_PG_APP_NAME==="0")return;const e=t.getStartupConf;if(typeof e!="function"||e.__lensmcp)return;const n=r(function(){const o=e.call(this);try{o&&typeof o=="object"&&emit_2.PROJECT&&(o.application_name=taggedAppName(typeof o.application_name=="string"?o.application_name:void 0,emit_2.PROJECT,(0,active_work_1.activeWorkGeneration)(),process.pid))}catch{}return o},"wrapped");n.__lensmcp=!0,t.getStartupConf=n}p(tagApplicationName,"tagApplicationName"),r(tagApplicationName,"tagApplicationName");function pgTap(t){const e=t?.Client?.prototype,n=e?.query;if(!e||typeof n!="function")return!1;try{(0,tap_pg_pool_1.pgPoolTap)(t)}catch{}try{tagApplicationName(e)}catch{}if(n.__lensmcp)return!0;const o=r(function(s,i,_){const f=s,d=typeof f=="string"?f:f?.text;if(!d||f&&typeof f=="object"&&typeof f.submit=="function")return n.call(this,s,i,_);if(SKIP_OPS.has(firstWord(d)))return n.call(this,s,i,_);const{op:R,table:c,sqlId:g,tables:P,name:$}=parseSql(d),k=connectionOf(this),m=R.toLowerCase(),D=`db:${m}:${c??"query"}`,y=Date.now(),A=(0,flow_context_1.flowContext)(),J=(0,callsite_1.captureSite)(),C=(0,tap_pg_pool_1.checkoutSite)(),b=(0,flow_context_1.liveFlowContext)();b&&(b.dbCallCount=(b.dbCallCount??0)+1);const h=(0,sql_accounting_1.beginSqlStatement)(b,A,y),M=clip(oneLine(d),500),N=paramPreview(Array.isArray(i)?i:s?.values),q=N&&(0,redact_1.redactSqlParams)(d,N),w=r((a,u)=>{const l=Date.now()-y,v=y-(statementSentAt.get(g)??-1/0)>=STATEMENT_EVERY_MS;let T;try{T=sites.site(g,J,{force:!!a||v||l>=SLOW_SITE_MS,...C?{fallback:C}:{}})}catch{}const x={sqlId:g,...P.length?{tables:P}:{},...$?{sqlName:$}:{},...k,...T?{site:T}:{},...v?{statement:clip(d.trim(),STATEMENT_MAX)}:{}};let E;if(a)E=(0,emit_1.lensEmitOutcome)("db","error",`${m}${c?" "+c:""} failed (${l}ms)`,{name:"postgres",kind:"db-query",op:m,...c?{table:c}:{},...x,query:M,...q?{params:q}:{},startedAt:y,error:String(a?.message??a),durationMs:l},`db:error:${m}:${c??"query"}`,A);else{const L=rowsOf(u),I=resultPreview(u);E=(0,emit_1.lensEmitOutcome)("db","info",`${m} ${c??"query"} (${L} rows, ${l}ms)`,{name:"postgres",kind:"db-query",op:m,...c?{table:c}:{},...x,query:M,...q?{params:q}:{},...I?{result:I}:{},startedAt:y,rows:L,durationMs:l},D,A)}v&&E==="written"&&(statementSentAt.delete(g),statementSentAt.set(g,y),statementSentAt.size>MAX_SHAPES_REMEMBERED&&statementSentAt.delete(statementSentAt.keys().next().value)),(0,sql_accounting_1.endSqlStatement)(h,{durationMs:l,failed:!!a,emitted:E})},"done"),O=typeof _=="function"?_:typeof i=="function"?i:void 0;if(O){const a=r((u,l)=>{try{w(u,l)}catch{}O(u,l)},"wrappedCb");try{return typeof i=="function"?n.call(this,s,a):n.call(this,s,i,a)}catch(u){throw(0,sql_accounting_1.abandonSqlStatement)(h),u}}let S;try{S=n.call(this,s,i)}catch(a){throw(0,sql_accounting_1.abandonSqlStatement)(h),a}return S&&typeof S.then=="function"?S.then(a=>w(void 0,a),a=>w(a,void 0)):(0,sql_accounting_1.abandonSqlStatement)(h),S},"wrapped");return o.__lensmcp=!0,e.query=o,!0}p(pgTap,"pgTap"),r(pgTap,"pgTap");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/node-instrumentation",
3
- "version": "1.25.2",
3
+ "version": "1.25.5",
4
4
  "main": "./index.js",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {
@@ -15,9 +15,9 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@lensmcp/core": "1.25.2",
19
- "@lensmcp/nest-instrumentation": "1.25.2",
20
- "@lensmcp/protocol-types": "1.25.2",
18
+ "@lensmcp/core": "1.25.5",
19
+ "@lensmcp/nest-instrumentation": "1.25.5",
20
+ "@lensmcp/protocol-types": "1.25.5",
21
21
  "tslib": "^2.3.0"
22
22
  },
23
23
  "optionalDependencies": {