@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.
@@ -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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"}
@@ -0,0 +1,2 @@
1
+ export * from './otelInstrumentation';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }