abxbus 2.4.19 → 2.4.22
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/index.d.ts +1 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/middleware_otel_tracing.d.ts +20 -2
- package/dist/cjs/middleware_otel_tracing.js +224 -2
- package/dist/cjs/middleware_otel_tracing.js.map +2 -2
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/middleware_otel_tracing.js +225 -2
- package/dist/esm/middleware_otel_tracing.js.map +2 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/middleware_otel_tracing.d.ts +20 -2
- package/package.json +4 -1
- package/src/index.ts +1 -0
- package/src/middleware_otel_tracing.ts +292 -3
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ROOT_CONTEXT,
|
|
3
|
+
SpanKind,
|
|
3
4
|
SpanStatusCode,
|
|
4
5
|
trace,
|
|
5
6
|
type Context,
|
|
6
7
|
type Span,
|
|
7
8
|
type SpanAttributeValue,
|
|
8
9
|
type SpanAttributes,
|
|
10
|
+
type SpanContext,
|
|
9
11
|
type Tracer,
|
|
10
12
|
} from '@opentelemetry/api'
|
|
13
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
14
|
+
import { resourceFromAttributes } from '@opentelemetry/resources'
|
|
15
|
+
import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
16
|
+
import type { SpanLimits, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
17
|
+
import { SpanImpl } from '@opentelemetry/sdk-trace-base/build/src/Span.js'
|
|
11
18
|
|
|
12
19
|
import type { BaseEvent } from './base_event.js'
|
|
13
20
|
import type { EventBus } from './event_bus.js'
|
|
@@ -15,11 +22,37 @@ import type { EventResult } from './event_result.js'
|
|
|
15
22
|
import type { EventBusMiddleware } from './middlewares.js'
|
|
16
23
|
import type { EventStatus } from './types.js'
|
|
17
24
|
|
|
18
|
-
type OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'>
|
|
25
|
+
type OpenTelemetryTraceApi = Pick<typeof trace, 'getTracer' | 'setSpan'> & Partial<Pick<typeof trace, 'setSpanContext'>>
|
|
26
|
+
|
|
27
|
+
export type OtelTracingSpanFactoryInput = {
|
|
28
|
+
name: string
|
|
29
|
+
span_context: SpanContext
|
|
30
|
+
parent_span_context?: SpanContext
|
|
31
|
+
attributes: SpanAttributes
|
|
32
|
+
start_time?: Date
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type OtelTracingSpanFactory = (input: OtelTracingSpanFactoryInput) => Span
|
|
36
|
+
|
|
37
|
+
type OtelTracingSpanProviderInternals = {
|
|
38
|
+
_activeSpanProcessor?: SpanProcessor
|
|
39
|
+
_config?: {
|
|
40
|
+
resource?: unknown
|
|
41
|
+
spanLimits?: SpanLimits
|
|
42
|
+
}
|
|
43
|
+
_resource?: unknown
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type OtelTracingSpanProvider = object
|
|
19
47
|
|
|
20
48
|
export type OtelTracingMiddlewareOptions = {
|
|
21
49
|
tracer?: Tracer
|
|
22
50
|
trace_api?: OpenTelemetryTraceApi
|
|
51
|
+
span_provider?: OtelTracingSpanProvider
|
|
52
|
+
span_factory?: OtelTracingSpanFactory
|
|
53
|
+
otlp_endpoint?: string
|
|
54
|
+
service_name?: string
|
|
55
|
+
instrumentation_name?: string
|
|
23
56
|
root_span_name?: string | ((eventbus: EventBus, event: BaseEvent) => string)
|
|
24
57
|
root_span_attributes?: SpanAttributes | ((eventbus: EventBus, event: BaseEvent) => SpanAttributes)
|
|
25
58
|
}
|
|
@@ -27,6 +60,8 @@ export type OtelTracingMiddlewareOptions = {
|
|
|
27
60
|
export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
28
61
|
private readonly tracer: Tracer
|
|
29
62
|
private readonly trace_api: OpenTelemetryTraceApi
|
|
63
|
+
private readonly span_factory?: OtelTracingSpanFactory
|
|
64
|
+
private readonly span_provider?: OtelTracingSpanProvider
|
|
30
65
|
private readonly root_span_name: OtelTracingMiddlewareOptions['root_span_name']
|
|
31
66
|
private readonly root_span_attributes: OtelTracingMiddlewareOptions['root_span_attributes']
|
|
32
67
|
private readonly root_spans = new Map<string, Span>()
|
|
@@ -39,12 +74,21 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
39
74
|
constructor(options: OtelTracingMiddlewareOptions = {}) {
|
|
40
75
|
this.trace_api = options.trace_api ?? trace
|
|
41
76
|
this.tracer = options.tracer ?? this.trace_api.getTracer('abxbus')
|
|
77
|
+
this.span_provider = options.span_provider ?? (options.otlp_endpoint ? createOtlpSpanProvider(options) : undefined)
|
|
78
|
+
this.span_factory =
|
|
79
|
+
options.span_factory ??
|
|
80
|
+
(this.span_provider
|
|
81
|
+
? createProviderSpanFactory(this.trace_api, this.span_provider, options.instrumentation_name ?? 'abxbus')
|
|
82
|
+
: undefined)
|
|
42
83
|
this.root_span_name = options.root_span_name
|
|
43
84
|
this.root_span_attributes = options.root_span_attributes
|
|
44
85
|
}
|
|
45
86
|
|
|
46
87
|
onEventChange(eventbus: EventBus, event: BaseEvent, status: EventStatus): void {
|
|
47
88
|
if (status === 'started') {
|
|
89
|
+
if (this.span_factory) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
48
92
|
this.startEventSpan(eventbus, event)
|
|
49
93
|
return
|
|
50
94
|
}
|
|
@@ -56,12 +100,15 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
56
100
|
|
|
57
101
|
onEventResultChange(eventbus: EventBus, event: BaseEvent, event_result: EventResult, status: EventStatus): void {
|
|
58
102
|
if (status === 'started') {
|
|
103
|
+
if (this.span_factory) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
59
106
|
this.startHandlerSpan(eventbus, event, event_result)
|
|
60
107
|
return
|
|
61
108
|
}
|
|
62
109
|
|
|
63
110
|
if (status === 'completed') {
|
|
64
|
-
this.completeHandlerSpan(event_result)
|
|
111
|
+
this.completeHandlerSpan(eventbus, event, event_result)
|
|
65
112
|
}
|
|
66
113
|
}
|
|
67
114
|
|
|
@@ -98,6 +145,11 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
98
145
|
}
|
|
99
146
|
|
|
100
147
|
private completeEventSpan(eventbus: EventBus, event: BaseEvent): void {
|
|
148
|
+
if (this.span_factory) {
|
|
149
|
+
this.completeEventSpanWithFactory(eventbus, event)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
101
153
|
const span = this.event_spans.get(event.event_id) ?? this.startEventSpan(eventbus, event)
|
|
102
154
|
if (event.event_errors.length > 0) {
|
|
103
155
|
recordSpanError(span, event.event_errors[0])
|
|
@@ -152,7 +204,12 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
152
204
|
return span
|
|
153
205
|
}
|
|
154
206
|
|
|
155
|
-
private completeHandlerSpan(event_result: EventResult): void {
|
|
207
|
+
private completeHandlerSpan(eventbus: EventBus, event: BaseEvent, event_result: EventResult): void {
|
|
208
|
+
if (this.span_factory) {
|
|
209
|
+
this.completeHandlerSpanWithFactory(eventbus, event, event_result)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
156
213
|
const span = this.handler_spans.get(event_result.id)
|
|
157
214
|
if (!span) {
|
|
158
215
|
return
|
|
@@ -226,12 +283,244 @@ export class OtelTracingMiddleware implements EventBusMiddleware {
|
|
|
226
283
|
|
|
227
284
|
return event.event_parent_id ? this.event_contexts.get(event.event_parent_id) : undefined
|
|
228
285
|
}
|
|
286
|
+
|
|
287
|
+
private completeEventSpanWithFactory(eventbus: EventBus, event: BaseEvent): void {
|
|
288
|
+
const root_event = rootEventForEvent(eventbus, event)
|
|
289
|
+
const trace_id = traceIdForRootEvent(root_event.event_id)
|
|
290
|
+
const event_context = eventSpanContext(trace_id, event.event_id)
|
|
291
|
+
const start_time = dateFromIso(event.event_started_at)
|
|
292
|
+
const end_time = endTimeAfterStart(start_time, dateFromIso(event.event_completed_at))
|
|
293
|
+
|
|
294
|
+
if (!event.event_parent_id) {
|
|
295
|
+
const root_span = this.span_factory!({
|
|
296
|
+
name: resolveRootSpanName(this.root_span_name, eventbus, event),
|
|
297
|
+
span_context: rootSpanContext(trace_id, event.event_id),
|
|
298
|
+
attributes: rootSpanAttributes(this.root_span_attributes, eventbus, event),
|
|
299
|
+
start_time,
|
|
300
|
+
})
|
|
301
|
+
root_span.setStatus({ code: SpanStatusCode.OK })
|
|
302
|
+
root_span.end(end_time)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const span = this.span_factory!({
|
|
306
|
+
name: `abxbus.event ${event.event_type}`,
|
|
307
|
+
span_context: event_context,
|
|
308
|
+
parent_span_context: parentSpanContextForEvent(event, trace_id),
|
|
309
|
+
attributes: eventSpanAttributes(eventbus, event),
|
|
310
|
+
start_time,
|
|
311
|
+
})
|
|
312
|
+
if (event.event_errors.length > 0) {
|
|
313
|
+
recordSpanError(span, event.event_errors[0])
|
|
314
|
+
} else {
|
|
315
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
316
|
+
}
|
|
317
|
+
span.end(end_time)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private completeHandlerSpanWithFactory(eventbus: EventBus, event: BaseEvent, event_result: EventResult): void {
|
|
321
|
+
const root_event = rootEventForEvent(eventbus, event)
|
|
322
|
+
const trace_id = traceIdForRootEvent(root_event.event_id)
|
|
323
|
+
const start_time = dateFromIso(event_result.started_at)
|
|
324
|
+
const span = this.span_factory!({
|
|
325
|
+
name: `abxbus.handler ${event.event_type} ${event_result.handler_name}`,
|
|
326
|
+
span_context: handlerSpanContext(trace_id, event_result.event_id, event_result.handler_id),
|
|
327
|
+
parent_span_context: eventSpanContext(trace_id, event.event_id),
|
|
328
|
+
attributes: handlerSpanAttributes(eventbus, event, event_result),
|
|
329
|
+
start_time,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
if (event_result.error !== undefined) {
|
|
333
|
+
recordSpanError(span, event_result.error)
|
|
334
|
+
} else {
|
|
335
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
336
|
+
}
|
|
337
|
+
span.end(endTimeAfterStart(start_time, dateFromIso(event_result.completed_at)))
|
|
338
|
+
}
|
|
229
339
|
}
|
|
230
340
|
|
|
231
341
|
function handlerSpanKey(event_id: string, handler_id: string): string {
|
|
232
342
|
return `${event_id}:${handler_id}`
|
|
233
343
|
}
|
|
234
344
|
|
|
345
|
+
function createOtlpSpanProvider(options: OtelTracingMiddlewareOptions): OtelTracingSpanProvider {
|
|
346
|
+
return new BasicTracerProvider({
|
|
347
|
+
resource: resourceFromAttributes({
|
|
348
|
+
'service.name': options.service_name ?? 'abxbus',
|
|
349
|
+
}),
|
|
350
|
+
spanProcessors: [
|
|
351
|
+
new SimpleSpanProcessor(
|
|
352
|
+
new OTLPTraceExporter({
|
|
353
|
+
url: normalizeOtlpTracesEndpoint(options.otlp_endpoint!),
|
|
354
|
+
})
|
|
355
|
+
),
|
|
356
|
+
],
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function createProviderSpanFactory(
|
|
361
|
+
trace_api: OpenTelemetryTraceApi,
|
|
362
|
+
provider: OtelTracingSpanProvider,
|
|
363
|
+
instrumentation_name: string
|
|
364
|
+
): OtelTracingSpanFactory {
|
|
365
|
+
const provider_internals = provider as OtelTracingSpanProviderInternals
|
|
366
|
+
return (input: OtelTracingSpanFactoryInput): Span => {
|
|
367
|
+
const span_processor = provider_internals._activeSpanProcessor
|
|
368
|
+
const span_limits = provider_internals._config?.spanLimits
|
|
369
|
+
const resource = provider_internals._resource ?? provider_internals._config?.resource
|
|
370
|
+
if (!span_processor || !span_limits || !resource) {
|
|
371
|
+
throw new Error('OtelTracingMiddleware span_provider must be an OpenTelemetry SDK trace provider with active span internals')
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const parent_context = input.parent_span_context
|
|
375
|
+
? (trace_api.setSpanContext ?? trace.setSpanContext)(ROOT_CONTEXT, input.parent_span_context)
|
|
376
|
+
: ROOT_CONTEXT
|
|
377
|
+
return new SpanImpl({
|
|
378
|
+
resource,
|
|
379
|
+
scope: { name: instrumentation_name },
|
|
380
|
+
context: parent_context,
|
|
381
|
+
spanContext: input.span_context,
|
|
382
|
+
parentSpanContext: input.parent_span_context,
|
|
383
|
+
name: input.name,
|
|
384
|
+
kind: SpanKind.INTERNAL,
|
|
385
|
+
attributes: input.attributes,
|
|
386
|
+
startTime: input.start_time,
|
|
387
|
+
spanProcessor: span_processor,
|
|
388
|
+
spanLimits: span_limits,
|
|
389
|
+
} as ConstructorParameters<typeof SpanImpl>[0])
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function normalizeOtlpTracesEndpoint(endpoint: string): string {
|
|
394
|
+
const trimmed = endpoint.replace(/\/+$/, '')
|
|
395
|
+
return trimmed.endsWith('/v1/traces') ? trimmed : `${trimmed}/v1/traces`
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function eventSpanAttributes(eventbus: EventBus, event: BaseEvent): SpanAttributes {
|
|
399
|
+
return compactAttributes({
|
|
400
|
+
'abxbus.bus.id': eventbus.id,
|
|
401
|
+
'abxbus.bus.name': eventbus.name,
|
|
402
|
+
'abxbus.event.id': event.event_id,
|
|
403
|
+
'abxbus.event.type': event.event_type,
|
|
404
|
+
'abxbus.event.version': event.event_version,
|
|
405
|
+
'abxbus.event.session_id': stringValue((event as { session_id?: unknown }).session_id),
|
|
406
|
+
'abxbus.event.parent_id': event.event_parent_id,
|
|
407
|
+
'abxbus.event.emitted_by_handler_id': event.event_emitted_by_handler_id,
|
|
408
|
+
'abxbus.event.path': event.event_path.join(' '),
|
|
409
|
+
'abxbus.event.status': event.event_status,
|
|
410
|
+
'abxbus.event.result_count': event.event_results.size,
|
|
411
|
+
'abxbus.event.error_count': event.event_errors.length,
|
|
412
|
+
'abxbus.event.child_count': event.event_children.length,
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function handlerSpanAttributes(eventbus: EventBus, event: BaseEvent, event_result: EventResult): SpanAttributes {
|
|
417
|
+
return compactAttributes({
|
|
418
|
+
'abxbus.bus.id': eventbus.id,
|
|
419
|
+
'abxbus.bus.name': eventbus.name,
|
|
420
|
+
'abxbus.event.id': event.event_id,
|
|
421
|
+
'abxbus.event.type': event.event_type,
|
|
422
|
+
'abxbus.handler.id': event_result.handler_id,
|
|
423
|
+
'abxbus.handler.name': event_result.handler_name,
|
|
424
|
+
'abxbus.handler.file_path': event_result.handler_file_path,
|
|
425
|
+
'abxbus.handler.event_pattern': event_result.handler.event_pattern,
|
|
426
|
+
'abxbus.event_result.id': event_result.id,
|
|
427
|
+
'abxbus.event_result.status': event_result.status,
|
|
428
|
+
'abxbus.handler.child_count': event_result.event_children.length,
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function rootSpanAttributes(
|
|
433
|
+
root_span_attributes: OtelTracingMiddlewareOptions['root_span_attributes'],
|
|
434
|
+
eventbus: EventBus,
|
|
435
|
+
event: BaseEvent
|
|
436
|
+
): SpanAttributes {
|
|
437
|
+
const session_id = stringValue((event as { session_id?: unknown }).session_id)
|
|
438
|
+
return compactAttributes({
|
|
439
|
+
...resolveAttributes(root_span_attributes, eventbus, event),
|
|
440
|
+
'abxbus.trace.root': true,
|
|
441
|
+
'abxbus.bus.id': eventbus.id,
|
|
442
|
+
'abxbus.bus.name': eventbus.name,
|
|
443
|
+
'abxbus.root_event.id': event.event_id,
|
|
444
|
+
'abxbus.root_event.type': event.event_type,
|
|
445
|
+
'abxbus.root_event.session_id': session_id,
|
|
446
|
+
'abxbus.root_event.status': event.event_status,
|
|
447
|
+
'abxbus.root_event.error_count': event.event_errors.length,
|
|
448
|
+
'abxbus.root_event.child_count': event.event_children.length,
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function rootEventForEvent(eventbus: EventBus, event: BaseEvent): BaseEvent {
|
|
453
|
+
let current = event._event_original ?? event
|
|
454
|
+
const seen = new Set<string>()
|
|
455
|
+
while (current.event_parent_id && !seen.has(current.event_id)) {
|
|
456
|
+
seen.add(current.event_id)
|
|
457
|
+
const parent = eventbus.findEventById(current.event_parent_id)
|
|
458
|
+
if (!parent) {
|
|
459
|
+
break
|
|
460
|
+
}
|
|
461
|
+
current = parent._event_original ?? parent
|
|
462
|
+
}
|
|
463
|
+
return current
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function parentSpanContextForEvent(event: BaseEvent, trace_id: string): SpanContext {
|
|
467
|
+
if (!event.event_parent_id) {
|
|
468
|
+
return rootSpanContext(trace_id, event.event_id)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (event.event_emitted_by_handler_id) {
|
|
472
|
+
return handlerSpanContext(trace_id, event.event_parent_id, event.event_emitted_by_handler_id)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return eventSpanContext(trace_id, event.event_parent_id)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function rootSpanContext(trace_id: string, event_id: string): SpanContext {
|
|
479
|
+
return {
|
|
480
|
+
traceId: trace_id,
|
|
481
|
+
spanId: deterministicSpanId(`abxbus.root:${event_id}`),
|
|
482
|
+
traceFlags: 1,
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function eventSpanContext(trace_id: string, event_id: string): SpanContext {
|
|
487
|
+
return {
|
|
488
|
+
traceId: trace_id,
|
|
489
|
+
spanId: deterministicSpanId(`abxbus.event:${event_id}`),
|
|
490
|
+
traceFlags: 1,
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function handlerSpanContext(trace_id: string, event_id: string, handler_id: string): SpanContext {
|
|
495
|
+
return {
|
|
496
|
+
traceId: trace_id,
|
|
497
|
+
spanId: deterministicSpanId(`abxbus.handler:${event_id}:${handler_id}`),
|
|
498
|
+
traceFlags: 1,
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function traceIdForRootEvent(event_id: string): string {
|
|
503
|
+
return `${fnv1a64Hex(`abxbus.trace.a:${event_id}`)}${fnv1a64Hex(`abxbus.trace.b:${event_id}`)}`
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function deterministicSpanId(input: string): string {
|
|
507
|
+
return fnv1a64Hex(input)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function fnv1a64Hex(input: string): string {
|
|
511
|
+
let hash = 0xcbf29ce484222325n
|
|
512
|
+
const prime = 0x100000001b3n
|
|
513
|
+
const mask = 0xffffffffffffffffn
|
|
514
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
515
|
+
hash ^= BigInt(input.charCodeAt(index))
|
|
516
|
+
hash = (hash * prime) & mask
|
|
517
|
+
}
|
|
518
|
+
if (hash === 0n) {
|
|
519
|
+
hash = 1n
|
|
520
|
+
}
|
|
521
|
+
return hash.toString(16).padStart(16, '0')
|
|
522
|
+
}
|
|
523
|
+
|
|
235
524
|
function dateFromIso(value: string | null | undefined): Date | undefined {
|
|
236
525
|
if (value == null) {
|
|
237
526
|
return undefined
|