Activepieces 代码审查指南:用 evlog 宽事件与结构化错误重构日志模式
2026/9/13 4:41:30 网站建设 项目流程

Activepieces 代码审查指南:用 evlog 宽事件与结构化错误重构日志模式

【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces

本篇指南基于 Activepieces 仓库内.agents/skills/review-logging-patterns技能附带的代码审查清单(code-review.md)整理而成,面向需要在 TypeScript/JavaScript 代码库中审查并改进日志与错误处理模式的开发者。你将掌握一套可立即落地的审查流程:扫描console.log泛滥、识别无上下文的泛化错误、为请求处理器补齐请求级日志,并学会把散落的日志与错误转换为 evlog 的「宽事件(wide event)」与「结构化错误(structured error)」,让每条日志都自带可排障的完整上下文。

背景:为什么要审查日志模式

Activepieces 服务端本身已在生产代码中实践了这套日志体系。在 packages/server/api/src/app/helper/logger/index.ts 中可以看到,API 服务通过evlogSetup.init()初始化全局日志门面,支持LOG_LEVELLOG_PRETTYLOG_SAMPLE_RATE_INFO(info 级采样率)、LOG_KEEP_SLOW_MS(慢请求保留阈值)等环境变量,并可通过HYPERDX_TOKENAXIOM_TOKENAXIOM_DATASETLOKI_URLBETTERSTACK_TOKENOTEL_ENABLEDLOG_FILE等配置把事件投递到 HyperDX、Axiom、Loki、BetterStack、OTLP 或本地文件:

// packages/server/api/src/app/helper/logger/index.ts(节选) return evlogSetup.init({ params: { serviceName: 'activepieces-api', version: apVersionUtil.getCurrentRelease(), logLevel, logPretty, sampleRateInfo, keepSlowMs, drainConfig: { hyperdxToken: environmentVariables.getEnvironment(AppSystemProp.HYPERDX_TOKEN), axiomToken: environmentVariables.getEnvironment(AppSystemProp.AXIOM_TOKEN), lokiUrl: environmentVariables.getEnvironment(AppSystemProp.LOKI_URL), betterstackToken: environmentVariables.getEnvironment(AppSystemProp.BETTERSTACK_TOKEN), otlpEnabled: environmentVariables.getEnvironment(AppSystemProp.OTEL_ENABLED) === 'true', }, }, })

同时,Activepieces 使用 Fastify 的FastifyBaseLogger作为日志门面类型(例如 action-run.service.ts 中import { FastifyBaseLogger } from 'fastify'),并通过log.child({ flowRunId, webhookId, flowId, flowVersionId })为每次流程运行、每个 Webhook 请求创建携带业务上下文的子日志器——这与本指南要讲的「在请求/任务作用域内累积上下文、最终一次性输出」的理念一脉相承。

理解了这个背景,下面进入完整的审查清单。

一、快速扫描(Quick Scan):三分钟定位问题

开始正式审查前,先用下面三项快速检查确定改进机会。它们覆盖了最常见的三类日志问题:散落的 console 语句、无上下文的错误、缺失日志的请求处理器。

1. Console 语句审计

在代码库中搜索以下模式:

// ❌ 需要查找并改造的模式 console.log(...) console.error(...) console.warn(...) console.info(...) console.debug(...)

需要追问的问题:

  • 同一个函数里是否存在多条 console 语句?
  • 它们是否在记录请求/响应数据?
  • 是否可以把它们合并成一个宽事件(wide event)?

关于宽事件的完整概念(为什么一次请求只应输出一条包含全部上下文的日志),参见仓库内的 wide-events.md。

2. 错误模式审计

搜索以下模式:

// ❌ 泛化错误 throw new Error('...') throw Error('...') // ❌ 无上下文的重新抛出 catch (error) { throw error } // ❌ 先记录再抛出(重复记录) catch (error) { console.error(error) throw error }

需要追问的问题:

  • 错误消息是否解释了「发生了什么」?
  • 是否有why字段说明根本原因?
  • 是否有fix字段给出解决方案建议?
  • 原始错误是否作为cause被保留下来?

3. 请求处理器审计

对每个 API 路由/处理器,检查:

// ❌ 缺少请求上下文 export default defineEventHandler(async (event) => { // 完全没有日志,或者散落着 console.log })

需要追问的问题:

  • 是否有请求作用域(request-scoped)的 logger?
  • 上下文是否在整个请求过程中被持续累积?
  • 是否在请求结束时只输出一次(single emit)?

二、详细审查(Detailed Review):三类转换的完整范式

快速扫描发现的问题,按下述三类范式逐一改造。

Console.log 转换

单个调试日志
// ❌ 改造前 console.log('Processing user:', userId) // ✅ 改造后 - 如果是更大操作的一部分 log.set({ user: { id: userId } }) // ✅ 改造后 - 如果是独立的调试信息 log.debug('user', `Processing user ${userId}`)
多条相关日志合并
// ❌ 改造前 console.log('Starting checkout') console.log('User:', user.id) console.log('Cart items:', cart.items.length) console.log('Total:', cart.total) // ✅ 改造后 - 合并为一条宽事件 log.info({ action: 'checkout', user: { id: user.id }, cart: { items: cart.items.length, total: cart.total }, })
请求生命周期日志
// server/api/process.post.ts // ❌ 改造前 export default defineEventHandler(async (event) => { console.log('Request started') const user = await getUser(event) console.log('User loaded') const result = await processData(user) console.log('Processing complete') return result }) // ✅ 改造后(Nuxt - 自动导入,无需 import) // Nitro v3: import { useLogger } from 'evlog/nitro/v3' // Nitro v2: import { useLogger } from 'evlog/nitro' export default defineEventHandler(async (event) => { const log = useLogger(event) const user = await getUser(event) log.set({ user: { id: user.id } }) const result = await processData(user) log.set({ result: { id: result.id } }) return result // emit() 自动触发 })

注意:在 Nuxt/Nitro 下emit()由框架在请求结束时自动调用;而在独立 TypeScript 场景(脚本、Worker)下必须手动调用emit(),参见 SKILL 中「Standalone TypeScript」一节的 SKILL.md。

错误转换

泛化错误
// ❌ 改造前 throw new Error('Failed to create user') // ✅ 改造后 throw createError({ message: 'Failed to create user', why: 'Email address already registered', fix: 'Use a different email or log in to existing account', link: 'https://your-app.com/docs/registration', })
无上下文的包装错误
// ❌ 改造前 try { await externalApi.call() } catch (error) { throw new Error('API call failed') } // ✅ 改造后 try { await externalApi.call() } catch (error) { throw createError({ message: 'External API call failed', why: `API returned: ${error.message}`, fix: 'Check API credentials and try again', link: 'https://api-docs.example.com/errors', cause: error, }) }
「先记录再抛出」反模式
// ❌ 改造前 try { await riskyOperation() } catch (error) { console.error('Operation failed:', error) throw error } // ✅ 改造后 - 记录宽事件,同时抛出带上下文的错误 try { await riskyOperation() } catch (error) { log.error(error, { step: 'riskyOperation' }) throw createError({ message: 'Operation failed', why: error.message, fix: 'Check input and retry', cause: error, }) }

请求处理器转换

完全无日志的处理器
// server/api/orders.post.ts // ❌ 改造前 export default defineEventHandler(async (event) => { const body = await readBody(event) const result = await processOrder(body) return result }) // ✅ 改造后(Nuxt - 自动导入,无需 import) // Nitro v3: import { useLogger } from 'evlog/nitro/v3' // Nitro v2: import { useLogger } from 'evlog/nitro' import { createError } from 'evlog' export default defineEventHandler(async (event) => { const log = useLogger(event) const body = await readBody(event) log.set({ order: { items: body.items?.length } }) try { const result = await processOrder(body) log.set({ result: { orderId: result.id, status: result.status } }) return result } catch (error) { log.error(error, { step: 'processOrder' }) throw createError({ message: 'Order processing failed', why: error.message, fix: 'Check the order data and try again', }) } // emit() 自动触发 })

三、审查清单汇总(Review Checklist Summary)

审查收尾时,逐项核对以下清单。

日志(Logging)

  • 生产代码中不存在裸console.log语句
  • 请求处理器使用useLogger(event)(Nuxt/Nitro)或createRequestLogger()(独立场景)
  • 上下文通过log.set()在整个请求过程中持续累积
  • emit()在使用useLogger()时自动触发,使用createRequestLogger()时手动触发
  • 宽事件包含:用户信息、业务上下文、结果(outcome)

错误(Errors)

  • 所有错误使用createError()而非new Error()(从evlog导入)
  • 每个错误都有清晰的message和恰当的status状态码
  • 复杂错误包含解释根因的why
  • 可修复的错误包含可操作步骤的fix
  • 有文档的错误包含link指向文档
  • 包装错误保留原始错误的cause
  • 仅面向运维或敏感的诊断信息放在internal,而非message/why/fix

internal字段只出现在服务端日志与 drain 输出中,不会进入 HTTP 错误响应体,也不会出现在客户端parseError()的结果中;它存放在非枚举 Symbol 上,JSON.stringify(error)不会泄露其内容。更完整的字段规范与错误模板参见 structured-errors.md。

前端错误处理(Frontend Error Handling)

  • API 错误被捕获并以完整上下文展示(message、why、fix)
  • Toast 或错误组件使用error.data.data中的结构化数据
  • 文档链接可点击操作(Toast 中的按钮/链接)

上下文(Context)

  • 用户上下文包含:id、套餐/订阅、相关业务数据
  • 请求上下文包含:method、path、requestId
  • 业务上下文与领域相关且对排障有用
  • 日志中不包含敏感数据(密码、令牌、完整卡号)

四、反模式汇总表

反模式修复方案
一个函数内多条console.loguseLogger(event).set()累积为单条宽事件
throw new Error('...')throw createError({ message, status, why, fix })
console.error(e); throw elog.error(e); throw createError(...)
请求处理器完全无日志添加useLogger(event)(Nuxt/Nitro)或createRequestLogger()(独立场景)
扁平化日志数据分组对象:{ user: {...}, cart: {...} }
缩写字段名使用描述性命名:用userId而不是uid

五、可直接复用的审查评论模板

审查者在 PR 评论中使用以下措辞,能帮助作者快速理解改法。

发现 Console.log

Consider using evlog's wide event pattern here. Instead of multiple console.log statements, useuseLogger(event)to accumulate context and emit a single comprehensive event.

泛化错误

This error would benefit from evlog's structured error pattern. Consider usingimport { createError } from 'evlog'andcreateError({ message, status, why, fix })to provide more debugging context.

缺少请求上下文

This handler would benefit from request-scoped logging. AdduseLogger(event)at the start to capture context throughout the request lifecycle.

正向反馈(日志质量好)

Nice use of wide events here! The context is well-structured and will be very useful for debugging.

六、进阶:审查后的落地与深化

宽事件应包含哪些字段

每条宽事件都应包含三层上下文(详见 wide-events.md):

  • 请求上下文methodpathrequestIdtraceId(用于分布式追踪);
  • 用户上下文user.iduser.planuser.accountAge等与业务相关的字段;
  • 业务上下文:按领域补充,如电商的cartpaymentorder,上传场景的upload.filenamesizemimeType

结果(outcome)通过status字段记录,durationemit()自动计算,无需手动计时。

安全红线:显式选择要记录的字段

// ❌ 危险 - 会把 password 也打出来 log.set({ user: body }) // ✅ 安全 - 显式选择字段 log.set({ user: { id: body.id, email: maskEmail(body.email), // password: body.password ← 永远不要包含 }, })

永远不要记录:密码、API 密钥、令牌、密钥、完整卡号、CVV、SSN、PII、会话令牌、JWT。

生产环境:排水管道与采样

生产环境建议用createDrainPipeline包裹 drain 适配器以获得批处理、指数退避重试与缓冲区溢出保护,并务必在服务close钩子中调用drain.flush(),否则进程退出时缓冲的事件会丢失(配置细节与反模式见 drain-pipeline.md)。结合 Activepieces 的实践,采样率与慢请求阈值这类策略都可以通过环境变量下发,例如LOG_SAMPLE_RATE_INFO控制 info 级事件的采样百分比,LOG_KEEP_SLOW_MS控制超过多少毫秒的请求必须保留——这正对应 evlog 的 head sampling(sampling.rates)与 tail sampling(sampling.keep)能力。

错误如何流向 HTTP 响应与前端

后端只需throw createError(...),框架会自动把结构化字段转成 HTTP 响应;前端用parseError()提取messagewhyfixlink后,既可展示在 Toast 中,也可把link渲染为可点击的「了解更多」按钮——让用户看到的不是一句「出错了」,而是发生了什么、为什么、怎么解决(示例见 structured-errors.md)。

结语:让审查有据可依

把这份清单作为每次涉及日志与错误处理改动的 PR 审查基线:先快速扫描三类高频问题,再按「宽事件 + 结构化错误」两个范式逐例改造,最后对照汇总清单逐项验收。配合仓库内的 wide-events.md、structured-errors.md 与 drain-pipeline.md 三份参考文档,以及 SKILL.md 中各框架(Nuxt、Next.js、Nitro、Express、Fastify、Hono、NestJS 等)的接入方式,你就能把「散落的 console 日志」逐步收敛为「一条宽事件讲清一次请求」,把「一句 failed」升级为「message + why + fix + cause 俱全」的可诊断错误。

【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询