Skip to content

Commit 83a24aa

Browse files
upstream
1 parent 587be22 commit 83a24aa

File tree

11 files changed

+91
-22
lines changed

11 files changed

+91
-22
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ function process_config(config, { cwd = process.cwd() } = {}) {
105105
validated.kit.files.hooks.client = path.resolve(cwd, validated.kit.files.hooks.client);
106106
validated.kit.files.hooks.server = path.resolve(cwd, validated.kit.files.hooks.server);
107107
validated.kit.files.hooks.universal = path.resolve(cwd, validated.kit.files.hooks.universal);
108+
} else if (key === 'tracing') {
109+
validated.kit.files.tracing.server = path.resolve(cwd, validated.kit.files.tracing.server);
108110
} else {
109111
// @ts-expect-error
110112
validated.kit.files[key] = path.resolve(cwd, validated.kit.files[key]);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ const get_defaults = (prefix = '') => ({
9494
appTemplate: join(prefix, 'src/app.html'),
9595
errorTemplate: join(prefix, 'src/error.html')
9696
},
97+
tracing: {
98+
server: join(prefix, 'src/tracing.server')
99+
},
97100
inlineStyleThreshold: 0,
98101
moduleExtensions: ['.js', '.ts'],
99102
output: { preloadStrategy: 'modulepreload', bundleStrategy: 'split' },

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ const options = object(
134134
server: string(join('src', 'hooks.server')),
135135
universal: string(join('src', 'hooks'))
136136
}),
137+
tracing: object({
138+
server: string(join('src', 'tracing.server'))
139+
}),
137140
lib: string(join('src', 'lib')),
138141
params: string(join('src', 'params')),
139142
routes: string(join('src', 'routes')),

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ export interface Adapter {
5151
* @param details.config The merged route config
5252
*/
5353
read?: (details: { config: any; route: { id: string } }) => boolean;
54+
55+
/**
56+
* Test support for `tracing`. To pass, the adapter must support `tracing.server.js` and
57+
* also deploy to a platform that supports `@opentelemetry/api`.
58+
*/
59+
tracing?: () => boolean;
5460
};
5561
/**
5662
* Creates an `Emulator`, which allows the adapter to influence the environment
@@ -453,6 +459,13 @@ export interface KitConfig {
453459
*/
454460
universal?: string;
455461
};
462+
/**
463+
* the location of your server tracing file
464+
* @default "src/tracing.server"
465+
*/
466+
tracing?: {
467+
server?: string;
468+
};
456469
/**
457470
* your app's internal library, accessible throughout the codebase as `$lib`
458471
* @default "src/lib"

packages/kit/src/exports/vite/dev/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ export async function dev(vite, vite_config, svelte_config) {
376376
sync.update(svelte_config, manifest_data, file);
377377
});
378378

379-
const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files;
379+
const { appTemplate, errorTemplate, serviceWorker, hooks, tracing } = svelte_config.kit.files;
380380

381381
// vite client only executes a full reload if the triggering html file path is index.html
382382
// kit defaults to src/app.html, so unless user changed that to index.html
@@ -394,7 +394,8 @@ export async function dev(vite, vite_config, svelte_config) {
394394
file === appTemplate ||
395395
file === errorTemplate ||
396396
file.startsWith(serviceWorker) ||
397-
file.startsWith(hooks.server)
397+
file.startsWith(hooks.server) ||
398+
file.startsWith(tracing.server)
398399
) {
399400
sync.server(svelte_config);
400401
}
@@ -501,6 +502,8 @@ export async function dev(vite, vite_config, svelte_config) {
501502
return;
502503
}
503504

505+
await vite.ssrLoadModule(tracing.server);
506+
504507
// we have to import `Server` before calling `set_assets`
505508
const { Server } = /** @type {import('types').ServerModule} */ (
506509
await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true })

packages/kit/src/runtime/server/respond.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { with_event } from '../app/server/event.js';
3838
import { record_span } from '../telemetry/record_span.js';
3939
import { merge_tracing } from '../utils.js';
4040
import { create_event_state, EVENT_STATE } from './event-state.js';
41+
import { otel } from '../telemetry/otel.js';
4142

4243
/* global __SVELTEKIT_ADAPTER_NAME__ */
4344
/* global __SVELTEKIT_DEV__ */
@@ -57,14 +58,16 @@ const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
5758

5859
let warned_on_devtools_json_request = false;
5960

61+
export const respond = propagate_context(internal_respond);
62+
6063
/**
6164
* @param {Request} request
6265
* @param {import('types').SSROptions} options
6366
* @param {import('@sveltejs/kit').SSRManifest} manifest
6467
* @param {import('types').SSRState} state
6568
* @returns {Promise<Response>}
6669
*/
67-
export async function respond(request, options, manifest, state) {
70+
export async function internal_respond(request, options, manifest, state) {
6871
/** URL but stripped from the potential `/__data.json` suffix and its search param */
6972
const url = new URL(request.url);
7073

@@ -697,3 +700,25 @@ export function load_page_nodes(page, manifest) {
697700
manifest._.nodes[page.leaf]()
698701
]);
699702
}
703+
704+
/**
705+
* It's likely that, in a distributed system, there are spans starting outside the SvelteKit server -- eg.
706+
* started on the frontend client, or in a service that calls the SvelteKit server. There are standardized
707+
* ways to represent this context in HTTP headers, so we can extract that context and run our tracing inside of it
708+
* so that when our traces are exported, they are associated with the correct parent context.
709+
* @param {typeof internal_respond} fn
710+
* @returns {typeof internal_respond}
711+
*/
712+
function propagate_context(fn) {
713+
return async (req, ...rest) => {
714+
if (otel === null) {
715+
return fn(req, ...rest);
716+
}
717+
718+
const { propagation, context } = await otel;
719+
const c = propagation.extract(context.active(), Object.fromEntries(req.headers));
720+
return context.with(c, async () => {
721+
return await fn(req, ...rest);
722+
});
723+
};
724+
}
Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
/** @import { Tracer, SpanStatusCode } from '@opentelemetry/api' */
1+
/** @import { Tracer, SpanStatusCode, PropagationAPI, ContextAPI } from '@opentelemetry/api' */
22

3-
/** @type {{ tracer: Tracer, SpanStatusCode: typeof SpanStatusCode } | null} */
3+
/** @type {Promise<{ tracer: Tracer, SpanStatusCode: typeof SpanStatusCode, propagation: PropagationAPI, context: ContextAPI }> | null} */
44
export let otel = null;
55

66
if (__SVELTEKIT_SERVER_TRACING_ENABLED__) {
7-
try {
8-
const module = await import('@opentelemetry/api');
9-
otel = {
10-
tracer: module.trace.getTracer('sveltekit'),
11-
SpanStatusCode: module.SpanStatusCode
12-
};
13-
} catch {
14-
throw new Error(
15-
'Tracing is enabled (see `config.kit.experimental.tracing.server` in your svelte.config.js), but `@opentelemetry/api` is not available. Have you installed it?'
16-
);
17-
}
7+
otel = import('@opentelemetry/api')
8+
.then((module) => {
9+
return {
10+
tracer: module.trace.getTracer('sveltekit'),
11+
propagation: module.propagation,
12+
context: module.context,
13+
SpanStatusCode: module.SpanStatusCode
14+
};
15+
})
16+
.catch(() => {
17+
throw new Error(
18+
'Tracing is enabled (see `config.kit.experimental.tracing.server` in your svelte.config.js), but `@opentelemetry/api` is not available. Have you installed it?'
19+
);
20+
});
1821
}

packages/kit/src/runtime/telemetry/otel.missing.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ vi.mock('@opentelemetry/api', () => {
99
});
1010

1111
test('otel should throw an error when tracing is enabled but @opentelemetry/api is not available', async () => {
12-
await expect(import('./otel.js')).rejects.toThrow(
12+
const { otel } = await import('./otel.js');
13+
await expect(otel).rejects.toThrow(
1314
'Tracing is enabled (see `config.kit.experimental.tracing.server` in your svelte.config.js), but `@opentelemetry/api` is not available. Have you installed it?'
1415
);
1516
});

packages/kit/src/runtime/telemetry/record_span.enabled.spec.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,20 @@ const { tracer, span } = vi.hoisted(() => {
3030
});
3131

3232
vi.mock(import('./otel.js'), async (original) => {
33-
const { otel } = await original();
33+
const { otel: unresolved_otel } = await original();
34+
const otel = await unresolved_otel;
3435

3536
if (otel === null) {
3637
throw new Error('Problem setting up tests; otel is null');
3738
}
3839

3940
return {
40-
otel: {
41+
otel: Promise.resolve({
4142
tracer,
42-
SpanStatusCode: otel.SpanStatusCode
43-
}
43+
SpanStatusCode: otel.SpanStatusCode,
44+
propagation: otel.propagation,
45+
context: otel.context
46+
})
4447
};
4548
});
4649

packages/kit/src/runtime/telemetry/record_span.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export async function record_span({ name, attributes, fn }) {
1616
return fn(noop_span);
1717
}
1818

19-
const { SpanStatusCode, tracer } = otel;
19+
const { SpanStatusCode, tracer } = await otel;
2020

2121
return tracer.startActiveSpan(name, { attributes }, async (span) => {
2222
try {

0 commit comments

Comments
 (0)