@composed-di/instrumentation-otel 0.6.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/OTELEventListener.d.ts +37 -0
- package/dist/OTELEventListener.d.ts.map +1 -0
- package/dist/OTELEventListener.js +95 -0
- package/dist/OTELEventListener.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/otelInstrumentation.d.ts +30 -0
- package/dist/otelInstrumentation.d.ts.map +1 -0
- package/dist/otelInstrumentation.js +105 -0
- package/dist/otelInstrumentation.js.map +1 -0
- package/package.json +55 -0
- package/src/index.ts +1 -0
- package/src/otelInstrumentation.ts +151 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { DisposeContext, EventSpan, InitializeContext, MethodCallContext, ServiceInstrumentation } from '@composed-di/instrumentation-core';
|
|
3
|
+
export interface OTELEventListenerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The tracer used to create spans. Defaults to a tracer obtained from
|
|
6
|
+
* the global tracer provider — the one `NodeSDK` (and therefore
|
|
7
|
+
* `@opentelemetry/auto-instrumentations-node`) registers on startup —
|
|
8
|
+
* so most setups can omit it. The global lookup is lazy: it also works
|
|
9
|
+
* when the listener is constructed before the SDK starts.
|
|
10
|
+
*/
|
|
11
|
+
tracer?: Tracer;
|
|
12
|
+
/**
|
|
13
|
+
* Record method arguments as the `composed_di.service.function.arguments`
|
|
14
|
+
* span attribute, serialized to JSON. Off by default: arguments may be
|
|
15
|
+
* large or contain secrets, and they end up wherever spans are exported.
|
|
16
|
+
*/
|
|
17
|
+
captureArguments?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Record return / resolved values as the
|
|
20
|
+
* `composed_di.service.function.result` span attribute, serialized to
|
|
21
|
+
* JSON. Off by default, for the same reasons as `captureArguments`.
|
|
22
|
+
* Applies to method call and initialize spans.
|
|
23
|
+
*/
|
|
24
|
+
captureResults?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare class OTELEventListener implements ServiceInstrumentation {
|
|
27
|
+
private readonly tracer;
|
|
28
|
+
private readonly captureArguments;
|
|
29
|
+
private readonly captureResults;
|
|
30
|
+
constructor(options?: OTELEventListenerOptions);
|
|
31
|
+
onInitialize(context: InitializeContext): EventSpan;
|
|
32
|
+
onDispose(context: DisposeContext): EventSpan;
|
|
33
|
+
onMethodCall(context: MethodCallContext): EventSpan;
|
|
34
|
+
private buildSpan;
|
|
35
|
+
private buildAttributes;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=OTELEventListener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OTELEventListener.d.ts","sourceRoot":"","sources":["../src/OTELEventListener.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,MAAM,EACP,MAAM,oBAAoB,CAAC;AAM5B,OAAO,EACL,cAAc,EAEd,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,mCAAmC,CAAC;AAG3C,MAAM,WAAW,wBAAwB;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,iBAAkB,YAAW,sBAAsB;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAE7B,OAAO,GAAE,wBAA6B;IAOlD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS;IAWnD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS;IAW7C,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS;IAYnD,OAAO,CAAC,SAAS;IAoCjB,OAAO,CAAC,eAAe;CAqBxB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTELEventListener = void 0;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
6
|
+
class OTELEventListener {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
var _a, _b, _c;
|
|
9
|
+
this.tracer =
|
|
10
|
+
(_a = options.tracer) !== null && _a !== void 0 ? _a : api_1.trace.getTracer('@composed-di/instrumentation-otel');
|
|
11
|
+
this.captureArguments = (_b = options.captureArguments) !== null && _b !== void 0 ? _b : false;
|
|
12
|
+
this.captureResults = (_c = options.captureResults) !== null && _c !== void 0 ? _c : false;
|
|
13
|
+
}
|
|
14
|
+
onInitialize(context) {
|
|
15
|
+
const attributes = this.buildAttributes({
|
|
16
|
+
key: context.key,
|
|
17
|
+
event: 'initialize',
|
|
18
|
+
className: 'ServiceFactory',
|
|
19
|
+
functionName: 'initialize',
|
|
20
|
+
});
|
|
21
|
+
const spanName = `ServiceFactory[${context.key.name}].initialize`;
|
|
22
|
+
return this.buildSpan(spanName, attributes, this.captureResults);
|
|
23
|
+
}
|
|
24
|
+
onDispose(context) {
|
|
25
|
+
const attributes = this.buildAttributes({
|
|
26
|
+
key: context.key,
|
|
27
|
+
event: 'dispose',
|
|
28
|
+
className: 'ServiceFactory',
|
|
29
|
+
functionName: 'dispose',
|
|
30
|
+
});
|
|
31
|
+
const spanName = `ServiceFactory[${context.key.name}].dispose`;
|
|
32
|
+
return this.buildSpan(spanName, attributes, false);
|
|
33
|
+
}
|
|
34
|
+
onMethodCall(context) {
|
|
35
|
+
const attributes = this.buildAttributes({
|
|
36
|
+
key: context.key,
|
|
37
|
+
event: 'call',
|
|
38
|
+
className: context.className,
|
|
39
|
+
functionName: context.functionName,
|
|
40
|
+
args: context.args,
|
|
41
|
+
});
|
|
42
|
+
const spanName = attributes[semantic_conventions_1.ATTR_CODE_FUNCTION_NAME];
|
|
43
|
+
return this.buildSpan(spanName, attributes, this.captureResults);
|
|
44
|
+
}
|
|
45
|
+
buildSpan(spanName, attributes, captureResult) {
|
|
46
|
+
const parentContext = api_1.context.active();
|
|
47
|
+
const span = this.tracer.startSpan(spanName, { attributes }, parentContext);
|
|
48
|
+
const spanContext = api_1.trace.setSpan(parentContext, span);
|
|
49
|
+
return {
|
|
50
|
+
run: (fn) => api_1.context.with(spanContext, fn),
|
|
51
|
+
end: (outcome) => {
|
|
52
|
+
if (outcome.type === 'failure') {
|
|
53
|
+
const error = outcome.error;
|
|
54
|
+
span.recordException(error instanceof Error ? error : String(error));
|
|
55
|
+
span.setAttribute(semantic_conventions_1.ATTR_ERROR_TYPE, error instanceof Error
|
|
56
|
+
? error.name || semantic_conventions_1.ERROR_TYPE_VALUE_OTHER
|
|
57
|
+
: semantic_conventions_1.ERROR_TYPE_VALUE_OTHER);
|
|
58
|
+
span.setStatus({
|
|
59
|
+
code: api_1.SpanStatusCode.ERROR,
|
|
60
|
+
message: error instanceof Error ? error.message : String(error),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (captureResult) {
|
|
64
|
+
span.setAttribute('composed_di.service.function.result', serialize(outcome.value));
|
|
65
|
+
}
|
|
66
|
+
span.end();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
buildAttributes(params) {
|
|
71
|
+
var _a;
|
|
72
|
+
const attributes = {
|
|
73
|
+
[semantic_conventions_1.ATTR_CODE_FUNCTION_NAME]: `${(_a = params.className) !== null && _a !== void 0 ? _a : params.key.name}.${params.functionName}`,
|
|
74
|
+
'composed_di.service.key': params.key.name,
|
|
75
|
+
'composed_di.service.event': params.event,
|
|
76
|
+
};
|
|
77
|
+
if (params.args && this.captureArguments) {
|
|
78
|
+
attributes['composed_di.service.function.arguments'] = serialize(params.args);
|
|
79
|
+
}
|
|
80
|
+
return attributes;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.OTELEventListener = OTELEventListener;
|
|
84
|
+
function serialize(value) {
|
|
85
|
+
var _a;
|
|
86
|
+
let text;
|
|
87
|
+
try {
|
|
88
|
+
text = (_a = JSON.stringify(value)) !== null && _a !== void 0 ? _a : String(value);
|
|
89
|
+
}
|
|
90
|
+
catch (_b) {
|
|
91
|
+
text = '[unserializable]';
|
|
92
|
+
}
|
|
93
|
+
return text;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=OTELEventListener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OTELEventListener.js","sourceRoot":"","sources":["../src/OTELEventListener.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./otelInstrumentation"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAqC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { DisposeContext, EventSpan, InitializeContext, MethodCallContext, ServiceInstrumentation } from '@composed-di/instrumentation-core';
|
|
3
|
+
export interface OTELInstrumentationOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The tracer used to create spans. Defaults to a tracer obtained from
|
|
6
|
+
* the global tracer provider — the one `NodeSDK` (and therefore
|
|
7
|
+
* `@opentelemetry/auto-instrumentations-node`) registers on startup —
|
|
8
|
+
* so most setups can omit it. The global lookup is lazy: it also works
|
|
9
|
+
* when the instrumentation is constructed before the SDK starts.
|
|
10
|
+
*/
|
|
11
|
+
tracer?: Tracer;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A ServiceInstrumentation that records service events as OTEL spans.
|
|
15
|
+
*
|
|
16
|
+
* Arguments and results are recorded (as the
|
|
17
|
+
* `composed_di.service.function.arguments` / `.result` attributes,
|
|
18
|
+
* serialized to JSON) exactly when `instrument()` delivers them — capture
|
|
19
|
+
* and redaction policy live in the InstrumentOptions, not here.
|
|
20
|
+
*/
|
|
21
|
+
export declare class OTELInstrumentation implements ServiceInstrumentation {
|
|
22
|
+
private readonly tracer;
|
|
23
|
+
constructor(options?: OTELInstrumentationOptions);
|
|
24
|
+
onInitialize(context: InitializeContext): EventSpan;
|
|
25
|
+
onDispose(context: DisposeContext): EventSpan;
|
|
26
|
+
onMethodCall(context: MethodCallContext): EventSpan;
|
|
27
|
+
private buildSpan;
|
|
28
|
+
private buildAttributes;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=otelInstrumentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otelInstrumentation.d.ts","sourceRoot":"","sources":["../src/otelInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,MAAM,EACP,MAAM,oBAAoB,CAAA;AAM3B,OAAO,EACL,cAAc,EAEd,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACvB,MAAM,mCAAmC,CAAA;AAG1C,MAAM,WAAW,0BAA0B;IACzC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,sBAAsB;IAChE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,OAAO,GAAE,0BAA+B;IAKpD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS;IAWnD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS;IAW7C,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS;IAYnD,OAAO,CAAC,SAAS;IAkCjB,OAAO,CAAC,eAAe;CAuBxB"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTELInstrumentation = void 0;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
6
|
+
/**
|
|
7
|
+
* A ServiceInstrumentation that records service events as OTEL spans.
|
|
8
|
+
*
|
|
9
|
+
* Arguments and results are recorded (as the
|
|
10
|
+
* `composed_di.service.function.arguments` / `.result` attributes,
|
|
11
|
+
* serialized to JSON) exactly when `instrument()` delivers them — capture
|
|
12
|
+
* and redaction policy live in the InstrumentOptions, not here.
|
|
13
|
+
*/
|
|
14
|
+
class OTELInstrumentation {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
var _a;
|
|
17
|
+
this.tracer =
|
|
18
|
+
(_a = options.tracer) !== null && _a !== void 0 ? _a : api_1.trace.getTracer('@composed-di/instrumentation-otel');
|
|
19
|
+
}
|
|
20
|
+
onInitialize(context) {
|
|
21
|
+
const attributes = this.buildAttributes({
|
|
22
|
+
key: context.key,
|
|
23
|
+
event: 'initialize',
|
|
24
|
+
className: 'ServiceFactory',
|
|
25
|
+
functionName: 'initialize',
|
|
26
|
+
});
|
|
27
|
+
const spanName = `ServiceFactory[${context.key.name}].initialize`;
|
|
28
|
+
return this.buildSpan(spanName, attributes);
|
|
29
|
+
}
|
|
30
|
+
onDispose(context) {
|
|
31
|
+
const attributes = this.buildAttributes({
|
|
32
|
+
key: context.key,
|
|
33
|
+
event: 'dispose',
|
|
34
|
+
className: 'ServiceFactory',
|
|
35
|
+
functionName: 'dispose',
|
|
36
|
+
});
|
|
37
|
+
const spanName = `ServiceFactory[${context.key.name}].dispose`;
|
|
38
|
+
return this.buildSpan(spanName, attributes);
|
|
39
|
+
}
|
|
40
|
+
onMethodCall(context) {
|
|
41
|
+
const attributes = this.buildAttributes({
|
|
42
|
+
key: context.key,
|
|
43
|
+
event: 'call',
|
|
44
|
+
className: context.className,
|
|
45
|
+
functionName: context.functionName,
|
|
46
|
+
args: context.args,
|
|
47
|
+
});
|
|
48
|
+
const spanName = attributes[semantic_conventions_1.ATTR_CODE_FUNCTION_NAME];
|
|
49
|
+
return this.buildSpan(spanName, attributes);
|
|
50
|
+
}
|
|
51
|
+
buildSpan(spanName, attributes) {
|
|
52
|
+
const parentContext = api_1.context.active();
|
|
53
|
+
const span = this.tracer.startSpan(spanName, { attributes }, parentContext);
|
|
54
|
+
const spanContext = api_1.trace.setSpan(parentContext, span);
|
|
55
|
+
return {
|
|
56
|
+
run: (fn) => api_1.context.with(spanContext, fn),
|
|
57
|
+
end: (outcome) => {
|
|
58
|
+
if (outcome.type === 'failure') {
|
|
59
|
+
const error = outcome.error;
|
|
60
|
+
span.recordException(error instanceof Error ? error : String(error));
|
|
61
|
+
span.setAttribute(semantic_conventions_1.ATTR_ERROR_TYPE, error instanceof Error
|
|
62
|
+
? error.name || semantic_conventions_1.ERROR_TYPE_VALUE_OTHER
|
|
63
|
+
: semantic_conventions_1.ERROR_TYPE_VALUE_OTHER);
|
|
64
|
+
span.setStatus({
|
|
65
|
+
code: api_1.SpanStatusCode.ERROR,
|
|
66
|
+
message: error instanceof Error ? error.message : String(error),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else if ('value' in outcome) {
|
|
70
|
+
// Present exactly when result capture is enabled in the
|
|
71
|
+
// InstrumentOptions; the value arrives already redacted.
|
|
72
|
+
span.setAttribute('composed_di.service.function.result', serialize(outcome.value));
|
|
73
|
+
}
|
|
74
|
+
span.end();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
buildAttributes(params) {
|
|
79
|
+
var _a;
|
|
80
|
+
const attributes = {
|
|
81
|
+
[semantic_conventions_1.ATTR_CODE_FUNCTION_NAME]: `${(_a = params.className) !== null && _a !== void 0 ? _a : params.key.name}.${params.functionName}`,
|
|
82
|
+
'composed_di.service.key': params.key.name,
|
|
83
|
+
'composed_di.service.event': params.event,
|
|
84
|
+
};
|
|
85
|
+
// Present exactly when argument capture is enabled in the
|
|
86
|
+
// InstrumentOptions; the args arrive already redacted.
|
|
87
|
+
if (params.args) {
|
|
88
|
+
attributes['composed_di.service.function.arguments'] = serialize(params.args);
|
|
89
|
+
}
|
|
90
|
+
return attributes;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.OTELInstrumentation = OTELInstrumentation;
|
|
94
|
+
function serialize(value) {
|
|
95
|
+
var _a;
|
|
96
|
+
let text;
|
|
97
|
+
try {
|
|
98
|
+
text = (_a = JSON.stringify(value)) !== null && _a !== void 0 ? _a : String(value);
|
|
99
|
+
}
|
|
100
|
+
catch (_b) {
|
|
101
|
+
text = '[unserializable]';
|
|
102
|
+
}
|
|
103
|
+
return text;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=otelInstrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otelInstrumentation.js","sourceRoot":"","sources":["../src/otelInstrumentation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@composed-di/instrumentation-otel",
|
|
3
|
+
"version": "0.6.0-alpha",
|
|
4
|
+
"private": false,
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dependency injection",
|
|
7
|
+
"di",
|
|
8
|
+
"observability",
|
|
9
|
+
"opentelemetry",
|
|
10
|
+
"otel",
|
|
11
|
+
"tracing"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Juan Herrera juanhr454@gmail.com",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/imherrera/composed-di.git",
|
|
18
|
+
"directory": "packages/instrumentation-otel"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"require": "./dist/index.js",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@opentelemetry/semantic-conventions": "^1.30.0",
|
|
40
|
+
"@composed-di/core": "^0.6.0-alpha",
|
|
41
|
+
"@composed-di/instrumentation-core": "^0.6.0-alpha"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@opentelemetry/api": "1.9.1",
|
|
45
|
+
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
46
|
+
"@opentelemetry/sdk-trace-base": "^2.9.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@opentelemetry/api": "^1.9.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc --build",
|
|
53
|
+
"clean": "tsc --build --clean"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './otelInstrumentation'
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Attributes,
|
|
3
|
+
context as otelContext,
|
|
4
|
+
SpanStatusCode,
|
|
5
|
+
trace,
|
|
6
|
+
Tracer,
|
|
7
|
+
} from '@opentelemetry/api'
|
|
8
|
+
import {
|
|
9
|
+
ATTR_CODE_FUNCTION_NAME,
|
|
10
|
+
ATTR_ERROR_TYPE,
|
|
11
|
+
ERROR_TYPE_VALUE_OTHER,
|
|
12
|
+
} from '@opentelemetry/semantic-conventions'
|
|
13
|
+
import {
|
|
14
|
+
DisposeContext,
|
|
15
|
+
EventOutcome,
|
|
16
|
+
EventSpan,
|
|
17
|
+
InitializeContext,
|
|
18
|
+
MethodCallContext,
|
|
19
|
+
ServiceInstrumentation,
|
|
20
|
+
} from '@composed-di/instrumentation-core'
|
|
21
|
+
import { ServiceKey } from '@composed-di/core'
|
|
22
|
+
|
|
23
|
+
export interface OTELInstrumentationOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The tracer used to create spans. Defaults to a tracer obtained from
|
|
26
|
+
* the global tracer provider — the one `NodeSDK` (and therefore
|
|
27
|
+
* `@opentelemetry/auto-instrumentations-node`) registers on startup —
|
|
28
|
+
* so most setups can omit it. The global lookup is lazy: it also works
|
|
29
|
+
* when the instrumentation is constructed before the SDK starts.
|
|
30
|
+
*/
|
|
31
|
+
tracer?: Tracer
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A ServiceInstrumentation that records service events as OTEL spans.
|
|
36
|
+
*
|
|
37
|
+
* Arguments and results are recorded (as the
|
|
38
|
+
* `composed_di.service.function.arguments` / `.result` attributes,
|
|
39
|
+
* serialized to JSON) exactly when `instrument()` delivers them — capture
|
|
40
|
+
* and redaction policy live in the InstrumentOptions, not here.
|
|
41
|
+
*/
|
|
42
|
+
export class OTELInstrumentation implements ServiceInstrumentation {
|
|
43
|
+
private readonly tracer: Tracer
|
|
44
|
+
|
|
45
|
+
constructor(options: OTELInstrumentationOptions = {}) {
|
|
46
|
+
this.tracer =
|
|
47
|
+
options.tracer ?? trace.getTracer('@composed-di/instrumentation-otel')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onInitialize(context: InitializeContext): EventSpan {
|
|
51
|
+
const attributes = this.buildAttributes({
|
|
52
|
+
key: context.key,
|
|
53
|
+
event: 'initialize',
|
|
54
|
+
className: 'ServiceFactory',
|
|
55
|
+
functionName: 'initialize',
|
|
56
|
+
})
|
|
57
|
+
const spanName = `ServiceFactory[${context.key.name}].initialize`
|
|
58
|
+
return this.buildSpan(spanName, attributes)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onDispose(context: DisposeContext): EventSpan {
|
|
62
|
+
const attributes = this.buildAttributes({
|
|
63
|
+
key: context.key,
|
|
64
|
+
event: 'dispose',
|
|
65
|
+
className: 'ServiceFactory',
|
|
66
|
+
functionName: 'dispose',
|
|
67
|
+
})
|
|
68
|
+
const spanName = `ServiceFactory[${context.key.name}].dispose`
|
|
69
|
+
return this.buildSpan(spanName, attributes)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onMethodCall(context: MethodCallContext): EventSpan {
|
|
73
|
+
const attributes = this.buildAttributes({
|
|
74
|
+
key: context.key,
|
|
75
|
+
event: 'call',
|
|
76
|
+
className: context.className,
|
|
77
|
+
functionName: context.functionName,
|
|
78
|
+
args: context.args,
|
|
79
|
+
})
|
|
80
|
+
const spanName = attributes[ATTR_CODE_FUNCTION_NAME]
|
|
81
|
+
return this.buildSpan(spanName, attributes)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private buildSpan(spanName: string, attributes: Attributes): EventSpan {
|
|
85
|
+
const parentContext = otelContext.active()
|
|
86
|
+
const span = this.tracer.startSpan(spanName, { attributes }, parentContext)
|
|
87
|
+
const spanContext = trace.setSpan(parentContext, span)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
run: (fn) => otelContext.with(spanContext, fn),
|
|
91
|
+
end: (outcome: EventOutcome) => {
|
|
92
|
+
if (outcome.type === 'failure') {
|
|
93
|
+
const error = outcome.error
|
|
94
|
+
span.recordException(error instanceof Error ? error : String(error))
|
|
95
|
+
span.setAttribute(
|
|
96
|
+
ATTR_ERROR_TYPE,
|
|
97
|
+
error instanceof Error
|
|
98
|
+
? error.name || ERROR_TYPE_VALUE_OTHER
|
|
99
|
+
: ERROR_TYPE_VALUE_OTHER,
|
|
100
|
+
)
|
|
101
|
+
span.setStatus({
|
|
102
|
+
code: SpanStatusCode.ERROR,
|
|
103
|
+
message: error instanceof Error ? error.message : String(error),
|
|
104
|
+
})
|
|
105
|
+
} else if ('value' in outcome) {
|
|
106
|
+
// Present exactly when result capture is enabled in the
|
|
107
|
+
// InstrumentOptions; the value arrives already redacted.
|
|
108
|
+
span.setAttribute(
|
|
109
|
+
'composed_di.service.function.result',
|
|
110
|
+
serialize(outcome.value),
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
span.end()
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private buildAttributes(params: {
|
|
119
|
+
key: ServiceKey<unknown>
|
|
120
|
+
event: 'initialize' | 'dispose' | 'call'
|
|
121
|
+
className?: string
|
|
122
|
+
functionName: string
|
|
123
|
+
args?: readonly unknown[]
|
|
124
|
+
}) {
|
|
125
|
+
const attributes: { [key: string]: string } = {
|
|
126
|
+
[ATTR_CODE_FUNCTION_NAME]: `${params.className ?? params.key.name}.${params.functionName}`,
|
|
127
|
+
'composed_di.service.key': params.key.name,
|
|
128
|
+
'composed_di.service.event': params.event,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Present exactly when argument capture is enabled in the
|
|
132
|
+
// InstrumentOptions; the args arrive already redacted.
|
|
133
|
+
if (params.args) {
|
|
134
|
+
attributes['composed_di.service.function.arguments'] = serialize(
|
|
135
|
+
params.args,
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return attributes
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function serialize(value: unknown): string {
|
|
144
|
+
let text: string
|
|
145
|
+
try {
|
|
146
|
+
text = JSON.stringify(value) ?? String(value)
|
|
147
|
+
} catch {
|
|
148
|
+
text = '[unserializable]'
|
|
149
|
+
}
|
|
150
|
+
return text
|
|
151
|
+
}
|