核心概念
最佳实践
安全指南、数据消毒和生产提示。了解不应记录的内容以及如何保护敏感数据。
本指南介绍 evlog 的安全最佳实践和生产注意事项。
不应记录的内容
宽事件功能强大,因为它能捕获全面的上下文信息。但这也容易导致意外记录敏感数据。永远不要记录:
| 类别 | 示例 | 风险 |
|---|---|---|
| 凭证 | 密码、API 密钥、令牌、密钥 | 账户泄露 |
| 支付数据 | 完整卡号、CVV、银行账户 | PCI 合规违规 |
| 个人信息(PII) | 社保号、护照号、驾驶证号 | 隐私法律违规(GDPR、CCPA) |
| 健康数据 | 医疗记录、诊断信息 | HIPAA 违规 |
| 身份验证信息 | 会话令牌、JWT、刷新令牌 | 会话劫持 |
日志通常可被整个团队访问,并可能存储在第三方服务中。请将它们视为半公开信息。
自动脱敏
保护个人身份信息(PII)的最简单方法是启用内置的自动脱敏功能:
nuxt.config.ts
evlog: {
redact: true,
}
这会自动在所有宽事件输出和任何数据发送前,屏蔽信用卡号(****1111)、电子邮件(a***@***.com)、IP 地址、电话号码、JWT、Bearer 令牌和 IBAN。完整的配置参考请参见自动脱敏。
自动脱敏是安全网,不能替代谨慎的日志记录习惯。应始终优先选择显式字段选择,并结合
redact: true 实现纵深防御。消毒模式
手动字段选择
最安全的方法是显式选择要记录的字段:
server/api/user/update.post.ts
import { useLogger } from 'evlog'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const body = await readBody(event)
// ❌ 永远不要记录完整的请求体
// log.set({ body })
// ✅ 显式选择安全字段
log.set({
user: {
id: body.id,
email: maskEmail(body.email),
// password: body.password ← 永远不要包含
},
})
})
辅助函数
创建用于消毒常见数据类型的实用函数:
server/utils/sanitize.ts
/** 掩码邮箱:john.doe@example.com → j***.d**@e***.com */
export function maskEmail(email: string): string {
const [local, domain] = email.split('@')
if (!domain) return '***'
const [domainName, tld] = domain.split('.')
return `${local[0]}***@${domainName[0]}***.${tld}`
}
/** 掩码银行卡号:4242424242424242 → ****4242 */
export function maskCard(card: string): string {
return `****${card.slice(-4)}`
}
/** 截断长 ID 以提升可读性 */
export function truncateId(id: string, length = 8): string {
if (id.length <= length) return id
return `${id.slice(0, length)}...`
}
/** 移除对象中的敏感字段 */
export function sanitize<T extends Record<string, unknown>>(
obj: T,
sensitiveKeys: string[] = ['password', 'token', 'secret', 'apiKey', 'authorization']
): Partial<T> {
const result = { ...obj }
for (const key of sensitiveKeys) {
if (key in result) {
delete result[key]
}
}
return result
}
使用示例:
server/api/checkout.post.ts
import { useLogger } from 'evlog'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const { user, card } = await readBody(event)
log.set({
user: {
id: user.id,
email: maskEmail(user.email),
},
payment: {
last4: maskCard(card.number),
// ❌ 永远不要记录:卡号、CVV、过期时间
},
})
})
排水钩子过滤
作为最后一道防线,在发送到外部服务前过滤敏感数据:
server/plugins/evlog-sanitize.ts
const SENSITIVE_KEYS = ['password', 'token', 'secret', 'apiKey', 'authorization', 'cookie']
function deepSanitize(obj: Record<string, unknown>): Record<string, unknown> {
const result: Record<string, unknown> = {}
for (const [key, value] of Object.entries(obj)) {
// 检查键名是否包含敏感关键词(不区分大小写)
if (SENSITIVE_KEYS.some(k => key.toLowerCase().includes(k))) {
result[key] = '[REDACTED]'
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
// 递归消毒嵌套对象
result[key] = deepSanitize(value as Record<string, unknown>)
} else {
result[key] = value
}
}
return result
}
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', (ctx) => {
// 在发送到外部服务前消毒事件
ctx.event = deepSanitize(ctx.event) as typeof ctx.event
})
})
排水钩子消毒是安全网,不能替代谨慎的日志记录习惯。始终在源头进行消毒。
生产检查清单
在部署到生产环境前,请确认:
日志配置
- 已设置服务名称(
env.service) - 已配置高流量路由的采样率
- 已设置外部服务(Axis、Loki 等)的日志排水
- 生产环境中已禁用美化输出(
pretty: false)
数据安全
- 已启用自动脱敏(
redact: true) - 日志中不包含密码或密钥
- 不记录完整的信用卡号(仅保留后 4 位)
- 不记录 API 密钥或令牌
- 个人信息已脱敏或省略(电子邮件、电话号码)
- 会话令牌未被记录
- 请求体被选择性记录(而非
log.set({ body }))
错误处理
- 错误使用
createError()并包含结构化字段 - 错误信息中不包含敏感数据
- 生产环境中堆栈跟踪不暴露内部路径
字段命名约定
在代码库中使用一致且分组的字段名称:
server/api/checkout.post.ts
// ✅ 良好 — 分组且描述清晰
log.set({
user: { id, plan, accountAge },
cart: { items, total, currency },
payment: { method, provider, last4 },
})
// ❌ 不良 — 平铺且缩写
log.set({
uid: '123',
n: 3,
t: 9999,
pm: 'card',
})
推荐字段结构
| 类别 | 字段 |
|---|---|
user | id, plan, role, accountAge |
request | method, path, requestId, traceId |
cart / order | items, total, currency, coupon |
payment | method, provider, last4, status |
outcome | status, duration, error |
采样策略
在规模较大时,日志量可能变得昂贵。请明智地使用采样:
nuxt.config.ts
export default defineNuxtConfig({
evlog: {
sampling: {
// 头部采样:每个级别按随机百分比采样
rates: {
info: 10, // 10% 的成功日志
warn: 50, // 50% 的警告日志
debug: 0, // 生产环境中不记录调试日志
error: 100, // 始终保留错误日志
},
// 尾部采样:根据结果强制保留
keep: [
{ duration: 1000 }, // 慢请求(≥1s)
{ status: 400 }, // 客户端/服务器错误
{ path: '/api/payments/**' }, // 关键路径
],
},
},
})
使用
$production 覆盖配置,以便在开发环境中保留完整日志,而在生产环境中进行采样。请参见Nuxt 框架指南。