abxbus 2.4.18 → 2.4.19
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/cjs/middleware_otel_tracing.d.ts +9 -1
- package/dist/cjs/middleware_otel_tracing.js +71 -4
- package/dist/cjs/middleware_otel_tracing.js.map +2 -2
- package/dist/esm/middleware_otel_tracing.js +76 -5
- package/dist/esm/middleware_otel_tracing.js.map +2 -2
- package/dist/types/middleware_otel_tracing.d.ts +9 -1
- package/package.json +1 -1
- package/src/middleware_otel_tracing.ts +98 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { trace, type Tracer } from '@opentelemetry/api';
|
|
1
|
+
import { trace, type SpanAttributes, type Tracer } from '@opentelemetry/api';
|
|
2
2
|
import type { BaseEvent } from './base_event.js';
|
|
3
3
|
import type { EventBus } from './event_bus.js';
|
|
4
4
|
import type { EventResult } from './event_result.js';
|
|
@@ -8,10 +8,16 @@ type OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>;
|
|
|
8
8
|
export type OtelTracingMiddlewareOptions = {
|
|
9
9
|
tracer?: Tracer;
|
|
10
10
|
trace_api?: OpenTelemetryTraceApi;
|
|
11
|
+
root_span_name?: string | ((eventbus: EventBus, event: BaseEvent) => string);
|
|
12
|
+
root_span_attributes?: SpanAttributes | ((eventbus: EventBus, event: BaseEvent) => SpanAttributes);
|
|
11
13
|
};
|
|
12
14
|
export declare class OtelTracingMiddleware implements EventBusMiddleware {
|
|
13
15
|
private readonly tracer;
|
|
14
16
|
private readonly trace_api;
|
|
17
|
+
private readonly root_span_name;
|
|
18
|
+
private readonly root_span_attributes;
|
|
19
|
+
private readonly root_spans;
|
|
20
|
+
private readonly root_contexts;
|
|
15
21
|
private readonly event_spans;
|
|
16
22
|
private readonly event_contexts;
|
|
17
23
|
private readonly handler_spans;
|
|
@@ -23,6 +29,8 @@ export declare class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
23
29
|
private completeEventSpan;
|
|
24
30
|
private startHandlerSpan;
|
|
25
31
|
private completeHandlerSpan;
|
|
32
|
+
private startRootSpan;
|
|
33
|
+
private completeRootSpan;
|
|
26
34
|
private parentContextForEvent;
|
|
27
35
|
}
|
|
28
36
|
export {};
|
|
@@ -25,6 +25,10 @@ var import_api = require("@opentelemetry/api");
|
|
|
25
25
|
class OtelTracingMiddleware {
|
|
26
26
|
tracer;
|
|
27
27
|
trace_api;
|
|
28
|
+
root_span_name;
|
|
29
|
+
root_span_attributes;
|
|
30
|
+
root_spans = /* @__PURE__ */ new Map();
|
|
31
|
+
root_contexts = /* @__PURE__ */ new Map();
|
|
28
32
|
event_spans = /* @__PURE__ */ new Map();
|
|
29
33
|
event_contexts = /* @__PURE__ */ new Map();
|
|
30
34
|
handler_spans = /* @__PURE__ */ new Map();
|
|
@@ -32,6 +36,8 @@ class OtelTracingMiddleware {
|
|
|
32
36
|
constructor(options = {}) {
|
|
33
37
|
this.trace_api = options.trace_api ?? import_api.trace;
|
|
34
38
|
this.tracer = options.tracer ?? this.trace_api.getTracer("abxbus");
|
|
39
|
+
this.root_span_name = options.root_span_name;
|
|
40
|
+
this.root_span_attributes = options.root_span_attributes;
|
|
35
41
|
}
|
|
36
42
|
onEventChange(eventbus, event, status) {
|
|
37
43
|
if (status === "started") {
|
|
@@ -56,7 +62,8 @@ class OtelTracingMiddleware {
|
|
|
56
62
|
if (existing) {
|
|
57
63
|
return existing;
|
|
58
64
|
}
|
|
59
|
-
const parent_context = this.parentContextForEvent(event) ??
|
|
65
|
+
const parent_context = this.parentContextForEvent(event) ?? this.startRootSpan(eventbus, event);
|
|
66
|
+
const start_time = dateFromIso(event.event_started_at);
|
|
60
67
|
const span = this.tracer.startSpan(
|
|
61
68
|
`abxbus.event ${event.event_type}`,
|
|
62
69
|
{
|
|
@@ -66,11 +73,12 @@ class OtelTracingMiddleware {
|
|
|
66
73
|
"abxbus.event.id": event.event_id,
|
|
67
74
|
"abxbus.event.type": event.event_type,
|
|
68
75
|
"abxbus.event.version": event.event_version,
|
|
76
|
+
"abxbus.event.session_id": stringValue(event.session_id),
|
|
69
77
|
"abxbus.event.parent_id": event.event_parent_id,
|
|
70
78
|
"abxbus.event.emitted_by_handler_id": event.event_emitted_by_handler_id,
|
|
71
79
|
"abxbus.event.path": event.event_path.join(" ")
|
|
72
80
|
}),
|
|
73
|
-
startTime:
|
|
81
|
+
startTime: start_time
|
|
74
82
|
},
|
|
75
83
|
parent_context
|
|
76
84
|
);
|
|
@@ -94,9 +102,12 @@ class OtelTracingMiddleware {
|
|
|
94
102
|
"abxbus.event.child_count": event.event_children.length
|
|
95
103
|
})
|
|
96
104
|
);
|
|
97
|
-
|
|
105
|
+
const start_time = dateFromIso(event.event_started_at);
|
|
106
|
+
const end_time = endTimeAfterStart(start_time, dateFromIso(event.event_completed_at));
|
|
107
|
+
span.end(end_time);
|
|
98
108
|
this.event_spans.delete(event.event_id);
|
|
99
109
|
this.event_contexts.delete(event.event_id);
|
|
110
|
+
this.completeRootSpan(event.event_id, start_time, end_time);
|
|
100
111
|
}
|
|
101
112
|
startHandlerSpan(eventbus, event, event_result) {
|
|
102
113
|
const existing = this.handler_spans.get(event_result.id);
|
|
@@ -143,10 +154,48 @@ class OtelTracingMiddleware {
|
|
|
143
154
|
"abxbus.handler.child_count": event_result.event_children.length
|
|
144
155
|
})
|
|
145
156
|
);
|
|
146
|
-
span.end(dateFromIso(event_result.completed_at));
|
|
157
|
+
span.end(endTimeAfterStart(dateFromIso(event_result.started_at), dateFromIso(event_result.completed_at)));
|
|
147
158
|
this.handler_spans.delete(event_result.id);
|
|
148
159
|
this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id));
|
|
149
160
|
}
|
|
161
|
+
startRootSpan(eventbus, event) {
|
|
162
|
+
const existing = this.root_contexts.get(event.event_id);
|
|
163
|
+
if (existing) {
|
|
164
|
+
return existing;
|
|
165
|
+
}
|
|
166
|
+
const session_id = stringValue(event.session_id);
|
|
167
|
+
const root_attributes = resolveAttributes(this.root_span_attributes, eventbus, event);
|
|
168
|
+
const root_span = this.tracer.startSpan(
|
|
169
|
+
resolveRootSpanName(this.root_span_name, eventbus, event),
|
|
170
|
+
{
|
|
171
|
+
attributes: compactAttributes({
|
|
172
|
+
...root_attributes,
|
|
173
|
+
"abxbus.trace.root": true,
|
|
174
|
+
"abxbus.bus.id": eventbus.id,
|
|
175
|
+
"abxbus.bus.name": eventbus.name,
|
|
176
|
+
"abxbus.root_event.id": event.event_id,
|
|
177
|
+
"abxbus.root_event.type": event.event_type,
|
|
178
|
+
"abxbus.root_event.session_id": session_id
|
|
179
|
+
}),
|
|
180
|
+
startTime: dateFromIso(event.event_started_at)
|
|
181
|
+
},
|
|
182
|
+
import_api.ROOT_CONTEXT
|
|
183
|
+
);
|
|
184
|
+
const root_context = this.trace_api.setSpan(import_api.ROOT_CONTEXT, root_span);
|
|
185
|
+
this.root_spans.set(event.event_id, root_span);
|
|
186
|
+
this.root_contexts.set(event.event_id, root_context);
|
|
187
|
+
return root_context;
|
|
188
|
+
}
|
|
189
|
+
completeRootSpan(event_id, start_time, end_time) {
|
|
190
|
+
const root_span = this.root_spans.get(event_id);
|
|
191
|
+
if (!root_span) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
root_span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
195
|
+
root_span.end(endTimeAfterStart(start_time, end_time));
|
|
196
|
+
this.root_spans.delete(event_id);
|
|
197
|
+
this.root_contexts.delete(event_id);
|
|
198
|
+
}
|
|
150
199
|
parentContextForEvent(event) {
|
|
151
200
|
if (event.event_parent_id && event.event_emitted_by_handler_id) {
|
|
152
201
|
const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id));
|
|
@@ -167,6 +216,24 @@ function dateFromIso(value) {
|
|
|
167
216
|
const date = new Date(value);
|
|
168
217
|
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
169
218
|
}
|
|
219
|
+
function endTimeAfterStart(start_time, end_time) {
|
|
220
|
+
if (!start_time || !end_time) {
|
|
221
|
+
return end_time;
|
|
222
|
+
}
|
|
223
|
+
return end_time.getTime() > start_time.getTime() ? end_time : new Date(start_time.getTime() + 1);
|
|
224
|
+
}
|
|
225
|
+
function resolveRootSpanName(root_span_name, eventbus, event) {
|
|
226
|
+
if (typeof root_span_name === "function") {
|
|
227
|
+
return root_span_name(eventbus, event);
|
|
228
|
+
}
|
|
229
|
+
return root_span_name ?? `abxbus.trace ${eventbus.name}`;
|
|
230
|
+
}
|
|
231
|
+
function resolveAttributes(attributes, eventbus, event) {
|
|
232
|
+
return typeof attributes === "function" ? attributes(eventbus, event) : attributes ?? {};
|
|
233
|
+
}
|
|
234
|
+
function stringValue(value) {
|
|
235
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
236
|
+
}
|
|
170
237
|
function compactAttributes(attributes) {
|
|
171
238
|
const compacted = {};
|
|
172
239
|
for (const [key, value] of Object.entries(attributes)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/middleware_otel_tracing.ts"],
|
|
4
|
-
"sourcesContent": ["import { ROOT_CONTEXT, SpanStatusCode, trace, type Context, type Span, type Tracer } from '@opentelemetry/api'\n\nimport type { BaseEvent } from './base_event.js'\nimport type { EventBus } from './event_bus.js'\nimport type { EventResult } from './event_result.js'\nimport type { EventBusMiddleware } from './middlewares.js'\nimport type { EventStatus } from './types.js'\n\ntype OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>\n\nexport type OtelTracingMiddlewareOptions = {\n tracer?: Tracer\n trace_api?: OpenTelemetryTraceApi\n}\n\nexport class OtelTracingMiddleware implements EventBusMiddleware {\n private readonly tracer: Tracer\n private readonly trace_api: OpenTelemetryTraceApi\n private readonly event_spans = new Map<string, Span>()\n private readonly event_contexts = new Map<string, Context>()\n private readonly handler_spans = new Map<string, Span>()\n private readonly handler_contexts = new Map<string, Context>()\n\n constructor(options: OtelTracingMiddlewareOptions = {}) {\n this.trace_api = options.trace_api ?? trace\n this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')\n }\n\n onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {\n if (status === 'started') {\n this.startEventSpan(eventbus, event)\n return\n }\n\n if (status === 'completed') {\n this.completeEventSpan(eventbus, event)\n }\n }\n\n onEventResultChange(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void {\n if (status === 'started') {\n this.startHandlerSpan(eventbus, event, event_result)\n return\n }\n\n if (status === 'completed') {\n this.completeHandlerSpan(event_result)\n }\n }\n\n private startEventSpan(eventbus: EventBus, event: BaseEvent): Span {\n const existing = this.event_spans.get(event.event_id)\n if (existing) {\n return existing\n }\n\n const parent_context = this.parentContextForEvent(event) ?? ROOT_CONTEXT\n const span = this.tracer.startSpan(\n `abxbus.event ${event.event_type}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.event.version': event.event_version,\n 'abxbus.event.parent_id': event.event_parent_id,\n 'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,\n 'abxbus.event.path': event.event_path.join(' '),\n }),\n startTime: dateFromIso(event.event_started_at),\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.event_spans.set(event.event_id, span)\n this.event_contexts.set(event.event_id, span_context)\n return span\n }\n\n private completeEventSpan(eventbus: EventBus, event: BaseEvent): void {\n const span = this.event_spans.get(event.event_id) ?? this.startEventSpan(eventbus, event)\n if (event.event_errors.length > 0) {\n recordSpanError(span, event.event_errors[0])\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event.status': event.event_status,\n 'abxbus.event.result_count': event.event_results.size,\n 'abxbus.event.error_count': event.event_errors.length,\n 'abxbus.event.child_count': event.event_children.length,\n })\n )\n span.end(dateFromIso(event.event_completed_at))\n this.event_spans.delete(event.event_id)\n this.event_contexts.delete(event.event_id)\n }\n\n private startHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): Span {\n const existing = this.handler_spans.get(event_result.id)\n if (existing) {\n return existing\n }\n\n const parent_context =\n this.event_contexts.get(event.event_id) ?? this.trace_api.setSpan(ROOT_CONTEXT, this.startEventSpan(eventbus, event))\n const span = this.tracer.startSpan(\n `abxbus.handler ${event.event_type} ${event_result.handler_name}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.handler.id': event_result.handler_id,\n 'abxbus.handler.name': event_result.handler_name,\n 'abxbus.handler.file_path': event_result.handler_file_path,\n 'abxbus.handler.event_pattern': event_result.handler.event_pattern,\n 'abxbus.event_result.id': event_result.id,\n }),\n startTime: dateFromIso(event_result.started_at),\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.handler_spans.set(event_result.id, span)\n this.handler_contexts.set(handlerSpanKey(event_result.event_id, event_result.handler_id), span_context)\n return span\n }\n\n private completeHandlerSpan(event_result: EventResult): void {\n const span = this.handler_spans.get(event_result.id)\n if (!span) {\n return\n }\n\n if (event_result.error !== undefined) {\n recordSpanError(span, event_result.error)\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event_result.status': event_result.status,\n 'abxbus.handler.child_count': event_result.event_children.length,\n })\n )\n span.end(dateFromIso(event_result.completed_at))\n this.handler_spans.delete(event_result.id)\n this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id))\n }\n\n private parentContextForEvent(event: BaseEvent): Context | undefined {\n if (event.event_parent_id && event.event_emitted_by_handler_id) {\n const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id))\n if (handler_context) {\n return handler_context\n }\n }\n\n return event.event_parent_id ? this.event_contexts.get(event.event_parent_id) : undefined\n }\n}\n\nfunction handlerSpanKey(event_id: string, handler_id: string): string {\n return `${event_id}:${handler_id}`\n}\n\nfunction dateFromIso(value: string | null | undefined): Date | undefined {\n if (value == null) {\n return undefined\n }\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? undefined : date\n}\n\nfunction compactAttributes(\n attributes: Record<string, string | number | boolean | null | undefined>\n): Record<string, string | number | boolean> {\n const compacted: Record<string, string | number | boolean> = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== null && value !== undefined) {\n compacted[key] = value\n }\n }\n return compacted\n}\n\nfunction recordSpanError(span: Span, error: unknown): void {\n if (error instanceof Error) {\n span.recordException(error)\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })\n return\n }\n\n const message = typeof error === 'string' ? error : 'Unknown abxbus handler error'\n span.recordException(message)\n span.setStatus({ code: SpanStatusCode.ERROR, message })\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n ROOT_CONTEXT,\n SpanStatusCode,\n trace,\n type Context,\n type Span,\n type SpanAttributeValue,\n type SpanAttributes,\n type Tracer,\n} from '@opentelemetry/api'\n\nimport type { BaseEvent } from './base_event.js'\nimport type { EventBus } from './event_bus.js'\nimport type { EventResult } from './event_result.js'\nimport type { EventBusMiddleware } from './middlewares.js'\nimport type { EventStatus } from './types.js'\n\ntype OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>\n\nexport type OtelTracingMiddlewareOptions = {\n tracer?: Tracer\n trace_api?: OpenTelemetryTraceApi\n root_span_name?: string | ((eventbus: EventBus, event: BaseEvent) => string)\n root_span_attributes?: SpanAttributes | ((eventbus: EventBus, event: BaseEvent) => SpanAttributes)\n}\n\nexport class OtelTracingMiddleware implements EventBusMiddleware {\n private readonly tracer: Tracer\n private readonly trace_api: OpenTelemetryTraceApi\n private readonly root_span_name: OtelTracingMiddlewareOptions['root_span_name']\n private readonly root_span_attributes: OtelTracingMiddlewareOptions['root_span_attributes']\n private readonly root_spans = new Map<string, Span>()\n private readonly root_contexts = new Map<string, Context>()\n private readonly event_spans = new Map<string, Span>()\n private readonly event_contexts = new Map<string, Context>()\n private readonly handler_spans = new Map<string, Span>()\n private readonly handler_contexts = new Map<string, Context>()\n\n constructor(options: OtelTracingMiddlewareOptions = {}) {\n this.trace_api = options.trace_api ?? trace\n this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')\n this.root_span_name = options.root_span_name\n this.root_span_attributes = options.root_span_attributes\n }\n\n onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {\n if (status === 'started') {\n this.startEventSpan(eventbus, event)\n return\n }\n\n if (status === 'completed') {\n this.completeEventSpan(eventbus, event)\n }\n }\n\n onEventResultChange(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void {\n if (status === 'started') {\n this.startHandlerSpan(eventbus, event, event_result)\n return\n }\n\n if (status === 'completed') {\n this.completeHandlerSpan(event_result)\n }\n }\n\n private startEventSpan(eventbus: EventBus, event: BaseEvent): Span {\n const existing = this.event_spans.get(event.event_id)\n if (existing) {\n return existing\n }\n\n const parent_context = this.parentContextForEvent(event) ?? this.startRootSpan(eventbus, event)\n const start_time = dateFromIso(event.event_started_at)\n const span = this.tracer.startSpan(\n `abxbus.event ${event.event_type}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.event.version': event.event_version,\n 'abxbus.event.session_id': stringValue((event as { session_id?: unknown }).session_id),\n 'abxbus.event.parent_id': event.event_parent_id,\n 'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,\n 'abxbus.event.path': event.event_path.join(' '),\n }),\n startTime: start_time,\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.event_spans.set(event.event_id, span)\n this.event_contexts.set(event.event_id, span_context)\n return span\n }\n\n private completeEventSpan(eventbus: EventBus, event: BaseEvent): void {\n const span = this.event_spans.get(event.event_id) ?? this.startEventSpan(eventbus, event)\n if (event.event_errors.length > 0) {\n recordSpanError(span, event.event_errors[0])\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event.status': event.event_status,\n 'abxbus.event.result_count': event.event_results.size,\n 'abxbus.event.error_count': event.event_errors.length,\n 'abxbus.event.child_count': event.event_children.length,\n })\n )\n const start_time = dateFromIso(event.event_started_at)\n const end_time = endTimeAfterStart(start_time, dateFromIso(event.event_completed_at))\n span.end(end_time)\n this.event_spans.delete(event.event_id)\n this.event_contexts.delete(event.event_id)\n this.completeRootSpan(event.event_id, start_time, end_time)\n }\n\n private startHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): Span {\n const existing = this.handler_spans.get(event_result.id)\n if (existing) {\n return existing\n }\n\n const parent_context =\n this.event_contexts.get(event.event_id) ?? this.trace_api.setSpan(ROOT_CONTEXT, this.startEventSpan(eventbus, event))\n const span = this.tracer.startSpan(\n `abxbus.handler ${event.event_type} ${event_result.handler_name}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.handler.id': event_result.handler_id,\n 'abxbus.handler.name': event_result.handler_name,\n 'abxbus.handler.file_path': event_result.handler_file_path,\n 'abxbus.handler.event_pattern': event_result.handler.event_pattern,\n 'abxbus.event_result.id': event_result.id,\n }),\n startTime: dateFromIso(event_result.started_at),\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.handler_spans.set(event_result.id, span)\n this.handler_contexts.set(handlerSpanKey(event_result.event_id, event_result.handler_id), span_context)\n return span\n }\n\n private completeHandlerSpan(event_result: EventResult): void {\n const span = this.handler_spans.get(event_result.id)\n if (!span) {\n return\n }\n\n if (event_result.error !== undefined) {\n recordSpanError(span, event_result.error)\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event_result.status': event_result.status,\n 'abxbus.handler.child_count': event_result.event_children.length,\n })\n )\n span.end(endTimeAfterStart(dateFromIso(event_result.started_at), dateFromIso(event_result.completed_at)))\n this.handler_spans.delete(event_result.id)\n this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id))\n }\n\n private startRootSpan(eventbus: EventBus, event: BaseEvent): Context {\n const existing = this.root_contexts.get(event.event_id)\n if (existing) {\n return existing\n }\n\n const session_id = stringValue((event as { session_id?: unknown }).session_id)\n const root_attributes = resolveAttributes(this.root_span_attributes, eventbus, event)\n const root_span = this.tracer.startSpan(\n resolveRootSpanName(this.root_span_name, eventbus, event),\n {\n attributes: compactAttributes({\n ...root_attributes,\n 'abxbus.trace.root': true,\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.root_event.id': event.event_id,\n 'abxbus.root_event.type': event.event_type,\n 'abxbus.root_event.session_id': session_id,\n }),\n startTime: dateFromIso(event.event_started_at),\n },\n ROOT_CONTEXT\n )\n const root_context = this.trace_api.setSpan(ROOT_CONTEXT, root_span)\n this.root_spans.set(event.event_id, root_span)\n this.root_contexts.set(event.event_id, root_context)\n return root_context\n }\n\n private completeRootSpan(event_id: string, start_time: Date | undefined, end_time: Date | undefined): void {\n const root_span = this.root_spans.get(event_id)\n if (!root_span) {\n return\n }\n\n root_span.setStatus({ code: SpanStatusCode.OK })\n root_span.end(endTimeAfterStart(start_time, end_time))\n this.root_spans.delete(event_id)\n this.root_contexts.delete(event_id)\n }\n\n private parentContextForEvent(event: BaseEvent): Context | undefined {\n if (event.event_parent_id && event.event_emitted_by_handler_id) {\n const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id))\n if (handler_context) {\n return handler_context\n }\n }\n\n return event.event_parent_id ? this.event_contexts.get(event.event_parent_id) : undefined\n }\n}\n\nfunction handlerSpanKey(event_id: string, handler_id: string): string {\n return `${event_id}:${handler_id}`\n}\n\nfunction dateFromIso(value: string | null | undefined): Date | undefined {\n if (value == null) {\n return undefined\n }\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? undefined : date\n}\n\nfunction endTimeAfterStart(start_time: Date | undefined, end_time: Date | undefined): Date | undefined {\n if (!start_time || !end_time) {\n return end_time\n }\n\n return end_time.getTime() > start_time.getTime() ? end_time : new Date(start_time.getTime() + 1)\n}\n\nfunction resolveRootSpanName(\n root_span_name: OtelTracingMiddlewareOptions['root_span_name'],\n eventbus: EventBus,\n event: BaseEvent\n): string {\n if (typeof root_span_name === 'function') {\n return root_span_name(eventbus, event)\n }\n return root_span_name ?? `abxbus.trace ${eventbus.name}`\n}\n\nfunction resolveAttributes(\n attributes: OtelTracingMiddlewareOptions['root_span_attributes'],\n eventbus: EventBus,\n event: BaseEvent\n): SpanAttributes {\n return typeof attributes === 'function' ? attributes(eventbus, event) : (attributes ?? {})\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction compactAttributes(attributes: Record<string, SpanAttributeValue | null | undefined>): SpanAttributes {\n const compacted: SpanAttributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== null && value !== undefined) {\n compacted[key] = value\n }\n }\n return compacted\n}\n\nfunction recordSpanError(span: Span, error: unknown): void {\n if (error instanceof Error) {\n span.recordException(error)\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })\n return\n }\n\n const message = typeof error === 'string' ? error : 'Unknown abxbus handler error'\n span.recordException(message)\n span.setStatus({ code: SpanStatusCode.ERROR, message })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ROOT_CONTEXT,
|
|
3
|
+
SpanStatusCode,
|
|
4
|
+
trace
|
|
5
|
+
} from "@opentelemetry/api";
|
|
2
6
|
class OtelTracingMiddleware {
|
|
3
7
|
tracer;
|
|
4
8
|
trace_api;
|
|
9
|
+
root_span_name;
|
|
10
|
+
root_span_attributes;
|
|
11
|
+
root_spans = /* @__PURE__ */ new Map();
|
|
12
|
+
root_contexts = /* @__PURE__ */ new Map();
|
|
5
13
|
event_spans = /* @__PURE__ */ new Map();
|
|
6
14
|
event_contexts = /* @__PURE__ */ new Map();
|
|
7
15
|
handler_spans = /* @__PURE__ */ new Map();
|
|
@@ -9,6 +17,8 @@ class OtelTracingMiddleware {
|
|
|
9
17
|
constructor(options = {}) {
|
|
10
18
|
this.trace_api = options.trace_api ?? trace;
|
|
11
19
|
this.tracer = options.tracer ?? this.trace_api.getTracer("abxbus");
|
|
20
|
+
this.root_span_name = options.root_span_name;
|
|
21
|
+
this.root_span_attributes = options.root_span_attributes;
|
|
12
22
|
}
|
|
13
23
|
onEventChange(eventbus, event, status) {
|
|
14
24
|
if (status === "started") {
|
|
@@ -33,7 +43,8 @@ class OtelTracingMiddleware {
|
|
|
33
43
|
if (existing) {
|
|
34
44
|
return existing;
|
|
35
45
|
}
|
|
36
|
-
const parent_context = this.parentContextForEvent(event) ??
|
|
46
|
+
const parent_context = this.parentContextForEvent(event) ?? this.startRootSpan(eventbus, event);
|
|
47
|
+
const start_time = dateFromIso(event.event_started_at);
|
|
37
48
|
const span = this.tracer.startSpan(
|
|
38
49
|
`abxbus.event ${event.event_type}`,
|
|
39
50
|
{
|
|
@@ -43,11 +54,12 @@ class OtelTracingMiddleware {
|
|
|
43
54
|
"abxbus.event.id": event.event_id,
|
|
44
55
|
"abxbus.event.type": event.event_type,
|
|
45
56
|
"abxbus.event.version": event.event_version,
|
|
57
|
+
"abxbus.event.session_id": stringValue(event.session_id),
|
|
46
58
|
"abxbus.event.parent_id": event.event_parent_id,
|
|
47
59
|
"abxbus.event.emitted_by_handler_id": event.event_emitted_by_handler_id,
|
|
48
60
|
"abxbus.event.path": event.event_path.join(" ")
|
|
49
61
|
}),
|
|
50
|
-
startTime:
|
|
62
|
+
startTime: start_time
|
|
51
63
|
},
|
|
52
64
|
parent_context
|
|
53
65
|
);
|
|
@@ -71,9 +83,12 @@ class OtelTracingMiddleware {
|
|
|
71
83
|
"abxbus.event.child_count": event.event_children.length
|
|
72
84
|
})
|
|
73
85
|
);
|
|
74
|
-
|
|
86
|
+
const start_time = dateFromIso(event.event_started_at);
|
|
87
|
+
const end_time = endTimeAfterStart(start_time, dateFromIso(event.event_completed_at));
|
|
88
|
+
span.end(end_time);
|
|
75
89
|
this.event_spans.delete(event.event_id);
|
|
76
90
|
this.event_contexts.delete(event.event_id);
|
|
91
|
+
this.completeRootSpan(event.event_id, start_time, end_time);
|
|
77
92
|
}
|
|
78
93
|
startHandlerSpan(eventbus, event, event_result) {
|
|
79
94
|
const existing = this.handler_spans.get(event_result.id);
|
|
@@ -120,10 +135,48 @@ class OtelTracingMiddleware {
|
|
|
120
135
|
"abxbus.handler.child_count": event_result.event_children.length
|
|
121
136
|
})
|
|
122
137
|
);
|
|
123
|
-
span.end(dateFromIso(event_result.completed_at));
|
|
138
|
+
span.end(endTimeAfterStart(dateFromIso(event_result.started_at), dateFromIso(event_result.completed_at)));
|
|
124
139
|
this.handler_spans.delete(event_result.id);
|
|
125
140
|
this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id));
|
|
126
141
|
}
|
|
142
|
+
startRootSpan(eventbus, event) {
|
|
143
|
+
const existing = this.root_contexts.get(event.event_id);
|
|
144
|
+
if (existing) {
|
|
145
|
+
return existing;
|
|
146
|
+
}
|
|
147
|
+
const session_id = stringValue(event.session_id);
|
|
148
|
+
const root_attributes = resolveAttributes(this.root_span_attributes, eventbus, event);
|
|
149
|
+
const root_span = this.tracer.startSpan(
|
|
150
|
+
resolveRootSpanName(this.root_span_name, eventbus, event),
|
|
151
|
+
{
|
|
152
|
+
attributes: compactAttributes({
|
|
153
|
+
...root_attributes,
|
|
154
|
+
"abxbus.trace.root": true,
|
|
155
|
+
"abxbus.bus.id": eventbus.id,
|
|
156
|
+
"abxbus.bus.name": eventbus.name,
|
|
157
|
+
"abxbus.root_event.id": event.event_id,
|
|
158
|
+
"abxbus.root_event.type": event.event_type,
|
|
159
|
+
"abxbus.root_event.session_id": session_id
|
|
160
|
+
}),
|
|
161
|
+
startTime: dateFromIso(event.event_started_at)
|
|
162
|
+
},
|
|
163
|
+
ROOT_CONTEXT
|
|
164
|
+
);
|
|
165
|
+
const root_context = this.trace_api.setSpan(ROOT_CONTEXT, root_span);
|
|
166
|
+
this.root_spans.set(event.event_id, root_span);
|
|
167
|
+
this.root_contexts.set(event.event_id, root_context);
|
|
168
|
+
return root_context;
|
|
169
|
+
}
|
|
170
|
+
completeRootSpan(event_id, start_time, end_time) {
|
|
171
|
+
const root_span = this.root_spans.get(event_id);
|
|
172
|
+
if (!root_span) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
root_span.setStatus({ code: SpanStatusCode.OK });
|
|
176
|
+
root_span.end(endTimeAfterStart(start_time, end_time));
|
|
177
|
+
this.root_spans.delete(event_id);
|
|
178
|
+
this.root_contexts.delete(event_id);
|
|
179
|
+
}
|
|
127
180
|
parentContextForEvent(event) {
|
|
128
181
|
if (event.event_parent_id && event.event_emitted_by_handler_id) {
|
|
129
182
|
const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id));
|
|
@@ -144,6 +197,24 @@ function dateFromIso(value) {
|
|
|
144
197
|
const date = new Date(value);
|
|
145
198
|
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
146
199
|
}
|
|
200
|
+
function endTimeAfterStart(start_time, end_time) {
|
|
201
|
+
if (!start_time || !end_time) {
|
|
202
|
+
return end_time;
|
|
203
|
+
}
|
|
204
|
+
return end_time.getTime() > start_time.getTime() ? end_time : new Date(start_time.getTime() + 1);
|
|
205
|
+
}
|
|
206
|
+
function resolveRootSpanName(root_span_name, eventbus, event) {
|
|
207
|
+
if (typeof root_span_name === "function") {
|
|
208
|
+
return root_span_name(eventbus, event);
|
|
209
|
+
}
|
|
210
|
+
return root_span_name ?? `abxbus.trace ${eventbus.name}`;
|
|
211
|
+
}
|
|
212
|
+
function resolveAttributes(attributes, eventbus, event) {
|
|
213
|
+
return typeof attributes === "function" ? attributes(eventbus, event) : attributes ?? {};
|
|
214
|
+
}
|
|
215
|
+
function stringValue(value) {
|
|
216
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
217
|
+
}
|
|
147
218
|
function compactAttributes(attributes) {
|
|
148
219
|
const compacted = {};
|
|
149
220
|
for (const [key, value] of Object.entries(attributes)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/middleware_otel_tracing.ts"],
|
|
4
|
-
"sourcesContent": ["import { ROOT_CONTEXT, SpanStatusCode, trace, type Context, type Span, type Tracer } from '@opentelemetry/api'\n\nimport type { BaseEvent } from './base_event.js'\nimport type { EventBus } from './event_bus.js'\nimport type { EventResult } from './event_result.js'\nimport type { EventBusMiddleware } from './middlewares.js'\nimport type { EventStatus } from './types.js'\n\ntype OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>\n\nexport type OtelTracingMiddlewareOptions = {\n tracer?: Tracer\n trace_api?: OpenTelemetryTraceApi\n}\n\nexport class OtelTracingMiddleware implements EventBusMiddleware {\n private readonly tracer: Tracer\n private readonly trace_api: OpenTelemetryTraceApi\n private readonly event_spans = new Map<string, Span>()\n private readonly event_contexts = new Map<string, Context>()\n private readonly handler_spans = new Map<string, Span>()\n private readonly handler_contexts = new Map<string, Context>()\n\n constructor(options: OtelTracingMiddlewareOptions = {}) {\n this.trace_api = options.trace_api ?? trace\n this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')\n }\n\n onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {\n if (status === 'started') {\n this.startEventSpan(eventbus, event)\n return\n }\n\n if (status === 'completed') {\n this.completeEventSpan(eventbus, event)\n }\n }\n\n onEventResultChange(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void {\n if (status === 'started') {\n this.startHandlerSpan(eventbus, event, event_result)\n return\n }\n\n if (status === 'completed') {\n this.completeHandlerSpan(event_result)\n }\n }\n\n private startEventSpan(eventbus: EventBus, event: BaseEvent): Span {\n const existing = this.event_spans.get(event.event_id)\n if (existing) {\n return existing\n }\n\n const parent_context = this.parentContextForEvent(event) ?? ROOT_CONTEXT\n const span = this.tracer.startSpan(\n `abxbus.event ${event.event_type}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.event.version': event.event_version,\n 'abxbus.event.parent_id': event.event_parent_id,\n 'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,\n 'abxbus.event.path': event.event_path.join(' '),\n }),\n startTime: dateFromIso(event.event_started_at),\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.event_spans.set(event.event_id, span)\n this.event_contexts.set(event.event_id, span_context)\n return span\n }\n\n private completeEventSpan(eventbus: EventBus, event: BaseEvent): void {\n const span = this.event_spans.get(event.event_id) ?? this.startEventSpan(eventbus, event)\n if (event.event_errors.length > 0) {\n recordSpanError(span, event.event_errors[0])\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event.status': event.event_status,\n 'abxbus.event.result_count': event.event_results.size,\n 'abxbus.event.error_count': event.event_errors.length,\n 'abxbus.event.child_count': event.event_children.length,\n })\n )\n span.end(dateFromIso(event.event_completed_at))\n this.event_spans.delete(event.event_id)\n this.event_contexts.delete(event.event_id)\n }\n\n private startHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): Span {\n const existing = this.handler_spans.get(event_result.id)\n if (existing) {\n return existing\n }\n\n const parent_context =\n this.event_contexts.get(event.event_id) ?? this.trace_api.setSpan(ROOT_CONTEXT, this.startEventSpan(eventbus, event))\n const span = this.tracer.startSpan(\n `abxbus.handler ${event.event_type} ${event_result.handler_name}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.handler.id': event_result.handler_id,\n 'abxbus.handler.name': event_result.handler_name,\n 'abxbus.handler.file_path': event_result.handler_file_path,\n 'abxbus.handler.event_pattern': event_result.handler.event_pattern,\n 'abxbus.event_result.id': event_result.id,\n }),\n startTime: dateFromIso(event_result.started_at),\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.handler_spans.set(event_result.id, span)\n this.handler_contexts.set(handlerSpanKey(event_result.event_id, event_result.handler_id), span_context)\n return span\n }\n\n private completeHandlerSpan(event_result: EventResult): void {\n const span = this.handler_spans.get(event_result.id)\n if (!span) {\n return\n }\n\n if (event_result.error !== undefined) {\n recordSpanError(span, event_result.error)\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event_result.status': event_result.status,\n 'abxbus.handler.child_count': event_result.event_children.length,\n })\n )\n span.end(dateFromIso(event_result.completed_at))\n this.handler_spans.delete(event_result.id)\n this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id))\n }\n\n private parentContextForEvent(event: BaseEvent): Context | undefined {\n if (event.event_parent_id && event.event_emitted_by_handler_id) {\n const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id))\n if (handler_context) {\n return handler_context\n }\n }\n\n return event.event_parent_id ? this.event_contexts.get(event.event_parent_id) : undefined\n }\n}\n\nfunction handlerSpanKey(event_id: string, handler_id: string): string {\n return `${event_id}:${handler_id}`\n}\n\nfunction dateFromIso(value: string | null | undefined): Date | undefined {\n if (value == null) {\n return undefined\n }\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? undefined : date\n}\n\nfunction compactAttributes(\n attributes: Record<string, string | number | boolean | null | undefined>\n): Record<string, string | number | boolean> {\n const compacted: Record<string, string | number | boolean> = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== null && value !== undefined) {\n compacted[key] = value\n }\n }\n return compacted\n}\n\nfunction recordSpanError(span: Span, error: unknown): void {\n if (error instanceof Error) {\n span.recordException(error)\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })\n return\n }\n\n const message = typeof error === 'string' ? error : 'Unknown abxbus handler error'\n span.recordException(message)\n span.setStatus({ code: SpanStatusCode.ERROR, message })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n ROOT_CONTEXT,\n SpanStatusCode,\n trace,\n type Context,\n type Span,\n type SpanAttributeValue,\n type SpanAttributes,\n type Tracer,\n} from '@opentelemetry/api'\n\nimport type { BaseEvent } from './base_event.js'\nimport type { EventBus } from './event_bus.js'\nimport type { EventResult } from './event_result.js'\nimport type { EventBusMiddleware } from './middlewares.js'\nimport type { EventStatus } from './types.js'\n\ntype OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>\n\nexport type OtelTracingMiddlewareOptions = {\n tracer?: Tracer\n trace_api?: OpenTelemetryTraceApi\n root_span_name?: string | ((eventbus: EventBus, event: BaseEvent) => string)\n root_span_attributes?: SpanAttributes | ((eventbus: EventBus, event: BaseEvent) => SpanAttributes)\n}\n\nexport class OtelTracingMiddleware implements EventBusMiddleware {\n private readonly tracer: Tracer\n private readonly trace_api: OpenTelemetryTraceApi\n private readonly root_span_name: OtelTracingMiddlewareOptions['root_span_name']\n private readonly root_span_attributes: OtelTracingMiddlewareOptions['root_span_attributes']\n private readonly root_spans = new Map<string, Span>()\n private readonly root_contexts = new Map<string, Context>()\n private readonly event_spans = new Map<string, Span>()\n private readonly event_contexts = new Map<string, Context>()\n private readonly handler_spans = new Map<string, Span>()\n private readonly handler_contexts = new Map<string, Context>()\n\n constructor(options: OtelTracingMiddlewareOptions = {}) {\n this.trace_api = options.trace_api ?? trace\n this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')\n this.root_span_name = options.root_span_name\n this.root_span_attributes = options.root_span_attributes\n }\n\n onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {\n if (status === 'started') {\n this.startEventSpan(eventbus, event)\n return\n }\n\n if (status === 'completed') {\n this.completeEventSpan(eventbus, event)\n }\n }\n\n onEventResultChange(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void {\n if (status === 'started') {\n this.startHandlerSpan(eventbus, event, event_result)\n return\n }\n\n if (status === 'completed') {\n this.completeHandlerSpan(event_result)\n }\n }\n\n private startEventSpan(eventbus: EventBus, event: BaseEvent): Span {\n const existing = this.event_spans.get(event.event_id)\n if (existing) {\n return existing\n }\n\n const parent_context = this.parentContextForEvent(event) ?? this.startRootSpan(eventbus, event)\n const start_time = dateFromIso(event.event_started_at)\n const span = this.tracer.startSpan(\n `abxbus.event ${event.event_type}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.event.version': event.event_version,\n 'abxbus.event.session_id': stringValue((event as { session_id?: unknown }).session_id),\n 'abxbus.event.parent_id': event.event_parent_id,\n 'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,\n 'abxbus.event.path': event.event_path.join(' '),\n }),\n startTime: start_time,\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.event_spans.set(event.event_id, span)\n this.event_contexts.set(event.event_id, span_context)\n return span\n }\n\n private completeEventSpan(eventbus: EventBus, event: BaseEvent): void {\n const span = this.event_spans.get(event.event_id) ?? this.startEventSpan(eventbus, event)\n if (event.event_errors.length > 0) {\n recordSpanError(span, event.event_errors[0])\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event.status': event.event_status,\n 'abxbus.event.result_count': event.event_results.size,\n 'abxbus.event.error_count': event.event_errors.length,\n 'abxbus.event.child_count': event.event_children.length,\n })\n )\n const start_time = dateFromIso(event.event_started_at)\n const end_time = endTimeAfterStart(start_time, dateFromIso(event.event_completed_at))\n span.end(end_time)\n this.event_spans.delete(event.event_id)\n this.event_contexts.delete(event.event_id)\n this.completeRootSpan(event.event_id, start_time, end_time)\n }\n\n private startHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): Span {\n const existing = this.handler_spans.get(event_result.id)\n if (existing) {\n return existing\n }\n\n const parent_context =\n this.event_contexts.get(event.event_id) ?? this.trace_api.setSpan(ROOT_CONTEXT, this.startEventSpan(eventbus, event))\n const span = this.tracer.startSpan(\n `abxbus.handler ${event.event_type} ${event_result.handler_name}`,\n {\n attributes: compactAttributes({\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.event.id': event.event_id,\n 'abxbus.event.type': event.event_type,\n 'abxbus.handler.id': event_result.handler_id,\n 'abxbus.handler.name': event_result.handler_name,\n 'abxbus.handler.file_path': event_result.handler_file_path,\n 'abxbus.handler.event_pattern': event_result.handler.event_pattern,\n 'abxbus.event_result.id': event_result.id,\n }),\n startTime: dateFromIso(event_result.started_at),\n },\n parent_context\n )\n const span_context = this.trace_api.setSpan(parent_context, span)\n this.handler_spans.set(event_result.id, span)\n this.handler_contexts.set(handlerSpanKey(event_result.event_id, event_result.handler_id), span_context)\n return span\n }\n\n private completeHandlerSpan(event_result: EventResult): void {\n const span = this.handler_spans.get(event_result.id)\n if (!span) {\n return\n }\n\n if (event_result.error !== undefined) {\n recordSpanError(span, event_result.error)\n } else {\n span.setStatus({ code: SpanStatusCode.OK })\n }\n span.setAttributes(\n compactAttributes({\n 'abxbus.event_result.status': event_result.status,\n 'abxbus.handler.child_count': event_result.event_children.length,\n })\n )\n span.end(endTimeAfterStart(dateFromIso(event_result.started_at), dateFromIso(event_result.completed_at)))\n this.handler_spans.delete(event_result.id)\n this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id))\n }\n\n private startRootSpan(eventbus: EventBus, event: BaseEvent): Context {\n const existing = this.root_contexts.get(event.event_id)\n if (existing) {\n return existing\n }\n\n const session_id = stringValue((event as { session_id?: unknown }).session_id)\n const root_attributes = resolveAttributes(this.root_span_attributes, eventbus, event)\n const root_span = this.tracer.startSpan(\n resolveRootSpanName(this.root_span_name, eventbus, event),\n {\n attributes: compactAttributes({\n ...root_attributes,\n 'abxbus.trace.root': true,\n 'abxbus.bus.id': eventbus.id,\n 'abxbus.bus.name': eventbus.name,\n 'abxbus.root_event.id': event.event_id,\n 'abxbus.root_event.type': event.event_type,\n 'abxbus.root_event.session_id': session_id,\n }),\n startTime: dateFromIso(event.event_started_at),\n },\n ROOT_CONTEXT\n )\n const root_context = this.trace_api.setSpan(ROOT_CONTEXT, root_span)\n this.root_spans.set(event.event_id, root_span)\n this.root_contexts.set(event.event_id, root_context)\n return root_context\n }\n\n private completeRootSpan(event_id: string, start_time: Date | undefined, end_time: Date | undefined): void {\n const root_span = this.root_spans.get(event_id)\n if (!root_span) {\n return\n }\n\n root_span.setStatus({ code: SpanStatusCode.OK })\n root_span.end(endTimeAfterStart(start_time, end_time))\n this.root_spans.delete(event_id)\n this.root_contexts.delete(event_id)\n }\n\n private parentContextForEvent(event: BaseEvent): Context | undefined {\n if (event.event_parent_id && event.event_emitted_by_handler_id) {\n const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id))\n if (handler_context) {\n return handler_context\n }\n }\n\n return event.event_parent_id ? this.event_contexts.get(event.event_parent_id) : undefined\n }\n}\n\nfunction handlerSpanKey(event_id: string, handler_id: string): string {\n return `${event_id}:${handler_id}`\n}\n\nfunction dateFromIso(value: string | null | undefined): Date | undefined {\n if (value == null) {\n return undefined\n }\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? undefined : date\n}\n\nfunction endTimeAfterStart(start_time: Date | undefined, end_time: Date | undefined): Date | undefined {\n if (!start_time || !end_time) {\n return end_time\n }\n\n return end_time.getTime() > start_time.getTime() ? end_time : new Date(start_time.getTime() + 1)\n}\n\nfunction resolveRootSpanName(\n root_span_name: OtelTracingMiddlewareOptions['root_span_name'],\n eventbus: EventBus,\n event: BaseEvent\n): string {\n if (typeof root_span_name === 'function') {\n return root_span_name(eventbus, event)\n }\n return root_span_name ?? `abxbus.trace ${eventbus.name}`\n}\n\nfunction resolveAttributes(\n attributes: OtelTracingMiddlewareOptions['root_span_attributes'],\n eventbus: EventBus,\n event: BaseEvent\n): SpanAttributes {\n return typeof attributes === 'function' ? attributes(eventbus, event) : (attributes ?? {})\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction compactAttributes(attributes: Record<string, SpanAttributeValue | null | undefined>): SpanAttributes {\n const compacted: SpanAttributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== null && value !== undefined) {\n compacted[key] = value\n }\n }\n return compacted\n}\n\nfunction recordSpanError(span: Span, error: unknown): void {\n if (error instanceof Error) {\n span.recordException(error)\n span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })\n return\n }\n\n const message = typeof error === 'string' ? error : 'Unknown abxbus handler error'\n span.recordException(message)\n span.setStatus({ code: SpanStatusCode.ERROR, message })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { trace, type Tracer } from '@opentelemetry/api';
|
|
1
|
+
import { trace, type SpanAttributes, type Tracer } from '@opentelemetry/api';
|
|
2
2
|
import type { BaseEvent } from './base_event.js';
|
|
3
3
|
import type { EventBus } from './event_bus.js';
|
|
4
4
|
import type { EventResult } from './event_result.js';
|
|
@@ -8,10 +8,16 @@ type OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>;
|
|
|
8
8
|
export type OtelTracingMiddlewareOptions = {
|
|
9
9
|
tracer?: Tracer;
|
|
10
10
|
trace_api?: OpenTelemetryTraceApi;
|
|
11
|
+
root_span_name?: string | ((eventbus: EventBus, event: BaseEvent) => string);
|
|
12
|
+
root_span_attributes?: SpanAttributes | ((eventbus: EventBus, event: BaseEvent) => SpanAttributes);
|
|
11
13
|
};
|
|
12
14
|
export declare class OtelTracingMiddleware implements EventBusMiddleware {
|
|
13
15
|
private readonly tracer;
|
|
14
16
|
private readonly trace_api;
|
|
17
|
+
private readonly root_span_name;
|
|
18
|
+
private readonly root_span_attributes;
|
|
19
|
+
private readonly root_spans;
|
|
20
|
+
private readonly root_contexts;
|
|
15
21
|
private readonly event_spans;
|
|
16
22
|
private readonly event_contexts;
|
|
17
23
|
private readonly handler_spans;
|
|
@@ -23,6 +29,8 @@ export declare class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
23
29
|
private completeEventSpan;
|
|
24
30
|
private startHandlerSpan;
|
|
25
31
|
private completeHandlerSpan;
|
|
32
|
+
private startRootSpan;
|
|
33
|
+
private completeRootSpan;
|
|
26
34
|
private parentContextForEvent;
|
|
27
35
|
}
|
|
28
36
|
export {};
|
package/package.json
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ROOT_CONTEXT,
|
|
3
|
+
SpanStatusCode,
|
|
4
|
+
trace,
|
|
5
|
+
type Context,
|
|
6
|
+
type Span,
|
|
7
|
+
type SpanAttributeValue,
|
|
8
|
+
type SpanAttributes,
|
|
9
|
+
type Tracer,
|
|
10
|
+
} from '@opentelemetry/api'
|
|
2
11
|
|
|
3
12
|
import type { BaseEvent } from './base_event.js'
|
|
4
13
|
import type { EventBus } from './event_bus.js'
|
|
@@ -11,11 +20,17 @@ type OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>
|
|
|
11
20
|
export type OtelTracingMiddlewareOptions = {
|
|
12
21
|
tracer?: Tracer
|
|
13
22
|
trace_api?: OpenTelemetryTraceApi
|
|
23
|
+
root_span_name?: string | ((eventbus: EventBus, event: BaseEvent) => string)
|
|
24
|
+
root_span_attributes?: SpanAttributes | ((eventbus: EventBus, event: BaseEvent) => SpanAttributes)
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
17
28
|
private readonly tracer: Tracer
|
|
18
29
|
private readonly trace_api: OpenTelemetryTraceApi
|
|
30
|
+
private readonly root_span_name: OtelTracingMiddlewareOptions['root_span_name']
|
|
31
|
+
private readonly root_span_attributes: OtelTracingMiddlewareOptions['root_span_attributes']
|
|
32
|
+
private readonly root_spans = new Map<string, Span>()
|
|
33
|
+
private readonly root_contexts = new Map<string, Context>()
|
|
19
34
|
private readonly event_spans = new Map<string, Span>()
|
|
20
35
|
private readonly event_contexts = new Map<string, Context>()
|
|
21
36
|
private readonly handler_spans = new Map<string, Span>()
|
|
@@ -24,6 +39,8 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
24
39
|
constructor(options: OtelTracingMiddlewareOptions = {}) {
|
|
25
40
|
this.trace_api = options.trace_api ?? trace
|
|
26
41
|
this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')
|
|
42
|
+
this.root_span_name = options.root_span_name
|
|
43
|
+
this.root_span_attributes = options.root_span_attributes
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {
|
|
@@ -54,7 +71,8 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
54
71
|
return existing
|
|
55
72
|
}
|
|
56
73
|
|
|
57
|
-
const parent_context = this.parentContextForEvent(event) ??
|
|
74
|
+
const parent_context = this.parentContextForEvent(event) ?? this.startRootSpan(eventbus, event)
|
|
75
|
+
const start_time = dateFromIso(event.event_started_at)
|
|
58
76
|
const span = this.tracer.startSpan(
|
|
59
77
|
`abxbus.event ${event.event_type}`,
|
|
60
78
|
{
|
|
@@ -64,11 +82,12 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
64
82
|
'abxbus.event.id': event.event_id,
|
|
65
83
|
'abxbus.event.type': event.event_type,
|
|
66
84
|
'abxbus.event.version': event.event_version,
|
|
85
|
+
'abxbus.event.session_id': stringValue((event as { session_id?: unknown }).session_id),
|
|
67
86
|
'abxbus.event.parent_id': event.event_parent_id,
|
|
68
87
|
'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,
|
|
69
88
|
'abxbus.event.path': event.event_path.join(' '),
|
|
70
89
|
}),
|
|
71
|
-
startTime:
|
|
90
|
+
startTime: start_time,
|
|
72
91
|
},
|
|
73
92
|
parent_context
|
|
74
93
|
)
|
|
@@ -93,9 +112,12 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
93
112
|
'abxbus.event.child_count': event.event_children.length,
|
|
94
113
|
})
|
|
95
114
|
)
|
|
96
|
-
|
|
115
|
+
const start_time = dateFromIso(event.event_started_at)
|
|
116
|
+
const end_time = endTimeAfterStart(start_time, dateFromIso(event.event_completed_at))
|
|
117
|
+
span.end(end_time)
|
|
97
118
|
this.event_spans.delete(event.event_id)
|
|
98
119
|
this.event_contexts.delete(event.event_id)
|
|
120
|
+
this.completeRootSpan(event.event_id, start_time, end_time)
|
|
99
121
|
}
|
|
100
122
|
|
|
101
123
|
private startHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): Span {
|
|
@@ -147,11 +169,53 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
147
169
|
'abxbus.handler.child_count': event_result.event_children.length,
|
|
148
170
|
})
|
|
149
171
|
)
|
|
150
|
-
span.end(dateFromIso(event_result.completed_at))
|
|
172
|
+
span.end(endTimeAfterStart(dateFromIso(event_result.started_at), dateFromIso(event_result.completed_at)))
|
|
151
173
|
this.handler_spans.delete(event_result.id)
|
|
152
174
|
this.handler_contexts.delete(handlerSpanKey(event_result.event_id, event_result.handler_id))
|
|
153
175
|
}
|
|
154
176
|
|
|
177
|
+
private startRootSpan(eventbus: EventBus, event: BaseEvent): Context {
|
|
178
|
+
const existing = this.root_contexts.get(event.event_id)
|
|
179
|
+
if (existing) {
|
|
180
|
+
return existing
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const session_id = stringValue((event as { session_id?: unknown }).session_id)
|
|
184
|
+
const root_attributes = resolveAttributes(this.root_span_attributes, eventbus, event)
|
|
185
|
+
const root_span = this.tracer.startSpan(
|
|
186
|
+
resolveRootSpanName(this.root_span_name, eventbus, event),
|
|
187
|
+
{
|
|
188
|
+
attributes: compactAttributes({
|
|
189
|
+
...root_attributes,
|
|
190
|
+
'abxbus.trace.root': true,
|
|
191
|
+
'abxbus.bus.id': eventbus.id,
|
|
192
|
+
'abxbus.bus.name': eventbus.name,
|
|
193
|
+
'abxbus.root_event.id': event.event_id,
|
|
194
|
+
'abxbus.root_event.type': event.event_type,
|
|
195
|
+
'abxbus.root_event.session_id': session_id,
|
|
196
|
+
}),
|
|
197
|
+
startTime: dateFromIso(event.event_started_at),
|
|
198
|
+
},
|
|
199
|
+
ROOT_CONTEXT
|
|
200
|
+
)
|
|
201
|
+
const root_context = this.trace_api.setSpan(ROOT_CONTEXT, root_span)
|
|
202
|
+
this.root_spans.set(event.event_id, root_span)
|
|
203
|
+
this.root_contexts.set(event.event_id, root_context)
|
|
204
|
+
return root_context
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private completeRootSpan(event_id: string, start_time: Date | undefined, end_time: Date | undefined): void {
|
|
208
|
+
const root_span = this.root_spans.get(event_id)
|
|
209
|
+
if (!root_span) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
root_span.setStatus({ code: SpanStatusCode.OK })
|
|
214
|
+
root_span.end(endTimeAfterStart(start_time, end_time))
|
|
215
|
+
this.root_spans.delete(event_id)
|
|
216
|
+
this.root_contexts.delete(event_id)
|
|
217
|
+
}
|
|
218
|
+
|
|
155
219
|
private parentContextForEvent(event: BaseEvent): Context | undefined {
|
|
156
220
|
if (event.event_parent_id && event.event_emitted_by_handler_id) {
|
|
157
221
|
const handler_context = this.handler_contexts.get(handlerSpanKey(event.event_parent_id, event.event_emitted_by_handler_id))
|
|
@@ -176,10 +240,35 @@ function dateFromIso(value: string | null | undefined): Date | undefined {
|
|
|
176
240
|
return Number.isNaN(date.getTime()) ? undefined : date
|
|
177
241
|
}
|
|
178
242
|
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
243
|
+
function endTimeAfterStart(start_time: Date | undefined, end_time: Date | undefined): Date | undefined {
|
|
244
|
+
if (!start_time || !end_time) {
|
|
245
|
+
return end_time
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return end_time.getTime() > start_time.getTime() ? end_time : new Date(start_time.getTime() + 1)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function resolveRootSpanName(root_span_name: OtelTracingMiddlewareOptions['root_span_name'], eventbus: EventBus, event: BaseEvent): string {
|
|
252
|
+
if (typeof root_span_name === 'function') {
|
|
253
|
+
return root_span_name(eventbus, event)
|
|
254
|
+
}
|
|
255
|
+
return root_span_name ?? `abxbus.trace ${eventbus.name}`
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function resolveAttributes(
|
|
259
|
+
attributes: OtelTracingMiddlewareOptions['root_span_attributes'],
|
|
260
|
+
eventbus: EventBus,
|
|
261
|
+
event: BaseEvent
|
|
262
|
+
): SpanAttributes {
|
|
263
|
+
return typeof attributes === 'function' ? attributes(eventbus, event) : (attributes ?? {})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function stringValue(value: unknown): string | undefined {
|
|
267
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function compactAttributes(attributes: Record<string, SpanAttributeValue | null | undefined>): SpanAttributes {
|
|
271
|
+
const compacted: SpanAttributes = {}
|
|
183
272
|
for (const [key, value] of Object.entries(attributes)) {
|
|
184
273
|
if (value !== null && value !== undefined) {
|
|
185
274
|
compacted[key] = value
|