@lensmcp/node-instrumentation
v1.13.0
Published
Zero-touch Node.js runtime instrumentation for LensMCP — fs/network/exec/db/redis/queue taps + NestJS auto-graft, injected by the runner, never imported by host source.
Maintainers
Readme
@lensmcp/node-instrumentation
Zero-touch Node runtime taps for LensMCP — the observability lens for coding agents.
@lensmcp/node-instrumentation instruments a running Node.js service without touching its source. It is loaded by the runner via node --require @lensmcp/node-instrumentation/register, before your app bundle, so it can patch Node built-ins and external modules (fs, fetch/http(s), child_process, pg, ioredis, bullmq, @nestjs/core) and the process streams in place. As your service runs, those patches emit LensMCP events onto the bus that the dashboard and MCP server consume.
The whole point is that the host stays completely ordinary. There are no emit helpers, no createLensmcpNestApp, no dual-mode bootstrap — your main.ts looks exactly like an uninstrumented service. Requiring the module is the installation: it must run before the app loads so the require hook sees every external on first require/import. Every tap is wrapped in try/catch and the bus writer fails silently, so instrumentation can never throw into — or break — the host.
Install
yarn add @lensmcp/node-instrumentationYou normally do not install or import this package yourself. The @lensmcp/cluster serve executor injects it into each service pod via --require and sets the relevant environment variables. Add it directly only if you are wiring a runner by hand.
Usage
Activate it by preloading register ahead of your entrypoint — this is what does the instrumentation:
node --require @lensmcp/node-instrumentation/register your-app.js@lensmcp/cluster does exactly this for you when it serves a pod, so under the cluster you do nothing.
If you need to install the taps programmatically (e.g. from a custom runner), call the idempotent installer exported from the package root:
import { installLensmcpNodeInstrumentation } from '@lensmcp/node-instrumentation';
installLensmcpNodeInstrumentation(); // safe to call more than once — guarded by a global markerThe package root also re-exports the individual tap functions (tapFs, tapEgress, tapExec, pgTap, redisTap, bullmqTap, nestTap, installChildAppBridge) and the bus helpers (lensEmit, PROJECT, EVENT_FILE) for advanced use.
What it taps
Built-ins and globals are patched at install; external modules (pg, ioredis, bullmq, @nestjs/core) are patched lazily through a require hook the first time they load.
| Layer | How it's patched | Emits (source / kind) |
|---|---|---|
| Filesystem | fs read/write/append/stream (sync, callback, promise) | fs / fs-ops — a 1s aggregate (read/write counts, bytes, top paths) |
| Network egress | global fetch + http/https.request wrap | external / egress — outbound method, host, status, duration (internal/loopback/RFC1918 destinations skipped) |
| Child processes | child_process spawn/fork/exec/execFile (+ sync forms) | exec / exec — command, exit code/signal, duration |
| Postgres | require hook → pg Client.prototype.query | db / db-query — op, table, query, params, row count, duration |
| Redis | require hook → ioredis Redis.prototype.sendCommand | redis / redis-op — op, key, command (BullMQ traffic and housekeeping excluded; rate-capped) |
| Queues | require hook → bullmq Queue/Worker prototypes | queue / queue-enqueued, queue-active, queue-completed, queue-failed, queue-process, plus a 2s queue-state snapshot |
| NestJS | require hook → @nestjs/core NestFactory.create grafts LensmcpModule | the events from @lensmcp/nest-instrumentation — class traces, request flows, memory |
| stdout / stderr | process.stdout/stderr write wrap | nestjs / runtime — one log event per line (original output is preserved) |
Under the cluster devserver (APP_RUNNER=1) the NestJS tap also makes a standard NestFactory.create(...) + app.listen(port) service pod-servable: listen is intercepted to init() the app and hand its raw request handler to a capture queue instead of binding TCP, with no devserver contract in your main.ts.
Configuration
All configuration is via environment variables (the cluster serve executor sets these for you).
| Variable | Effect |
|---|---|
| LENSMCP_AUTO=0 | Disable all instrumentation — no taps are installed. |
| LENSMCP_NEST=0 | Keep the other taps but skip the NestJS graft (NestFactory.create is left untouched). |
| LENSMCP_LOGS=0 | Disable the stdout/stderr log tap. |
| LENSMCP_PROJECT | Service identity stamped on every event. Falls back to SERVICE_NAME, then "app". |
| LENSMCP_EVENT_FILE | Path to the JSONL event bus. Defaults to $CWD/.lensmcp/events.jsonl. |
The stdout/stderr tap and the zero-touch pod bootstrap additionally require APP_RUNNER=1 (set by the cluster runner); outside a pod they are no-ops so a standalone --require run stays quiet.
How it fits
@lensmcp/node-instrumentation is injected by @lensmcp/cluster, which serves each service as a hot-swappable pod with this module preloaded. The taps write BaseEvent-shaped JSON lines to the LensMCP bus, where they are picked up by @lensmcp/core and surfaced through the MCP server and dashboard.
It pairs with @lensmcp/nest-instrumentation (a direct dependency): the NestJS tap grafts that package's LensmcpModule onto your root module to add class-level traces, request flows, and memory tracking — again without any import in host code.
Part of LensMCP. Apache-2.0.
