默认情况下,useLogger 接受任意字段,这对快速入门非常有用。但随着代码库的扩大,不一致的问题会逐渐显现:一条路由记录 user,另一条记录 account,第三条记录 userId。字段类型化通过可选的编译时类型安全机制来解决这个问题。
checkout.post.ts·EDITING
1import { useLogger } from 'evlog'
2interface CheckoutFields {
user: { id: string; plan: string }
cart: { items: number; total: number }
action: string
6}
8const log = useLogger<CheckoutFields>(event)
10
11
problems · 0tsserver
No type errors. Excess properties on the literal would be flagged here.
useLogger<CheckoutFields>(event)
Excess property checking happens at the literal — autocomplete only suggests known keys.
基本用法
为你的字段定义一个接口,并将其作为泛型传递给 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 随后会在编译时捕获拼写错误或未知字段,而这是字段名称错误成本最低的阶段:一旦事件进入你的 drain,错误的键就已经被索引、被查询,并且已经出现在某人的仪表板中。
内部字段
有些字段由 evlog 自行设置。无论你的类型如何,通过 InternalFields,status 和 service 始终会被接受:
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 的限制,而不是模块的限制。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 }
}