@composed-di/instrumentation-core 0.6.0-alpha → 0.7.0-alpha

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/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from './serviceInstrumentation';
2
- export * from './instrument';
3
2
  export * from './redaction';
4
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAA;AACxC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAA;AACxC,cAAc,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -15,6 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./serviceInstrumentation"), exports);
18
- __exportStar(require("./instrument"), exports);
19
18
  __exportStar(require("./redaction"), exports);
20
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2DAAwC;AACxC,+CAA4B;AAC5B,8CAA2B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2DAAwC;AACxC,8CAA2B"}
@@ -19,7 +19,7 @@ export type Mask<T, K extends Extract<keyof T, string>> = T[K] extends (...args:
19
19
  /**
20
20
  * Marks a service — or specific properties of it — as sensitive, so the
21
21
  * values flowing through its events are redacted. Passed to
22
- * {@link instrument} via InstrumentOptions.redactionRules and applied
22
+ * {@link ServiceInstrumentation.instrument} via the capture options' `redact` list and applied
23
23
  * centrally, after the capture flags: values a rule matches are scrubbed
24
24
  * before the instrumentation ever sees them, and when capture is off
25
25
  * there is nothing to redact. Returned by
@@ -31,16 +31,16 @@ export type Mask<T, K extends Extract<keyof T, string>> = T[K] extends (...args:
31
31
  export interface RedactionRule<T> {
32
32
  readonly key: ServiceKey<T>;
33
33
  /**
34
- * The args to report for a call to `functionName`: unchanged if not
34
+ * The args to report for a call to `methodName`: unchanged if not
35
35
  * redacted, otherwise blanked or run through a custom `maskArgs`.
36
36
  */
37
- maskArgs(functionName: string, args: readonly unknown[]): readonly unknown[];
37
+ maskArgs(methodName: string, args: readonly unknown[]): readonly unknown[];
38
38
  /**
39
39
  * The value to report for a method call's success outcome: unchanged
40
40
  * if not redacted, otherwise blanked or run through a custom
41
41
  * `maskResult`.
42
42
  */
43
- maskResult(functionName: string, result: unknown): unknown;
43
+ maskResult(methodName: string, result: unknown): unknown;
44
44
  }
45
45
  /**
46
46
  * Fluent, single-key rule builder returned by {@link redactionRule}.
@@ -1 +1 @@
1
- {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrE,GAAG,IAAI,EAAE,MAAM,CAAC,KACb,MAAM,CAAC,GACR;IACE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IACjC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAA;CACnC,GACD,KAAK,CAAA;AAeT;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAE3B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,OAAO,EAAE,CAAA;IAE5E;;;;OAIG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAA;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,oBAAoB,CAAC,CAAC;IAKrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;gBAEpC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/C,yCAAyC;IACzC,SAAS,IAAI,IAAI;IAMjB;;;;OAIG;IACH,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAM5E;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI;IAOnD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC;CAyC5B;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAE5E"}
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrE,GAAG,IAAI,EAAE,MAAM,CAAC,KACb,MAAM,CAAC,GACR;IACE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IACjC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAA;CACnC,GACD,KAAK,CAAA;AAeT;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAE3B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,OAAO,EAAE,CAAA;IAE1E;;;;OAIG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,oBAAoB,CAAC,CAAC;IAKrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;gBAEpC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/C,yCAAyC;IACzC,SAAS,IAAI,IAAI;IAMjB;;;;OAIG;IACH,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAM5E;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI;IAOnD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC;CAyC5B;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAE5E"}
package/dist/redaction.js CHANGED
@@ -75,8 +75,8 @@ class RedactionRuleBuilder {
75
75
  const overrides = this.overrides;
76
76
  return {
77
77
  key: this.key,
78
- maskArgs(functionName, args) {
79
- const override = overrides[functionName];
78
+ maskArgs(methodName, args) {
79
+ const override = overrides[methodName];
80
80
  const redacted = override ? override.redacted : redactAllFlag;
81
81
  if (!redacted) {
82
82
  return args;
@@ -89,8 +89,8 @@ class RedactionRuleBuilder {
89
89
  // delegate still sees the call's arity.
90
90
  args.map(() => exports.REDACTED_VALUE);
91
91
  },
92
- maskResult(functionName, result) {
93
- const override = overrides[functionName];
92
+ maskResult(methodName, result) {
93
+ const override = overrides[methodName];
94
94
  const redacted = override ? override.redacted : redactAllFlag;
95
95
  if (!redacted) {
96
96
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
- import type { ServiceKey } from '@composed-di/core';
1
+ import { ServiceFactory, ServiceKey, ServiceModule } from '@composed-di/core';
2
+ import type { RedactionRule } from './redaction';
3
+ type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]>;
2
4
  /**
3
5
  * A handle representing a single in-flight operation (initialization,
4
6
  * disposal, or method call), returned by a ServiceInstrumentation when the
@@ -9,7 +11,7 @@ import type { ServiceKey } from '@composed-di/core';
9
11
  * handle, a correlation id) without any bookkeeping to pair concurrent
10
12
  * start/finish events.
11
13
  */
12
- export interface EventSpan {
14
+ export interface OperationSpan {
13
15
  /**
14
16
  * Wrapper around the operation itself. The instrumented factory invokes
15
17
  * the operation as `run(() => operation())`, so the instrumentation can
@@ -37,23 +39,23 @@ export interface EventSpan {
37
39
  * @param outcome - How the operation finished, and its value or error.
38
40
  * @return void
39
41
  */
40
- end(outcome: EventOutcome): void;
42
+ end(outcome: OperationOutcome): void;
41
43
  }
42
44
  /**
43
- * How an operation finished, delivered to EventSpan.end: `success` may
45
+ * How an operation finished, delivered to OperationSpan.end: `success` may
44
46
  * carry the return or resolved value of a method call (initialize and
45
47
  * dispose outcomes never carry one); `failure` carries the error that was
46
48
  * thrown or rejected.
47
49
  *
48
- * Whether `value` is present is decided by {@link instrument}, not by the
49
- * implementation: it is absent unless result capture is enabled in the
50
- * InstrumentOptions, and holds the redacted value when a redaction rule
51
- * matches. Implementations must record the value exactly when it is
50
+ * Whether `value` is present is decided by {@link ServiceInstrumentation.instrument},
51
+ * not by the implementation: it is absent unless result capture is enabled
52
+ * in the InstrumentOptions, and holds the redacted value when a redaction
53
+ * rule matches. Implementations must record the value exactly when it is
52
54
  * present (`'value' in outcome` — a captured `undefined` return is
53
55
  * delivered as a present `value: undefined`) and must not record any
54
56
  * result when it is absent.
55
57
  */
56
- export type EventOutcome = {
58
+ export type OperationOutcome = {
57
59
  type: 'success';
58
60
  value?: unknown;
59
61
  } | {
@@ -99,53 +101,128 @@ export interface MethodCallContext {
99
101
  /**
100
102
  * The name of the method that is being called.
101
103
  */
102
- functionName: string;
104
+ methodName: string;
103
105
  /**
104
106
  * The arguments to report for this call, passed by reference;
105
107
  * implementations must not mutate them.
106
108
  *
107
- * Whether they are present is decided by {@link instrument}, not by the
108
- * implementation: absent unless argument capture is enabled in the
109
- * InstrumentOptions, and already redacted when a redaction rule matches.
110
- * Implementations must record the arguments exactly when present and
111
- * must not record any arguments when absent.
109
+ * Whether they are present is decided by {@link ServiceInstrumentation.instrument},
110
+ * not by the implementation: absent unless argument capture is enabled in
111
+ * the InstrumentOptions, and already redacted when a redaction rule
112
+ * matches. Implementations must record the arguments exactly when present
113
+ * and must not record any arguments when absent.
112
114
  */
113
115
  args?: readonly unknown[];
114
116
  }
115
117
  /**
116
- * Interface for instrumenting services wrapped by {@link instrument}.
117
- * Implement this interface to observe lifecycle events and method calls
118
- * of the instrumented services.
118
+ * What runtime values are delivered to the instrumentation, and how they
119
+ * are scrubbed first. Nothing is captured by default: values may be large
120
+ * or contain secrets, and they end up wherever the instrumentation
121
+ * exports them.
122
+ */
123
+ export interface CaptureOptions {
124
+ /**
125
+ * Deliver method arguments to the instrumentation (as
126
+ * MethodCallContext.args). When off, the instrumentation never sees the
127
+ * arguments at all.
128
+ */
129
+ arguments?: boolean;
130
+ /**
131
+ * Deliver method call return / resolved values to the instrumentation
132
+ * (as the success outcome's `value`). When off, the instrumentation
133
+ * never sees the values at all. Initialize and dispose outcomes never
134
+ * carry a value: the service instance is not useful information to
135
+ * report.
136
+ */
137
+ results?: boolean;
138
+ /**
139
+ * Per-service redaction applied to whatever the capture flags let
140
+ * through: matched arguments and success values are blanked (or run
141
+ * through the rule's custom mask) before the instrumentation sees them.
142
+ * The capture flags are the primary gate — when capture is off there is
143
+ * nothing to redact, and rules cannot re-enable delivery.
144
+ */
145
+ redact?: readonly RedactionRule<any>[];
146
+ }
147
+ /**
148
+ * Configuration for {@link ServiceInstrumentation.instrument}. Capture
149
+ * policy lives here, not in the ServiceInstrumentation subclasses: what a
150
+ * subclass receives is exactly what it is allowed to record, so no
151
+ * subclass carries its own capture flags or redaction logic.
152
+ */
153
+ export interface InstrumentOptions {
154
+ /**
155
+ * What runtime values (arguments, results) reach the instrumentation,
156
+ * and the redaction applied to them first. Nothing is captured when
157
+ * omitted.
158
+ */
159
+ capture?: CaptureOptions;
160
+ }
161
+ /**
162
+ * Base class for instrumenting services. Extend it and override the hooks
163
+ * to observe lifecycle events and method calls of the services wrapped by
164
+ * {@link ServiceInstrumentation.instrument}.
119
165
  *
120
- * Instrumentation is strictly observational: implementations see every
121
- * operation but must never alter it — see the EventSpan contract.
166
+ * Instrumentation is strictly observational: subclasses see every
167
+ * operation but must never alter it — see the OperationSpan contract.
122
168
  *
123
- * Each method is invoked when the corresponding operation starts and may
124
- * return an EventSpan that is notified when that operation finishes.
169
+ * Each hook is invoked when the corresponding operation starts and may
170
+ * return an OperationSpan that is notified when that operation finishes.
125
171
  * Returning nothing opts out of completion tracking for that call.
126
172
  */
127
- export interface ServiceInstrumentation {
173
+ export declare class ServiceInstrumentation {
128
174
  /**
129
175
  * Invoked at the start of the initialization process for a specific service.
130
176
  *
131
177
  * @param context - Context of the initialization, including the service key.
132
- * @return An EventSpan notified when initialization finishes, or void.
178
+ * @return An OperationSpan notified when initialization finishes, or void.
133
179
  */
134
- onInitialize?(context: InitializeContext): EventSpan | void;
180
+ onInitialize?(context: InitializeContext): OperationSpan | void;
135
181
  /**
136
182
  * Invoked when the disposal process for a service starts.
137
183
  *
138
184
  * @param context - Context of the disposal, including the service key.
139
- * @return An EventSpan notified when disposal finishes, or void.
185
+ * @return An OperationSpan notified when disposal finishes, or void.
140
186
  */
141
- onDispose?(context: DisposeContext): EventSpan | void;
187
+ onDispose?(context: DisposeContext): OperationSpan | void;
142
188
  /**
143
189
  * Invoked when a method call starts on a service instance.
144
190
  *
145
191
  * @param context - Context of the invocation, including the service key,
146
192
  * the method name, and its arguments.
147
- * @return An EventSpan notified when the call finishes, or void.
193
+ * @return An OperationSpan notified when the call finishes, or void.
194
+ */
195
+ onMethodCall?(context: MethodCallContext): OperationSpan | void;
196
+ /**
197
+ * Wraps service factories so this instrumentation is notified when a
198
+ * service is initialized or disposed and when a method is called on a
199
+ * service instance. Service instances are wrapped in a Proxy to observe
200
+ * method calls, and errors are rethrown after being reported, so
201
+ * behavior is otherwise unchanged.
202
+ *
203
+ * ServiceModule entries are flattened into their factories, so an
204
+ * already-built module can be instrumented as a whole. Compose the
205
+ * result with `ServiceModule.from`:
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * const module = ServiceModule.from(
210
+ * otel.instrument([db, cache, api], {
211
+ * capture: {
212
+ * arguments: true,
213
+ * results: true,
214
+ * redact: [redactionRule(VaultKey).redact('getSecret').build()],
215
+ * },
216
+ * }),
217
+ * );
218
+ * ```
219
+ *
220
+ * @param entries - An array of ServiceModule or factory instances to wrap.
221
+ * @param options - The capture and redaction policy applied before
222
+ * anything reaches this instrumentation.
223
+ * @return The wrapped factories, ready to be passed to ServiceModule.from.
148
224
  */
149
- onMethodCall?(context: MethodCallContext): EventSpan | void;
225
+ instrument(entries: (ServiceModule | GenericFactory)[], options?: InstrumentOptions): GenericFactory[];
150
226
  }
227
+ export {};
151
228
  //# sourceMappingURL=serviceInstrumentation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceInstrumentation.d.ts","sourceRoot":"","sources":["../src/serviceInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;;;;;;;;;OAaG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;IAEtB;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAEvC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAExB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IAEpB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,CAAA;IAE3D;;;;;OAKG;IACH,SAAS,CAAC,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAAA;IAErD;;;;;;OAMG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,CAAA;CAC5D"}
1
+ {"version":3,"file":"serviceInstrumentation.d.ts","sourceRoot":"","sources":["../src/serviceInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,KAAK,cAAc,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;AAEzE;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;OAaG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;IAEtB;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAEvC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IAExB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;IAElB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,SAAS,aAAa,CAAC,GAAG,CAAC,EAAE,CAAA;CACvC;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAsB;IACjC;;;;;OAKG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,aAAa,GAAG,IAAI;IAE/D;;;;;OAKG;IACH,SAAS,CAAC,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI;IAEzD;;;;;;OAMG;IACH,YAAY,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,aAAa,GAAG,IAAI;IAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,UAAU,CACR,OAAO,EAAE,CAAC,aAAa,GAAG,cAAc,CAAC,EAAE,EAC3C,OAAO,GAAE,iBAAsB,GAC9B,cAAc,EAAE;CAKpB"}
@@ -1,3 +1,219 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ServiceInstrumentation = void 0;
13
+ const core_1 = require("@composed-di/core");
14
+ /**
15
+ * Base class for instrumenting services. Extend it and override the hooks
16
+ * to observe lifecycle events and method calls of the services wrapped by
17
+ * {@link ServiceInstrumentation.instrument}.
18
+ *
19
+ * Instrumentation is strictly observational: subclasses see every
20
+ * operation but must never alter it — see the OperationSpan contract.
21
+ *
22
+ * Each hook is invoked when the corresponding operation starts and may
23
+ * return an OperationSpan that is notified when that operation finishes.
24
+ * Returning nothing opts out of completion tracking for that call.
25
+ */
26
+ class ServiceInstrumentation {
27
+ /**
28
+ * Wraps service factories so this instrumentation is notified when a
29
+ * service is initialized or disposed and when a method is called on a
30
+ * service instance. Service instances are wrapped in a Proxy to observe
31
+ * method calls, and errors are rethrown after being reported, so
32
+ * behavior is otherwise unchanged.
33
+ *
34
+ * ServiceModule entries are flattened into their factories, so an
35
+ * already-built module can be instrumented as a whole. Compose the
36
+ * result with `ServiceModule.from`:
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const module = ServiceModule.from(
41
+ * otel.instrument([db, cache, api], {
42
+ * capture: {
43
+ * arguments: true,
44
+ * results: true,
45
+ * redact: [redactionRule(VaultKey).redact('getSecret').build()],
46
+ * },
47
+ * }),
48
+ * );
49
+ * ```
50
+ *
51
+ * @param entries - An array of ServiceModule or factory instances to wrap.
52
+ * @param options - The capture and redaction policy applied before
53
+ * anything reaches this instrumentation.
54
+ * @return The wrapped factories, ready to be passed to ServiceModule.from.
55
+ */
56
+ instrument(entries, options = {}) {
57
+ return entries
58
+ .flatMap((e) => (e instanceof core_1.ServiceModule ? e.factories : [e]))
59
+ .map((factory) => instrumentedServiceFactory(this, options, factory));
60
+ }
61
+ }
62
+ exports.ServiceInstrumentation = ServiceInstrumentation;
63
+ function buildCapturePolicy(options, key) {
64
+ var _a, _b, _c;
65
+ const capture = options.capture;
66
+ const rule = (_a = capture === null || capture === void 0 ? void 0 : capture.redact) === null || _a === void 0 ? void 0 : _a.find((r) => r.key === key);
67
+ const captureArguments = (_b = capture === null || capture === void 0 ? void 0 : capture.arguments) !== null && _b !== void 0 ? _b : false;
68
+ const captureResults = (_c = capture === null || capture === void 0 ? void 0 : capture.results) !== null && _c !== void 0 ? _c : false;
69
+ return {
70
+ args: (methodName, args) => captureArguments
71
+ ? rule
72
+ ? rule.maskArgs(methodName, args)
73
+ : args
74
+ : undefined,
75
+ result: (methodName, value) => captureResults
76
+ ? { value: rule ? rule.maskResult(methodName, value) : value }
77
+ : undefined,
78
+ };
79
+ }
80
+ /**
81
+ * Wraps a given service factory with instrumentation to notify a
82
+ * ServiceInstrumentation of lifecycle events and method calls.
83
+ *
84
+ * For each of initialize, dispose, and method calls, the instrumentation
85
+ * is invoked at the start of the operation and may return an OperationSpan
86
+ * whose `end` is called with the outcome when the operation finishes.
87
+ * Errors are rethrown after being reported.
88
+ *
89
+ * @param instrumentation The instrumentation to notify.
90
+ * @param options The capture policy.
91
+ * @param delegate The original service factory to be instrumented.
92
+ * @return A new service factory that provides the same dependencies but includes event notification logic.
93
+ */
94
+ function instrumentedServiceFactory(instrumentation, options, delegate) {
95
+ const key = delegate.provides;
96
+ const capturePolicy = buildCapturePolicy(options, key);
97
+ return core_1.ServiceFactory.singleton({
98
+ scope: delegate.scope,
99
+ provides: delegate.provides,
100
+ dependsOn: delegate.dependsOn,
101
+ initialize: (...args) => __awaiter(this, void 0, void 0, function* () {
102
+ var _a;
103
+ const span = (_a = instrumentation.onInitialize) === null || _a === void 0 ? void 0 : _a.call(instrumentation, { key });
104
+ if (!span) {
105
+ return delegate.initialize(...args);
106
+ }
107
+ try {
108
+ const instance = yield span.run(() => delegate.initialize(...args));
109
+ span.end({ type: 'success' });
110
+ return observeMethodCalls(instance, instrumentation, key, capturePolicy);
111
+ }
112
+ catch (error) {
113
+ span.end({ type: 'failure', error });
114
+ throw error;
115
+ }
116
+ }),
117
+ dispose: () => {
118
+ var _a;
119
+ const dispose = delegate.dispose;
120
+ if (dispose) {
121
+ const span = (_a = instrumentation.onDispose) === null || _a === void 0 ? void 0 : _a.call(instrumentation, { key });
122
+ if (span) {
123
+ try {
124
+ span.run(dispose);
125
+ span.end({ type: 'success' });
126
+ }
127
+ catch (error) {
128
+ span.end({ type: 'failure', error });
129
+ throw error;
130
+ }
131
+ }
132
+ else {
133
+ dispose();
134
+ }
135
+ }
136
+ },
137
+ });
138
+ }
139
+ /**
140
+ * Wraps an object with a Proxy to notify the instrumentation of method calls.
141
+ *
142
+ * Methods returning a promise report their outcome when the promise
143
+ * settles, not when the method returns.
144
+ *
145
+ * @param thing The object whose method calls need to be observed.
146
+ * @param instrumentation The instrumentation notified of method call events.
147
+ * @param key The service key used to identify the service in events.
148
+ * @param capturePolicy The capture policy deciding what argument and result
149
+ * values (if any) are delivered with each event.
150
+ * @return A Proxy wrapping the input object, with all method calls being reported.
151
+ */
152
+ function observeMethodCalls(thing, instrumentation, key, capturePolicy) {
153
+ if (typeof thing !== 'object' || thing === null) {
154
+ return thing;
155
+ }
156
+ const className = classNameOf(thing);
157
+ return new Proxy(thing, {
158
+ get(target, prop) {
159
+ const value = Reflect.get(target, prop);
160
+ if (typeof value === 'function' && typeof prop === 'string') {
161
+ return (...args) => {
162
+ var _a;
163
+ const span = (_a = instrumentation.onMethodCall) === null || _a === void 0 ? void 0 : _a.call(instrumentation, {
164
+ key,
165
+ className,
166
+ methodName: prop,
167
+ args: capturePolicy.args(prop, args),
168
+ });
169
+ try {
170
+ const result = invokeWithin(span, () => value.apply(target, args));
171
+ if (result instanceof Promise) {
172
+ return result.then((resolved) => {
173
+ span === null || span === void 0 ? void 0 : span.end(Object.assign({ type: 'success' }, capturePolicy.result(prop, resolved)));
174
+ return resolved;
175
+ }, (error) => {
176
+ span === null || span === void 0 ? void 0 : span.end({ type: 'failure', error });
177
+ throw error;
178
+ });
179
+ }
180
+ span === null || span === void 0 ? void 0 : span.end(Object.assign({ type: 'success' }, capturePolicy.result(prop, result)));
181
+ return result;
182
+ }
183
+ catch (error) {
184
+ span === null || span === void 0 ? void 0 : span.end({ type: 'failure', error });
185
+ throw error;
186
+ }
187
+ };
188
+ }
189
+ return value;
190
+ },
191
+ });
192
+ }
193
+ /**
194
+ * Invokes an operation through the OperationSpan's `run` wrapper when the
195
+ * instrumentation returned a span, so it can establish ambient state
196
+ * (tracing context) around the operation; invokes the operation directly
197
+ * otherwise.
198
+ *
199
+ * @param span The OperationSpan returned by the instrumentation, if any.
200
+ * @param fn The thunk performing the operation.
201
+ * @returns The value returned by `fn`.
202
+ */
203
+ function invokeWithin(span, fn) {
204
+ return span ? span.run(fn) : fn();
205
+ }
206
+ /**
207
+ * Resolves the class name of a service instance, or undefined for values
208
+ * that are not instances of a named class (plain object literals,
209
+ * null-prototype objects).
210
+ *
211
+ * @param thing The service instance to inspect.
212
+ * @returns The constructor name, or undefined when there is none to report.
213
+ */
214
+ function classNameOf(thing) {
215
+ var _a;
216
+ const name = (_a = thing.constructor) === null || _a === void 0 ? void 0 : _a.name;
217
+ return name && name !== 'Object' ? name : undefined;
218
+ }
3
219
  //# sourceMappingURL=serviceInstrumentation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceInstrumentation.js","sourceRoot":"","sources":["../src/serviceInstrumentation.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"serviceInstrumentation.js","sourceRoot":"","sources":["../src/serviceInstrumentation.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@composed-di/instrumentation-core",
3
- "version": "0.6.0-alpha",
3
+ "version": "0.7.0-alpha",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "dependency injection",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@composed-di/core": "^0.6.0-alpha"
38
+ "@composed-di/core": "^0.7.0-alpha"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc --build",
package/src/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export * from './serviceInstrumentation'
2
- export * from './instrument'
3
2
  export * from './redaction'
package/src/redaction.ts CHANGED
@@ -39,7 +39,7 @@ interface PropertyOverride {
39
39
  /**
40
40
  * Marks a service — or specific properties of it — as sensitive, so the
41
41
  * values flowing through its events are redacted. Passed to
42
- * {@link instrument} via InstrumentOptions.redactionRules and applied
42
+ * {@link ServiceInstrumentation.instrument} via the capture options' `redact` list and applied
43
43
  * centrally, after the capture flags: values a rule matches are scrubbed
44
44
  * before the instrumentation ever sees them, and when capture is off
45
45
  * there is nothing to redact. Returned by
@@ -52,17 +52,17 @@ export interface RedactionRule<T> {
52
52
  readonly key: ServiceKey<T>
53
53
 
54
54
  /**
55
- * The args to report for a call to `functionName`: unchanged if not
55
+ * The args to report for a call to `methodName`: unchanged if not
56
56
  * redacted, otherwise blanked or run through a custom `maskArgs`.
57
57
  */
58
- maskArgs(functionName: string, args: readonly unknown[]): readonly unknown[]
58
+ maskArgs(methodName: string, args: readonly unknown[]): readonly unknown[]
59
59
 
60
60
  /**
61
61
  * The value to report for a method call's success outcome: unchanged
62
62
  * if not redacted, otherwise blanked or run through a custom
63
63
  * `maskResult`.
64
64
  */
65
- maskResult(functionName: string, result: unknown): unknown
65
+ maskResult(methodName: string, result: unknown): unknown
66
66
  }
67
67
 
68
68
  /**
@@ -140,8 +140,8 @@ export class RedactionRuleBuilder<T> {
140
140
 
141
141
  return {
142
142
  key: this.key,
143
- maskArgs(functionName, args) {
144
- const override = overrides[functionName]
143
+ maskArgs(methodName, args) {
144
+ const override = overrides[methodName]
145
145
  const redacted = override ? override.redacted : redactAllFlag
146
146
  if (!redacted) {
147
147
  return args
@@ -154,8 +154,8 @@ export class RedactionRuleBuilder<T> {
154
154
  // delegate still sees the call's arity.
155
155
  args.map(() => REDACTED_VALUE)
156
156
  },
157
- maskResult(functionName, result) {
158
- const override = overrides[functionName]
157
+ maskResult(methodName, result) {
158
+ const override = overrides[methodName]
159
159
  const redacted = override ? override.redacted : redactAllFlag
160
160
  if (!redacted) {
161
161
  return result
@@ -1,4 +1,7 @@
1
- import type { ServiceKey } from '@composed-di/core'
1
+ import { ServiceFactory, ServiceKey, ServiceModule } from '@composed-di/core'
2
+ import type { RedactionRule } from './redaction'
3
+
4
+ type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]>
2
5
 
3
6
  /**
4
7
  * A handle representing a single in-flight operation (initialization,
@@ -10,7 +13,7 @@ import type { ServiceKey } from '@composed-di/core'
10
13
  * handle, a correlation id) without any bookkeeping to pair concurrent
11
14
  * start/finish events.
12
15
  */
13
- export interface EventSpan {
16
+ export interface OperationSpan {
14
17
  /**
15
18
  * Wrapper around the operation itself. The instrumented factory invokes
16
19
  * the operation as `run(() => operation())`, so the instrumentation can
@@ -39,24 +42,24 @@ export interface EventSpan {
39
42
  * @param outcome - How the operation finished, and its value or error.
40
43
  * @return void
41
44
  */
42
- end(outcome: EventOutcome): void
45
+ end(outcome: OperationOutcome): void
43
46
  }
44
47
 
45
48
  /**
46
- * How an operation finished, delivered to EventSpan.end: `success` may
49
+ * How an operation finished, delivered to OperationSpan.end: `success` may
47
50
  * carry the return or resolved value of a method call (initialize and
48
51
  * dispose outcomes never carry one); `failure` carries the error that was
49
52
  * thrown or rejected.
50
53
  *
51
- * Whether `value` is present is decided by {@link instrument}, not by the
52
- * implementation: it is absent unless result capture is enabled in the
53
- * InstrumentOptions, and holds the redacted value when a redaction rule
54
- * matches. Implementations must record the value exactly when it is
54
+ * Whether `value` is present is decided by {@link ServiceInstrumentation.instrument},
55
+ * not by the implementation: it is absent unless result capture is enabled
56
+ * in the InstrumentOptions, and holds the redacted value when a redaction
57
+ * rule matches. Implementations must record the value exactly when it is
55
58
  * present (`'value' in outcome` — a captured `undefined` return is
56
59
  * delivered as a present `value: undefined`) and must not record any
57
60
  * result when it is absent.
58
61
  */
59
- export type EventOutcome =
62
+ export type OperationOutcome =
60
63
  | { type: 'success'; value?: unknown }
61
64
  | { type: 'failure'; error: unknown }
62
65
 
@@ -103,56 +106,349 @@ export interface MethodCallContext {
103
106
  /**
104
107
  * The name of the method that is being called.
105
108
  */
106
- functionName: string
109
+ methodName: string
107
110
 
108
111
  /**
109
112
  * The arguments to report for this call, passed by reference;
110
113
  * implementations must not mutate them.
111
114
  *
112
- * Whether they are present is decided by {@link instrument}, not by the
113
- * implementation: absent unless argument capture is enabled in the
114
- * InstrumentOptions, and already redacted when a redaction rule matches.
115
- * Implementations must record the arguments exactly when present and
116
- * must not record any arguments when absent.
115
+ * Whether they are present is decided by {@link ServiceInstrumentation.instrument},
116
+ * not by the implementation: absent unless argument capture is enabled in
117
+ * the InstrumentOptions, and already redacted when a redaction rule
118
+ * matches. Implementations must record the arguments exactly when present
119
+ * and must not record any arguments when absent.
117
120
  */
118
121
  args?: readonly unknown[]
119
122
  }
120
123
 
121
124
  /**
122
- * Interface for instrumenting services wrapped by {@link instrument}.
123
- * Implement this interface to observe lifecycle events and method calls
124
- * of the instrumented services.
125
+ * What runtime values are delivered to the instrumentation, and how they
126
+ * are scrubbed first. Nothing is captured by default: values may be large
127
+ * or contain secrets, and they end up wherever the instrumentation
128
+ * exports them.
129
+ */
130
+ export interface CaptureOptions {
131
+ /**
132
+ * Deliver method arguments to the instrumentation (as
133
+ * MethodCallContext.args). When off, the instrumentation never sees the
134
+ * arguments at all.
135
+ */
136
+ arguments?: boolean
137
+
138
+ /**
139
+ * Deliver method call return / resolved values to the instrumentation
140
+ * (as the success outcome's `value`). When off, the instrumentation
141
+ * never sees the values at all. Initialize and dispose outcomes never
142
+ * carry a value: the service instance is not useful information to
143
+ * report.
144
+ */
145
+ results?: boolean
146
+
147
+ /**
148
+ * Per-service redaction applied to whatever the capture flags let
149
+ * through: matched arguments and success values are blanked (or run
150
+ * through the rule's custom mask) before the instrumentation sees them.
151
+ * The capture flags are the primary gate — when capture is off there is
152
+ * nothing to redact, and rules cannot re-enable delivery.
153
+ */
154
+ redact?: readonly RedactionRule<any>[]
155
+ }
156
+
157
+ /**
158
+ * Configuration for {@link ServiceInstrumentation.instrument}. Capture
159
+ * policy lives here, not in the ServiceInstrumentation subclasses: what a
160
+ * subclass receives is exactly what it is allowed to record, so no
161
+ * subclass carries its own capture flags or redaction logic.
162
+ */
163
+ export interface InstrumentOptions {
164
+ /**
165
+ * What runtime values (arguments, results) reach the instrumentation,
166
+ * and the redaction applied to them first. Nothing is captured when
167
+ * omitted.
168
+ */
169
+ capture?: CaptureOptions
170
+ }
171
+
172
+ /**
173
+ * Base class for instrumenting services. Extend it and override the hooks
174
+ * to observe lifecycle events and method calls of the services wrapped by
175
+ * {@link ServiceInstrumentation.instrument}.
125
176
  *
126
- * Instrumentation is strictly observational: implementations see every
127
- * operation but must never alter it — see the EventSpan contract.
177
+ * Instrumentation is strictly observational: subclasses see every
178
+ * operation but must never alter it — see the OperationSpan contract.
128
179
  *
129
- * Each method is invoked when the corresponding operation starts and may
130
- * return an EventSpan that is notified when that operation finishes.
180
+ * Each hook is invoked when the corresponding operation starts and may
181
+ * return an OperationSpan that is notified when that operation finishes.
131
182
  * Returning nothing opts out of completion tracking for that call.
132
183
  */
133
- export interface ServiceInstrumentation {
184
+ export class ServiceInstrumentation {
134
185
  /**
135
186
  * Invoked at the start of the initialization process for a specific service.
136
187
  *
137
188
  * @param context - Context of the initialization, including the service key.
138
- * @return An EventSpan notified when initialization finishes, or void.
189
+ * @return An OperationSpan notified when initialization finishes, or void.
139
190
  */
140
- onInitialize?(context: InitializeContext): EventSpan | void
191
+ onInitialize?(context: InitializeContext): OperationSpan | void
141
192
 
142
193
  /**
143
194
  * Invoked when the disposal process for a service starts.
144
195
  *
145
196
  * @param context - Context of the disposal, including the service key.
146
- * @return An EventSpan notified when disposal finishes, or void.
197
+ * @return An OperationSpan notified when disposal finishes, or void.
147
198
  */
148
- onDispose?(context: DisposeContext): EventSpan | void
199
+ onDispose?(context: DisposeContext): OperationSpan | void
149
200
 
150
201
  /**
151
202
  * Invoked when a method call starts on a service instance.
152
203
  *
153
204
  * @param context - Context of the invocation, including the service key,
154
205
  * the method name, and its arguments.
155
- * @return An EventSpan notified when the call finishes, or void.
206
+ * @return An OperationSpan notified when the call finishes, or void.
207
+ */
208
+ onMethodCall?(context: MethodCallContext): OperationSpan | void
209
+
210
+ /**
211
+ * Wraps service factories so this instrumentation is notified when a
212
+ * service is initialized or disposed and when a method is called on a
213
+ * service instance. Service instances are wrapped in a Proxy to observe
214
+ * method calls, and errors are rethrown after being reported, so
215
+ * behavior is otherwise unchanged.
216
+ *
217
+ * ServiceModule entries are flattened into their factories, so an
218
+ * already-built module can be instrumented as a whole. Compose the
219
+ * result with `ServiceModule.from`:
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * const module = ServiceModule.from(
224
+ * otel.instrument([db, cache, api], {
225
+ * capture: {
226
+ * arguments: true,
227
+ * results: true,
228
+ * redact: [redactionRule(VaultKey).redact('getSecret').build()],
229
+ * },
230
+ * }),
231
+ * );
232
+ * ```
233
+ *
234
+ * @param entries - An array of ServiceModule or factory instances to wrap.
235
+ * @param options - The capture and redaction policy applied before
236
+ * anything reaches this instrumentation.
237
+ * @return The wrapped factories, ready to be passed to ServiceModule.from.
238
+ */
239
+ instrument(
240
+ entries: (ServiceModule | GenericFactory)[],
241
+ options: InstrumentOptions = {},
242
+ ): GenericFactory[] {
243
+ return entries
244
+ .flatMap((e) => (e instanceof ServiceModule ? e.factories : [e]))
245
+ .map((factory) => instrumentedServiceFactory(this, options, factory))
246
+ }
247
+ }
248
+
249
+ /**
250
+ * The capture policy of one instrumented factory: resolves what to report
251
+ * for a call's arguments and result, combining the capture flags with the
252
+ * factory's redaction rule (if any). This is the single place that
253
+ * decides visibility — instrumentations record what they receive and
254
+ * nothing else.
255
+ */
256
+ interface CapturePolicy {
257
+ /**
258
+ * The arguments to deliver in MethodCallContext, or undefined when
259
+ * argument capture is off.
156
260
  */
157
- onMethodCall?(context: MethodCallContext): EventSpan | void
261
+ args(
262
+ methodName: string,
263
+ args: readonly unknown[],
264
+ ): readonly unknown[] | undefined
265
+
266
+ /**
267
+ * The result to deliver with a method call's success outcome: the
268
+ * (possibly redacted) value, wrapped so spreading it into the outcome
269
+ * preserves the value-presence contract — a captured `undefined` return
270
+ * yields `{ value: undefined }` (key present), while capture off yields
271
+ * undefined (no key at all).
272
+ */
273
+ result(methodName: string, value: unknown): { value: unknown } | undefined
274
+ }
275
+
276
+ function buildCapturePolicy(
277
+ options: InstrumentOptions,
278
+ key: ServiceKey<unknown>,
279
+ ): CapturePolicy {
280
+ const capture = options.capture
281
+ const rule = capture?.redact?.find((r) => r.key === key)
282
+ const captureArguments = capture?.arguments ?? false
283
+ const captureResults = capture?.results ?? false
284
+
285
+ return {
286
+ args: (methodName, args) =>
287
+ captureArguments
288
+ ? rule
289
+ ? rule.maskArgs(methodName, args)
290
+ : args
291
+ : undefined,
292
+ result: (methodName, value) =>
293
+ captureResults
294
+ ? { value: rule ? rule.maskResult(methodName, value) : value }
295
+ : undefined,
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Wraps a given service factory with instrumentation to notify a
301
+ * ServiceInstrumentation of lifecycle events and method calls.
302
+ *
303
+ * For each of initialize, dispose, and method calls, the instrumentation
304
+ * is invoked at the start of the operation and may return an OperationSpan
305
+ * whose `end` is called with the outcome when the operation finishes.
306
+ * Errors are rethrown after being reported.
307
+ *
308
+ * @param instrumentation The instrumentation to notify.
309
+ * @param options The capture policy.
310
+ * @param delegate The original service factory to be instrumented.
311
+ * @return A new service factory that provides the same dependencies but includes event notification logic.
312
+ */
313
+ function instrumentedServiceFactory<T, D extends readonly ServiceKey<any>[]>(
314
+ instrumentation: ServiceInstrumentation,
315
+ options: InstrumentOptions,
316
+ delegate: ServiceFactory<any, D>,
317
+ ): ServiceFactory<T, D> {
318
+ const key = delegate.provides
319
+ const capturePolicy = buildCapturePolicy(options, key)
320
+
321
+ return ServiceFactory.singleton({
322
+ scope: delegate.scope,
323
+ provides: delegate.provides,
324
+ dependsOn: delegate.dependsOn,
325
+ initialize: async (...args) => {
326
+ const span = instrumentation.onInitialize?.({ key })
327
+ if (!span) {
328
+ return delegate.initialize(...args)
329
+ }
330
+
331
+ try {
332
+ const instance = await span.run(() => delegate.initialize(...args))
333
+ span.end({ type: 'success' })
334
+ return observeMethodCalls(instance, instrumentation, key, capturePolicy)
335
+ } catch (error) {
336
+ span.end({ type: 'failure', error })
337
+ throw error
338
+ }
339
+ },
340
+ dispose: () => {
341
+ const dispose = delegate.dispose
342
+ if (dispose) {
343
+ const span = instrumentation.onDispose?.({ key })
344
+ if (span) {
345
+ try {
346
+ span.run(dispose)
347
+ span.end({ type: 'success' })
348
+ } catch (error) {
349
+ span.end({ type: 'failure', error })
350
+ throw error
351
+ }
352
+ } else {
353
+ dispose()
354
+ }
355
+ }
356
+ },
357
+ })
358
+ }
359
+
360
+ /**
361
+ * Wraps an object with a Proxy to notify the instrumentation of method calls.
362
+ *
363
+ * Methods returning a promise report their outcome when the promise
364
+ * settles, not when the method returns.
365
+ *
366
+ * @param thing The object whose method calls need to be observed.
367
+ * @param instrumentation The instrumentation notified of method call events.
368
+ * @param key The service key used to identify the service in events.
369
+ * @param capturePolicy The capture policy deciding what argument and result
370
+ * values (if any) are delivered with each event.
371
+ * @return A Proxy wrapping the input object, with all method calls being reported.
372
+ */
373
+ function observeMethodCalls(
374
+ thing: any,
375
+ instrumentation: ServiceInstrumentation,
376
+ key: ServiceKey<unknown>,
377
+ capturePolicy: CapturePolicy,
378
+ ): any {
379
+ if (typeof thing !== 'object' || thing === null) {
380
+ return thing
381
+ }
382
+
383
+ const className = classNameOf(thing)
384
+
385
+ return new Proxy(thing, {
386
+ get(target, prop) {
387
+ const value = Reflect.get(target, prop)
388
+ if (typeof value === 'function' && typeof prop === 'string') {
389
+ return (...args: unknown[]) => {
390
+ const span = instrumentation.onMethodCall?.({
391
+ key,
392
+ className,
393
+ methodName: prop,
394
+ args: capturePolicy.args(prop, args),
395
+ })
396
+ try {
397
+ const result = invokeWithin(span, () => value.apply(target, args))
398
+ if (result instanceof Promise) {
399
+ return result.then(
400
+ (resolved) => {
401
+ span?.end({
402
+ type: 'success',
403
+ ...capturePolicy.result(prop, resolved),
404
+ })
405
+ return resolved
406
+ },
407
+ (error) => {
408
+ span?.end({ type: 'failure', error })
409
+ throw error
410
+ },
411
+ )
412
+ }
413
+ span?.end({
414
+ type: 'success',
415
+ ...capturePolicy.result(prop, result),
416
+ })
417
+ return result
418
+ } catch (error) {
419
+ span?.end({ type: 'failure', error })
420
+ throw error
421
+ }
422
+ }
423
+ }
424
+ return value
425
+ },
426
+ })
427
+ }
428
+
429
+ /**
430
+ * Invokes an operation through the OperationSpan's `run` wrapper when the
431
+ * instrumentation returned a span, so it can establish ambient state
432
+ * (tracing context) around the operation; invokes the operation directly
433
+ * otherwise.
434
+ *
435
+ * @param span The OperationSpan returned by the instrumentation, if any.
436
+ * @param fn The thunk performing the operation.
437
+ * @returns The value returned by `fn`.
438
+ */
439
+ function invokeWithin<T>(span: OperationSpan | void, fn: () => T): T {
440
+ return span ? span.run(fn) : fn()
441
+ }
442
+
443
+ /**
444
+ * Resolves the class name of a service instance, or undefined for values
445
+ * that are not instances of a named class (plain object literals,
446
+ * null-prototype objects).
447
+ *
448
+ * @param thing The service instance to inspect.
449
+ * @returns The constructor name, or undefined when there is none to report.
450
+ */
451
+ function classNameOf(thing: object): string | undefined {
452
+ const name = thing.constructor?.name
453
+ return name && name !== 'Object' ? name : undefined
158
454
  }
package/src/instrument.ts DELETED
@@ -1,285 +0,0 @@
1
- import { ServiceFactory, ServiceKey, ServiceModule } from '@composed-di/core'
2
- import type {
3
- EventOutcome,
4
- EventSpan,
5
- ServiceInstrumentation,
6
- } from './serviceInstrumentation'
7
- import type { RedactionRule } from './redaction'
8
-
9
- type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]>
10
-
11
- /**
12
- * Configuration for {@link instrument}. Capture policy lives here, not in
13
- * the ServiceInstrumentation implementations: what an implementation
14
- * receives is exactly what it is allowed to record, so no implementation
15
- * carries its own capture flags or redaction logic.
16
- */
17
- export interface InstrumentOptions {
18
- /**
19
- * The instrumentation notified of service lifecycle events and method
20
- * calls.
21
- */
22
- instrumentation: ServiceInstrumentation
23
-
24
- /**
25
- * Deliver method arguments to the instrumentation (as
26
- * MethodCallContext.args). Off by default: arguments may be large or
27
- * contain secrets, and they end up wherever the instrumentation exports
28
- * them. When off, the instrumentation never sees the arguments at all.
29
- */
30
- captureArguments?: boolean
31
-
32
- /**
33
- * Deliver method call return / resolved values to the instrumentation
34
- * (as the success outcome's `value`). Off by default, for the same
35
- * reasons as `captureArguments`. When off, the instrumentation never
36
- * sees the values at all. Initialize and dispose outcomes never carry a
37
- * value: the service instance is not useful information to report.
38
- */
39
- captureResults?: boolean
40
-
41
- /**
42
- * Per-service redaction applied to whatever the capture flags let
43
- * through: matched arguments and success values are blanked (or run
44
- * through the rule's custom mask) before the instrumentation sees them.
45
- * The capture flags are the primary gate — when capture is off there is
46
- * nothing to redact, and rules cannot re-enable delivery.
47
- */
48
- redactionRules?: readonly RedactionRule<any>[]
49
- }
50
-
51
- /**
52
- * Wraps service factories with instrumentation, so the given
53
- * ServiceInstrumentation is notified when a service is initialized or
54
- * disposed and when a method is called on a service instance, and may
55
- * return an EventSpan per operation to observe its completion. Service
56
- * instances are wrapped in a Proxy to observe method calls, and errors
57
- * are rethrown after being reported, so behavior is otherwise unchanged.
58
- *
59
- * ServiceModule entries are flattened into their factories, so an
60
- * already-built module can be instrumented as a whole. Compose the result
61
- * with `ServiceModule.from`:
62
- *
63
- * @example
64
- * ```ts
65
- * const module = ServiceModule.from(
66
- * instrument([db, cache, api], {
67
- * instrumentation: otel,
68
- * captureArguments: true,
69
- * captureResults: true,
70
- * redactionRules: [redactionRule(VaultKey).redact('getSecret').build()],
71
- * }),
72
- * );
73
- * ```
74
- *
75
- * @param entries - An array of ServiceModule or factory instances to wrap.
76
- * @param options - The instrumentation to notify, plus the capture and
77
- * redaction policy applied before anything reaches it.
78
- * @return The wrapped factories, ready to be passed to ServiceModule.from.
79
- */
80
- export function instrument(
81
- entries: (ServiceModule | GenericFactory)[],
82
- options: InstrumentOptions,
83
- ): GenericFactory[] {
84
- return entries
85
- .flatMap((e) => (e instanceof ServiceModule ? e.factories : [e]))
86
- .map((factory) => makeObservable(options, factory))
87
- }
88
-
89
- /**
90
- * The capture policy of one instrumented factory: resolves what to report
91
- * for a call's arguments and a success outcome, combining the capture
92
- * flags with the factory's redaction rule (if any). This is the single
93
- * place that decides visibility — instrumentations record what they
94
- * receive and nothing else.
95
- */
96
- interface Capture {
97
- /**
98
- * The arguments to deliver in MethodCallContext, or undefined when
99
- * argument capture is off.
100
- */
101
- args(
102
- functionName: string,
103
- args: readonly unknown[],
104
- ): readonly unknown[] | undefined
105
-
106
- /**
107
- * The success outcome to deliver to EventSpan.end for a method call:
108
- * carries the (possibly redacted) value when result capture is on, no
109
- * value otherwise.
110
- */
111
- success(functionName: string, value: unknown): EventOutcome
112
- }
113
-
114
- function makeCapture(
115
- options: InstrumentOptions,
116
- key: ServiceKey<unknown>,
117
- ): Capture {
118
- const rule = options.redactionRules?.find((r) => r.key === key)
119
- const captureArguments = options.captureArguments ?? false
120
- const captureResults = options.captureResults ?? false
121
-
122
- return {
123
- args: (functionName, args) =>
124
- captureArguments
125
- ? rule
126
- ? rule.maskArgs(functionName, args)
127
- : args
128
- : undefined,
129
- success: (functionName, value) =>
130
- captureResults
131
- ? {
132
- type: 'success',
133
- value: rule ? rule.maskResult(functionName, value) : value,
134
- }
135
- : { type: 'success' },
136
- }
137
- }
138
-
139
- /**
140
- * Wraps a given service factory with instrumentation to notify a
141
- * ServiceInstrumentation of lifecycle events and method calls.
142
- *
143
- * For each of initialize, dispose, and method calls, the instrumentation
144
- * is invoked at the start of the operation and may return an EventSpan
145
- * whose `end` is called with the outcome when the operation finishes.
146
- * Errors are rethrown after being reported.
147
- *
148
- * @param options The instrumentation to notify and the capture policy.
149
- * @param delegate The original service factory to be instrumented.
150
- * @return A new service factory that provides the same dependencies but includes event notification logic.
151
- */
152
- function makeObservable<T, D extends readonly ServiceKey<any>[]>(
153
- options: InstrumentOptions,
154
- delegate: ServiceFactory<any, D>,
155
- ): ServiceFactory<T, D> {
156
- const instrumentation = options.instrumentation
157
- const key = delegate.provides
158
- const capture = makeCapture(options, key)
159
-
160
- return ServiceFactory.singleton({
161
- scope: delegate.scope,
162
- provides: delegate.provides,
163
- dependsOn: delegate.dependsOn,
164
- initialize: async (...args) => {
165
- const span = instrumentation.onInitialize?.({ key })
166
- if (!span) {
167
- return delegate.initialize(...args)
168
- }
169
-
170
- try {
171
- const instance = await span.run(() => delegate.initialize(...args))
172
- span.end({ type: 'success' })
173
- return observeMethodCalls(instance, instrumentation, key, capture)
174
- } catch (error) {
175
- span.end({ type: 'failure', error })
176
- throw error
177
- }
178
- },
179
- dispose: () => {
180
- const dispose = delegate.dispose
181
- if (dispose) {
182
- const span = instrumentation.onDispose?.({ key })
183
- if (span) {
184
- try {
185
- span.run(dispose)
186
- span.end({ type: 'success' })
187
- } catch (error) {
188
- span.end({ type: 'failure', error })
189
- throw error
190
- }
191
- }
192
- }
193
- },
194
- })
195
- }
196
-
197
- /**
198
- * Wraps an object with a Proxy to notify the instrumentation of method calls.
199
- *
200
- * Methods returning a promise report their outcome when the promise
201
- * settles, not when the method returns.
202
- *
203
- * @param thing The object whose method calls need to be observed.
204
- * @param instrumentation The instrumentation notified of method call events.
205
- * @param key The service key used to identify the service in events.
206
- * @param capture The capture policy deciding what argument and result
207
- * values (if any) are delivered with each event.
208
- * @return A Proxy wrapping the input object, with all method calls being reported.
209
- */
210
- function observeMethodCalls(
211
- thing: any,
212
- instrumentation: ServiceInstrumentation,
213
- key: ServiceKey<unknown>,
214
- capture: Capture,
215
- ): any {
216
- if (typeof thing !== 'object' || thing === null) {
217
- return thing
218
- }
219
-
220
- const className = classNameOf(thing)
221
-
222
- return new Proxy(thing, {
223
- get(target, prop) {
224
- const value = Reflect.get(target, prop)
225
- if (typeof value === 'function' && typeof prop === 'string') {
226
- return (...args: unknown[]) => {
227
- const span = instrumentation.onMethodCall?.({
228
- key,
229
- className,
230
- functionName: prop,
231
- args: capture.args(prop, args),
232
- })
233
- try {
234
- const result = invokeWithin(span, () => value.apply(target, args))
235
- if (result instanceof Promise) {
236
- return result.then(
237
- (resolved) => {
238
- span?.end(capture.success(prop, resolved))
239
- return resolved
240
- },
241
- (error) => {
242
- span?.end({ type: 'failure', error })
243
- throw error
244
- },
245
- )
246
- }
247
- span?.end(capture.success(prop, result))
248
- return result
249
- } catch (error) {
250
- span?.end({ type: 'failure', error })
251
- throw error
252
- }
253
- }
254
- }
255
- return value
256
- },
257
- })
258
- }
259
-
260
- /**
261
- * Invokes an operation through the EventSpan's `run` wrapper when the
262
- * instrumentation returned a span, so it can establish ambient state
263
- * (tracing context) around the operation; invokes the operation directly
264
- * otherwise.
265
- *
266
- * @param span The EventSpan returned by the instrumentation, if any.
267
- * @param fn The thunk performing the operation.
268
- * @returns The value returned by `fn`.
269
- */
270
- function invokeWithin<T>(span: EventSpan | void, fn: () => T): T {
271
- return span ? span.run(fn) : fn()
272
- }
273
-
274
- /**
275
- * Resolves the class name of a service instance, or undefined for values
276
- * that are not instances of a named class (plain object literals,
277
- * null-prototype objects).
278
- *
279
- * @param thing The service instance to inspect.
280
- * @returns The constructor name, or undefined when there is none to report.
281
- */
282
- function classNameOf(thing: object): string | undefined {
283
- const name = thing.constructor?.name
284
- return name && name !== 'Object' ? name : undefined
285
- }