@composed-di/observability 0.5.0-alpha → 0.7.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js.map +1 -1
- package/dist/dashboardClient.d.ts +17 -11
- package/dist/dashboardClient.d.ts.map +1 -1
- package/dist/dashboardClient.js +28 -11
- package/dist/dashboardClient.js.map +1 -1
- package/dist/dashboardEventListener.d.ts +27 -8
- package/dist/dashboardEventListener.d.ts.map +1 -1
- package/dist/dashboardEventListener.js +38 -16
- package/dist/dashboardEventListener.js.map +1 -1
- package/dist/dashboardHtml.d.ts +6 -0
- package/dist/dashboardHtml.d.ts.map +1 -1
- package/dist/dashboardHtml.js +618 -98
- package/dist/dashboardHtml.js.map +1 -1
- package/dist/dashboardInstrumentation.d.ts +44 -0
- package/dist/dashboardInstrumentation.d.ts.map +1 -0
- package/dist/dashboardInstrumentation.js +119 -0
- package/dist/dashboardInstrumentation.js.map +1 -0
- package/dist/dashboardServer.d.ts +12 -8
- package/dist/dashboardServer.d.ts.map +1 -1
- package/dist/dashboardServer.js +52 -14
- package/dist/dashboardServer.js.map +1 -1
- package/dist/events.d.ts +20 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/moduleGraph.d.ts.map +1 -1
- package/dist/moduleGraph.js.map +1 -1
- package/package.json +24 -23
- package/src/cli.ts +19 -19
- package/src/dashboardClient.ts +94 -83
- package/src/dashboardHtml.ts +620 -100
- package/src/dashboardInstrumentation.ts +153 -0
- package/src/dashboardServer.ts +183 -148
- package/src/events.ts +55 -35
- package/src/index.ts +5 -5
- package/src/moduleGraph.ts +9 -9
- package/src/dashboardEventListener.ts +0 -104
package/src/events.ts
CHANGED
|
@@ -1,71 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event and state types shared between the dashboard
|
|
2
|
+
* Event and state types shared between the dashboard instrumentation, the
|
|
3
3
|
* dashboard server, and the browser client (over the SSE wire).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/** Classifies what a span represents in the service lifecycle. */
|
|
7
|
-
export type SpanKind = 'initialize' | 'dispose' | 'call'
|
|
7
|
+
export type SpanKind = 'initialize' | 'dispose' | 'call'
|
|
8
8
|
|
|
9
9
|
/** Emitted when a traced function starts executing. */
|
|
10
10
|
export interface SpanStart {
|
|
11
|
-
type: 'start'
|
|
11
|
+
type: 'start'
|
|
12
12
|
/** Monotonically increasing span id, unique per tracer. */
|
|
13
|
-
id: number
|
|
13
|
+
id: number
|
|
14
14
|
/** Id of the span that was active when this one started, if any. */
|
|
15
|
-
parentId: number | null
|
|
15
|
+
parentId: number | null
|
|
16
16
|
/** The qualified span name, e.g. "Database.query". */
|
|
17
|
-
name: string
|
|
17
|
+
name: string
|
|
18
18
|
/** The service the span belongs to, or null if it could not be attributed. */
|
|
19
|
-
service: string | null
|
|
19
|
+
service: string | null
|
|
20
20
|
/** The method or lifecycle step, e.g. "query" or "initialize". */
|
|
21
|
-
method: string
|
|
22
|
-
kind: SpanKind
|
|
21
|
+
method: string
|
|
22
|
+
kind: SpanKind
|
|
23
23
|
/** Epoch milliseconds. */
|
|
24
|
-
time: number
|
|
24
|
+
time: number
|
|
25
|
+
/** Method arguments serialized to JSON, when capture is enabled. */
|
|
26
|
+
args?: string
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** Emitted when a traced function finishes (or throws/rejects). */
|
|
28
30
|
export interface SpanEnd {
|
|
29
|
-
type: 'end'
|
|
31
|
+
type: 'end'
|
|
30
32
|
/** Matches the id of the corresponding SpanStart. */
|
|
31
|
-
id: number
|
|
33
|
+
id: number
|
|
32
34
|
/** Epoch milliseconds. */
|
|
33
|
-
time: number
|
|
34
|
-
durationMs: number
|
|
35
|
+
time: number
|
|
36
|
+
durationMs: number
|
|
35
37
|
/** The error message when the traced function threw or rejected. */
|
|
36
|
-
error: string | null
|
|
38
|
+
error: string | null
|
|
39
|
+
/** Return / resolved value serialized to JSON, when capture is enabled. */
|
|
40
|
+
result?: string
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
export type SpanEvent = SpanStart | SpanEnd
|
|
43
|
+
export type SpanEvent = SpanStart | SpanEnd
|
|
40
44
|
|
|
41
45
|
export type ServiceStatus =
|
|
42
46
|
| 'pending'
|
|
43
47
|
| 'initializing'
|
|
44
48
|
| 'ready'
|
|
45
49
|
| 'error'
|
|
46
|
-
| 'disposed'
|
|
50
|
+
| 'disposed'
|
|
51
|
+
|
|
52
|
+
/** Aggregated counters for a single method of a service. */
|
|
53
|
+
export interface MethodStats {
|
|
54
|
+
/** Number of completed call spans for this method. */
|
|
55
|
+
calls: number
|
|
56
|
+
/** Number of those calls that ended with an error. */
|
|
57
|
+
errors: number
|
|
58
|
+
/** Sum of completed call durations, for averaging. */
|
|
59
|
+
totalMs: number
|
|
60
|
+
/** Duration of the most recently completed call. */
|
|
61
|
+
lastMs: number
|
|
62
|
+
}
|
|
47
63
|
|
|
48
64
|
/** Aggregated per-service counters maintained by the dashboard. */
|
|
49
65
|
export interface ServiceStats {
|
|
50
|
-
status: ServiceStatus
|
|
66
|
+
status: ServiceStatus
|
|
51
67
|
/** Duration of the last successful initialization, if any. */
|
|
52
|
-
initMs: number | null
|
|
68
|
+
initMs: number | null
|
|
53
69
|
/** Number of completed method-call spans. */
|
|
54
|
-
calls: number
|
|
70
|
+
calls: number
|
|
55
71
|
/** Number of spans (of any kind) that ended with an error. */
|
|
56
|
-
errors: number
|
|
72
|
+
errors: number
|
|
57
73
|
/** Sum of completed method-call durations, for averaging. */
|
|
58
|
-
totalCallMs: number
|
|
74
|
+
totalCallMs: number
|
|
75
|
+
/** Per-method call breakdown, keyed by method name. */
|
|
76
|
+
methods: Record<string, MethodStats>
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
export interface GraphNode {
|
|
62
|
-
name: string
|
|
80
|
+
name: string
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
/** A dependency edge: `from` depends on `to`. */
|
|
66
84
|
export interface GraphEdge {
|
|
67
|
-
from: string
|
|
68
|
-
to: string
|
|
85
|
+
from: string
|
|
86
|
+
to: string
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
/**
|
|
@@ -74,21 +92,23 @@ export interface GraphEdge {
|
|
|
74
92
|
* updated per-service stats so the client needs no correlation logic.
|
|
75
93
|
*/
|
|
76
94
|
export interface WireEvent {
|
|
77
|
-
span: SpanEvent
|
|
95
|
+
span: SpanEvent
|
|
78
96
|
/** Service of the span; on end events resolved from the matching start. */
|
|
79
|
-
service: string | null
|
|
80
|
-
method: string
|
|
81
|
-
kind: SpanKind
|
|
97
|
+
service: string | null
|
|
98
|
+
method: string
|
|
99
|
+
kind: SpanKind
|
|
82
100
|
/** Service of the parent span, when the span was started by another service. */
|
|
83
|
-
parentService: string | null
|
|
101
|
+
parentService: string | null
|
|
102
|
+
/** Serialized arguments; on end events resolved from the matching start. */
|
|
103
|
+
args: string | null
|
|
84
104
|
/** Updated stats for `service`, present when the event changed them. */
|
|
85
|
-
stats: ServiceStats | null
|
|
105
|
+
stats: ServiceStats | null
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
/** Full state sent to a client when it connects. */
|
|
89
109
|
export interface DashboardSnapshot {
|
|
90
|
-
nodes: GraphNode[]
|
|
91
|
-
edges: GraphEdge[]
|
|
92
|
-
services: Record<string, ServiceStats
|
|
93
|
-
recent: WireEvent[]
|
|
110
|
+
nodes: GraphNode[]
|
|
111
|
+
edges: GraphEdge[]
|
|
112
|
+
services: Record<string, ServiceStats>
|
|
113
|
+
recent: WireEvent[]
|
|
94
114
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from './events'
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './dashboardClient'
|
|
4
|
-
export * from './dashboardServer'
|
|
5
|
-
export * from './moduleGraph'
|
|
1
|
+
export * from './events'
|
|
2
|
+
export * from './dashboardInstrumentation'
|
|
3
|
+
export * from './dashboardClient'
|
|
4
|
+
export * from './dashboardServer'
|
|
5
|
+
export * from './moduleGraph'
|
package/src/moduleGraph.ts
CHANGED
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
ServiceKey,
|
|
3
3
|
ServiceModule,
|
|
4
4
|
ServiceSelectorKey,
|
|
5
|
-
} from '@composed-di/core'
|
|
6
|
-
import { GraphEdge, GraphNode } from './events'
|
|
5
|
+
} from '@composed-di/core'
|
|
6
|
+
import { GraphEdge, GraphNode } from './events'
|
|
7
7
|
|
|
8
8
|
export interface ModuleGraph {
|
|
9
|
-
nodes: GraphNode[]
|
|
10
|
-
edges: GraphEdge[]
|
|
9
|
+
nodes: GraphNode[]
|
|
10
|
+
edges: GraphEdge[]
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -18,20 +18,20 @@ export interface ModuleGraph {
|
|
|
18
18
|
export function moduleGraph(module: ServiceModule): ModuleGraph {
|
|
19
19
|
const nodes = module.factories.map((factory) => ({
|
|
20
20
|
name: factory.provides.name,
|
|
21
|
-
}))
|
|
21
|
+
}))
|
|
22
22
|
|
|
23
23
|
const edges = module.factories.flatMap((factory) =>
|
|
24
24
|
factory.dependsOn.flatMap((dependency: ServiceKey<unknown>) => {
|
|
25
25
|
const targets =
|
|
26
26
|
dependency instanceof ServiceSelectorKey
|
|
27
27
|
? dependency.values
|
|
28
|
-
: [dependency]
|
|
28
|
+
: [dependency]
|
|
29
29
|
return targets.map((target) => ({
|
|
30
30
|
from: factory.provides.name,
|
|
31
31
|
to: target.name,
|
|
32
|
-
}))
|
|
32
|
+
}))
|
|
33
33
|
}),
|
|
34
|
-
)
|
|
34
|
+
)
|
|
35
35
|
|
|
36
|
-
return { nodes, edges }
|
|
36
|
+
return { nodes, edges }
|
|
37
37
|
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import { performance } from 'node:perf_hooks';
|
|
3
|
-
import type {
|
|
4
|
-
DisposeContext,
|
|
5
|
-
EventSpan,
|
|
6
|
-
InitializeContext,
|
|
7
|
-
MethodCallContext,
|
|
8
|
-
ServiceEventListener,
|
|
9
|
-
} from '@composed-di/core';
|
|
10
|
-
import { SpanEvent, SpanKind } from './events';
|
|
11
|
-
|
|
12
|
-
/** The span context propagated across sync and async call boundaries. */
|
|
13
|
-
interface SpanContext {
|
|
14
|
-
id: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A ServiceEventListener that turns service events into structured
|
|
19
|
-
* start/end span events for the realtime dashboard.
|
|
20
|
-
*
|
|
21
|
-
* - Uses AsyncLocalStorage to link spans to the span that was active when
|
|
22
|
-
* they started, which lets the dashboard draw cross-service call edges
|
|
23
|
-
* (e.g. UserService.getUser -> Database.query). Because a listener does
|
|
24
|
-
* not control the invocation of the operation it observes, the context
|
|
25
|
-
* is entered with `enterWith` and can outlive the span within the same
|
|
26
|
-
* synchronous frame; the dashboard tolerates this, as parents are only
|
|
27
|
-
* resolved among still-open spans.
|
|
28
|
-
* - Emits events synchronously to subscribers; it never buffers.
|
|
29
|
-
*/
|
|
30
|
-
export class DashboardEventListener implements ServiceEventListener {
|
|
31
|
-
private readonly context = new AsyncLocalStorage<SpanContext>();
|
|
32
|
-
private readonly listeners = new Set<(event: SpanEvent) => void>();
|
|
33
|
-
private nextId = 1;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Subscribes to span events.
|
|
37
|
-
*
|
|
38
|
-
* @returns A function that removes the subscription.
|
|
39
|
-
*/
|
|
40
|
-
subscribe(listener: (event: SpanEvent) => void): () => void {
|
|
41
|
-
this.listeners.add(listener);
|
|
42
|
-
return () => this.listeners.delete(listener);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
onInitialize({ key }: InitializeContext): EventSpan {
|
|
46
|
-
return this.startSpan(key.name, 'initialize', 'initialize');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
onDispose({ key }: DisposeContext): EventSpan {
|
|
50
|
-
return this.startSpan(key.name, 'dispose', 'dispose');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
onMethodCall({ key, methodName }: MethodCallContext): EventSpan {
|
|
54
|
-
return this.startSpan(key.name, methodName, 'call');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private startSpan(service: string, method: string, kind: SpanKind): EventSpan {
|
|
58
|
-
const id = this.nextId++;
|
|
59
|
-
const parent = this.context.getStore();
|
|
60
|
-
|
|
61
|
-
this.emit({
|
|
62
|
-
type: 'start',
|
|
63
|
-
id,
|
|
64
|
-
parentId: parent?.id ?? null,
|
|
65
|
-
name: `${service}.${method}`,
|
|
66
|
-
service,
|
|
67
|
-
method,
|
|
68
|
-
kind,
|
|
69
|
-
time: Date.now(),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const startedAt = performance.now();
|
|
73
|
-
let ended = false;
|
|
74
|
-
const end = (error: string | null) => {
|
|
75
|
-
if (ended) return;
|
|
76
|
-
ended = true;
|
|
77
|
-
this.emit({
|
|
78
|
-
type: 'end',
|
|
79
|
-
id,
|
|
80
|
-
time: Date.now(),
|
|
81
|
-
durationMs: performance.now() - startedAt,
|
|
82
|
-
error,
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// The observed operation runs right after this hook returns, in the
|
|
87
|
-
// same synchronous frame, so entering the context here makes this span
|
|
88
|
-
// the parent of any spans started inside the operation.
|
|
89
|
-
this.context.enterWith({ id });
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
end: () => end(null),
|
|
93
|
-
error: (error) => end(errorMessage(error)),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private emit(event: SpanEvent): void {
|
|
98
|
-
this.listeners.forEach((listener) => listener(event));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function errorMessage(error: unknown): string {
|
|
103
|
-
return error instanceof Error ? error.message : String(error);
|
|
104
|
-
}
|