SvelteKit
The evlog/sveltekit adapter provides handle and handleError hooks that auto-create a request-scoped logger accessible via event.locals.log and useLogger(), emitting a wide event when the response completes.
Set up evlog in my SvelteKit app
Quick Start
1. Install
pnpm add evlog
bun add evlog
yarn add evlog
npm install evlog
2. Create evlog.config.ts
import { defineEvlog } from 'evlog'
export default defineEvlog({
service: 'my-api',
})
Every option lives in this file: sampling, drain, enrich, route filters. npx evlog init writes it for you. See Configuration.
3. Create hooks
import { initLogger, toLoggerConfig, toMiddlewareOptions } from 'evlog'
import { createEvlogHooks } from 'evlog/sveltekit'
import config from '../evlog.config'
initLogger(toLoggerConfig(config))
export const { handle, handleError } = createEvlogHooks(toMiddlewareOptions(config))
toLoggerConfig passes the logger options of the file and toMiddlewareOptions the request options, so the sampling, drain, enrich and route filters all apply.
evlog/vite plugin strips log.debug() from production builds and injects source locations. It does not read evlog.config.ts, so add it without a service; the initLogger call above runs after its auto-initialization and replaces it.4. Type your locals
import type { RequestLogger } from 'evlog'
declare global {
namespace App {
interface Locals {
log: RequestLogger
}
}
}
export {}
Wide Events
Build up context progressively through your handler. One request = one wide event:
import { json } from '@sveltejs/kit'
import type { RequestHandler } from './$types'
export const GET: RequestHandler = async ({ locals, params }) => {
locals.log.set({ user: { id: params.id } })
const user = await db.findUser(params.id)
locals.log.set({ user: { name: user.name, plan: user.plan } })
const orders = await db.findOrders(params.id)
locals.log.set({ orders: { count: orders.length, totalRevenue: sum(orders) } })
return json({ user, orders })
}
All fields are merged into a single wide event emitted when the request completes:
14:58:15 INFO [my-api] GET /api/users/usr_123 200 in 12ms
├─ orders: count=2 totalRevenue=6298
├─ user: id=usr_123 name=Alice plan=pro
└─ requestId: 4a8ff3a8-...
useLogger()
Use useLogger() to access the request-scoped logger from anywhere in the call stack without passing locals through your service layer:
import { useLogger } from 'evlog/sveltekit'
export async function findUser(id: string) {
const log = useLogger()
log.set({ user: { id } })
const user = await db.findUser(id)
log.set({ user: { name: user.name, plan: user.plan } })
return user
}
import { json } from '@sveltejs/kit'
import { findUser } from '$lib/services/user'
import type { RequestHandler } from './$types'
export const GET: RequestHandler = async ({ params }) => {
const user = await findUser(params.id)
return json(user)
}
Both event.locals.log and useLogger() return the same logger instance. useLogger() uses AsyncLocalStorage to propagate the logger across async boundaries.
Background work (log.fork)
Use locals.log.fork(label, fn) for a child wide event. See After emit in Wide events.
import { useLogger } from 'evlog/sveltekit'
import type { RequestHandler } from './$types'
export const POST: RequestHandler = async ({ locals }) => {
locals.log.fork!('process', async () => {
const log = useLogger()
log.set({ step: 'done' })
})
return new Response(JSON.stringify({ ok: true }))
}
Error Handling
Use createError for structured errors with why, fix, and link fields. The handleError hook captures thrown errors automatically:
import { json } from '@sveltejs/kit'
import { createError } from 'evlog'
import type { RequestHandler } from './$types'
export const POST: RequestHandler = async ({ locals, request }) => {
const { cartId } = await request.json()
locals.log.set({ cart: { id: cartId } })
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
fix: 'Try a different payment method',
link: 'https://docs.example.com/payments/declined',
})
}
The error is captured and logged with both the custom context and structured error fields:
14:58:20 ERROR [my-api] POST /api/checkout 402 in 3ms
├─ error: name=EvlogError message=Payment failed status=402
├─ cart: id=cart_456
└─ requestId: 880a50ac-...
Configuration
Every option goes in evlog.config.ts. See the Configuration reference for the full list.
Drain & Enrichers
Set drain and enrich in evlog.config.ts so they reach the app through toMiddlewareOptions(config). You can also pass them inline in the hooks options; an inline key replaces the one from the file instead of merging with it:
import { createEvlogHooks } from 'evlog/sveltekit'
import { createAxiomDrain } from 'evlog/axiom'
import { createUserAgentEnricher } from 'evlog/enrichers'
const userAgent = createUserAgentEnricher()
export const { handle, handleError } = createEvlogHooks({
drain: createAxiomDrain(),
enrich: (ctx) => {
userAgent(ctx)
ctx.event.region = process.env.FLY_REGION
},
})
Pipeline (Batching & Retry)
For production, wrap your adapter with createDrainPipeline to batch events and retry on failure:
import type { DrainContext } from 'evlog'
import { createEvlogHooks } from 'evlog/sveltekit'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'
const pipeline = createDrainPipeline<DrainContext>({
batch: { size: 50, intervalMs: 5000 },
retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())
export const { handle, handleError } = createEvlogHooks({ drain })
drain.flush() on server shutdown to ensure all buffered events are sent. See the Pipeline docs for all options.Tail Sampling
Use keep to force-retain specific events regardless of head sampling:
export const { handle, handleError } = createEvlogHooks({
drain: createAxiomDrain(),
keep: (ctx) => {
if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
},
})
Route Filtering
Control which routes are logged with include and exclude patterns:
export const { handle, handleError } = createEvlogHooks({
include: ['/api/**'],
exclude: ['/_internal/**', '/health'],
routes: {
'/api/auth/**': { service: 'auth-service' },
'/api/payment/**': { service: 'payment-service' },
},
})
Run Locally
git clone https://github.com/evloghq/evlog.git
cd evlog
pnpm install
pnpm example sveltekit
Open http://localhost:5173 to explore the interactive test UI.
Next Steps
Deepen your SvelteKit integration:
- Wide Events: Design comprehensive events with context layering
- Adapters: Send logs to Axiom, Sentry, PostHog, and more
- Sampling: Control log volume with head and tail sampling
- Structured Errors: Throw errors with
why,fix, andlinkfields