@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 +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/redaction.d.ts +4 -4
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +4 -4
- package/dist/redaction.js.map +1 -1
- package/dist/serviceInstrumentation.d.ts +106 -29
- package/dist/serviceInstrumentation.d.ts.map +1 -1
- package/dist/serviceInstrumentation.js +216 -0
- package/dist/serviceInstrumentation.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -1
- package/src/redaction.ts +8 -8
- package/src/serviceInstrumentation.ts +325 -29
- package/src/instrument.ts +0 -285
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAA;AACxC,cAAc,
|
|
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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2DAAwC;AACxC,8CAA2B"}
|
package/dist/redaction.d.ts
CHANGED
|
@@ -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
|
|
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 `
|
|
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(
|
|
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(
|
|
43
|
+
maskResult(methodName: string, result: unknown): unknown;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Fluent, single-key rule builder returned by {@link redactionRule}.
|
package/dist/redaction.d.ts.map
CHANGED
|
@@ -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,
|
|
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(
|
|
79
|
-
const override = overrides[
|
|
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(
|
|
93
|
-
const override = overrides[
|
|
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;
|
package/dist/redaction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":";;;AA0KA,sCAEC;AA1KD;;;GAGG;AACU,QAAA,cAAc,GAAG,YAAY,CAAA;AA6D1C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,oBAAoB;IAK/B,YAA6B,GAAkB;QAAlB,QAAG,GAAH,GAAG,CAAe;QAJvC,kBAAa,GAAG,KAAK,CAAA;QACrB,cAAS,GAAG,KAAK,CAAA;QACR,cAAS,GAAqC,EAAE,CAAA;IAEf,CAAC;IAEnD,yCAAyC;IACzC,SAAS;QACP,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAqC,IAAO,EAAE,IAAiB;QACnE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mBAAK,QAAQ,EAAE,IAAI,IAAK,IAAI,CAAE,CAAA;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAiC;QAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QAC5C,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK;QACH,gEAAgE;QAChE,qEAAqE;QACrE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,CAAC,GAAG,CAAC,IAAI,yDAAyD;gBACrF,4DAA4D,CAC/D,CAAA;QACH,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAA;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAA;QAEhC,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":";;;AA0KA,sCAEC;AA1KD;;;GAGG;AACU,QAAA,cAAc,GAAG,YAAY,CAAA;AA6D1C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,oBAAoB;IAK/B,YAA6B,GAAkB;QAAlB,QAAG,GAAH,GAAG,CAAe;QAJvC,kBAAa,GAAG,KAAK,CAAA;QACrB,cAAS,GAAG,KAAK,CAAA;QACR,cAAS,GAAqC,EAAE,CAAA;IAEf,CAAC;IAEnD,yCAAyC;IACzC,SAAS;QACP,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAqC,IAAO,EAAE,IAAiB;QACnE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,mBAAK,QAAQ,EAAE,IAAI,IAAK,IAAI,CAAE,CAAA;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAiC;QAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QAC5C,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK;QACH,gEAAgE;QAChE,qEAAqE;QACrE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,CAAC,GAAG,CAAC,IAAI,yDAAyD;gBACrF,4DAA4D,CAC/D,CAAA;QACH,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAA;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAA;QAEhC,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,CAAC,UAAU,EAAE,IAAI;gBACvB,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;gBACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAA;gBAC7D,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAO,IAAI,CAAA;gBACb,CAAC;gBACD,OAAO,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,QAAQ;oBACvB,CAAC,CAAC,uDAAuD;wBACvD,oCAAoC;wBACpC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;oBAC9B,CAAC,CAAC,4DAA4D;wBAC5D,wCAAwC;wBACxC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,sBAAc,CAAC,CAAA;YACpC,CAAC;YACD,UAAU,CAAC,UAAU,EAAE,MAAM;gBAC3B,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;gBACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAA;gBAC7D,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,OAAO,MAAM,CAAA;gBACf,CAAC;gBACD,OAAO,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,UAAU;oBACzB,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC;oBAC7B,CAAC,CAAC,sBAAc,CAAA;YACpB,CAAC;SACoB,CAAA;IACzB,CAAC;CACF;AA7ED,oDA6EC;AAED,SAAgB,aAAa,CAAI,GAAkB;IACjD,OAAO,IAAI,oBAAoB,CAAC,GAAG,CAAC,CAAA;AACtC,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
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
|
|
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:
|
|
42
|
+
end(outcome: OperationOutcome): void;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
|
-
* How an operation finished, delivered to
|
|
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},
|
|
49
|
-
* implementation: it is absent unless result capture is enabled
|
|
50
|
-
* InstrumentOptions, and holds the redacted value when a redaction
|
|
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
|
|
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
|
-
|
|
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},
|
|
108
|
-
* implementation: absent unless argument capture is enabled in
|
|
109
|
-
* InstrumentOptions, and already redacted when a redaction rule
|
|
110
|
-
* Implementations must record the arguments exactly when present
|
|
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
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
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:
|
|
121
|
-
* operation but must never alter it — see the
|
|
166
|
+
* Instrumentation is strictly observational: subclasses see every
|
|
167
|
+
* operation but must never alter it — see the OperationSpan contract.
|
|
122
168
|
*
|
|
123
|
-
* Each
|
|
124
|
-
* return an
|
|
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
|
|
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
|
|
178
|
+
* @return An OperationSpan notified when initialization finishes, or void.
|
|
133
179
|
*/
|
|
134
|
-
onInitialize?(context: InitializeContext):
|
|
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
|
|
185
|
+
* @return An OperationSpan notified when disposal finishes, or void.
|
|
140
186
|
*/
|
|
141
|
-
onDispose?(context: DisposeContext):
|
|
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
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
38
|
+
"@composed-di/core": "^0.7.0-alpha"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc --build",
|
package/src/index.ts
CHANGED
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
|
|
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 `
|
|
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(
|
|
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(
|
|
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(
|
|
144
|
-
const override = overrides[
|
|
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(
|
|
158
|
-
const override = overrides[
|
|
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
|
|
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
|
|
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:
|
|
45
|
+
end(outcome: OperationOutcome): void
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
|
-
* How an operation finished, delivered to
|
|
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},
|
|
52
|
-
* implementation: it is absent unless result capture is enabled
|
|
53
|
-
* InstrumentOptions, and holds the redacted value when a redaction
|
|
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
|
|
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
|
-
|
|
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},
|
|
113
|
-
* implementation: absent unless argument capture is enabled in
|
|
114
|
-
* InstrumentOptions, and already redacted when a redaction rule
|
|
115
|
-
* Implementations must record the arguments exactly when present
|
|
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
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
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:
|
|
127
|
-
* operation but must never alter it — see the
|
|
177
|
+
* Instrumentation is strictly observational: subclasses see every
|
|
178
|
+
* operation but must never alter it — see the OperationSpan contract.
|
|
128
179
|
*
|
|
129
|
-
* Each
|
|
130
|
-
* return an
|
|
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
|
|
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
|
|
189
|
+
* @return An OperationSpan notified when initialization finishes, or void.
|
|
139
190
|
*/
|
|
140
|
-
onInitialize?(context: InitializeContext):
|
|
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
|
|
197
|
+
* @return An OperationSpan notified when disposal finishes, or void.
|
|
147
198
|
*/
|
|
148
|
-
onDispose?(context: DisposeContext):
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|