核心概念

字段类型化

通过 TypeScript 模块扩展为宽事件添加编译时类型安全。防止拼写错误,并确保代码库中字段名称的一致性。

默认情况下,useLogger 接受任意字段,这对快速入门非常有用。但随着代码库的扩大,不一致的问题会逐渐显现:一条路由记录 user,另一条记录 account,第三条记录 userId。字段类型化通过可选的编译时类型安全机制来解决这个问题。

基本用法

为你的字段定义一个接口,并将其作为泛型传递给 useLogger

server/api/checkout.post.ts
import { useLogger } from 'evlog'

interface CheckoutFields {
  user: { id: string; plan: string }
  cart: { items: number; total: number }
  action: string
}

export default defineEventHandler(async (event) => {
  const log = useLogger<CheckoutFields>(event)

  log.set({ user: { id: '123', plan: 'pro' } })  // 正确
  log.set({ cart: { items: 3, total: 9999 } })    // 正确
  log.set({ action: 'checkout' })                  // 正确

  log.set({ account: '...' })                      // TypeScript 错误
  log.set({ usr: { id: '123' } })                  // TypeScript 错误

  return { success: true }
})

TypeScript 在编译阶段捕捉拼写错误和未知字段,避免它们进入生产环境。

内部字段

evlog 会内部设置一些字段(如 statusservice)。无论你的类型定义如何,这些字段始终会被接受,通过 InternalFields 类型实现:

server/api/checkout.post.ts
log.set({ status: 200 })    // 正确 - 内部字段
log.set({ service: 'api' }) // 正确 - 内部字段

你不需要在接口中包含 statusservice

非类型化用法

不使用泛型时,useLogger 仍然接受任意字段:

server/api/example.ts
const log = useLogger(event)
log.set({ anything: true, nested: { deep: 'value' }) // 正确

类型化字段是完全可选的。

Nuxt 自动导入

在使用 useLogger<T> 时,必须使用显式导入。由于 TypeScript 的限制,Nuxt 自动导入无法为泛型提供多余属性检查。
server/api/checkout.post.ts
// 可行 - 显式导入保留类型检查
import { useLogger } from 'evlog'
const log = useLogger<MyFields>(event)
log.set({ typo: 'oops' }) // TypeScript 错误

// 不可行 - 自动导入丢失多余属性检查
const log = useLogger<MyFields>(event)
log.set({ typo: 'oops' }) // 没有错误(静默接受)

自动导入对非类型化用法完全有效。仅在需要类型化字段时使用显式导入。

独立使用

同样的泛型也适用于 createRequestLoggercreateWorkersLogger

import { createRequestLogger } from 'evlog'

interface MyFields {
  action: string
  userId: string
}

const log = createRequestLogger<MyFields>({
  method: 'POST',
  path: '/checkout',
})

log.set({ action: 'checkout', userId: '123' }) // 正确
log.set({ unknown: true })                      // TypeScript 错误

设计建议

每个领域一个接口

按领域而非路由定义字段接口:

server/types/log-fields.ts
export interface AuthFields {
  user: { id: string; email: string; role: string }
  action: string
  mfaUsed: boolean
}

export interface PaymentFields {
  user: { id: string; plan: string }
  order: { id: string; total: number; currency: string }
  payment: { method: string; last4: string }
}
server/api/auth/login.post.ts
import { useLogger } from 'evlog'
import type { AuthFields } from '~/server/types/log-fields'

export default defineEventHandler(async (event) => {
  const log = useLogger<AuthFields>(event)
  // ...
})

保持接口专注

只包含路由实际设置的字段,接口无需完全映射你的数据模型:

server/types/evlog.ts
// 过于宽泛 - 大多数路由不会设置所有字段
interface EverythingFields {
  user: FullUserProfile
  order: CompleteOrder
  payment: PaymentDetails
  shipping: ShippingInfo
}

// 专注 - 仅当前路由设置的字段
interface CheckoutFields {
  user: { id: string; plan: string }
  cart: { items: number; total: number }
}

后续步骤

  • 宽事件:设计具有上下文层次结构的有效宽事件
  • 最佳实践:防止敏感数据泄露的安全指南
  • 配置:所有 initLogger 和中间件选项