研发协作
用 evlog 审查并改造…
在 TS/JS 项目中审查日志与错误处理模式,并落地 evlog(结构化宽事件、结构化错误、drain、采样、脱敏、AI SDK 观测)。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
用 evlog 审查并改造 TypeSc特定
解决思路
- 先判断是否可用 CLI:在 Nuxt、Nitro、Next.js、TanStack Start、Hono 上优先用 @evlog/cli(独立包,早期但值得一试),它只读磁盘项目、不依赖流量或配置;若 CLI 不可用、框架无适配器或用户拒绝,则走下面的手动配置。安装任何东西前先询问用户,一次性使用优先 npx / pnpm dlx。
- 用 evlog init 做初始化:先跑 `npx @evlog/cli init --dry-run --yes` 预览不写入,与用户确认后再执行 `npx @evlog/cli init --yes --service my-app --drain fs --prodDrain axiom --extras enrichers,pipeline,sampling --sampling medium`。可用 flag:--framework(覆盖检测:nuxt、nitro、next、tanstack-start、hono)、--prodDrain(逗号分隔:axiom、otlp、posthog、sentry、better-stack、datadog、hyperdx)、--extras(enrichers、pipeline、sampling、vite、error-catalog、audit-catalog、ai、better-auth)、--enrichers、--sampling(all、low、medium、high、very-high)、--apps(monorepo 指定 workspace 包)、--no-install。
- 用 evlog map 打分:运行 `npx @evlog/cli map --no-write`(agent 用 `npx @evlog/cli map --json --no-write`),得到项目分数、仍为 dark 的入口点、FIX FIRST(最值得修的三处)和 GOING FURTHER(catalogs、audit 覆盖、AI 日志、auth 身份等不加分的机会);单文件可用 `npx @evlog/cli map <file> --no-write` 查看 handler 可采用的形态。按 FIX FIRST 顺序修,改动保持最小(useLogger()、log.set()、log.audit()、createError({ why, fix })),再带 --no-write 重跑看分数变化;只有用户想写入 evlog.map.json 时才省略 --no-write。
- 在 CI 中锁定分数:`pnpm add -D @evlog/cli`(项目本地、由 lockfile 固定,先询问)后,用 `pnpm exec evlog map --min-score 80` 做绝对阈值门禁(低于阈值退出 1),用 `pnpm exec evlog map --baseline` 做棘轮门禁(本 PR 让情况变差就退出 1)。--baseline 会按入口点和按需求逐项对比已提交的 evlog.map.json,因此“一个路由加了埋点、另一个被破坏”即使总分不变也会失败;用注释关掉一个通过的检查也算回归;新增未埋点路由只标记为 NEW AND DARK 而不失败。流程:提交一次 evlog.map.json,把 --baseline 加进 CI,需要接受有意变更时再不带 --baseline 重跑 map。注意适配器和规则仍在演进,分数可能随版本变化。
- 按框架接入:Nuxt 在 nuxt.config.ts 的 modules 加 'evlog/nuxt' 并配置 evlog.env.service 与 include(如 ['/api/**']),useLogger、log、parseError 自动导入,但 createError 必须从 evlog 导入(裸用会解析到 h3 的,丢失 why、fix、link);drain/enrich/tail sampling 用 server/plugins 里的 Nitro hooks(如 'evlog:drain' 配 createAxiomDrain());客户端 transport 在 evlog.transport.enabled 打开,日志发往 /api/_evlog/ingest,组件里 log、setIdentity、clearIdentity 自动导入。
- Next.js 分步接入:Step 1 建 lib/evlog.ts 作为唯一出口,用 createEvlog() 配置 service、sampling.rates/keep、routes、keep、enrich、drain(drain 用 createDrainPipeline 批处理后再包 createAxiomDrain),导出 { withEvlog, useLogger, log, createError };Step 2 用 withEvlog() 包裹 route handler,内部 useLogger() 零参数(走 AsyncLocalStorage)并 log.set() 分组字段;Step 3 Server Actions 同样用 withEvlog() 包裹;Step 4 可选在 proxy.ts 用 evlogMiddleware() 设置 x-request-id 与计时头;Step 5 在 app/layout.tsx 用 EvlogProvider 包裹根布局并配置 transport;Step 6 客户端组件从 evlog/next/client 导入 log、setIdentity、clearIdentity 记录;Step 7 可选在根 instrumentation.ts 用 defineNodeInstrumentation(() => import('./lib/evlog')) 或手写 register/onRequestError,并在 lib/evlog.ts 导出 createInstrumentation();Step 8 建 app/api/evlog/ingest/route.ts 接收客户端日志,校验 origin 与 host 一致、校验 timestamp/level 合法,剥离 service 字段后以 [CLIENT LOG] 输出。
- 其他框架接入:SvelteKit 在 src/hooks.server.ts 调 initLogger 并用 createEvlogHooks() 导出 handle/handleError,通过 event.locals.log 或 useLogger() 取 logger;Nitro v3 在 nitro.config.ts 的 modules 加 evlog({ env: { service } }),路由里从 evlog/nitro/v3 导入 useLogger(event);TanStack Start 用 Nitro v3 配置(experimental.asyncContext: true),在 __root.tsx 加 evlogErrorHandler 中间件,用 useRequest() 从 nitro/context 取 req.context.log;Nitro v2 用 defineNitroConfig + evlog({...}),从 evlog/nitro 导入 useLogger;NestJS 在 AppModule imports EvlogModule.forRoot()(异步配置用 forRootAsync() 配合 DI),用 useLogger() 取请求级 logger;Express 用 app.use(evlog()),通过 req.log 或 useLogger() 取 logger;Hono 用 app.use(evlog()),通过 c.get('log') 或 useLogger() 取 logger(Cloudflare Workers 上 useLogger() 需要 nodejs_compat 或 nodejs_als 兼容标志,c.get('log') 不需要),错误处理在 app.onError 里用 parseError() 并把 parsed.status as ContentfulStatusCode 传给 c.json();Fastify 用 await app.register(evlog),request.log 即 evlog 宽事件 logger(遮蔽 pino,fastify.log 仍可访问 pino),也可用 useLogger();Elysia 用 .use(evlog()),从 handler 的 { log } 或 useLogger() 取 logger;React Router 在 react-router.config.ts 开 future.v8_middleware,在 app/root.tsx 导出 middleware: [evlog()],loader/action 里用 context.get(loggerContext),也可用 useLogger();oRPC 用 os.$context<EvlogOrpcContext>().use(evlog()) 暴露 context.log,并用 withEvlog(new RPCHandler(router)) 包裹 handler,工具模块用 useLogger();Cloudflare Workers 用 initWorkersLogger 加 withEvlog 包裹默认导出,返回时自动发一个宽事件、异步 drain 用 waitUntil 保活、流式响应延迟到 body 完成,requestId 取 x-request-id(回退 cf-ray),手动控制场景用 createWorkersLogger(request) + log.emit(),Workers 上没有 ALS 的 useLogger(),需显式传 log;AWS Lambda 无 HTTP 中间件生命周期,按 standalone 处理,关键规则是每次调用一个 logger、绝不用模块级共享 logger(执行环境复用会串字段),模块加载时 initLogger 一次,handler 内每个 record 用 createLogger({ messageId }) 并在 finally 里 log.emit();Astro 在 src/middleware.ts 用 defineMiddleware 创建 createRequestLogger 挂到 locals.log,在 try/catch 中 emit 并记录错误,并在 src/env.d.ts 里声明 Locals.log 类型。
- Vite 系框架(SvelteKit、Astro、SolidStart、React+Vite 等)在 vite.config.ts 加 evlog 插件,配置 service、autoImports: true(自动导入 log、createEvlogError、parseError)、strip: ['debug'](生产移除 log.debug())、sourceLocation: true(注入 file:line)、client.transport.endpoint;服务端中间件(drain、enrich、keep、routes)仍在框架集成里配置,Vite 插件只负责构建期 DX。
- Standalone TypeScript 用 initLogger({ env: { service, environment } }) 初始化,createRequestLogger({ jobId }) 创建后 log.set() 并手动 log.emit()(standalone 必须手动 emit)。
- 按需配置通用选项:env.service/service、enabled、pretty、silent(仍会进 drain)、include、exclude(优先于 include)、routes(按路由指定 service)、minLevel(全局 log API 与客户端 log 的硬阈值,不影响请求宽事件)、sampling.rates(头部采样百分比)、sampling.keep(尾部采样条件如 [{ status: 400 }, { duration: 1000 }])、drain、enrich、keep、redact(生产默认开启)。
- Nitro 系(Nuxt、Nitro v2/v3)用 hooks:'evlog:drain'(enrich 之后发往外部服务)、'evlog:enrich'(emit 后 drain 前补充派生上下文)、'evlog:emit:keep'(自定义尾部采样)、'close'(服务关闭时 flush drain pipeline 缓冲)。
- 按目标服务选择 drain 适配器并配置对应环境变量:Axiom(evlog/axiom,AXIOM_API_KEY、AXIOM_DATASET)、OTLP(evlog/otlp,OTLP_ENDPOINT 或 OTEL_EXPORTER_OTLP_ENDPOINT)、HyperDX(evlog/hyperdx,HYPERDX_API_KEY,可选 HYPERDX_OTLP_ENDPOINT,默认 https://in-otel.hyperdx.io)、PostHog(evlog/posthog,POSTHOG_API_KEY、POSTHOG_HOST)、Sentry(evlog/sentry,SENTRY_DSN)、Better Stack(evlog/better-stack,BETTER_STACK_API_KEY)、Datadog(evlog/datadog,DD_API_KEY 或 DATADOG_API_KEY,可选 DD_SITE / DATADOG_LOGS_URL)、Grafana Loki(evlog/loki,LOKI_ENDPOINT,可选 LOKI_API_KEY + LOKI_USER 或 LOKI_TENANT_ID)、ClickHouse(evlog/clickhouse,CLICKHOUSE_ENDPOINT,可选 CLICKHOUSE_USER / CLICKHOUSE_PASSWORD / CLICKHOUSE_DATABASE / CLICKHOUSE_TABLE)、File System(evlog/fs,无)、Memory(evlog/memory,进程内环形缓冲,可选 EVLOG_MEMORY_STORE、EVLOG_MEMORY_MAX_EVENTS,用 readMemoryLogs() 读回,适合 dev 日志端点)、NuxtHub(@evlog/nuxthub 独立包,无环境变量,用 evlog.retention: '7d' 设置保留期,支持 d/h/m)、HTTP 浏览器上报(evlog/http,代码里配 endpoint;evlog/browser 已废弃,下个大版本移除)。使用规范环境变量名,各框架通用。
- 按框架挂 drain:Nuxt/Nitro 在 server/plugins/evlog-drain.ts 里 hook 'evlog:drain';Hono/Express/Elysia 在 evlog({ drain }) 中间件选项里传;Fastify 在 app.register(evlog, { drain }) 里传;NestJS 在 EvlogModule.forRoot({ drain }) 里传;Next.js 把 pipeline(createAxiomDrain()) 传给 createEvlog();standalone 传给 initLogger()。批处理、重试与缓冲溢出配置见 references/drain-pipeline.md。
- 用内置 enricher 补充上下文:createUserAgentEnricher()、createGeoEnricher()、createRequestSizeEnricher()、createTraceContextEnricher()(均来自 evlog/enrichers,各自接受 { overwrite?: boolean },默认 false),或用 createDefaultEnrichers() 一次组合四个。Nuxt/Nitro 在 server/plugins/evlog-enrich.ts 里 hook 'evlog:enrich' 逐个调用;Next.js 在 createEvlog 的 enrich 回调里循环调用并追加自定义字段(如 ctx.event.region = process.env.VERCEL_REGION)。
- 开启并定制自动脱敏(PII 保护):脱敏在控制台输出前、任何 drain 看到数据前生效,生产环境(NODE_ENV === 'production')默认开启、开发环境关闭,采用智能部分掩码保留调试上下文。可用 redact: false 关闭;用 redact.paths 追加自定义路径(如 'user.password'、'headers.authorization');用 redact.builtins 只保留指定内置项(如 ['email', 'creditCard']);用 redact.builtins: false + paths + patterns 只用自定义规则(此时用扁平 [REDACTED] 替换)。内置模式与掩码示例:creditCard 4111111111111111 → ****1111;email alice@example.com → a***@***.com;ipv4 192.168.1.100 → ***.***.***.100;phone +33 6 12 34 56 78 → +33 ****5678;jwt eyJhbGciOi... → eyJ***.***;bearer Bearer sk_live_abc... → Bearer ***;iban FR76 3000 6000 ...189 → FR76****189。所有框架通用。
- 接入 AI SDK 观测(evlog/ai,peer 依赖 ai >=6.0.168 <8.0.0):用 createAILogger(log) 创建,用 ai.wrap('anthropic/claude-sonnet-4.6')(接受字符串或模型对象)包裹模型,通过模型中间件透明捕获 generateText、streamText、ToolLoopAgent 的调用;需要工具执行耗时与总生成墙钟时间时,在 telemetry.integrations 里加 createEvlogIntegration(ai),会补充 ai.tools(每个工具的 { name, durationMs, success, error? })和 ai.totalDurationMs;embed 后用 ai.captureEmbed({ usage, model, dimensions }) 记录,embedMany 额外传 count;传 cost 定价表(如 { 'claude-sonnet-4.6': { input: 3, output: 15 } })得到 ai.estimatedCost。宽事件的 ai 字段包含 calls、model、provider、inputTokens、outputTokens、totalTokens、cacheReadTokens、reasoningTokens、finishReason、toolCalls、steps、msToFirstChunk、msToFinish、tokensPerSecond、error、tools、totalDurationMs、embedding、estimatedCost。
- 用结构化错误替代裸 Error:从 evlog 导入 createError(Nuxt 中也必须,裸 createError 是 h3 的)。最小用法 createError({ message, status }),标准用法加 why,完整用法再加 fix、link、cause;仅后端可见的诊断信息放 internal(如 { correlationId, resourceId }),它只进宽事件/drain,绝不进 HTTP body 或 parseError()。前端用 parseError(err) 提取 message、status、why、fix、link(internal 永不返回给客户端)。常见模式与模板见 references/structured-errors.md。
- 安全敏感操作(auth、billing、admin、数据导出)用 evlog 的 audit 层:在宽事件上加类型化 audit 字段,而不是另起一个 logger。调用 log.audit({ action: 'invoice.refund', actor: { type: 'user', id: user.id }, target: { type: 'invoice', id: invoice.id }, outcome: 'success' }),端到端配置(log.audit、withAudit、denials、auditEnricher、auditOnly、signed、mockAudit)见 build-audit-logs 技能与 https://www.evlog.dev/use-cases/audit/overview。
- 按反模式清单逐项检测并修复:一个函数里多个 console.log → 用 log.set() 合成单个宽事件;throw new Error('...') → throw createError({ message, status, why, fix });console.error(e); throw e → log.error(e); throw createError(...);请求处理器无日志 → 加 useLogger(event) / useLogger() / createRequestLogger();扁平日志数据 { uid, n, t } → 分组对象 { user: {...}, cart: {...} };记录敏感数据 log.set({ user: body }) → 只写显式字段 { user: { id: body.id, plan: body.plan } } 并开启 redact: true;把仅支持用的 ID 放进 why/message → 改用 createError({ ..., internal: { ... } })。完整清单见 references/code-review.md。
- AI 相关反模式检测与修复:在 onFinish 里手工统计 token → 用 ai.wrap() 由中间件自动捕获;console.log('tokens:', result.usage) → 用 ai.wrap() 得到结构化 ai.* 字段;完全没有 AI 可观测性 → 加 createAILogger(log) + ai.wrap();没有工具执行耗时 → 在 telemetry.integrations 加 createEvlogIntegration(ai);手工算成本 → 用 createAILogger() 的 cost 选项。
- 按需推荐相关能力(发现对应需求时指向 evlog.dev 文档):Better Auth 应用事件缺认证用户身份 → evlog/better-auth 的 identifyUser()、createAuthMiddleware()、客户端身份同步;字段命名在各处漂移 → evlog/catalog 的类型化字段与 error/audit catalogs;需要横切钩子(请求开始/结束、客户端日志、扩展 logger)→ definePlugin 插件;开发时实时看日志或做日志查看器 → createStreamDrain(evlog/stream,SSE)+ readFsLogs / tailFsLogs(evlog/fs);agent 需要在 dev 通过 HTTP 查询日志 → Memory 适配器 + readMemoryLogs() 放在仅 dev 的端点后。
- 按当前任务加载参考文件,不要一次全加载:设计宽事件 → references/wide-events.md;改进错误 → references/structured-errors.md;完整代码审查 → references/code-review.md;drain pipeline 配置 → references/drain-pipeline.md。
适用边界 · 注意事项
- 项目不是 TypeScript/JavaScript,或目标框架不在 evlog 支持列表内(Nuxt、Nitro v2/v3、Next.js、TanStack Start、Hono、SvelteKit、React Router、NestJS、Express、Fastify、Elysia、oRPC、Cloudflare Workers、AWS Lambda、Astro、standalone TS)。
- 用户不愿引入 evlog 依赖、不愿安装 @evlog/cli,或明确拒绝改动日志/错误处理代码。
- 需要的是与 evlog 无关的通用日志方案(如已有成熟的 pino/winston/OpenTelemetry 体系且不打算迁移)。