默认情况下,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 会内部设置一些字段(如 status、service)。无论你的类型定义如何,这些字段始终会被接受,通过 InternalFields 类型实现:
server/api/checkout.post.ts
log.set({ status: 200 }) // 正确 - 内部字段
log.set({ service: 'api' }) // 正确 - 内部字段
你不需要在接口中包含 status 或 service。
非类型化用法
不使用泛型时,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' }) // 没有错误(静默接受)
自动导入对非类型化用法完全有效。仅在需要类型化字段时使用显式导入。
独立使用
同样的泛型也适用于 createRequestLogger 和 createWorkersLogger:
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 错误
import { createWorkersLogger } from 'evlog/workers'
interface MyFields {
action: string
}
const log = createWorkersLogger<MyFields>(request)
log.set({ action: 'process' }) // 正确
设计建议
每个领域一个接口
按领域而非路由定义字段接口:
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 }
}