Skip to content

Commit 4af22aa

Browse files
feat: add universal IDs to client nodes
1 parent c4dcfc9 commit 4af22aa

36 files changed

+1988
-882
lines changed

packages/kit/src/core/config/index.spec.js

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const get_defaults = (prefix = '') => ({
101101
serviceWorker: {
102102
register: true
103103
},
104-
tracing: false,
104+
tracing: undefined,
105105
typescript: {},
106106
paths: {
107107
base: '',
@@ -407,44 +407,22 @@ test('errors on loading config with incorrect default export', async () => {
407407
});
408408

409409
test('accepts valid tracing values', () => {
410-
// Test boolean values
411410
assert.doesNotThrow(() => {
412411
validate_config({
413412
kit: {
414-
tracing: true
415-
}
416-
});
417-
});
418-
419-
assert.doesNotThrow(() => {
420-
validate_config({
421-
kit: {
422-
tracing: false
423-
}
424-
});
425-
});
426-
427-
// Test string values
428-
assert.doesNotThrow(() => {
429-
validate_config({
430-
kit: {
431-
tracing: 'server'
432-
}
433-
});
434-
});
435-
436-
assert.doesNotThrow(() => {
437-
validate_config({
438-
kit: {
439-
tracing: 'client'
413+
experimental: {
414+
tracing: 'server'
415+
}
440416
}
441417
});
442418
});
443419

444420
assert.doesNotThrow(() => {
445421
validate_config({
446422
kit: {
447-
tracing: undefined
423+
experimental: {
424+
tracing: undefined
425+
}
448426
}
449427
});
450428
});
@@ -454,27 +432,33 @@ test('errors on invalid tracing values', () => {
454432
assert.throws(() => {
455433
validate_config({
456434
kit: {
457-
// @ts-expect-error - given value expected to throw
458-
tracing: 'invalid'
435+
experimental: {
436+
// @ts-expect-error - given value expected to throw
437+
tracing: true
438+
}
459439
}
460440
});
461-
}, /^config\.kit\.tracing should be true, false, "server", or "client"$/);
441+
}, /^config\.kit\.tracing should be undefined or "server"$/);
462442

463443
assert.throws(() => {
464444
validate_config({
465445
kit: {
466-
// @ts-expect-error - given value expected to throw
467-
tracing: 42
446+
experimental: {
447+
// @ts-expect-error - given value expected to throw
448+
tracing: false
449+
}
468450
}
469451
});
470-
}, /^config\.kit\.tracing should be true, false, "server", or "client"$/);
452+
}, /^config\.kit\.tracing should be undefined or "server"$/);
471453

472454
assert.throws(() => {
473455
validate_config({
474456
kit: {
475-
// @ts-expect-error - given value expected to throw
476-
tracing: null
457+
experimental: {
458+
// @ts-expect-error - given value expected to throw
459+
tracing: 'client'
460+
}
477461
}
478462
});
479-
}, /^config\.kit\.tracing should be true, false, "server", or "client"$/);
463+
}, /^config\.kit\.tracing should be undefined or "server"$/);
480464
});

packages/kit/src/core/config/options.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ const options = object(
120120
privatePrefix: string('')
121121
}),
122122

123+
experimental: object({
124+
tracing: validate(undefined, (input, keypath) => {
125+
if (input !== 'server') {
126+
throw new Error(`${keypath} should be undefined or "server"`);
127+
}
128+
return input;
129+
})
130+
}),
131+
123132
files: object({
124133
assets: string('static'),
125134
hooks: object({
@@ -270,12 +279,6 @@ const options = object(
270279
files: fun((filename) => !/\.DS_Store/.test(filename))
271280
}),
272281

273-
tracing: validate(false, (input, keypath) => {
274-
if (typeof input === 'boolean') return input;
275-
if (input === 'server' || input === 'client') return input;
276-
throw new Error(`${keypath} should be true, false, "server", or "client"`);
277-
}),
278-
279282
typescript: object({
280283
config: fun((config) => config)
281284
}),

packages/kit/src/core/sync/write_client_manifest.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,6 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
174174
175175
export const hash = ${s(kit.router.type === 'hash')};
176176
177-
export const tracing = ${s(kit.tracing === true || kit.tracing === 'client')};
178-
179177
export const decode = (type, value) => decoders[type](value);
180178
181179
export { default as root } from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';

packages/kit/src/core/sync/write_server.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import { set_building, set_prerendering } from '__sveltekit/environment';
3333
import { set_assets } from '__sveltekit/paths';
3434
import { set_manifest, set_read_implementation } from '__sveltekit/server';
3535
import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js';
36+
import { get_tracer, enable_tracing } from '${runtime_directory}/telemetry/get_tracer.js';
37+
38+
if (${s(config.kit.tracing === 'server')}) {
39+
enable_tracing();
40+
}
3641
3742
export const options = {
3843
app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
@@ -60,7 +65,7 @@ export const options = {
6065
.replace(/%sveltekit\.status%/g, '" + status + "')
6166
.replace(/%sveltekit\.error\.message%/g, '" + message + "')}
6267
},
63-
tracing: ${config.kit.tracing === true || config.kit.tracing === 'server'},
68+
tracer: get_tracer(),
6469
version_hash: ${s(hash(config.kit.version.name))}
6570
};
6671

packages/kit/src/exports/hooks/sequence.js

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/** @import { Handle, RequestEvent, ResolveOptions } from '@sveltejs/kit' */
2+
/** @import { MaybePromise } from 'types' */
3+
import { with_event } from '../../runtime/app/server/event.js';
4+
import { get_tracer } from '../../runtime/telemetry/get_tracer.js';
5+
import { record_span } from '../../runtime/telemetry/record_span.js';
6+
17
/**
28
* A helper function for sequencing multiple `handle` calls in a middleware-like manner.
39
* The behavior for the `handle` options is as follows:
@@ -66,56 +72,76 @@
6672
* first post-processing
6773
* ```
6874
*
69-
* @param {...import('@sveltejs/kit').Handle} handlers The chain of `handle` functions
70-
* @returns {import('@sveltejs/kit').Handle}
75+
* @param {...Handle} handlers The chain of `handle` functions
76+
* @returns {Handle}
7177
*/
7278
export function sequence(...handlers) {
7379
const length = handlers.length;
7480
if (!length) return ({ event, resolve }) => resolve(event);
7581

76-
return ({ event, resolve }) => {
82+
return async ({ event, resolve }) => {
83+
const rootSpan = /** @type {NonNullable<RequestEvent['tracing']>['rootSpan']} */ (
84+
event.tracing?.rootSpan
85+
);
86+
const tracer = await get_tracer();
7787
return apply_handle(0, event, {});
7888

7989
/**
8090
* @param {number} i
81-
* @param {import('@sveltejs/kit').RequestEvent} event
82-
* @param {import('@sveltejs/kit').ResolveOptions | undefined} parent_options
83-
* @returns {import('types').MaybePromise<Response>}
91+
* @param {RequestEvent} event
92+
* @param {ResolveOptions | undefined} parent_options
93+
* @returns {MaybePromise<Response>}
8494
*/
8595
function apply_handle(i, event, parent_options) {
8696
const handle = handlers[i];
8797

88-
return handle({
89-
event,
90-
resolve: (event, options) => {
91-
/** @type {import('@sveltejs/kit').ResolveOptions['transformPageChunk']} */
92-
const transformPageChunk = async ({ html, done }) => {
93-
if (options?.transformPageChunk) {
94-
html = (await options.transformPageChunk({ html, done })) ?? '';
95-
}
98+
return record_span({
99+
tracer,
100+
name: 'sveltekit.handle.child',
101+
attributes: {
102+
'sveltekit.handle.child.index': i
103+
},
104+
fn: async (span) => {
105+
const traced_event = { ...event, tracing: { rootSpan, currentSpan: span } };
106+
return await with_event(traced_event, () =>
107+
handle({
108+
event: traced_event,
109+
resolve: (event, options) => {
110+
/** @type {ResolveOptions['transformPageChunk']} */
111+
const transformPageChunk = async ({ html, done }) => {
112+
if (options?.transformPageChunk) {
113+
html = (await options.transformPageChunk({ html, done })) ?? '';
114+
}
96115

97-
if (parent_options?.transformPageChunk) {
98-
html = (await parent_options.transformPageChunk({ html, done })) ?? '';
99-
}
116+
if (parent_options?.transformPageChunk) {
117+
html = (await parent_options.transformPageChunk({ html, done })) ?? '';
118+
}
100119

101-
return html;
102-
};
120+
return html;
121+
};
103122

104-
/** @type {import('@sveltejs/kit').ResolveOptions['filterSerializedResponseHeaders']} */
105-
const filterSerializedResponseHeaders =
106-
parent_options?.filterSerializedResponseHeaders ??
107-
options?.filterSerializedResponseHeaders;
123+
/** @type {ResolveOptions['filterSerializedResponseHeaders']} */
124+
const filterSerializedResponseHeaders =
125+
parent_options?.filterSerializedResponseHeaders ??
126+
options?.filterSerializedResponseHeaders;
108127

109-
/** @type {import('@sveltejs/kit').ResolveOptions['preload']} */
110-
const preload = parent_options?.preload ?? options?.preload;
128+
/** @type {ResolveOptions['preload']} */
129+
const preload = parent_options?.preload ?? options?.preload;
111130

112-
return i < length - 1
113-
? apply_handle(i + 1, event, {
114-
transformPageChunk,
115-
filterSerializedResponseHeaders,
116-
preload
117-
})
118-
: resolve(event, { transformPageChunk, filterSerializedResponseHeaders, preload });
131+
return i < length - 1
132+
? apply_handle(i + 1, event, {
133+
transformPageChunk,
134+
filterSerializedResponseHeaders,
135+
preload
136+
})
137+
: resolve(event, {
138+
transformPageChunk,
139+
filterSerializedResponseHeaders,
140+
preload
141+
});
142+
}
143+
})
144+
);
119145
}
120146
});
121147
}

packages/kit/src/exports/public.d.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '../types/private.js';
1919
import { BuildData, SSRNodeLoader, SSRRoute, ValidatedConfig } from 'types';
2020
import type { SvelteConfig } from '@sveltejs/vite-plugin-svelte';
21+
import { Span } from '@opentelemetry/api';
2122

2223
export { PrerenderOption } from '../types/private.js';
2324

@@ -401,6 +402,15 @@ export interface KitConfig {
401402
*/
402403
privatePrefix?: string;
403404
};
405+
/** Experimental features. Here be dragons. Breaking changes may occur in minor releases. */
406+
experimental?: {
407+
/**
408+
* Whether to enable serverside OpenTelemetry tracing for SvelteKit operations including handle hooks, load functions, and form actions.
409+
* @default undefined
410+
* @since 2.22.0 // TODO: update this before publishing
411+
*/
412+
tracing?: 'server';
413+
};
404414
/**
405415
* Where to find various files within your project.
406416
*/
@@ -686,16 +696,6 @@ export interface KitConfig {
686696
*/
687697
files?(filepath: string): boolean;
688698
};
689-
/**
690-
* Whether to enable OpenTelemetry tracing for SvelteKit operations including handle hooks, load functions, and form actions.
691-
* - `true` - Enable tracing for both server and client
692-
* - `false` - Disable tracing
693-
* - `'server'` - Enable tracing only on the server side
694-
* - `'client'` - Enable tracing only on the client side
695-
* @default false
696-
* @since 2.22.0
697-
*/
698-
tracing?: boolean | 'server' | 'client';
699699
typescript?: {
700700
/**
701701
* A function that allows you to edit the generated `tsconfig.json`. You can mutate the config (recommended) or return a new one.
@@ -977,6 +977,16 @@ export interface LoadEvent<
977977
* ```
978978
*/
979979
untrack: <T>(fn: () => T) => T;
980+
981+
/**
982+
* Access to spans for tracing. If tracing is not enabled, this will be `undefined`.
983+
*/
984+
tracing?: {
985+
/** The root span for the request. This span is named `sveltekit.handle.root`. */
986+
rootSpan: Span;
987+
/** The span associated with the current `load` function. */
988+
currentSpan: Span;
989+
};
980990
}
981991

982992
export interface NavigationEvent<
@@ -1252,6 +1262,16 @@ export interface RequestEvent<
12521262
* `true` for `+server.js` calls coming from SvelteKit without the overhead of actually making an HTTP request. This happens when you make same-origin `fetch` requests on the server.
12531263
*/
12541264
isSubRequest: boolean;
1265+
1266+
/**
1267+
* Access to spans for tracing. If tracing is not enabled, this will be `undefined`.
1268+
*/
1269+
tracing?: {
1270+
/** The root span for the request. This span is named `sveltekit.handle.root`. */
1271+
rootSpan: Span;
1272+
/** The span associated with the current `handle` hook, `load` function, or server action. */
1273+
currentSpan: Span;
1274+
};
12551275
}
12561276

12571277
/**
@@ -1408,6 +1428,16 @@ export interface ServerLoadEvent<
14081428
* ```
14091429
*/
14101430
untrack: <T>(fn: () => T) => T;
1431+
1432+
/**
1433+
* Access to spans for tracing. If tracing is not enabled, this will be `undefined`.
1434+
*/
1435+
tracing?: {
1436+
/** The root span for the request. This span is named `sveltekit.handle.root`. */
1437+
rootSpan: Span;
1438+
/** The span associated with the current `load` function. */
1439+
currentSpan: Span;
1440+
};
14111441
}
14121442

14131443
/**

0 commit comments

Comments
 (0)