@lensmcp/node-instrumentation 1.25.0 → 1.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/tap-pg-pool.d.ts +5 -0
- package/lib/tap-pg-pool.js +2 -0
- package/lib/tap-pg.d.ts +8 -0
- package/lib/tap-pg.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Wraps the `connect` that `pg.Pool` inherits from pg-pool. True once patched (or already). */
|
|
2
|
+
export declare function pgPoolTap(pg: unknown): boolean;
|
|
3
|
+
/** Tests only: one sampling pass now, and a clean slate. */
|
|
4
|
+
export declare function samplePgPoolsForTests(now?: number): void;
|
|
5
|
+
export declare function resetPgPoolTapForTests(): void;
|
|
@@ -0,0 +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");
|
package/lib/tap-pg.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the connection calls itself to Postgres: the app's own name, then which service, which app generation and which
|
|
3
|
+
* process opened it — `foodguard lens:engine:g12:p83466` (generations count per process, so the pid tells two pods'
|
|
4
|
+
* apart). Every FoodGuard service connected as `foodguard`, and behind Docker's port proxy
|
|
5
|
+
* every client address is the same, so `pg_stat_activity` could not say whose connections were whose, nor that an
|
|
6
|
+
* app a hot swap replaced still held some. The suffix is kept whole; the app's part is cut to fit.
|
|
7
|
+
*/
|
|
8
|
+
export declare function taggedAppName(own: string | undefined, project: string, generation: number, pid?: number): string;
|
|
1
9
|
export declare function pgTap(exportsObject: unknown): boolean;
|
package/lib/tap-pg.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
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");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/node-instrumentation",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.2",
|
|
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.
|
|
19
|
-
"@lensmcp/nest-instrumentation": "1.25.
|
|
20
|
-
"@lensmcp/protocol-types": "1.25.
|
|
18
|
+
"@lensmcp/core": "1.25.2",
|
|
19
|
+
"@lensmcp/nest-instrumentation": "1.25.2",
|
|
20
|
+
"@lensmcp/protocol-types": "1.25.2",
|
|
21
21
|
"tslib": "^2.3.0"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|