mcpField
C
create-evlog-framework-integration

create-evlog-framework-integration

github.com/evloghq开发1.9k531 次安装2 天前更新
代码仓库

关于 create-evlog-framework-integration

这款仓库 Skill 面向为 evlog 添加框架集成的贡献者,介绍共享 manifest 方案和自定义中间件的适用场景。它列出必要 API、测试要求以及包和文档改动。

主要功能

  1. 01Manifest 与自定义集成路径
  2. 02共享中间件和存储工具
  3. 03集成所需 API
  4. 04框架专用测试指导
  5. 05构建、包和文档清单

适用场景

  1. 01为框架添加 evlog 支持
  2. 02选择集成架构
  3. 03准备集成测试与包改动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/evloghq/evlog --skill create-evlog-framework-integration

安装后在 Claude Code 中输入/create-evlog-framework-integration确认已加载。

详细介绍

创建 evlog 框架集成

为 evlog 添加新的框架集成。对于具有请求/响应中间件结构的框架,建议采用基于 evlog/toolkit 中 defineFrameworkIntegration 的清单模式。对于生命周期从根本上不同的框架,则回退到较低层级的 createMiddlewareLogger。

两种方式

  • 清单模式(首选,约 30–80 行胶合代码)。在模块级别调用一次 defineFrameworkIntegration({ name, extractRequest, attachLogger, storage? }),然后编写一个简短的中间件,调用 integration.start(ctx, options),并在 runWith 中运行框架的 next()。参考实现:packages/evlog/src/{hono,express,fastify,elysia,nestjs,orpc,react-router,sveltekit,workers}/index.ts 中的所有实现均采用此模式。
  • 自定义模式:当框架生命周期不符合标准中间件模式时,直接使用 createMiddlewareLogger。当前采用自定义模式的集成包括:Next.js(src/next/)、Nitro v2/v3(src/nitro/、src/nitro-v3/)、Eve(src/eve/)。

清单模式现在已覆盖所有经典 HTTP 框架。仅当无法在生命周期开始时同步提取请求时(服务器操作、模块级钩子、Agent 轮次),才使用自定义模式。

必需的 API 接口(来自 AGENTS.md)

每个框架集成都必须提供:

  1. 接受完整 BaseEvlogOptions(drain、enrich、keep、include、exclude、routes、plugins)的 evlog() 中间件/插件
  2. useLogger()(由 ALS 支持)。有两个获准的例外:Workers(ALS 在此需要兼容标志;defineWorkerFetch 会改为附加日志记录器)和 Effect(日志记录器是 Effect 上下文中的 EvlogLogger 服务)
  3. 支持 log.fork()(向清单提供 storage 时自动启用)
  4. 框架原生访问器(c.get('log')、req.log、event.locals.log,……)

PR 标题

feat({framework}): add {Framework} middleware integration

范围时机注意事项:语义 PR 检查会从基分支读取范围列表,因此新范围无法验证引入该范围的 PR。请先通过一个小型 PR 注册该范围,或者在引入 PR 中使用不带范围的标题(feat: add {Framework} middleware integration)。

检查点清单

#文件操作
1packages/evlog/src/{framework}/index.ts创建集成源码
2packages/evlog/tsdown.config.ts添加构建入口和 external
3packages/evlog/package.json添加 exports、typesVersions、可选 peer dep 和关键词
4packages/evlog/test/frameworks/{framework}.test.ts创建测试(使用真实请求驱动器和 describeStandardHttpMatrix)
5packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap由 pnpm run build 和 pnpm test 重新生成
6apps/docs/content/4.integrate/frameworks/{NN}.{framework}.md创建框架文档页面
7apps/docs/content/4.integrate/frameworks/00.overview.md添加表格行和卡片
8apps/docs/content/1.start/3.installation.md在“选择你的框架”中添加卡片
9apps/docs/content/0.landing.md添加框架代码片段位置
10apps/docs/app/components/features/FeatureFrameworks.vue添加框架标签页
11skills/review-logging-patterns/SKILL.md添加框架设置部分并更新 frontmatter 描述
12packages/evlog/README.md添加框架部分,并在框架支持表中添加一行
13examples/{framework}/创建带测试 UI 的示例应用(由 pnpm example {framework} 自动发现,无需根脚本)
14.changeset/{framework}-integration.md创建 changeset(minor)
15.github/workflows/semantic-pull-request.yml + .github/pull_request_template.md在两个文件中将 {framework} 注册为 PR 范围

重要:完成所有 15 个检查点后,才能认为任务已完成。

命名约定

占位符示例(Hono)用途
{framework}hono目录名、导入路径、文件名、PR 范围
{Framework}Hono类型/接口名称中的 PascalCase

共享工具

所有集成都共用相同的核心工具。**绝不要重新实现 shared/ 中已有的逻辑。**这些工具也通过 evlog/toolkit 向社区构建的集成公开提供(参见自定义集成文档)。

工具位置用途
defineFrameworkIntegration../shared/integration清单工厂:提取请求、创建日志记录器、挂载,并使用 ALS 运行
createMiddlewareLogger../shared/middleware更底层的生命周期(自定义模式):创建日志记录器、路由筛选、尾部采样、发送、丰富和排空
BaseEvlogOptions../shared/middleware面向用户的基础选项类型,包含 drain、enrich、keep、include、exclude、routes、plugins
createLoggerStorage../shared/storage(evlog/toolkit/storage)返回 { storage, useLogger } 的工厂,用于基于 AsyncLocalStorage 的 useLogger()。在 Workers / edge 上优先使用 evlog/toolkit/storage
shouldDeferEmitForResponse../shared/streamResponse延迟发送宽事件,直到流式响应体关闭(参见 Hono/Elysia)

defineFrameworkIntegration 会自动:

  • 规范化 Web Headers 和 Node IncomingHttpHeaders(因此无需选择头部提取器)
  • 在不存在 requestId 时生成一个
  • 调用 createMiddlewareLogger,并提供其 { logger, finish, skipped, middlewareOptions }
  • 在提供 storage 时自动挂载 log.fork()
  • 提供 runWith(fn),以便在集成的 ALS 中运行下游处理器
  • 在运行时提供 waitUntil 时转发它(Workers、Workers 上的 Hono)

第 1 步:集成源码

创建 packages/evlog/src/{framework}/index.ts。

模板结构(清单模式)
import type { AuditableLogger } from '../audit'
import { defineFrameworkIntegration } from '../shared/integration'
import type { BaseEvlogOptions } from '../shared/middleware'
import { createLoggerStorage } from '../shared/storage'

const { storage, useLogger } = createLoggerStorage(
  'middleware context. Make sure the evlog middleware is registered before your routes.',
  'evlog:{framework}',
)

export type Evlog{Framework}Options = BaseEvlogOptions
export { useLogger }

// Type augmentation for typed logger access (framework-specific):
// - Express: declare module 'express-serve-static-core' { interface Request { log: AuditableLogger } }
// - Hono:    export type EvlogVariables = { Variables: { log: AuditableLogger } }

const integration = defineFrameworkIntegration<{Framework}Context>({
  name: '{framework}',
  extractRequest: (ctx) => ({
    method: /* ctx.method */,
    path: /* ctx.path */,
    headers: /* Web Headers OR Node headers OR plain object */,
    requestId: /* x-request-id header or undefined → auto-generated */,
  }),
  attachLogger: (ctx, logger) => {
    // Store in framework-idiomatic location:
    // - Hono:    c.set('log', logger)
    // - Express: req.log = logger
  },
  storage,
})

export function evlog(options: Evlog{Framework}Options = {}): FrameworkMiddleware {
  return async (ctx, next) => {
    const { skipped, finish, runWith } = integration.start(ctx, options)
    if (skipped) {
      await next()
      return
    }
    try {
      await runWith(() => next())
      await finish({ status: /* extract status from ctx */ })
    } catch (error) {
      await finish({ error: error as Error })
      throw error
    }
  }
}
参考实现
  • Hono:src/hono/index.ts。c.set('log', logger) + ALS useLogger(),通过 shouldDeferEmitForResponse 延迟流式响应,检测 waitUntil
  • Express:src/express/index.ts。req.log、ALS 存储、使用 res.on('finish') 获取终止状态
  • Fastify:src/fastify/index.ts。Fastify 钩子(onRequest / onResponse / onError)、fastify-plugin 包装器
  • Elysia:src/elysia/index.ts。使用 .derive({ as: 'global' }) 的插件、storage.enterWith 风格的 ALS、延迟流式响应
  • NestJS:src/nestjs/index.ts。基于清单实现 EvlogModule.forRoot() / forRootAsync()。在 @nestjs/platform-fastify 上,中间件只能看到原始请求,因此通过 HttpAdapterHost 注册的 preHandler 钩子会将日志记录器复制到 FastifyRequest.log
  • oRPC:src/orpc/index.ts。evlog() 过程级中间件 + withEvlog(handler) 包装器
  • React Router:src/react-router/index.ts。loggerContext = createContext<AuditableLogger>()
  • SvelteKit:src/sveltekit/index.ts。evlog() handle + evlogHandleError() + createEvlogHooks()
  • Workers:src/workers/index.ts。defineWorkerFetch / withEvlog,不使用 ALS useLogger()(兼容性标志限制)

对于使用 enterWith() 绑定的集成,请使用 src/shared/asyncStorageScope.ts 中的 createSharedEnterWithStorage。它的能力探测会在临时作用域和独立存储中运行,从而在 Bun 和 Node 上保留导入方的异步上下文。

核心架构规则
  1. 优先使用 defineFrameworkIntegration:它会处理请求头规范化、请求 ID 生成、ALS、分叉附加和 waitUntil。
  2. 状态和错误报告由框架侧负责:成功时调用 finish({ status }),失败时调用 finish({ error })。finish 会执行 emit、enrich、drain 和插件钩子。
  3. 调用 finish({ error }) 后,重新抛出错误,以便框架自身的错误处理程序仍能运行。
  4. 流式响应:如果框架可以返回流式响应体,则将 emit 延迟到流关闭时(shouldDeferEmitForResponse;参见 Hono 和 Elysia)。
  5. 框架 SDK 是可选的 peer dependency:切勿将其打包。
  6. 切勿重复实现流水线逻辑:runEnrichAndDrain 是 createMiddlewareLogger/finish 的内部实现。
  7. 导出类型辅助工具,以便进行带类型的上下文访问(例如,Hono 的 EvlogVariables)。
何时回退到自定义模式

在以下情况下,直接使用 createMiddlewareLogger(跳过 defineFrameworkIntegration):

  • 中间件没有明确的“请求进入/响应退出”配对(Next.js App Router server actions、Eve Agent 回合)。
  • 日志记录器的创建跨越由模块系统管理的多个生命周期阶段(Nitro 插件和钩子)。
  • 只有在响应流完成后才能确定状态,而框架又没有提供相应的钩子。

第 2 步:构建配置

在 packages/evlog/tsdown.config.ts 中添加构建入口:

'{framework}/index': 'src/{framework}/index.ts',

同时将框架 SDK 添加到 external 数组中(例如,'elysia'、'fastify')。

第 3 步:包导出

在 packages/evlog/package.json 中:

在 exports 中(放在最后一个框架条目之后):

"./{framework}": {
  "types": "./dist/{framework}/index.d.mts",
  "import": "./dist/{framework}/index.mjs",
  "default": "./dist/{framework}/index.mjs"
}

在 typesVersions["*"] 中:"{framework}": ["./dist/{framework}/index.d.mts"]

在 peerDependencies 中(版本范围)和 peerDependenciesMeta 中("optional": true),并将框架名称添加到 keywords。

如果某个导出没有匹配的 typesVersions["*"] 或 tsdown.config.ts 条目,或者其 import 和 default 未指向与 types 同目录的 .mjs 文件,test/toolkit/entrypoint-registry.test.ts 就会失败。该测试无需构建即可运行。

第 4 步:测试

创建 packages/evlog/test/frameworks/{framework}.test.ts。请先阅读 packages/evlog/test/README.md,特别是 Framework runtime fidelity 表格。

以下两点不可妥协:

  1. 使用真实请求驱动程序。 使用框架自带的驱动程序:supertest(Express/NestJS)、app.request()(Hono)、app.inject()(Fastify)、app.handle(new Request(...))(Elysia)。如果没有适用于 Node.js 的驱动程序,则直接使用真实输入形态调用面向用户的接口(参见 SvelteKit 和 React Router 测试)。切勿提取内部实现来测试替代品。
  2. 接入共享测试矩阵。 从 test/helpers/frameworkMatrix.ts 调用 describeStandardHttpMatrix({ name, mount })。它会为所有 HTTP 框架覆盖标准测试组(事件发出、x-request-id、路由服务)。

在测试矩阵之外,还要覆盖框架特有的部分:

  1. 框架原生访问器返回日志记录器(c.get('log')、req.log 等)
  2. 错误处理。捕获错误、事件包含错误级别和详情、重新抛出错误
  3. 路由过滤。跳过的路由不会创建日志记录器,也不会执行 drain/enrich
  4. 上下文累积。logger.set() 中的数据会出现在发出的事件中
  5. Drain/enrich/keep 回调(使用 test/helpers/framework.ts 中的 createPipelineSpies()、assertHttpEventEmitted、waitForDrainCalls、findEventViaDrain)
  6. Drain/enrich 错误恢复能力。这些环节中的错误绝不会中断请求
  7. useLogger():与原生访问器返回同一个日志记录器,可跨异步边界使用,并且在上下文之外调用时会抛出错误。对于不带 ALS 的集成,跳过此项,并测试它提供的访问器:在 Workers 上,该访问器是处理程序的第四个参数,由 defineWorkerFetch / withEvlog 提供
  8. 流式响应(如适用)。事件延迟到响应体关闭后再发出

任何涉及时间的测试都使用 fake timers;使用 defined() 代替 !。

第 5 步:框架文档页面

创建 apps/docs/content/4.integrate/frameworks/{NN}.{framework}.md,编写一份全面且独立完整的指南。检查现有文件,确定下一个补零编号。

Frontmatter:

---
title: {Framework}
description: Using evlog with {Framework}: automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in {Framework} applications.
navigation:
  title: {Framework}
  icon: i-simple-icons-{framework}
links:
  - label: Source Code
    icon: i-simple-icons-github
    to: https://github.com/evloghq/evlog/tree/main/examples/{framework}
    color: neutral
    variant: subtle
---

章节(参照 Express/Hono/Elysia 页面):

  1. 快速开始:安装 + 注册中间件(可复制粘贴的最简设置)
  2. 宽事件:逐步使用 log.set()
  3. useLogger():无需传入请求即可从服务中访问日志记录器;对于不支持 ALS 的集成,则介绍其提供的替代访问器
  4. 错误处理:createError() + parseError() + 框架错误处理器
  5. Drain 和 Enricher:带内联示例的中间件选项
  6. Pipeline(批处理与重试):createDrainPipeline 示例
  7. 尾部采样:keep 回调
  8. 路由筛选:include / exclude / routes
  9. 客户端日志记录:HTTP drain(evlog/http)(仅当该框架支持客户端日志记录时)
  10. 本地运行:克隆 + pnpm example {framework}
  11. 卡片组:链接到 GitHub 源码

第 6 步:概览和安装卡片

在 apps/docs/content/4.integrate/frameworks/00.overview.md 中:

  1. 在概览表中新增一行:框架名称、导入、类型、日志记录器访问方式、状态
  2. 在相应章节中添加卡片;如果引导设置或访问模式是新的,也在 API 速查表中新增一行

在 apps/docs/content/1.start/3.installation.md 中:在 “Choose Your Framework” ::card-group 中添加一张卡片,顺序与框架概览一致。

第 7 步:着陆页

在 apps/docs/content/0.landing.md 中,找到 FeatureFrameworks MDC 区块(插槽 #nuxt、#nextjs、#hono 等),并添加一个新插槽:

  #{framework}
  ```ts [src/index.ts]
  // Framework-specific code example showing evlog usage

## Step 8: FeatureFrameworks Component

Update `apps/docs/app/components/features/FeatureFrameworks.vue`:

1. Add the framework to the `frameworks` array with its icon and the next available `tab` index
2. Add a `<div v-if="activeTab === {N}">` with `<slot name="{framework}" />` in the template

Icons use Simple Icons format: `i-simple-icons-{name}`.

## Step 9: Update the Public Skill

In `skills/review-logging-patterns/SKILL.md` (published on evlog.dev):

1. Add `### {Framework}` in the **"Framework Setup"** section, in the same order as the docs
2. Include: import + `initLogger` + middleware setup; native logger access; a `useLogger()` snippet, or the accessor that replaces it when the integration has no ALS; full pipeline example (`drain`, `enrich`, `keep`)
3. Update the `description:` line in the YAML frontmatter to mention the new framework name

## Step 10: Update README

In `packages/evlog/README.md` (root `README.md` is a symlink):

1. Add a `## {Framework}` section near the other framework sections with a minimal setup snippet and a link to the example app
2. Add a row to the **Framework Support** table

Keep the snippet short: init, register middleware, one route handler showing logger access.

## Step 11: Example App

Create `examples/{framework}/` with a runnable app demonstrating all evlog features. It is auto-discovered by the root runner: `pnpm example {framework}` (which loads the root `.env` via dotenv, so no root `package.json` change is needed).

The app must include:

1. **`evlog()` middleware** with `drain` (PostHog) and `enrich` callbacks
2. **Health route**: basic `log.set()` usage
3. **Data route**: context accumulation with user/business data, using `useLogger()` in a service function, or the integration's own accessor when it has no ALS
4. **Error route**: `createError()` with status/why/fix/link
5. **Error handler**: framework's error handler with `parseError()` + manual `log.error()`
6. **Test UI**: served at `/`, a self-contained HTML page with buttons to hit each route and display JSON responses

**Drain must use PostHog** (`createPostHogDrain()` from `evlog/posthog`). `POSTHOG_API_KEY` is set in the root `.env` (maintainer's key, not committed), so every example exercises a real external drain. Without the env var the drain resolves to `null` and skips, so someone cloning the repo sends nothing anywhere unless they opt in with their own key. Enable pretty printing for readable local output.

**Type the `enrich` callback parameter explicitly**, as `(ctx: EnrichContext) => ...` with `type EnrichContext` imported from `evlog`.

### Test UI

Reference: `examples/hono/src/ui.ts`, a single `src/ui.ts` exporting `testUI()` returning a self-contained dark-theme HTML string (route list with method badges, click-to-fetch, JSON display, status colors, response time). Register the `/` route **before** the evlog middleware so it isn't logged.

### Required files

| File | Purpose |
|------|---------|
| `src/index.ts` | App with all features demonstrated |
| `src/ui.ts` | Test UI |
| `package.json` | `dev` and `start` scripts (`bun --watch src/index.ts` / `bun src/index.ts`) |
| `tsconfig.json` | TypeScript config (if needed) |
| `README.md` | How to run + link to the UI |

There is also `examples/community-framework-skeleton/` showing the community-facing (toolkit-only) variant, so keep it in mind if the new integration changes the toolkit contract.

## Step 12: Changeset

Create `.changeset/{framework}-integration.md`:

```markdown
---
"evlog": minor
---

feat({framework}): add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling support

第 13 步:PR 作用域

按字母顺序将 {framework} 添加到 .github/workflows/semantic-pull-request.yml 的 scopes 列表中,并且添加到 .github/pull_request_template.md 的 Scopes 章节中。请谨记 PR 标题章节提到的时机注意事项:此项注册仅对基分支中已包含该注册的 PR 生效。

验证

全新安装后,先在仓库根目录运行 pnpm run dev:prepare,然后:

pnpm run dev:prepare
cd packages/evlog
pnpm run build    # required before test — api-surface snapshot is gated on dist/
pnpm run test
pnpm run lint

接着对示例进行类型检查:

cd examples/{framework}
pnpm exec tsc --noEmit

评论

支持 Markdown