review-logging-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Review 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:
  • console.log
    spam → wide events with
    useLogger(event)
  • throw new Error('...')
    createError({ message, status, why, fix })
  • Scattered request logs →
    useLogger(event)
    (Nuxt/Nitro) or
    createRequestLogger()
    (standalone)
在以下场景使用本技能:
  • 审查代码是否符合日志最佳实践
  • 用户要求改进日志系统
  • 将console.log语句转换为结构化日志
  • 通过补充上下文优化错误处理
  • 在API路由中设置请求作用域日志
  • 排查日志难以搜索/过滤的问题
核心转换:
  • 冗余
    console.log
    → 使用
    useLogger(event)
    生成宽事件
  • throw new Error('...')
    createError({ message, status, why, fix })
  • 零散请求日志 →
    useLogger(event)
    (Nuxt/Nitro)或
    createRequestLogger()
    (独立项目)

Quick Reference

快速参考

Working on...Resource
Wide events patternsreferences/wide-events.md
Error handlingreferences/structured-errors.md
Code review checklistreferences/code-review.md
Log draining & adaptersSee "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 usage
typescript
// 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 evlog
bash
npm install evlog

Nuxt 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
parseError()
to extract all fields at the top level:
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
log
API works on both server and client. In Nuxt, it's auto-imported:
typescript
// 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.
log
API同时支持服务端和客户端。在Nuxt中已自动导入:
typescript
// 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:
  1. Client logs are sent to
    /api/_evlog/ingest
    via POST
  2. Server enriches with environment context (service, version, etc.)
  3. evlog:drain
    hook is called with
    source: 'client'
  4. 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,  // 将客户端日志发送到服务端
    },
  },
})
启用后:
  1. 客户端日志通过POST请求发送到
    /api/_evlog/ingest
  2. 服务端补充环境上下文(服务名称、版本等)
  3. 调用
    evlog:drain
    钩子,参数
    source: 'client'
  4. 外部服务接收日志
在分流钩子中识别客户端日志:
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

内置适配器

AdapterImportUse Case
Axiom
evlog/axiom
Axiom datasets for querying and dashboards
OTLP
evlog/otlp
OpenTelemetry for Grafana, Datadog, Honeycomb, etc.
适配器导入路径适用场景
Axiom
evlog/axiom
Axiom数据集,用于查询和仪表盘展示
OTLP
evlog/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
NUXT_AXIOM_TOKEN
and
NUXT_AXIOM_DATASET
environment variables.
OTLP:
typescript
// server/plugins/evlog-drain.ts
import { createOTLPDrain } from 'evlog/otlp'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
})
Set
NUXT_OTLP_ENDPOINT
environment variable.
Axiom:
typescript
// server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
设置环境变量
NUXT_AXIOM_TOKEN
NUXT_AXIOM_DATASET
OTLP:
typescript
// server/plugins/evlog-drain.ts
import { createOTLPDrain } from 'evlog/otlp'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
})
设置环境变量
NUXT_OTLP_ENDPOINT

Multiple 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

禁止记录的内容

CategoryExamplesRisk
CredentialsPasswords, API keys, tokensAccount compromise
Payment dataFull card numbers, CVVPCI violation
Personal data (PII)SSN, unmasked emailsGDPR/CCPA violation
AuthenticationSession tokens, JWTsSession 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:
  1. Console.log statements → Replace with
    useLogger(event).set()
    or wide events
  2. Generic errors → Add
    status
    ,
    why
    ,
    fix
    , and
    link
    fields with
    createError()
  3. Scattered request logs → Use
    useLogger(event)
    (Nuxt/Nitro) or
    createRequestLogger()
    (standalone)
  4. Missing context → Add user, business, and outcome context with
    log.set()
  5. No duration tracking → Let
    emit()
    handle it automatically
  6. No frontend error handling → Catch errors and display toasts with structured data
  7. Sensitive data in logs → Check for passwords, tokens, full card numbers, PII
  8. Client-side logging → Use
    log
    API for debugging in Vue components
  9. Client log centralization → Enable
    transport.enabled: true
    to send client logs to server
  10. Missing log draining → Set up adapters (
    evlog/axiom
    ,
    evlog/otlp
    ) for production log export
审查代码时,请检查以下内容:
  1. console.log语句 → 替换为
    useLogger(event).set()
    或宽事件
  2. 通用错误 → 使用
    createError()
    添加
    status
    why
    fix
    link
    字段
  3. 零散请求日志 → 使用
    useLogger(event)
    (Nuxt/Nitro)或
    createRequestLogger()
    (独立项目)
  4. 缺失上下文 → 使用
    log.set()
    添加用户、业务和结果上下文
  5. 无耗时跟踪 → 让
    emit()
    自动处理耗时统计
  6. 无前端错误处理 → 捕获错误并使用结构化数据展示提示信息
  7. 日志中包含敏感数据 → 检查是否包含密码、令牌、完整卡号、PII信息
  8. 客户端日志 → 在Vue组件中使用
    log
    API进行调试
  9. 客户端日志集中管理 → 启用
    transport.enabled: true
    将客户端日志发送到服务端
  10. 缺失日志导出 → 配置适配器(
    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
请勿一次性加载所有文件 - 仅加载当前任务所需的文件。