SvelteKit • Advanced

Observability

On this page

Available since 2.31

Sometimes, you may need to observe how your application is behaving in order to improve performance or find the root cause of a pesky bug. To help with this, SvelteKit can emit server-side OpenTelemetry spans for the following:

  • The handle hook and handle functions running in a sequence (these will show up as children of each other and the root handle hook)
  • Server load functions and universal load functions when they’re run on the server
  • Form actions
  • Remote functions

Just telling SvelteKit to emit spans won’t get you far, though — you need to actually collect them somewhere to be able to view them. SvelteKit provides src/instrumentation.server.ts as a place to write your tracing setup and instrumentation code. It’s guaranteed to be run prior to your application code being imported, providing your deployment platform supports it and your adapter is aware of it.

Both of these features are currently experimental, meaning they are likely to contain bugs and are subject to change without notice. You must opt in by adding the kit.experimental.tracing.server and kit.experimental.instrumentation.server option in your svelte.config.js:

import type { Config } from '@sveltejs/kit';
const const config: Configconst config: Configconfig: Config = {
	Config.kit?: KitConfig | undefined

SvelteKit options.

@seehttps://svelte.dev/docs/kit/configuration
Config.kit?: KitConfig | undefined

SvelteKit options.

@seehttps://svelte.dev/docs/kit/configuration
kit
: {
KitConfig.experimental?: {
    explicitEnvironmentVariables?: boolean;
    tracing?: {
        server?: boolean;
    };
    instrumentation?: {
        server?: boolean;
    };
    remoteFunctions?: boolean;
    forkPreloads?: boolean;
    handleRenderingErrors?: boolean;
} | undefined

Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release.

KitConfig.experimental?: {
    explicitEnvironmentVariables?: boolean;
    tracing?: {
        server?: boolean;
    };
    instrumentation?: {
        server?: boolean;
    };
    remoteFunctions?: boolean;
    forkPreloads?: boolean;
    handleRenderingErrors?: boolean;
} | undefined

Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release.

experimental
: {
tracing?: {
    server?: boolean;
} | undefined

Options for enabling server-side OpenTelemetry tracing for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@default{ server: false, serverFile: false }@since2.31.0
tracing?: {
    server?: boolean;
} | undefined

Options for enabling server-side OpenTelemetry tracing for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@default{ server: false, serverFile: false }@since2.31.0
tracing
: {
server?: boolean | undefined

Enables server-side OpenTelemetry span emission for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@defaultfalse@since2.31.0
server?: boolean | undefined

Enables server-side OpenTelemetry span emission for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@defaultfalse@since2.31.0
server
: true
},
instrumentation?: {
    server?: boolean;
} | undefined
@since2.31.0
instrumentation?: {
    server?: boolean;
} | undefined
@since2.31.0
instrumentation
: {
server?: boolean | undefined

Enables instrumentation.server.js for tracing and observability instrumentation.

@defaultfalse@since2.31.0
server?: boolean | undefined

Enables instrumentation.server.js for tracing and observability instrumentation.

@defaultfalse@since2.31.0
server
: true
} } } }; export default const config: Configconst config: Configconfig;
/** @type {import('@sveltejs/kit').Config} */
const const config: Config
@type{import('@sveltejs/kit').Config}
const config: Config
@type{import('@sveltejs/kit').Config}
config
= {
Config.kit?: KitConfig | undefined

SvelteKit options.

@seehttps://svelte.dev/docs/kit/configuration
Config.kit?: KitConfig | undefined

SvelteKit options.

@seehttps://svelte.dev/docs/kit/configuration
kit
: {
KitConfig.experimental?: {
    explicitEnvironmentVariables?: boolean;
    tracing?: {
        server?: boolean;
    };
    instrumentation?: {
        server?: boolean;
    };
    remoteFunctions?: boolean;
    forkPreloads?: boolean;
    handleRenderingErrors?: boolean;
} | undefined

Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release.

KitConfig.experimental?: {
    explicitEnvironmentVariables?: boolean;
    tracing?: {
        server?: boolean;
    };
    instrumentation?: {
        server?: boolean;
    };
    remoteFunctions?: boolean;
    forkPreloads?: boolean;
    handleRenderingErrors?: boolean;
} | undefined

Experimental features. Here be dragons. These are not subject to semantic versioning, so breaking changes or removal can happen in any release.

experimental
: {
tracing?: {
    server?: boolean;
} | undefined

Options for enabling server-side OpenTelemetry tracing for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@default{ server: false, serverFile: false }@since2.31.0
tracing?: {
    server?: boolean;
} | undefined

Options for enabling server-side OpenTelemetry tracing for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@default{ server: false, serverFile: false }@since2.31.0
tracing
: {
server?: boolean | undefined

Enables server-side OpenTelemetry span emission for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@defaultfalse@since2.31.0
server?: boolean | undefined

Enables server-side OpenTelemetry span emission for SvelteKit operations including the handle hook, load functions, form actions, and remote functions.

@defaultfalse@since2.31.0
server
: true
},
instrumentation?: {
    server?: boolean;
} | undefined
@since2.31.0
instrumentation?: {
    server?: boolean;
} | undefined
@since2.31.0
instrumentation
: {
server?: boolean | undefined

Enables instrumentation.server.js for tracing and observability instrumentation.

@defaultfalse@since2.31.0
server?: boolean | undefined

Enables instrumentation.server.js for tracing and observability instrumentation.

@defaultfalse@since2.31.0
server
: true
} } } }; export default const config: Config
@type{import('@sveltejs/kit').Config}
const config: Config
@type{import('@sveltejs/kit').Config}
config
;

Tracing — and more significantly, observability instrumentation — can have a nontrivial overhead. Before you go all-in on tracing, consider whether or not you really need it, or if it might be more appropriate to turn it on in development and preview environments only.

Augmenting the built-in tracing

SvelteKit provides access to the root span and the current span on the request event. The root span is the one associated with your root handle function, and the current span could be associated with handle, load, a form action, or a remote function, depending on the context. You can annotate these spans with any attributes you wish to record:

// @filename: ambient.d.ts
declare module '$lib/auth-core' {
	export function 
function getAuthenticatedUser(): Promise<{
    id: string;
}>
function getAuthenticatedUser(): Promise<{
    id: string;
}>
getAuthenticatedUser
(): interface Promise<T>

Represents the completion of an asynchronous operation

interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<{ id: stringid: stringid: string }>
} // @filename: index.js // cut import { function getRequestEvent(): RequestEvent

Returns the current RequestEvent. Can be used inside server hooks, server load functions, actions, and endpoints (and functions called by them).

In environments without AsyncLocalStorage, this must be called synchronously (i.e. not after an await).

@since2.20.0
getRequestEvent
} from '$app/server';
import {
function getAuthenticatedUser(): Promise<{
    id: string;
}>
getAuthenticatedUser
} from '$lib/auth-core';
async function function authenticate(): Promise<void>authenticate() { const
const user: {
    id: string;
}
user
= await
function getAuthenticatedUser(): Promise<{
    id: string;
}>
getAuthenticatedUser
();
const const event: RequestEvent<Record<string, string>, string | null>event = function getRequestEvent(): RequestEvent

Returns the current RequestEvent. Can be used inside server hooks, server load functions, actions, and endpoints (and functions called by them).

In environments without AsyncLocalStorage, this must be called synchronously (i.e. not after an await).

@since2.20.0
getRequestEvent
();
const event: RequestEvent<Record<string, string>, string | null>event.
RequestEvent<Record<string, string>, string | null>.tracing: {
    enabled: boolean;
    root: Span;
    current: Span;
}

Access to spans for tracing. If tracing is not enabled, these spans will do nothing.

@since2.31.0
tracing
.root: Span

The root span for the request. This span is named sveltekit.handle.root.

root
.Span.setAttribute(key: string, value: SpanAttributeValue): Span

Sets an attribute to the span.

Sets a single Attribute with the key and value passed as arguments.

@paramkey the key for this attribute.@paramvalue the value for this attribute. Setting a value null or undefined is invalid and will result in undefined behavior.
setAttribute
('userId',
const user: {
    id: string;
}
user
.id: stringid);
}

Development quickstart

To view your first trace, you’ll need to set up a local collector. We’ll use Jaeger in this example, as they provide an easy-to-use quickstart command. Once your collector is running locally:

  • Turn on the experimental flags mentioned earlier in your svelte.config.js file
  • Use your package manager to install the dependencies you’ll need:
    npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-proto import-in-the-middle
  • Create src/instrumentation.server.js with the following:
import { import NodeSDKNodeSDK } from '@opentelemetry/sdk-node';
Cannot find module '@opentelemetry/sdk-node' or its corresponding type declarations.
import { import getNodeAutoInstrumentationsgetNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
Cannot find module '@opentelemetry/auto-instrumentations-node' or its corresponding type declarations.
import { import OTLPTraceExporterOTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
Cannot find module '@opentelemetry/exporter-trace-otlp-proto' or its corresponding type declarations.
import { import createAddHookMessageChannelcreateAddHookMessageChannel } from 'import-in-the-middle';
Cannot find module 'import-in-the-middle' or its corresponding type declarations.
import { function register<Data = any>(specifier: string | URL, parentURL?: string | URL, options?: Module.RegisterOptions<Data>): void (+1 overload)

Register a module that exports hooks that customize Node.js module resolution and loading behavior. See Customization hooks.

This feature requires --allow-worker if used with the Permission Model.

@sincev20.6.0, v18.19.0@deprecatedUse `module.registerHooks()` instead.@paramspecifier Customization hooks to be registered; this should be the same string that would be passed to `import()`, except that if it is relative, it is resolved relative to `parentURL`.@paramparentURL f you want to resolve `specifier` relative to a base URL, such as `import.meta.url`, you can pass that URL here.
register
} from 'node:module';
const { const registerOptions: anyregisterOptions } = import createAddHookMessageChannelcreateAddHookMessageChannel(); register<any>(specifier: string | URL, parentURL?: string | URL, options?: Module.RegisterOptions<any> | undefined): void (+1 overload)

Register a module that exports hooks that customize Node.js module resolution and loading behavior. See Customization hooks.

This feature requires --allow-worker if used with the Permission Model.

@sincev20.6.0, v18.19.0@deprecatedUse `module.registerHooks()` instead.@paramspecifier Customization hooks to be registered; this should be the same string that would be passed to `import()`, except that if it is relative, it is resolved relative to `parentURL`.@paramparentURL f you want to resolve `specifier` relative to a base URL, such as `import.meta.url`, you can pass that URL here.
register
('import-in-the-middle/hook.mjs', import.meta.ImportMeta.url: stringurl, const registerOptions: anyregisterOptions);
const const sdk: anysdk = new import NodeSDKNodeSDK({ serviceName: stringserviceName: 'test-sveltekit-tracing', traceExporter: anytraceExporter: new import OTLPTraceExporterOTLPTraceExporter(), instrumentations: any[]instrumentations: [import getNodeAutoInstrumentationsgetNodeAutoInstrumentations()] }); const sdk: anysdk.start();

Now, server-side requests will begin generating traces, which you can view in Jaeger’s web console at localhost:16686.

@opentelemetry/api

SvelteKit uses @opentelemetry/api to generate its spans. This is declared as an optional peer dependency so that users not needing traces see no impact on install size or runtime performance. In most cases, if you’re configuring your application to collect SvelteKit’s spans, you’ll end up installing a library like @opentelemetry/sdk-node or @vercel/otel, which in turn depend on @opentelemetry/api, which will satisfy SvelteKit’s dependency as well. If you see an error from SvelteKit telling you it can’t find @opentelemetry/api, it may just be because you haven’t set up your trace collection yet. If you have done that and are still seeing the error, you can install @opentelemetry/api yourself.