review-logging-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReview logging patterns
审查日志模式
Review and improve logging patterns in TypeScript/JavaScript codebases. Transform scattered console.logs into structured wide events and convert generic errors into self-documenting structured errors.
审查并改进TypeScript/JavaScript代码库中的日志模式,将零散的console.log转换为结构化宽事件,把通用错误转换为自文档化的结构化错误。
When to Use
适用场景
Use this skill when:
- Reviewing code for logging best practices
- User asks to improve their logging
- Converting console.log statements to structured logging
- Improving error handling with better context
- Setting up request-scoped logging in API routes
- Debugging why logs are hard to search/filter
Key transformations:
- spam → wide events with
console.loguseLogger(event) - →
throw new Error('...')createError({ message, status, why, fix }) - Scattered request logs → (Nuxt/Nitro) or
useLogger(event)(standalone)createRequestLogger()
在以下场景使用本技能:
- 审查代码是否符合日志最佳实践
- 用户要求改进日志系统
- 将console.log语句转换为结构化日志
- 通过补充上下文优化错误处理
- 在API路由中设置请求作用域日志
- 排查日志难以搜索/过滤的问题
核心转换:
- 冗余→ 使用
console.log生成宽事件useLogger(event) - →
throw new Error('...')createError({ message, status, why, fix }) - 零散请求日志 → (Nuxt/Nitro)或
useLogger(event)(独立项目)createRequestLogger()
Quick Reference
快速参考
| Working on... | Resource |
|---|---|
| Wide events patterns | references/wide-events.md |
| Error handling | references/structured-errors.md |
| Code review checklist | references/code-review.md |
| Log draining & adapters | See "Log Draining & Adapters" section below |
| 处理场景... | 参考资源 |
|---|---|
| 宽事件模式 | references/wide-events.md |
| 错误处理 | references/structured-errors.md |
| 代码审查清单 | references/code-review.md |
| 日志导出与适配器 | 见下方「日志导出与适配器」章节 |
Important: Auto-imports in Nuxt
重要提示:Nuxt中的自动导入
In Nuxt applications, all evlog functions are auto-imported - no import statements needed:
typescript
// server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
// useLogger is auto-imported - no import needed!
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
return { success: true }
})vue
<!-- In Vue components - log is auto-imported -->
<script setup>
log.info('checkout', 'User initiated checkout')
</script>在Nuxt应用中,所有evlog函数均支持自动导入 - 无需编写导入语句:
typescript
// server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
// useLogger已自动导入 - 无需手动导入!
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
return { success: true }
})vue
<!-- Vue组件中 - log已自动导入 -->
<script setup>
log.info('checkout', '用户发起结账流程')
</script>Core Philosophy
核心理念
The Problem with Traditional Logging
传统日志的问题
typescript
// ❌ Scattered logs - impossible to correlate during incidents
console.log('Request received')
console.log('User authenticated')
console.log('Loading cart')
console.log('Processing payment')
console.log('Payment failed')typescript
// ❌ 零散日志 - 故障排查时无法关联上下文
console.log('收到请求')
console.log('用户已认证')
console.log('加载购物车')
console.log('处理支付')
console.log('支付失败')The Solution: Wide Events
解决方案:宽事件
typescript
// server/api/checkout.post.ts
// No import needed in Nuxt - useLogger is auto-imported!
// ✅ One comprehensive event per request
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: '123', plan: 'premium' } })
log.set({ cart: { items: 3, total: 9999 } })
log.error(error, { step: 'payment' })
// emit() called automatically at request end
})typescript
// server/api/checkout.post.ts
// Nuxt中无需导入 - useLogger已自动导入!
// ✅ 每个请求对应一个完整的宽事件
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: '123', plan: 'premium' } })
log.set({ cart: { items: 3, total: 9999 } })
log.error(error, { step: 'payment' })
// 请求结束时自动调用emit()
})Anti-Patterns to Detect
需要检测的反模式
1. Console.log Spam
1. 冗余console.log
typescript
// ❌ Multiple logs for one logical operation
console.log('Starting checkout')
console.log('User:', userId)
console.log('Cart:', cart)
console.log('Payment result:', result)Transform to:
typescript
// ✅ Single wide event
log.info({
action: 'checkout',
userId,
cart,
result,
duration: '1.2s'
})typescript
// ❌ 单个逻辑操作对应多条日志
console.log('开始结账')
console.log('用户ID:', userId)
console.log('购物车:', cart)
console.log('支付结果:', result)转换为:
typescript
// ✅ 单个宽事件
log.info({
action: 'checkout',
userId,
cart,
result,
duration: '1.2s'
})2. Generic Error Messages
2. 通用错误信息
typescript
// ❌ Useless error
throw new Error('Something went wrong')
// ❌ Missing context
throw new Error('Payment failed')Transform to:
typescript
import { createError } from 'evlog'
// ✅ Self-documenting error
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
fix: 'Try a different payment method or contact your bank',
link: 'https://docs.example.com/payments/declined',
cause: originalError,
})typescript
// ❌ 无意义的错误
throw new Error('发生了一些错误')
// ❌ 缺失上下文
throw new Error('支付失败')转换为:
typescript
import { createError } from 'evlog'
// ✅ 自文档化错误
throw createError({
message: '支付失败',
status: 402,
why: '发卡行拒绝该卡片',
fix: '尝试其他支付方式或联系您的银行',
link: 'https://docs.example.com/payments/declined',
cause: originalError,
})3. Missing Request Context
3. 缺失请求上下文
typescript
// server/api/orders.post.ts
// ❌ No way to correlate logs
export default defineEventHandler(async (event) => {
console.log('Processing request')
const user = await getUser(event)
console.log('Got user', user.id)
// ...
})Transform to (Nuxt/Nitro):
typescript
// server/api/orders.post.ts
// useLogger is auto-imported in Nuxt - no import needed!
// ✅ Request-scoped with full context
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const user = await getUser(event)
log.set({ user: { id: user.id, plan: user.plan } })
// ... do work, accumulate context ...
// emit() called automatically
})Transform to (Standalone TypeScript):
typescript
// scripts/process-job.ts
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ jobId: job.id, type: 'sync' })
log.set({ source: job.source, target: job.target })
// ... do work ...
log.emit() // Manual emit for standalone usagetypescript
// server/api/orders.post.ts
// ❌ 无法关联日志上下文
export default defineEventHandler(async (event) => {
console.log('处理请求中')
const user = await getUser(event)
console.log('获取用户信息', user.id)
// ...
})转换为(Nuxt/Nitro):
typescript
// server/api/orders.post.ts
// Nuxt中useLogger已自动导入 - 无需手动导入!
// ✅ 带完整上下文的请求作用域日志
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const user = await getUser(event)
log.set({ user: { id: user.id, plan: user.plan } })
// ... 执行业务逻辑,积累上下文 ...
// 自动调用emit()
})转换为(独立TypeScript项目):
typescript
// scripts/process-job.ts
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ jobId: job.id, type: 'sync' })
log.set({ source: job.source, target: job.target })
// ... 执行业务逻辑 ...
log.emit() // 独立项目中需手动调用emit()Installation
安装步骤
bash
npm install evlogbash
npm install evlogNuxt Integration
Nuxt集成
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: {
service: 'my-app',
environment: process.env.NODE_ENV,
},
// Optional: only log specific routes (supports glob patterns)
include: ['/api/**'],
// Optional: send client logs to server (default: false)
transport: {
enabled: true,
},
},
})typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
env: {
service: 'my-app',
environment: process.env.NODE_ENV,
},
// 可选:仅记录特定路由(支持通配符模式)
include: ['/api/**'],
// 可选:将客户端日志发送到服务端(默认:false)
transport: {
enabled: true,
},
},
})Nitro Integration
Nitro集成
typescript
// nitro.config.ts
export default defineNitroConfig({
plugins: ['evlog/nitro'],
})typescript
// nitro.config.ts
export default defineNitroConfig({
plugins: ['evlog/nitro'],
})Structured Error Levels
结构化错误级别
Not all errors need the same level of detail. Use the appropriate level:
不同错误需要的详细程度不同,请选择合适的级别:
Minimal (internal errors)
极简级别(内部错误)
typescript
throw createError({ message: 'Database connection failed', status: 500 })typescript
throw createError({ message: '数据库连接失败', status: 500 })Standard (user-facing errors)
标准级别(面向用户的错误)
typescript
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
})typescript
throw createError({
message: '支付失败',
status: 402,
why: '发卡行拒绝该卡片',
})Complete (documented errors with actionable fix)
完整级别(带可操作修复方案的文档化错误)
typescript
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer - insufficient funds',
fix: 'Please use a different payment method or contact your bank',
link: 'https://docs.example.com/payments/declined',
})typescript
throw createError({
message: '支付失败',
status: 402,
why: '发卡行拒绝该卡片 - 余额不足',
fix: '请使用其他支付方式或联系您的银行',
link: 'https://docs.example.com/payments/declined',
})Frontend Integration
前端集成
evlog errors work with any Nitro-powered framework. When thrown, they're automatically converted to HTTP responses with structured data.
Use to extract all fields at the top level:
parseError()typescript
import { createError, parseError } from 'evlog'
// Backend - just throw the error
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined',
fix: 'Try another card',
link: 'https://docs.example.com/payments',
})
// Frontend - use parseError() for direct access
try {
await $fetch('/api/checkout')
} catch (err) {
const error = parseError(err)
// Direct access: error.message, error.why, error.fix, error.link
toast.add({
title: error.message,
description: error.why,
color: 'error',
actions: error.link
? [{ label: 'Learn more', onClick: () => window.open(error.link) }]
: undefined,
})
if (error.fix) console.info(`💡 Fix: ${error.fix}`)
}The difference: A generic error shows "An error occurred". A structured error shows the message, explains why, suggests a fix, and links to docs.
evlog错误可与任何基于Nitro的框架配合使用。抛出错误时,会自动转换为带结构化数据的HTTP响应。
使用提取顶层所有字段:
parseError()typescript
import { createError, parseError } from 'evlog'
// 后端 - 直接抛出错误
throw createError({
message: '支付失败',
status: 402,
why: '卡片被拒绝',
fix: '尝试其他卡片',
link: 'https://docs.example.com/payments',
})
// 前端 - 使用parseError()直接访问字段
try {
await $fetch('/api/checkout')
} catch (err) {
const error = parseError(err)
// 直接访问:error.message, error.why, error.fix, error.link
toast.add({
title: error.message,
description: error.why,
color: 'error',
actions: error.link
? [{ label: '了解更多', onClick: () => window.open(error.link) }]
: undefined,
})
if (error.fix) console.info(`💡 修复建议: ${error.fix}`)
}区别:通用错误仅显示"发生错误",而结构化错误会显示错误信息、原因说明、修复建议及文档链接。
Client-Side Logging
客户端日志
The API works on both server and client. In Nuxt, it's auto-imported:
logtypescript
// In Vue components, composables, or client-side code
log.info('checkout', 'User initiated checkout')
log.error({ action: 'payment', error: 'validation_failed' })
log.warn('form', 'Invalid email format')
log.debug({ component: 'CartDrawer', itemCount: 3 })Client logs output to the browser console with colored tags in development.
logtypescript
// Vue组件、组合式函数或客户端代码中
log.info('checkout', '用户发起结账流程')
log.error({ action: 'payment', error: 'validation_failed' })
log.warn('form', '邮箱格式无效')
log.debug({ component: 'CartDrawer', itemCount: 3 })开发环境下,客户端日志会带彩色标签输出到浏览器控制台。
Client Transport
客户端传输
To send client logs to the server for centralized logging, enable the transport:
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
transport: {
enabled: true, // Send client logs to server
},
},
})When enabled:
- Client logs are sent to via POST
/api/_evlog/ingest - Server enriches with environment context (service, version, etc.)
- hook is called with
evlog:drainsource: 'client' - External services receive the log
Identify client logs in your drain hook:
typescript
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
if (ctx.event.source === 'client') {
// Handle client logs specifically
}
})如需将客户端日志发送到服务端进行集中管理,启用传输功能:
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['evlog/nuxt'],
evlog: {
transport: {
enabled: true, // 将客户端日志发送到服务端
},
},
})启用后:
- 客户端日志通过POST请求发送到
/api/_evlog/ingest - 服务端补充环境上下文(服务名称、版本等)
- 调用钩子,参数
evlog:drainsource: 'client' - 外部服务接收日志
在分流钩子中识别客户端日志:
typescript
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
if (ctx.event.source === 'client') {
// 专门处理客户端日志
}
})Log Draining & Adapters
日志导出与适配器
evlog provides built-in adapters to send logs to external observability platforms.
evlog提供内置适配器,可将日志发送到外部可观测性平台。
Built-in Adapters
内置适配器
| Adapter | Import | Use Case |
|---|---|---|
| Axiom | | Axiom datasets for querying and dashboards |
| OTLP | | OpenTelemetry for Grafana, Datadog, Honeycomb, etc. |
| 适配器 | 导入路径 | 适用场景 |
|---|---|---|
| Axiom | | Axiom数据集,用于查询和仪表盘展示 |
| OTLP | | OpenTelemetry,支持Grafana、Datadog、Honeycomb等平台 |
Quick Setup
快速配置
Axiom:
typescript
// server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})Set and environment variables.
NUXT_AXIOM_TOKENNUXT_AXIOM_DATASETOTLP:
typescript
// server/plugins/evlog-drain.ts
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
})Set environment variable.
NUXT_OTLP_ENDPOINTAxiom:
typescript
// server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})设置环境变量和。
NUXT_AXIOM_TOKENNUXT_AXIOM_DATASETOTLP:
typescript
// server/plugins/evlog-drain.ts
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
})设置环境变量。
NUXT_OTLP_ENDPOINTMultiple Destinations
多目标导出
typescript
import { createAxiomDrain } from 'evlog/axiom'
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
const axiom = createAxiomDrain()
const otlp = createOTLPDrain()
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
await Promise.allSettled([axiom(ctx), otlp(ctx)])
})
})typescript
import { createAxiomDrain } from 'evlog/axiom'
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
const axiom = createAxiomDrain()
const otlp = createOTLPDrain()
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
await Promise.allSettled([axiom(ctx), otlp(ctx)])
})
})Custom Adapter
自定义适配器
typescript
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
// ctx.event contains the full wide event
await fetch('https://your-service.com/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(ctx.event),
})
})
})typescript
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
// ctx.event包含完整的宽事件数据
await fetch('https://your-service.com/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(ctx.event),
})
})
})Security: Preventing Sensitive Data Leakage
安全:防止敏感数据泄露
Wide events capture comprehensive context, making it easy to accidentally log sensitive data.
宽事件会捕获全面的上下文信息,容易意外记录敏感数据。
What NOT to Log
禁止记录的内容
| Category | Examples | Risk |
|---|---|---|
| Credentials | Passwords, API keys, tokens | Account compromise |
| Payment data | Full card numbers, CVV | PCI violation |
| Personal data (PII) | SSN, unmasked emails | GDPR/CCPA violation |
| Authentication | Session tokens, JWTs | Session hijacking |
| 类别 | 示例 | 风险 |
|---|---|---|
| 凭证信息 | 密码、API密钥、令牌 | 账户被盗 |
| 支付数据 | 完整卡号、CVV码 | 违反PCI规范 |
| 个人身份信息(PII) | 社保号、未脱敏邮箱 | 违反GDPR/CCPA法规 |
| 认证信息 | 会话令牌、JWT | 会话劫持 |
Safe Logging Pattern
安全日志模式
typescript
// ❌ DANGEROUS - logs everything including password
const body = await readBody(event)
log.set({ user: body })
// ✅ SAFE - explicitly select fields
log.set({
user: {
id: body.id,
plan: body.plan,
// password: body.password ← NEVER include
},
})typescript
// ❌ 危险 - 记录所有内容,包括密码
const body = await readBody(event)
log.set({ user: body })
// ✅ 安全 - 显式选择需要记录的字段
log.set({
user: {
id: body.id,
plan: body.plan,
// password: body.password ← 绝对不能包含
},
})Sanitization Helpers
脱敏工具函数
typescript
// server/utils/sanitize.ts
export function maskEmail(email: string): string {
const [local, domain] = email.split('@')
return `${local[0]}***@${domain}`
}
export function maskCard(card: string): string {
return `****${card.slice(-4)}`
}typescript
// server/utils/sanitize.ts
export function maskEmail(email: string): string {
const [local, domain] = email.split('@')
return `${local[0]}***@${domain}`
}
export function maskCard(card: string): string {
return `****${card.slice(-4)}`
}Review Checklist
审查清单
When reviewing code, check for:
- Console.log statements → Replace with or wide events
useLogger(event).set() - Generic errors → Add ,
status,why, andfixfields withlinkcreateError() - Scattered request logs → Use (Nuxt/Nitro) or
useLogger(event)(standalone)createRequestLogger() - Missing context → Add user, business, and outcome context with
log.set() - No duration tracking → Let handle it automatically
emit() - No frontend error handling → Catch errors and display toasts with structured data
- Sensitive data in logs → Check for passwords, tokens, full card numbers, PII
- Client-side logging → Use API for debugging in Vue components
log - Client log centralization → Enable to send client logs to server
transport.enabled: true - Missing log draining → Set up adapters (,
evlog/axiom) for production log exportevlog/otlp
审查代码时,请检查以下内容:
- console.log语句 → 替换为或宽事件
useLogger(event).set() - 通用错误 → 使用添加
createError()、status、why和fix字段link - 零散请求日志 → 使用(Nuxt/Nitro)或
useLogger(event)(独立项目)createRequestLogger() - 缺失上下文 → 使用添加用户、业务和结果上下文
log.set() - 无耗时跟踪 → 让自动处理耗时统计
emit() - 无前端错误处理 → 捕获错误并使用结构化数据展示提示信息
- 日志中包含敏感数据 → 检查是否包含密码、令牌、完整卡号、PII信息
- 客户端日志 → 在Vue组件中使用API进行调试
log - 客户端日志集中管理 → 启用将客户端日志发送到服务端
transport.enabled: true - 缺失日志导出 → 配置适配器(、
evlog/axiom)用于生产环境日志导出evlog/otlp
Loading Reference Files
加载参考文件
Load reference files based on what you're working on:
- Designing wide events → references/wide-events.md
- Improving errors → references/structured-errors.md
- Full code review → references/code-review.md
DO NOT load all files at once - load only what's needed for the current task.
根据当前处理的场景加载对应的参考文件:
- 设计宽事件 → references/wide-events.md
- 优化错误处理 → references/structured-errors.md
- 完整代码审查 → references/code-review.md
请勿一次性加载所有文件 - 仅加载当前任务所需的文件。