nuxt-devtools-observatory 0.1.30 → 0.1.32
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/README.md +117 -30
- package/client/.env.example +2 -1
- package/client/dist/assets/index-5Wl1XYRH.js +17 -0
- package/client/dist/assets/index-DT_QUiIh.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/src/App.vue +4 -0
- package/client/src/components/Flamegraph.vue +442 -0
- package/client/src/components/SpanInspector.vue +446 -0
- package/client/src/components/TraceFilter.vue +342 -0
- package/client/src/components/WaterfallView.vue +443 -0
- package/client/src/composables/composable-search.ts +124 -0
- package/client/src/composables/trace-render-aggregation.ts +254 -0
- package/client/src/composables/useExportImport.ts +63 -0
- package/client/src/composables/useTraceFilter.ts +160 -0
- package/client/src/stores/observatory.ts +13 -1
- package/client/src/views/ComposableTracker.vue +65 -30
- package/client/src/views/RenderHeatmap.vue +63 -1
- package/client/src/views/TraceViewer.vue +1212 -0
- package/client/src/views/TransitionTimeline.vue +1 -6
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +31 -45
- package/dist/runtime/composables/composable-registry.d.ts +19 -0
- package/dist/runtime/composables/composable-registry.js +63 -5
- package/dist/runtime/composables/render-registry.js +74 -110
- package/dist/runtime/composables/transition-registry.js +103 -28
- package/dist/runtime/instrumentation/asyncData.d.ts +9 -0
- package/dist/runtime/instrumentation/asyncData.js +49 -0
- package/dist/runtime/instrumentation/component.d.ts +2 -0
- package/dist/runtime/instrumentation/component.js +126 -0
- package/dist/runtime/instrumentation/fetch.d.ts +2 -0
- package/dist/runtime/instrumentation/fetch.js +89 -0
- package/dist/runtime/instrumentation/route.d.ts +6 -0
- package/dist/runtime/instrumentation/route.js +41 -0
- package/dist/runtime/nitro/fetch-capture.d.ts +1 -2
- package/dist/runtime/nitro/fetch-capture.js +85 -7
- package/dist/runtime/nitro/ssr-trace-store.d.ts +85 -0
- package/dist/runtime/nitro/ssr-trace-store.js +84 -0
- package/dist/runtime/plugin.js +82 -2
- package/dist/runtime/tracing/context.d.ts +9 -0
- package/dist/runtime/tracing/context.js +15 -0
- package/dist/runtime/tracing/trace.d.ts +25 -0
- package/dist/runtime/tracing/trace.js +0 -0
- package/dist/runtime/tracing/traceStore.d.ts +39 -0
- package/dist/runtime/tracing/traceStore.js +101 -0
- package/dist/runtime/tracing/tracing.d.ts +27 -0
- package/dist/runtime/tracing/tracing.js +48 -0
- package/package.json +5 -1
- package/client/.env +0 -16
- package/client/dist/assets/index-BCaKoHBH.js +0 -17
- package/client/dist/assets/index-BmGW_M3W.css +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-request SSR trace collector.
|
|
3
|
+
*
|
|
4
|
+
* Each incoming SSR page request gets its own record keyed by a unique
|
|
5
|
+
* requestId (stored on H3 `event.context`). Spans are accumulated during the
|
|
6
|
+
* request lifecycle and drained once the HTML is rendered (via the
|
|
7
|
+
* `render:html` Nitro hook), so they can be injected into the page as inline
|
|
8
|
+
* JSON for the client plugin to pick up.
|
|
9
|
+
*/
|
|
10
|
+
export interface SsrSpan {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
/** Milliseconds relative to request start (0 = request began). */
|
|
15
|
+
startTime: number;
|
|
16
|
+
endTime?: number;
|
|
17
|
+
durationMs?: number;
|
|
18
|
+
status: 'ok' | 'error' | 'active';
|
|
19
|
+
metadata?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface SsrTraceRecord {
|
|
22
|
+
traceId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
spans: SsrSpan[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Open a new per-request SSR record. A `navigation` span covering the full
|
|
28
|
+
* request is pre-populated; its end time is filled in when `drainSsrRecord`
|
|
29
|
+
* is called.
|
|
30
|
+
* @param {string} requestId - Unique identifier for the HTTP request, stored in `event.context`.
|
|
31
|
+
* @param {string} route - Request pathname (e.g. `/dashboard`).
|
|
32
|
+
* @param {string} method - HTTP method in upper-case (e.g. `GET`).
|
|
33
|
+
* @returns {SsrTraceRecord} The newly created `SsrTraceRecord` keyed by `requestId`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createSsrRecord(requestId: string, route: string, method: string): SsrTraceRecord;
|
|
36
|
+
/**
|
|
37
|
+
* Append an SSR-side fetch span. `startMs` / `endMs` are milliseconds
|
|
38
|
+
* relative to request start (same origin as `createSsrRecord`).
|
|
39
|
+
* @param {string} requestId - The request identifier returned by `createSsrRecord`.
|
|
40
|
+
* @param {object} opts - Span options.
|
|
41
|
+
* @param {string} opts.url - The request URL or path that was fetched.
|
|
42
|
+
* @param {string} opts.method - HTTP method in upper-case (e.g. `GET`).
|
|
43
|
+
* @param {number} opts.startMs - Span start, in ms relative to the request start time.
|
|
44
|
+
* @param {number} opts.endMs - Span end, in ms relative to the request start time.
|
|
45
|
+
* @param {number} [opts.statusCode] - Optional HTTP response status code.
|
|
46
|
+
* @param {boolean} [opts.error] - Set to `true` to mark the span status as `error`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function addSsrFetchSpan(requestId: string, opts: {
|
|
49
|
+
url: string;
|
|
50
|
+
method: string;
|
|
51
|
+
startMs: number;
|
|
52
|
+
endMs: number;
|
|
53
|
+
statusCode?: number;
|
|
54
|
+
error?: boolean;
|
|
55
|
+
}): void;
|
|
56
|
+
/**
|
|
57
|
+
* Append a generic SSR phase span (e.g. `render:html`, `afterResponse`) to
|
|
58
|
+
* an existing request record.
|
|
59
|
+
* @param {string} requestId - The request identifier returned by `createSsrRecord`.
|
|
60
|
+
* @param {object} opts - Span options.
|
|
61
|
+
* @param {string} opts.name - Human-readable span name.
|
|
62
|
+
* @param {string} [opts.type] - Span type. Defaults to `server`.
|
|
63
|
+
* @param {number} opts.startMs - Span start, in ms relative to request start.
|
|
64
|
+
* @param {number} opts.endMs - Span end, in ms relative to request start.
|
|
65
|
+
* @param {boolean} [opts.error] - Set to `true` to mark the span status as `error`.
|
|
66
|
+
* @param {Record<string, unknown>} [opts.metadata] - Additional metadata fields.
|
|
67
|
+
*/
|
|
68
|
+
export declare function addSsrPhaseSpan(requestId: string, opts: {
|
|
69
|
+
name: string;
|
|
70
|
+
type?: string;
|
|
71
|
+
startMs: number;
|
|
72
|
+
endMs: number;
|
|
73
|
+
error?: boolean;
|
|
74
|
+
metadata?: Record<string, unknown>;
|
|
75
|
+
}): void;
|
|
76
|
+
/**
|
|
77
|
+
* Finalize and remove the record for `requestId`. The pre-populated
|
|
78
|
+
* navigation span is closed with `durationMs`. Returns `undefined` if the
|
|
79
|
+
* requestId is unknown (e.g. non-page requests that never called
|
|
80
|
+
* `createSsrRecord`).
|
|
81
|
+
* @param {string} requestId - The request identifier returned by `createSsrRecord`.
|
|
82
|
+
* @param {number} durationMs - Total SSR request duration in milliseconds, used to close the navigation span.
|
|
83
|
+
* @returns {SsrTraceRecord | undefined} The completed `SsrTraceRecord`, or `undefined` if no record exists for `requestId`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function drainSsrRecord(requestId: string, durationMs: number): SsrTraceRecord | undefined;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2
|
+
let _counter = 0;
|
|
3
|
+
function newId(prefix) {
|
|
4
|
+
_counter = (_counter + 1) % 999999;
|
|
5
|
+
return `${prefix}_ssr_${Date.now()}_${_counter}`;
|
|
6
|
+
}
|
|
7
|
+
export function createSsrRecord(requestId, route, method) {
|
|
8
|
+
const record = {
|
|
9
|
+
traceId: newId("trace"),
|
|
10
|
+
name: `ssr:${route}`,
|
|
11
|
+
spans: [
|
|
12
|
+
{
|
|
13
|
+
id: newId("span"),
|
|
14
|
+
name: "ssr:navigation",
|
|
15
|
+
type: "navigation",
|
|
16
|
+
startTime: 0,
|
|
17
|
+
status: "active",
|
|
18
|
+
metadata: {
|
|
19
|
+
origin: "ssr",
|
|
20
|
+
route,
|
|
21
|
+
method
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
pending.set(requestId, record);
|
|
27
|
+
return record;
|
|
28
|
+
}
|
|
29
|
+
export function addSsrFetchSpan(requestId, opts) {
|
|
30
|
+
const record = pending.get(requestId);
|
|
31
|
+
if (!record) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const durationMs = Math.max(opts.endMs - opts.startMs, 0);
|
|
35
|
+
record.spans.push({
|
|
36
|
+
id: newId("span"),
|
|
37
|
+
name: opts.url,
|
|
38
|
+
type: "fetch",
|
|
39
|
+
startTime: opts.startMs,
|
|
40
|
+
endTime: opts.endMs,
|
|
41
|
+
durationMs,
|
|
42
|
+
status: opts.error ? "error" : "ok",
|
|
43
|
+
metadata: {
|
|
44
|
+
origin: "ssr",
|
|
45
|
+
url: opts.url,
|
|
46
|
+
method: opts.method,
|
|
47
|
+
statusCode: opts.statusCode
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function addSsrPhaseSpan(requestId, opts) {
|
|
52
|
+
const record = pending.get(requestId);
|
|
53
|
+
if (!record) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const durationMs = Math.max(opts.endMs - opts.startMs, 0);
|
|
57
|
+
record.spans.push({
|
|
58
|
+
id: newId("span"),
|
|
59
|
+
name: opts.name,
|
|
60
|
+
type: opts.type ?? "server",
|
|
61
|
+
startTime: opts.startMs,
|
|
62
|
+
endTime: opts.endMs,
|
|
63
|
+
durationMs,
|
|
64
|
+
status: opts.error ? "error" : "ok",
|
|
65
|
+
metadata: {
|
|
66
|
+
origin: "ssr",
|
|
67
|
+
...opts.metadata ?? {}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function drainSsrRecord(requestId, durationMs) {
|
|
72
|
+
const record = pending.get(requestId);
|
|
73
|
+
pending.delete(requestId);
|
|
74
|
+
if (!record) {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
const navSpan = record.spans[0];
|
|
78
|
+
if (navSpan) {
|
|
79
|
+
navSpan.endTime = durationMs;
|
|
80
|
+
navSpan.durationMs = durationMs;
|
|
81
|
+
navSpan.status = "ok";
|
|
82
|
+
}
|
|
83
|
+
return record;
|
|
84
|
+
}
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -5,6 +5,10 @@ import { setupFetchRegistry } from "./composables/fetch-registry.js";
|
|
|
5
5
|
import { setupProvideInjectRegistry } from "./composables/provide-inject-registry.js";
|
|
6
6
|
import { setupRenderRegistry } from "./composables/render-registry.js";
|
|
7
7
|
import { setupTransitionRegistry } from "./composables/transition-registry.js";
|
|
8
|
+
import { setupComponentInstrumentation } from "./instrumentation/component.js";
|
|
9
|
+
import { setupFetchInstrumentation } from "./instrumentation/fetch.js";
|
|
10
|
+
import { setupRouteInstrumentation } from "./instrumentation/route.js";
|
|
11
|
+
import { traceStore } from "./tracing/traceStore.js";
|
|
8
12
|
export default defineNuxtPlugin(() => {
|
|
9
13
|
if (!import.meta.dev) {
|
|
10
14
|
return;
|
|
@@ -41,7 +45,55 @@ export default defineNuxtPlugin(() => {
|
|
|
41
45
|
if (config.transitionTracker) {
|
|
42
46
|
registries.transition = setupTransitionRegistry();
|
|
43
47
|
}
|
|
48
|
+
function mergeSsrSpans() {
|
|
49
|
+
if (!import.meta.client) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const el = document.getElementById("__observatory_ssr_spans__");
|
|
53
|
+
if (!el) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let record;
|
|
57
|
+
try {
|
|
58
|
+
record = JSON.parse(el.textContent ?? "");
|
|
59
|
+
} catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!record?.traceId || !Array.isArray(record.spans)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const navDurationMs = record.spans[0]?.durationMs ?? 0;
|
|
66
|
+
const traceStartTime = performance.now() - navDurationMs;
|
|
67
|
+
traceStore.createTrace({
|
|
68
|
+
id: record.traceId,
|
|
69
|
+
name: record.name,
|
|
70
|
+
startTime: traceStartTime,
|
|
71
|
+
metadata: { origin: "ssr" }
|
|
72
|
+
});
|
|
73
|
+
for (const span of record.spans) {
|
|
74
|
+
traceStore.addSpan({
|
|
75
|
+
id: span.id,
|
|
76
|
+
traceId: record.traceId,
|
|
77
|
+
name: span.name,
|
|
78
|
+
type: span.type,
|
|
79
|
+
startTime: traceStartTime + span.startTime,
|
|
80
|
+
endTime: span.endTime !== void 0 ? traceStartTime + span.endTime : void 0,
|
|
81
|
+
status: span.status,
|
|
82
|
+
metadata: { ...span.metadata ?? {}, origin: "ssr" }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
traceStore.endTrace(record.traceId, {
|
|
86
|
+
endTime: traceStartTime + navDurationMs,
|
|
87
|
+
status: "ok",
|
|
88
|
+
metadata: { origin: "ssr" }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
44
91
|
if (import.meta.client) {
|
|
92
|
+
if (config.traceViewer) {
|
|
93
|
+
setupComponentInstrumentation(nuxtApp);
|
|
94
|
+
setupFetchInstrumentation(nuxtApp);
|
|
95
|
+
mergeSsrSpans();
|
|
96
|
+
}
|
|
45
97
|
delete window.__observatory__;
|
|
46
98
|
window.__observatory__ = registries;
|
|
47
99
|
const composableRegistry = registries.composable;
|
|
@@ -121,6 +173,11 @@ export default defineNuxtPlugin(() => {
|
|
|
121
173
|
});
|
|
122
174
|
if (import.meta.client) {
|
|
123
175
|
const router = useRouter();
|
|
176
|
+
if (config.traceViewer) {
|
|
177
|
+
setupRouteInstrumentation(nuxtApp, {
|
|
178
|
+
getCurrentPath: () => router.currentRoute.value.path ?? "/"
|
|
179
|
+
});
|
|
180
|
+
}
|
|
124
181
|
router.beforeEach(
|
|
125
182
|
(_to, from) => {
|
|
126
183
|
if (!from || from.name === void 0) {
|
|
@@ -175,7 +232,8 @@ export default defineNuxtPlugin(() => {
|
|
|
175
232
|
fetch: Array.isArray(snapshot.fetch) ? snapshot.fetch.length : 0,
|
|
176
233
|
composables: Array.isArray(snapshot.composables) ? snapshot.composables.length : 0,
|
|
177
234
|
renders: Array.isArray(snapshot.renders) ? snapshot.renders.length : 0,
|
|
178
|
-
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0
|
|
235
|
+
transitions: Array.isArray(snapshot.transitions) ? snapshot.transitions.length : 0,
|
|
236
|
+
traces: Array.isArray(snapshot.traces) ? snapshot.traces.length : 0
|
|
179
237
|
});
|
|
180
238
|
lastSnapshotSignature = JSON.stringify(snapshot);
|
|
181
239
|
import.meta.hot.send("observatory:snapshot", snapshot);
|
|
@@ -207,13 +265,35 @@ export default defineNuxtPlugin(() => {
|
|
|
207
265
|
const hasGetSnapshot = reg && typeof reg.getSnapshot === "function";
|
|
208
266
|
snapshot[key === "composable" ? "composables" : key === "render" ? "renders" : key === "transition" ? "transitions" : key] = hasGetSnapshot ? safeParse(reg.getSnapshot(), fallback) : fallback;
|
|
209
267
|
}
|
|
268
|
+
snapshot.traces = config.traceViewer ? traceStore.getAllTraces().map((trace) => ({
|
|
269
|
+
id: trace.id,
|
|
270
|
+
name: trace.name,
|
|
271
|
+
startTime: trace.startTime,
|
|
272
|
+
endTime: trace.endTime,
|
|
273
|
+
durationMs: trace.durationMs,
|
|
274
|
+
status: trace.status,
|
|
275
|
+
metadata: trace.metadata,
|
|
276
|
+
spans: trace.spans.map((span) => ({
|
|
277
|
+
id: span.id,
|
|
278
|
+
traceId: span.traceId,
|
|
279
|
+
parentSpanId: span.parentSpanId,
|
|
280
|
+
name: span.name,
|
|
281
|
+
type: span.type,
|
|
282
|
+
startTime: span.startTime,
|
|
283
|
+
endTime: span.endTime,
|
|
284
|
+
durationMs: span.durationMs,
|
|
285
|
+
status: span.status,
|
|
286
|
+
metadata: span.metadata
|
|
287
|
+
}))
|
|
288
|
+
})) : [];
|
|
210
289
|
snapshot.features = {
|
|
211
290
|
fetchDashboard: !!registries.fetch,
|
|
212
291
|
provideInjectGraph: !!registries.provideInject,
|
|
213
292
|
composableTracker: !!registries.composable,
|
|
214
293
|
composableNavigationMode,
|
|
215
294
|
renderHeatmap: !!registries.render,
|
|
216
|
-
transitionTracker: !!registries.transition
|
|
295
|
+
transitionTracker: !!registries.transition,
|
|
296
|
+
traceViewer: !!config.traceViewer
|
|
217
297
|
};
|
|
218
298
|
return snapshot;
|
|
219
299
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare const TRACE_CONTEXT_KEY = "__observatory_trace_context__";
|
|
2
|
+
type TraceContextCarrier = {
|
|
3
|
+
[TRACE_CONTEXT_KEY]?: {
|
|
4
|
+
currentTraceId?: string;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export declare function setCurrentTraceId(traceId: string | undefined, carrier?: TraceContextCarrier): void;
|
|
8
|
+
export declare function getCurrentTraceId(carrier?: TraceContextCarrier): string | undefined;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const TRACE_CONTEXT_KEY = "__observatory_trace_context__";
|
|
2
|
+
function getGlobalCarrier() {
|
|
3
|
+
return globalThis;
|
|
4
|
+
}
|
|
5
|
+
export function setCurrentTraceId(traceId, carrier) {
|
|
6
|
+
const target = carrier ?? getGlobalCarrier();
|
|
7
|
+
if (!target[TRACE_CONTEXT_KEY]) {
|
|
8
|
+
target[TRACE_CONTEXT_KEY] = {};
|
|
9
|
+
}
|
|
10
|
+
target[TRACE_CONTEXT_KEY].currentTraceId = traceId;
|
|
11
|
+
}
|
|
12
|
+
export function getCurrentTraceId(carrier) {
|
|
13
|
+
const target = carrier ?? getGlobalCarrier();
|
|
14
|
+
return target[TRACE_CONTEXT_KEY]?.currentTraceId;
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SpanType = 'render' | 'component' | 'transition' | 'fetch' | 'composable' | 'navigation' | 'custom' | (string & {});
|
|
2
|
+
export type SpanStatus = 'active' | 'ok' | 'error' | 'cancelled';
|
|
3
|
+
export interface Span {
|
|
4
|
+
id: string;
|
|
5
|
+
traceId: string;
|
|
6
|
+
parentSpanId?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: SpanType;
|
|
9
|
+
startTime: number;
|
|
10
|
+
endTime?: number;
|
|
11
|
+
durationMs?: number;
|
|
12
|
+
status: SpanStatus;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export type TraceStatus = 'active' | 'ok' | 'error' | 'cancelled';
|
|
16
|
+
export interface Trace {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
startTime: number;
|
|
20
|
+
endTime?: number;
|
|
21
|
+
durationMs?: number;
|
|
22
|
+
status: TraceStatus;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
spans: Span[];
|
|
25
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Span, SpanStatus, Trace, TraceStatus } from './trace.js';
|
|
2
|
+
export interface CreateTraceInput {
|
|
3
|
+
id?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
startTime?: number;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface AddSpanInput {
|
|
9
|
+
id?: string;
|
|
10
|
+
traceId: string;
|
|
11
|
+
parentSpanId?: string;
|
|
12
|
+
name: string;
|
|
13
|
+
type: Span['type'];
|
|
14
|
+
startTime?: number;
|
|
15
|
+
endTime?: number;
|
|
16
|
+
status?: SpanStatus;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface EndTraceInput {
|
|
20
|
+
endTime?: number;
|
|
21
|
+
status?: TraceStatus;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export declare class TraceStore {
|
|
25
|
+
private readonly traces;
|
|
26
|
+
createTrace(input?: CreateTraceInput): Trace;
|
|
27
|
+
addSpan(input: AddSpanInput): Span;
|
|
28
|
+
endTrace(traceId: string, input?: EndTraceInput): Trace | null;
|
|
29
|
+
endSpan(spanId: string, traceId: string, input?: {
|
|
30
|
+
endTime?: number;
|
|
31
|
+
status?: SpanStatus;
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
}): Span | null;
|
|
34
|
+
getTrace(traceId: string): Trace | undefined;
|
|
35
|
+
getAllTraces(): Trace[];
|
|
36
|
+
clear(): void;
|
|
37
|
+
private ensureTrace;
|
|
38
|
+
}
|
|
39
|
+
export declare const traceStore: TraceStore;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
function createId(prefix) {
|
|
2
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
3
|
+
return `${prefix}_${Date.now()}_${random}`;
|
|
4
|
+
}
|
|
5
|
+
function computeDuration(startTime, endTime) {
|
|
6
|
+
return Math.max(endTime - startTime, 0);
|
|
7
|
+
}
|
|
8
|
+
export class TraceStore {
|
|
9
|
+
traces = /* @__PURE__ */ new Map();
|
|
10
|
+
createTrace(input = {}) {
|
|
11
|
+
const startTime = input.startTime ?? performance.now();
|
|
12
|
+
const trace = {
|
|
13
|
+
id: input.id ?? createId("trace"),
|
|
14
|
+
name: input.name ?? "trace",
|
|
15
|
+
startTime,
|
|
16
|
+
status: "active",
|
|
17
|
+
metadata: input.metadata,
|
|
18
|
+
spans: []
|
|
19
|
+
};
|
|
20
|
+
this.traces.set(trace.id, trace);
|
|
21
|
+
return trace;
|
|
22
|
+
}
|
|
23
|
+
addSpan(input) {
|
|
24
|
+
const trace = this.ensureTrace(input.traceId, input.startTime);
|
|
25
|
+
const startTime = input.startTime ?? performance.now();
|
|
26
|
+
const endTime = input.endTime;
|
|
27
|
+
const span = {
|
|
28
|
+
id: input.id ?? createId("span"),
|
|
29
|
+
traceId: trace.id,
|
|
30
|
+
parentSpanId: input.parentSpanId,
|
|
31
|
+
name: input.name,
|
|
32
|
+
type: input.type,
|
|
33
|
+
startTime,
|
|
34
|
+
endTime,
|
|
35
|
+
durationMs: endTime !== void 0 ? computeDuration(startTime, endTime) : void 0,
|
|
36
|
+
status: input.status ?? (endTime !== void 0 ? "ok" : "active"),
|
|
37
|
+
metadata: input.metadata
|
|
38
|
+
};
|
|
39
|
+
trace.spans.push(span);
|
|
40
|
+
if (trace.endTime !== void 0) {
|
|
41
|
+
trace.durationMs = computeDuration(trace.startTime, trace.endTime);
|
|
42
|
+
}
|
|
43
|
+
return span;
|
|
44
|
+
}
|
|
45
|
+
endTrace(traceId, input = {}) {
|
|
46
|
+
const trace = this.traces.get(traceId);
|
|
47
|
+
if (!trace) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const endTime = input.endTime ?? performance.now();
|
|
51
|
+
trace.endTime = endTime;
|
|
52
|
+
trace.durationMs = computeDuration(trace.startTime, endTime);
|
|
53
|
+
trace.status = input.status ?? "ok";
|
|
54
|
+
if (input.metadata) {
|
|
55
|
+
trace.metadata = {
|
|
56
|
+
...trace.metadata ?? {},
|
|
57
|
+
...input.metadata
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return trace;
|
|
61
|
+
}
|
|
62
|
+
endSpan(spanId, traceId, input = {}) {
|
|
63
|
+
const trace = this.traces.get(traceId);
|
|
64
|
+
if (!trace) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const span = trace.spans.find((item) => item.id === spanId);
|
|
68
|
+
if (!span) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const endTime = input.endTime ?? performance.now();
|
|
72
|
+
span.endTime = endTime;
|
|
73
|
+
span.durationMs = computeDuration(span.startTime, endTime);
|
|
74
|
+
span.status = input.status ?? "ok";
|
|
75
|
+
if (input.metadata) {
|
|
76
|
+
span.metadata = {
|
|
77
|
+
...span.metadata ?? {},
|
|
78
|
+
...input.metadata
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return span;
|
|
82
|
+
}
|
|
83
|
+
getTrace(traceId) {
|
|
84
|
+
return this.traces.get(traceId);
|
|
85
|
+
}
|
|
86
|
+
getAllTraces() {
|
|
87
|
+
return [...this.traces.values()];
|
|
88
|
+
}
|
|
89
|
+
clear() {
|
|
90
|
+
this.traces.clear();
|
|
91
|
+
}
|
|
92
|
+
ensureTrace(traceId, startTime) {
|
|
93
|
+
const existing = this.traces.get(traceId);
|
|
94
|
+
if (existing) {
|
|
95
|
+
return existing;
|
|
96
|
+
}
|
|
97
|
+
const trace = this.createTrace({ id: traceId, startTime });
|
|
98
|
+
return trace;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export const traceStore = new TraceStore();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type TraceStore } from './traceStore.js';
|
|
2
|
+
import type { Span, SpanStatus, SpanType, Trace } from './trace.js';
|
|
3
|
+
export interface StartSpanInput {
|
|
4
|
+
name: string;
|
|
5
|
+
type: SpanType;
|
|
6
|
+
traceId?: string;
|
|
7
|
+
parentSpanId?: string;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
startTime?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface EndSpanInput {
|
|
12
|
+
endTime?: number;
|
|
13
|
+
status?: SpanStatus;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface SpanHandle {
|
|
17
|
+
trace: Trace;
|
|
18
|
+
span: Span;
|
|
19
|
+
end: (input?: EndSpanInput) => Span;
|
|
20
|
+
}
|
|
21
|
+
export interface StartSpanOptions {
|
|
22
|
+
store?: TraceStore;
|
|
23
|
+
carrier?: object;
|
|
24
|
+
traceName?: string;
|
|
25
|
+
traceMetadata?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export declare function startSpan(input: StartSpanInput, options?: StartSpanOptions): SpanHandle;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getCurrentTraceId, setCurrentTraceId } from "./context.js";
|
|
2
|
+
import { traceStore } from "./traceStore.js";
|
|
3
|
+
export function startSpan(input, options = {}) {
|
|
4
|
+
const store = options.store ?? traceStore;
|
|
5
|
+
const activeTraceId = input.traceId ?? getCurrentTraceId(options.carrier);
|
|
6
|
+
let trace = activeTraceId ? store.getTrace(activeTraceId) : void 0;
|
|
7
|
+
if (!trace) {
|
|
8
|
+
trace = store.createTrace({
|
|
9
|
+
id: activeTraceId,
|
|
10
|
+
name: options.traceName ?? input.name,
|
|
11
|
+
metadata: options.traceMetadata,
|
|
12
|
+
startTime: input.startTime
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
setCurrentTraceId(trace.id, options.carrier);
|
|
16
|
+
const span = store.addSpan({
|
|
17
|
+
traceId: trace.id,
|
|
18
|
+
parentSpanId: input.parentSpanId,
|
|
19
|
+
name: input.name,
|
|
20
|
+
type: input.type,
|
|
21
|
+
metadata: input.metadata,
|
|
22
|
+
startTime: input.startTime
|
|
23
|
+
});
|
|
24
|
+
let ended = false;
|
|
25
|
+
const end = (endInput = {}) => {
|
|
26
|
+
if (ended) {
|
|
27
|
+
return span;
|
|
28
|
+
}
|
|
29
|
+
const endedSpan = store.endSpan(span.id, trace.id, {
|
|
30
|
+
endTime: endInput.endTime,
|
|
31
|
+
status: endInput.status,
|
|
32
|
+
metadata: endInput.metadata
|
|
33
|
+
});
|
|
34
|
+
ended = true;
|
|
35
|
+
if (endedSpan) {
|
|
36
|
+
span.endTime = endedSpan.endTime;
|
|
37
|
+
span.durationMs = endedSpan.durationMs;
|
|
38
|
+
span.status = endedSpan.status;
|
|
39
|
+
span.metadata = endedSpan.metadata;
|
|
40
|
+
}
|
|
41
|
+
return span;
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
trace,
|
|
45
|
+
span,
|
|
46
|
+
end
|
|
47
|
+
};
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-devtools-observatory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "Nuxt DevTools: useFetch Dashboard, provide/inject Graph, Composable Tracker, Render Heatmap",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"./runtime/fetch-registry": {
|
|
28
28
|
"types": "./dist/runtime/composables/fetch-registry.d.ts",
|
|
29
29
|
"import": "./dist/runtime/composables/fetch-registry.js"
|
|
30
|
+
},
|
|
31
|
+
"./runtime/async-data-instrumentation": {
|
|
32
|
+
"types": "./dist/runtime/instrumentation/asyncData.d.ts",
|
|
33
|
+
"import": "./dist/runtime/instrumentation/asyncData.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"files": [
|
package/client/.env
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Observatory registry/configurable limits
|
|
2
|
-
VITE_OBSERVATORY_FETCH_DASHBOARD=true
|
|
3
|
-
VITE_OBSERVATORY_PROVIDE_INJECT_GRAPH=true
|
|
4
|
-
VITE_OBSERVATORY_COMPOSABLE_TRACKER=true
|
|
5
|
-
VITE_OBSERVATORY_RENDER_HEATMAP=true
|
|
6
|
-
VITE_OBSERVATORY_TRANSITION_TRACKER=true
|
|
7
|
-
VITE_OBSERVATORY_MAX_FETCH_ENTRIES=200
|
|
8
|
-
VITE_OBSERVATORY_MAX_PAYLOAD_BYTES=10000
|
|
9
|
-
VITE_OBSERVATORY_MAX_TRANSITIONS=500
|
|
10
|
-
VITE_OBSERVATORY_MAX_COMPOSABLE_HISTORY=50
|
|
11
|
-
VITE_OBSERVATORY_MAX_COMPOSABLE_ENTRIES=300
|
|
12
|
-
VITE_OBSERVATORY_MAX_RENDER_TIMELINE=100
|
|
13
|
-
VITE_OBSERVATORY_HEATMAP_THRESHOLD_COUNT=3
|
|
14
|
-
VITE_OBSERVATORY_HEATMAP_THRESHOLD_TIME=1600
|
|
15
|
-
VITE_OBSERVATORY_HEATMAP_HIDE_INTERNALS=true
|
|
16
|
-
VITE_OBSERVATORY_INSTRUMENT_SERVER=true
|