@lensmcp/node-instrumentation 1.25.9 → 1.25.11
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/README.md +33 -0
- package/index.d.ts +2 -0
- package/index.js +1 -1
- package/lib/emit-custom.d.ts +130 -0
- package/lib/emit-custom.js +1 -0
- package/lib/emit.d.ts +2 -1
- package/lib/install.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -34,6 +34,39 @@ installLensmcpNodeInstrumentation(); // safe to call more than once — guarded
|
|
|
34
34
|
|
|
35
35
|
The 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.
|
|
36
36
|
|
|
37
|
+
## Reporting a custom source's events (`lensEmitCustom`)
|
|
38
|
+
|
|
39
|
+
The one call a host makes on purpose. A service that declares a custom source in its `project.json` `cluster.sources` (an outbox table, a background loop, a store the taps do not model, a channel they do not classify — see the docs' *Custom sources* page) can report what happens to it, where it happens — inside the request or job — so the event joins that flow:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { lensEmitCustom } from '@lensmcp/node-instrumentation';
|
|
43
|
+
|
|
44
|
+
lensEmitCustom({ kind: 'queue', name: 'engine backlog', event: 'enqueued', item: row.id, group: `${tenantId}/${kind}` });
|
|
45
|
+
lensEmitCustom({ kind: 'queue', name: 'engine backlog', event: 'failed', item: row.id, error: err.message });
|
|
46
|
+
lensEmitCustom({ kind: 'loop', name: 'admission', event: 'run-end', outcome: 'ok', items: 3, durationMs: 40 });
|
|
47
|
+
lensEmitCustom({ kind: 'store', name: 'documents', event: 'write', object: `sha256:${hash}`, group: tenantId, bytes: buf.length, durationMs });
|
|
48
|
+
lensEmitCustom({ kind: 'store', name: 'documents', event: 'error', object: `sha256:${hash}`, error: err.message });
|
|
49
|
+
lensEmitCustom({ kind: 'traffic', name: 'partners', event: 'outbound', channel: 'registry', counterpart: 'registry.gov', outcome: 'rate-limited', durationMs: 90 });
|
|
50
|
+
lensEmitCustom({ kind: 'traffic', name: 'meta', event: 'inbound', channel: 'webhook', counterpart: 'meta', outcome: 'ok', bytes, correlation: messageId });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `kind` — `queue` (events `enqueued` · `admitted` · `completed` · `failed` · `parked`), `loop` (`run-start` · `run-end`, with `outcome` `ok` · `failed` · `skipped`), `store` (`write` · `delete` · `error`) or `traffic` (`inbound` · `outbound`, with `outcome` `ok` · `failed` · `rate-limited`). `name` is the declared source's name; `group` is the snapshot group's key.
|
|
54
|
+
- **A store's `object` is an id or a hash of the object's key — never a name that holds personal data** (a person's name, an uploaded file's own name, an email): the event is written to the lens's bus, shown on the dashboard and read by agents. `bytes` is read on a `write` only.
|
|
55
|
+
- **A traffic event must say its `channel`** — the lens counts per channel; one without is refused. When the source also answers a snapshot, it is the snapshot channel's `key`. `correlation` joins an inbound message to the outbound one it caused.
|
|
56
|
+
- **A push-only traffic source** is declared with no `path`: the lens polls nothing and counts what the service pushes, per channel, over its last 10 minutes:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
"sources": [{ "kind": "traffic", "name": "partners", "thresholds": { "failurePct": 5, "p95Ms": 2000, "rateLimited": 1 } }]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Written as a `source: 'custom'`, `category: 'backend'` event (`raw.kind: 'source-event'`, `provenance: 'reported'`) with the current flow's ids. A failed item or run, a store `error` and a `failed` or `rate-limited` message are `warning`s. Flood-guarded per (service, kind, name, event) — and per channel for traffic, so a busy channel never holds back another's — apart from the taps. A source's first 50 channels get a bucket of their own (1,000 across the process); further channels share the source's one (said once on stderr), so a channel minted per message cannot bypass the guard.
|
|
63
|
+
- **A no-op outside the lens.** The writer is installed by the runner's preload; in production or in your own tests the call returns `false` and writes nothing. It never throws and never awaits. A call it cannot read (an unknown kind or event, a blank name, a traffic event with no channel, an outcome the kind does not have) returns `false` and is said once on stderr; `item`/`group`/`object`/`channel`/`counterpart`/`correlation` are cut to 200 characters, `error` to 500; a size or duration that is not a count is left out.
|
|
64
|
+
- **No dependency needed.** The same writer is on a registered global, so a service that does not depend on this package can call it without an import:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
(globalThis as any)[Symbol.for('lensmcp.emitCustom')]?.({ kind: 'queue', name: 'engine backlog', event: 'enqueued', item: row.id, group });
|
|
68
|
+
```
|
|
69
|
+
|
|
37
70
|
## What it taps
|
|
38
71
|
|
|
39
72
|
Built-ins and globals are patched at install; external modules (`pg`, `ioredis`, `bullmq`, `@nestjs/core`, `@nestjs/common`) are patched lazily through a require hook the first time they load.
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { lensEmit, isLensWriting, PROJECT, EVENT_FILE, EVENT_MAX_BYTES, EVENT_MAX_FILES, maybeRotateEventFile } from './lib/emit';
|
|
2
2
|
export type { LensSeverity } from './lib/emit';
|
|
3
|
+
export { lensEmitCustom, EMIT_CUSTOM, installCustomEmit } from './lib/emit-custom';
|
|
4
|
+
export type { CustomSourceEvent } from './lib/emit-custom';
|
|
3
5
|
export { createEventFloodGuard } from './lib/flood-guard';
|
|
4
6
|
export type { FloodGuard, FloodGuardOptions, FloodAdmit } from './lib/flood-guard';
|
|
5
7
|
export { installRequireHook } from './lib/require-hook';
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var p=Object.defineProperty;var i=(t,r)=>p(t,"name",{value:r,configurable:!0});var s=Object.defineProperty,o=i((t,r)=>s(t,"name",{value:r,configurable:!0}),"o"),n=Object.defineProperty,e=o((t,r)=>n(t,"name",{value:r,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.resetActiveWorkForTests=exports.installActiveWorkProbe=exports.beginActiveWork=exports.activeWorkSnapshot=exports.installLensmcpNodeInstrumentation=exports.installChildAppBridge=exports.nestTap=exports.bullmqTap=exports.redisTap=exports.pgTap=exports.tapExec=exports.tapFs=exports.stopEventLoopTapForTests=exports.tapEventLoop=exports.EXCHANGE_ID=exports.resetIngressTapForTests=exports.tapIngress=exports.resetEgressTapForTests=exports.tapEgress=exports.installRequireHook=exports.createEventFloodGuard=exports.installCustomEmit=exports.EMIT_CUSTOM=exports.lensEmitCustom=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 emit_custom_1=require("./lib/emit-custom");Object.defineProperty(exports,"lensEmitCustom",{enumerable:!0,get:e(function(){return emit_custom_1.lensEmitCustom},"get")}),Object.defineProperty(exports,"EMIT_CUSTOM",{enumerable:!0,get:e(function(){return emit_custom_1.EMIT_CUSTOM},"get")}),Object.defineProperty(exports,"installCustomEmit",{enumerable:!0,get:e(function(){return emit_custom_1.installCustomEmit},"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")}),Object.defineProperty(exports,"resetEgressTapForTests",{enumerable:!0,get:e(function(){return tap_egress_1.resetEgressTapForTests},"get")});var tap_ingress_1=require("./lib/tap-ingress");Object.defineProperty(exports,"tapIngress",{enumerable:!0,get:e(function(){return tap_ingress_1.tapIngress},"get")}),Object.defineProperty(exports,"resetIngressTapForTests",{enumerable:!0,get:e(function(){return tap_ingress_1.resetIngressTapForTests},"get")}),Object.defineProperty(exports,"EXCHANGE_ID",{enumerable:!0,get:e(function(){return tap_ingress_1.EXCHANGE_ID},"get")});var tap_event_loop_1=require("./lib/tap-event-loop");Object.defineProperty(exports,"tapEventLoop",{enumerable:!0,get:e(function(){return tap_event_loop_1.tapEventLoop},"get")}),Object.defineProperty(exports,"stopEventLoopTapForTests",{enumerable:!0,get:e(function(){return tap_event_loop_1.stopEventLoopTapForTests},"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")});var active_work_1=require("./lib/active-work");Object.defineProperty(exports,"activeWorkSnapshot",{enumerable:!0,get:e(function(){return active_work_1.activeWorkSnapshot},"get")}),Object.defineProperty(exports,"beginActiveWork",{enumerable:!0,get:e(function(){return active_work_1.beginActiveWork},"get")}),Object.defineProperty(exports,"installActiveWorkProbe",{enumerable:!0,get:e(function(){return active_work_1.installActiveWorkProbe},"get")}),Object.defineProperty(exports,"resetActiveWorkForTests",{enumerable:!0,get:e(function(){return active_work_1.resetActiveWorkForTests},"get")});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CUSTOM SOURCES, the push half (issues/closed/foodguard-81, contract: `@lensmcp/protocol-types` `sources.ts`) — an app
|
|
3
|
+
* REPORTS a happening about a source it declares in project.json `cluster.sources`, which no tap can see: an outbox row
|
|
4
|
+
* written, taken, finished, failed or set aside; a background loop's run beginning and ending; an object written to or
|
|
5
|
+
* deleted from a store the taps do not model; a message in or out over a channel they do not classify. The poll says
|
|
6
|
+
* how much waits; only the app can say WHEN one item moved and in which request — so the event is written with the flow
|
|
7
|
+
* context it ran in, and the `enqueued` of an outbox row shows inside the flow of the answer whose transaction wrote it,
|
|
8
|
+
* its `completed` inside the job's.
|
|
9
|
+
*
|
|
10
|
+
* Three rules shape it:
|
|
11
|
+
* - **A no-op unless the lens installed it.** The writer reaches the app through a REGISTERED global
|
|
12
|
+
* ({@link EMIT_CUSTOM}) that `installLensmcpNodeInstrumentation` sets — the runner's `--require` preload, so only a
|
|
13
|
+
* pod under the lens. A process that merely imports this package (production, the app's own tests) has no hook and
|
|
14
|
+
* {@link lensEmitCustom} writes nothing: the bus file's default is under `cwd`, and production must not grow a
|
|
15
|
+
* `.lensmcp` folder because an app reports its outbox. Going through the global also means an app that bundles its
|
|
16
|
+
* OWN copy of this package still writes through the preloaded instance — the one whose flow context was primed.
|
|
17
|
+
* - **The app's words, typed and bounded.** A closed set of kinds and events (`queue` · `loop` · `store` · `traffic`,
|
|
18
|
+
* each with its own events and outcomes), strings cut, counts kept only when they are counts. What is refused is
|
|
19
|
+
* said once on stderr — a call the app makes thousands of times must neither throw into it nor print thousands of
|
|
20
|
+
* lines.
|
|
21
|
+
* - **Its own flood buckets.** One per (service, kind, name, event) — and for `traffic` per channel too — in a guard
|
|
22
|
+
* apart from the taps' shared one: a burst of `enqueued` (a bulk import writing 5,000 rows) never starves the
|
|
23
|
+
* `completed` that says the work moved, a busy channel never starves another's counts, and the app's reports never
|
|
24
|
+
* spend a tap's budget, nor a tap's theirs. The channels are bounded ({@link CHANNEL_BUCKETS_PER_SOURCE}): an app
|
|
25
|
+
* that mints a channel per message cannot mint a fresh burst per message.
|
|
26
|
+
*
|
|
27
|
+
* An app that does not depend on this package calls the hook with no import:
|
|
28
|
+
*
|
|
29
|
+
* (globalThis as any)[Symbol.for('lensmcp.emitCustom')]?.({ kind: 'queue', name: 'engine backlog', event: 'enqueued', item: row.id, group });
|
|
30
|
+
*
|
|
31
|
+
* Never throws, never awaits: every path is caught, and the write is the bus writer's synchronous append.
|
|
32
|
+
*/
|
|
33
|
+
import { type LoopOutcome, type QueueEventName, type StoreEventName, type TrafficEventName, type TrafficOutcome } from '@lensmcp/protocol-types';
|
|
34
|
+
/**
|
|
35
|
+
* One happening an app reports about a source it declares. `name` is the source's `name` in project.json
|
|
36
|
+
* `cluster.sources` — the lens joins the event to that declaration (one it cannot find is listed as undeclared).
|
|
37
|
+
* `group` is the snapshot group's key (the wire's `key`); it travels on the bus as `group` because the bus redacts any
|
|
38
|
+
* field NAMED `key`.
|
|
39
|
+
*/
|
|
40
|
+
export type CustomSourceEvent = {
|
|
41
|
+
kind: 'queue';
|
|
42
|
+
name: string;
|
|
43
|
+
event: QueueEventName;
|
|
44
|
+
/** The item's own id (an outbox row's), at most 200 characters. */
|
|
45
|
+
item?: string;
|
|
46
|
+
/** The snapshot group it belongs to (`<tenant>/<kind>`), at most 200 characters. */
|
|
47
|
+
group?: string;
|
|
48
|
+
/** A `failed` (or `parked`) item: why, at most 500 characters. */
|
|
49
|
+
error?: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'loop';
|
|
52
|
+
name: string;
|
|
53
|
+
event: 'run-start';
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'loop';
|
|
56
|
+
name: string;
|
|
57
|
+
event: 'run-end';
|
|
58
|
+
outcome?: LoopOutcome;
|
|
59
|
+
/** Items the run took. */
|
|
60
|
+
items?: number;
|
|
61
|
+
durationMs?: number;
|
|
62
|
+
/** A failed run: why, at most 500 characters. */
|
|
63
|
+
error?: string;
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'store';
|
|
66
|
+
name: string;
|
|
67
|
+
/** An object written, deleted, or an operation on it that failed (a `warning`). */
|
|
68
|
+
event: StoreEventName;
|
|
69
|
+
/**
|
|
70
|
+
* The object: its id, or a hash of its key (`sha256:ab12…`) — NEVER a name that holds personal data (a person's
|
|
71
|
+
* name, an uploaded file's own name, an email): the event is written to the bus, shown on the dashboard and read
|
|
72
|
+
* by agents. At most 200 characters.
|
|
73
|
+
*/
|
|
74
|
+
object?: string;
|
|
75
|
+
/** The snapshot group it belongs to (a bucket, a tenant's folder — the wire's group `key`), at most 200 characters. */
|
|
76
|
+
group?: string;
|
|
77
|
+
/** A `write`'s size. Not read on a `delete` or an `error`: the contract's `bytes` of a store is what was written. */
|
|
78
|
+
bytes?: number;
|
|
79
|
+
durationMs?: number;
|
|
80
|
+
/** An `error`: why, at most 500 characters. */
|
|
81
|
+
error?: string;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'traffic';
|
|
84
|
+
name: string;
|
|
85
|
+
/** A message that came in, or one that went out. */
|
|
86
|
+
event: TrafficEventName;
|
|
87
|
+
/**
|
|
88
|
+
* REQUIRED — the lens counts per channel (a push-only source is counted from these alone): an event without one
|
|
89
|
+
* is refused. When the source also answers a snapshot, the snapshot channel's `key`. At most 200 characters.
|
|
90
|
+
*/
|
|
91
|
+
channel: string;
|
|
92
|
+
/** Who is at the other end — a partner, a host, a queue — at most 200 characters. */
|
|
93
|
+
counterpart?: string;
|
|
94
|
+
/** How it went: `failed` and `rate-limited` (an HTTP 429, a quota) are `warning`s. */
|
|
95
|
+
outcome?: TrafficOutcome;
|
|
96
|
+
durationMs?: number;
|
|
97
|
+
/** The message's size. */
|
|
98
|
+
bytes?: number;
|
|
99
|
+
/** What joins an inbound message to the outbound one it caused (a request id, a message id), at most 200 characters. */
|
|
100
|
+
correlation?: string;
|
|
101
|
+
/** A failed message: why, at most 500 characters. */
|
|
102
|
+
error?: string;
|
|
103
|
+
};
|
|
104
|
+
/** The registered global the preloaded instance publishes its writer on — `Symbol.for`, so every copy derives it. */
|
|
105
|
+
export declare const EMIT_CUSTOM: unique symbol;
|
|
106
|
+
/**
|
|
107
|
+
* The writer — what the hook runs. Validates `e` (it may come from an untyped caller through the global), writes one
|
|
108
|
+
* `source: 'custom'` event, and returns whether it reached the bus (`false`: refused, held back by its flood bucket, or
|
|
109
|
+
* the lens offline). Not exported from the package: the app calls {@link lensEmitCustom} or the global.
|
|
110
|
+
*/
|
|
111
|
+
export declare function emitCustomHere(e: unknown): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Publish this instance's writer on {@link EMIT_CUSTOM} — `installLensmcpNodeInstrumentation` calls it, so a pod under
|
|
114
|
+
* the lens has it and nothing else does. The FIRST instance keeps it: the preloaded one, whose flow context is primed,
|
|
115
|
+
* is the one every later copy should write through. Returns whether this call installed it.
|
|
116
|
+
*/
|
|
117
|
+
export declare function installCustomEmit(): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Report a happening about a source this service declares in project.json `cluster.sources` — an outbox row
|
|
120
|
+
* `enqueued` / `admitted` / `completed` / `failed` / `parked`, a loop's `run-start` / `run-end`, a store's object
|
|
121
|
+
* `write` / `delete` / `error`, a traffic channel's message `inbound` / `outbound` — as a `source: 'custom'` event in
|
|
122
|
+
* the flow it runs in. Call it where the thing happens, inside the request or job.
|
|
123
|
+
*
|
|
124
|
+
* Returns whether the event was written. Under the lens: `false` when it was refused (said once on stderr) or held back
|
|
125
|
+
* by its flood bucket. NOT under the lens (production, tests, a script): always `false`, and nothing is written —
|
|
126
|
+
* nothing installed the hook. Never throws, never awaits.
|
|
127
|
+
*
|
|
128
|
+
* Without depending on this package: `(globalThis as any)[Symbol.for('lensmcp.emitCustom')]?.({ … })` does the same.
|
|
129
|
+
*/
|
|
130
|
+
export declare function lensEmitCustom(e: CustomSourceEvent): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var M=Object.defineProperty;var l=(e,t)=>M(e,"name",{value:t,configurable:!0});var O=Object.defineProperty,d=l((e,t)=>O(e,"name",{value:t,configurable:!0}),"d"),C=Object.defineProperty,c=d((e,t)=>C(e,"name",{value:t,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EMIT_CUSTOM=void 0,exports.emitCustomHere=emitCustomHere,exports.installCustomEmit=installCustomEmit,exports.lensEmitCustom=lensEmitCustom;const protocol_types_1=require("@lensmcp/protocol-types"),emit_1=require("./emit"),flood_guard_1=require("./flood-guard"),flow_context_1=require("./flow-context");exports.EMIT_CUSTOM=Symbol.for("lensmcp.emitCustom");const ITEM_MAX=200,ERROR_MAX=500,NAME_RE=/^[^\u0000-\u001f\u007f]{1,100}$/,customGuard=(0,flood_guard_1.createEventFloodGuard)({burst:30,perSec:5}),CHANNEL_BUCKETS_PER_SOURCE=protocol_types_1.TRAFFIC_PUSH_CHANNELS,CHANNEL_BUCKETS_MAX=1e3,channelBuckets=new Map;let channelBucketCount=0;function trafficBucket(e,t,n,m){const s=`custom:${emit_1.PROJECT}:traffic:${e}:${t}`,r=channelBuckets.get(e);return r?.has(n)?`${s}:${n}`:(r?.size??0)<CHANNEL_BUCKETS_PER_SOURCE&&channelBucketCount<CHANNEL_BUCKETS_MAX?(r?r.add(n):channelBuckets.set(e,new Set([n])),channelBucketCount++,`${s}:${n}`):(say(channelBucketCount>=CHANNEL_BUCKETS_MAX?`${m}: ${CHANNEL_BUCKETS_MAX} channels already have a flood bucket in this process \u2014 this source's further channels share one`:`${m}: more than ${CHANNEL_BUCKETS_PER_SOURCE} channels \u2014 its further channels share one flood bucket (is a channel minted per message?)`),s)}l(trafficBucket,"trafficBucket"),d(trafficBucket,"trafficBucket"),c(trafficBucket,"trafficBucket");const SAID_MAX=50,said=new Set;function refuse(e){return say(`${e} \u2014 not written`),!1}l(refuse,"refuse"),d(refuse,"refuse"),c(refuse,"refuse");function say(e){try{if(said.has(e)||said.size>=SAID_MAX)return;said.add(e);const t=said.size===SAID_MAX?" (further problems with lensEmitCustom are not said)":"";console.warn(`[lensmcp] lensEmitCustom: ${e}${t}`)}catch{}}l(say,"say"),d(say,"say"),c(say,"say");function shown(e){return typeof e=="string"?JSON.stringify(e.length>40?`${e.slice(0,39)}\u2026`:e):e===null?"null":typeof e}l(shown,"shown"),d(shown,"shown"),c(shown,"shown");const isObj=c(e=>typeof e=="object"&&e!==null&&!Array.isArray(e),"isObj"),cut=c((e,t)=>e.length>t?`${e.slice(0,t-1)}\u2026`:e,"cut");function idText(e,t){const n=typeof e=="string"?e:typeof e=="number"&&Number.isFinite(e)||typeof e=="bigint"?String(e):void 0;return n?cut(n,t):void 0}l(idText,"idText"),d(idText,"idText"),c(idText,"idText");function errorText(e){const t=typeof e=="string"?e:e instanceof Error?e.message:void 0;return t?cut(t,ERROR_MAX):void 0}l(errorText,"errorText"),d(errorText,"errorText"),c(errorText,"errorText");const oneLine=c((e,t)=>cut(e.replace(/\s+/g," ").trim(),t),"oneLine"),text=c((e,t)=>typeof e=="string"&&e?cut(e,t):void 0,"text");function summary(e,t,n){return`${[e,...t.filter(m=>m!==!1)].join(", ")}${n?` \u2014 ${oneLine(n,120)}`:""}`}l(summary,"summary"),d(summary,"summary"),c(summary,"summary");const sized=c(e=>`${String(e).replace(/\B(?=(\d{3})+(?!\d))/g,",")} byte${e===1?"":"s"}`,"sized");function emitCustomHere(e){try{if(!isObj(e))return refuse(`the event is ${shown(e)}, not { kind, name, event }`);const t=e.kind;if(!protocol_types_1.SOURCE_KINDS_READ.includes(t))return refuse(`kind ${shown(t)} is not a source kind (${protocol_types_1.SOURCE_KINDS_READ.join(", ")})`);const n=t,m=e.name;if(typeof m!="string"||!m.trim()||!NAME_RE.test(m))return refuse(`${n} name ${shown(m)} is not 1\u2013100 characters without control characters (the declared source's name)`);const s=`${n} ${JSON.stringify(m)}`,r=e.event,S=protocol_types_1.SOURCE_PUSH_EVENTS[n];if(typeof r!="string"||!S.includes(r))return refuse(`${s}: event ${shown(r)} is not a ${n} event (${S.join(", ")})`);const u={kind:"source-event",provenance:protocol_types_1.SOURCE_PROVENANCE,sourceKind:n,name:m,event:r};let p="info",E=r,_=`custom:${emit_1.PROJECT}:${n}:${m}:${r}`;if(n==="queue"){const i=idText(e.item,ITEM_MAX),o=idText(e.group,ITEM_MAX),a=errorText(e.error);i&&(u.item=i),o&&(u.group=o),a&&(u.error=a),r==="failed"&&(p="warning"),E=summary(`${r}${i?` ${oneLine(i,80)}`:""}${o?` \xB7 ${oneLine(o,80)}`:""}`,[],a)}else if(n==="loop"){if(r==="run-end"){const i=e.outcome;if(!isOutcome(n,i))return refuse(`${s}: outcome ${shown(i)} is not a run outcome (${protocol_types_1.SOURCE_PUSH_OUTCOMES.loop.join(", ")})`);const o=count(e.items,s,"items",!1),a=count(e.durationMs,s,"durationMs",!0),f=errorText(e.error);i!==void 0&&(u.outcome=i),o!==void 0&&(u.items=o),a!==void 0&&(u.durationMs=a),f&&(u.error=f),i==="failed"&&(p="warning"),E=summary(i?`${r} ${i}`:r,[o!==void 0&&`${o} item${o===1?"":"s"}`,a!==void 0&&`${a} ms`],f)}}else if(n==="store"){const i=idText(e.object,ITEM_MAX),o=idText(e.group,ITEM_MAX),a=r==="write"?count(e.bytes,s,"bytes",!1):void 0,f=count(e.durationMs,s,"durationMs",!0),$=errorText(e.error);i&&(u.object=i),o&&(u.group=o),a!==void 0&&(u.bytes=a),f!==void 0&&(u.durationMs=f),$&&(u.error=$),r==="error"&&(p="warning"),E=summary(`${r}${i?` ${oneLine(i,80)}`:""}${o?` \xB7 ${oneLine(o,80)}`:""}`,[a!==void 0&&sized(a),f!==void 0&&`${f} ms`],$)}else{const i=e.channel;if(typeof i!="string"||!i.trim())return refuse(`${s}: channel ${shown(i)} is not a channel \u2014 every traffic event says its channel (the snapshot channel's key)`);const o=e.outcome;if(!isOutcome(n,o))return refuse(`${s}: outcome ${shown(o)} is not a message outcome (${protocol_types_1.SOURCE_PUSH_OUTCOMES.traffic.join(", ")})`);const a=cut(i,ITEM_MAX),f=text(e.counterpart,ITEM_MAX),$=count(e.durationMs,s,"durationMs",!0),h=count(e.bytes,s,"bytes",!1),g=idText(e.correlation,ITEM_MAX),v=errorText(e.error);u.channel=a,f&&(u.counterpart=f),o!==void 0&&(u.outcome=o),$!==void 0&&(u.durationMs=$),h!==void 0&&(u.bytes=h),g&&(u.correlation=g),v&&(u.error=v),(o==="failed"||o==="rate-limited")&&(p="warning");const b=f?` ${r==="inbound"?"\u2190":"\u2192"} ${oneLine(f,80)}`:"";E=summary(`${r} ${oneLine(a,80)}${b}${o?` ${o}`:""}`,[h!==void 0&&sized(h),$!==void 0&&`${$} ms`],v),_=trafficBucket(m,r,a,s)}const y=(0,flow_context_1.flowContext)();return(0,emit_1.lensEmitOutcome)(protocol_types_1.CUSTOM_EVENT_SOURCE,p,`${n} ${emit_1.PROJECT}/${m} (reported): ${E}`,u,_,y?{flowId:y.flowId,requestId:y.requestId,runId:y.runId}:void 0,"backend",customGuard)==="written"}catch{return!1}}l(emitCustomHere,"emitCustomHere"),d(emitCustomHere,"emitCustomHere"),c(emitCustomHere,"emitCustomHere");function isOutcome(e,t){return t===void 0||typeof t=="string"&&protocol_types_1.SOURCE_PUSH_OUTCOMES[e].includes(t)}l(isOutcome,"isOutcome"),d(isOutcome,"isOutcome"),c(isOutcome,"isOutcome");function count(e,t,n,m){if(e===void 0)return;const s=typeof e=="number"&&m&&Number.isFinite(e)?Math.round(e):e;if(typeof s=="number"&&Number.isSafeInteger(s)&&s>=0)return s;say(`${t}: ${n} ${typeof e=="number"?String(e):shown(e)} is not a count \u2014 written without it`)}l(count,"count"),d(count,"count"),c(count,"count");function installCustomEmit(){try{const e=globalThis;if(typeof e[exports.EMIT_CUSTOM]=="function")return!1;const t=emitCustomHere;return e[exports.EMIT_CUSTOM]=t,!0}catch{return!1}}l(installCustomEmit,"installCustomEmit"),d(installCustomEmit,"installCustomEmit"),c(installCustomEmit,"installCustomEmit");function lensEmitCustom(e){try{const t=globalThis[exports.EMIT_CUSTOM];return typeof t=="function"?t(e)===!0:!1}catch{return!1}}l(lensEmitCustom,"lensEmitCustom"),d(lensEmitCustom,"lensEmitCustom"),c(lensEmitCustom,"lensEmitCustom");
|
package/lib/emit.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ export declare function maybeRotateEventFile(file: string, addedBytes: number, c
|
|
|
26
26
|
maxBytes?: number;
|
|
27
27
|
maxFiles?: number;
|
|
28
28
|
}): void;
|
|
29
|
-
|
|
29
|
+
/** `warning`: an app's reported failure (`lensEmitCustom`) — worth a look, not a fault the lens saw. */
|
|
30
|
+
export type LensSeverity = 'info' | 'warning' | 'error';
|
|
30
31
|
export type LogSeverity = 'debug' | 'info' | 'warning' | 'error' | 'fatal';
|
|
31
32
|
/** Service identity stamped on every event (set by the cluster serve executor). */
|
|
32
33
|
export declare const PROJECT: string;
|
package/lib/install.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var i=Object.defineProperty;var n=(t,r)=>i(t,"name",{value:r,configurable:!0});var p=Object.defineProperty,a=n((t,r)=>p(t,"name",{value:r,configurable:!0}),"a"),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"),emit_custom_1=require("./emit-custom"),tap_egress_1=require("./tap-egress"),tap_ai_1=require("./tap-ai"),tap_fs_1=require("./tap-fs"),tap_exec_1=require("./tap-exec"),tap_pg_1=require("./tap-pg"),tap_redis_1=require("./tap-redis"),active_work_1=require("./active-work"),tap_bullmq_1=require("./tap-bullmq"),tap_nest_1=require("./tap-nest"),tap_nest_logger_1=require("./tap-nest-logger"),tap_event_loop_1=require("./tap-event-loop"),tap_ingress_1=require("./tap-ingress"),tap_stdout_1=require("./tap-stdout");function installLensmcpNodeInstrumentation(){const t=globalThis;if(!t.__lensmcpNodeInstrumented&&(t.__lensmcpNodeInstrumented=!0,process.env.LENSMCP_AUTO!=="0")){try{(0,active_work_1.installActiveWorkProbe)()}catch{}try{(0,flow_context_1.primeFlowContext)()}catch{}try{(0,emit_custom_1.installCustomEmit)()}catch{}try{(0,tap_egress_1.tapEgress)()}catch{}try{(0,tap_ingress_1.tapIngress)()}catch{}try{(0,tap_ai_1.tapAi)()}catch{}try{(0,tap_fs_1.tapFs)()}catch{}try{(0,tap_exec_1.tapExec)()}catch{}try{(0,tap_stdout_1.tapStdout)()}catch{}try{(0,tap_event_loop_1.tapEventLoop)()}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,"@nestjs/common":tap_nest_logger_1.nestLoggerTap})}catch{}try{(0,tap_nest_1.installChildAppBridge)()}catch{}}}n(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation"),a(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation"),e(installLensmcpNodeInstrumentation,"installLensmcpNodeInstrumentation");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/node-instrumentation",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.11",
|
|
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.11",
|
|
19
|
+
"@lensmcp/nest-instrumentation": "1.25.11",
|
|
20
|
+
"@lensmcp/protocol-types": "1.25.11",
|
|
21
21
|
"tslib": "^2.3.0"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|