框架

SvelteKit

源代码
自动创建宽事件、结构化错误、排水适配器和丰富处理程序,以及 SvelteKit 应用中的尾采样。

evlog/sveltekit 适配器提供 handlehandleError 钩子,这些钩子会自动创建一个请求作用域的日志记录器,通过 event.locals.loguseLogger() 访问,并在请求完成时发送一个宽事件。

提示
在 SvelteKit 应用中设置 evlog。

- 安装 evlog:pnpm add evlog
- 将 evlog/vite 插件添加到 vite.config.ts 并传入服务名称(自动初始化、调试剥离)
- 在 hooks.server.ts 中导出 evlog/sveltekit 的 handle 和 handleError
- 在路由和服务中通过 event.locals.log 或 useLogger() 访问日志记录器
- 使用 log.set() 积累上下文,使用 createError() 抛出结构化错误
- 请求完成时自动发送宽事件

文档:https://www.evlog.dev/frameworks/sveltekit
适配器:https://www.evlog.dev/adapters

快速开始

1. 安装

终端
bun add evlog

2. 添加 Vite 插件

vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite'
import evlog from 'evlog/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    sveltekit(),
    evlog({
      service: 'my-api',
    }),
  ],
})

请查阅 Vite 插件文档 了解所有选项。

3. 创建钩子

src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'

export const { handle, handleError } = createEvlogHooks()

4. 定义 locals 类型

src/app.d.ts
import type { RequestLogger } from 'evlog'

declare global {
  namespace App {
    interface Locals {
      log: RequestLogger
    }
  }
}

export {}

宽事件

在处理程序中逐步构建上下文。一个请求对应一个宽事件:

src/routes/api/users/[id]/+server.ts
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 })
}

所有字段会在请求完成时合并为一个宽事件并发送:

终端输出
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()

使用 useLogger() 可以从调用栈中的任何位置访问请求作用域的日志记录器,而无需将 locals 传递到服务层:

src/lib/services/user.ts
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
}
src/routes/api/users/[id]/+server.ts
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)
}

event.locals.loguseLogger() 返回相同的日志记录器实例。useLogger() 使用 AsyncLocalStorage 在异步边界之间传播日志记录器。

错误处理

使用 createError 创建包含 whyfixlink 字段的结构化错误。handleError 钩子会自动捕获抛出的错误:

src/routes/api/checkout/+server.ts
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',
  })
}

错误会被捕获并记录,同时包含自定义上下文和结构化错误字段:

终端输出
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-...

配置

请查阅 配置参考 了解所有可用选项(initLogger、中间件选项、采样、静默模式等)。

排水与丰富处理

在钩子选项中直接配置排水适配器和丰富处理程序:

src/hooks.server.ts
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
  },
})

管道(批处理与重试)

在生产环境中,将适配器包装在 createDrainPipeline 中,以批处理事件并在失败时重试:

src/hooks.server.ts
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() 以确保所有缓冲事件都已发送。请查阅 管道文档 了解所有选项。

尾采样

使用 keep 强制保留特定事件,无论头部采样如何:

src/hooks.server.ts
export const { handle, handleError } = createEvlogHooks({
  drain: createAxiomDrain(),
  keep: (ctx) => {
    if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
  },
})

路由过滤

使用 includeexclude 模式控制记录哪些路由:

src/hooks.server.ts
export const { handle, handleError } = createEvlogHooks({
  include: ['/api/**'],
  exclude: ['/_internal/**', '/health'],
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
  },
})

本地运行

终端
git clone https://github.com/hugorcd/evlog.git
cd evlog
bun install
bun run example:sveltekit

打开 http://localhost:5173 探索交互式测试界面。

源代码

在 GitHub 上浏览完整的 SvelteKit 示例源代码。

下一步

深入学习 SvelteKit 集成:

  • 宽事件:设计具有上下文层级的全面事件
  • 适配器:将日志发送到 Axiom、Sentry、PostHog 等
  • 采样:使用头部和尾部采样控制日志量
  • 结构化错误:抛出包含 whyfixlink 字段的错误