关于 create-evlog-adapter
这款仓库 Skill 指导贡献者为 evlog 添加内置 drain 适配器,说明适配器架构与配置方式。它提供涵盖实现、测试、包导出、文档和发布工作的清单。
主要功能
- 01使用 defineHttpDrain 实现适配器
- 02标准配置解析
- 03请求编码与发送辅助函数
- 04单元及端到端测试要求
- 05构建、导出和文档清单
适用场景
- 01添加新的日志服务适配器
- 02提交拉取请求前检查适配器改动
- 03规划包与文档更新
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ evloghq/ evlog --skill create-evlog-adapter安装后在 Claude Code 中输入/create-evlog-adapter确认已加载。
详细介绍
创建 evlog 适配器
为 evlog 添加一个新的内置适配器。所有适配器都遵循相同的架构,并基于 evlog/toolkit 中的公共工具包原语构建,因此社区适配器与内置适配器具有相同的结构。
PR 标题
feat({name}): add the {Name} drain adapter
近期示例:feat(loki): add the Grafana Loki drain adapter、feat(clickhouse): add the ClickHouse drain adapter。将适配器名称用作 Conventional Commits 的作用域,并注册该作用域(见接触点 11)。
作用域时间注意事项:语义化 PR 检查会从基分支读取作用域列表,因此全新作用域无法通过引入它的 PR 的验证。可以先通过一个小型 PR 注册作用域(Loki/ClickHouse 的做法),或者在引入作用域的 PR 中使用不带作用域的标题(feat: add the {Name} drain adapter)。
接触点检查清单
| # | 文件 | 操作 |
|---|---|---|
| 1 | packages/evlog/src/adapters/{name}.ts | 创建适配器源码(基于 ../shared/drain 中的 defineHttpDrain 构建) |
| 2 | packages/evlog/tsdown.config.ts | 添加构建入口 |
| 3 | packages/evlog/package.json | 添加 exports 和 typesVersions 条目 |
| 4 | packages/evlog/test/adapters/{name}.test.ts | 创建单元测试(使用 test/helpers/fetch.ts) |
| 5 | packages/evlog/test/e2e/{name}.e2e.ts | 创建由环境变量控制的端到端测试;如果支持自托管,则扩展 Docker 沙箱 |
| 6 | packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap | 构建后运行测试即可重新生成(先运行 pnpm run build,再运行 pnpm test) |
| 7 | apps/docs/content/4.integrate/adapters/{category}/{NN}.{name}.md | 在相应分类下创建适配器文档页面 |
| 8 | apps/docs/content/4.integrate/adapters/01.overview.md | 在概览中添加适配器(frontmatter 链接和卡片) |
| 9 | skills/review-logging-patterns/SKILL.md | 在 Drain Adapters 表格中添加适配器行,并更新 frontmatter 描述 |
| 10 | .changeset/{name}-adapter.md | 创建 changeset(minor),描述该适配器 |
| 11 | .github/workflows/semantic-pull-request.yml + .github/pull_request_template.md | 在两个文件中注册 {name} 作为 PR 作用域 |
| 12 | .agents/skills/create-adapter/references/upstream-alignment.md | 添加适配器对应行:接收文档 URL、官方客户端仓库和包、构建请求的文件、客户端为提供商的其他功能(例如问题、指标或用户资料)提供数据的文件(如果提供商只存储我们发送的内容,则写 none),以及将 Aligned to 设为编写适配器时查阅的客户端版本 |
重要:必须完成全部 12 个接触点,才能认为任务已完成。
命名约定
请一致使用以下占位符:
| 占位符 | 示例(Loki) | 用途 |
|---|---|---|
{name} | loki | 文件名、导入路径、环境变量后缀、PR 作用域 |
{Name} | Loki | 函数/接口名称中的 PascalCase |
{NAME} | LOKI | 环境变量前缀中的 SCREAMING_CASE |
标准选项命名(请使用以下确切名称):
| 概念 | 标准选项名称 |
|---|---|
| Bearer 类型 API 密钥 | apiKey |
| 接收 API 的基础 URL | endpoint |
| 服务标识符 | serviceName |
| 请求超时时间(ms) | timeout |
| 瞬态故障时的重试次数 | retries |
如果服务沿用了不同名称(token、sourceToken 等),请通过 applyDeprecatedAlias 将其保留为已弃用的别名。Axiom 和 Better Stack 中有相应示例。
步骤 1:适配器源码:基于 defineHttpDrain
创建 packages/evlog/src/adapters/{name}.ts。完整的注释模板请参阅 references/adapter-template.md。loki.ts 和 clickhouse.ts 是最新的参考实现。
契约为 defineHttpDrain<TConfig>({ name, label, resolve, encode })。你只需实现两部分逻辑:
resolve():生成完全解析的配置;如果应跳过,则返回null。标准优先级(overrides →runtimeConfig.evlog.{name}→runtimeConfig.{name}→ env vars)请使用resolveAdapterConfig。在ConfigField.env中,先列出NUXT_{NAME}_*,再列出{NAME}_*,以静默兼容 Nuxt;面向用户的消息中只显示{NAME}_*,并使用formatPublicEnvKeys。encode(events, config):一个私有函数encode{Name}Request(events, config): HttpDrainRequest,用于批量返回{ url, headers, body }。HTTP 传输、身份标头、重试、超时和错误日志由defineHttpDrain(通过httpPost)处理。
关键规则:
- 单一工厂函数。 导出一个
create{Name}Drain(overrides?: Partial<{Name}Config>)。不要创建双 API 工厂函数:如果某个服务支持多种摄取模式(日志与事件),请通过mode选项提供(参见 PostHog)。 - 适配器中不得包含 HTTP 代码。 切勿直接调用
fetch。如果服务确实需要自定义传输(如二进制封装、非 HTTP),请改用../shared/drain中的defineDrain,参见fs.ts和memory.ts。 - 编码保持一致。 独立的
sendTo{Name}/sendBatchTo{Name}辅助函数必须复用同一个私有函数encode{Name}Request(),并通过sendEncodedDrainRequest(request, { label, source, timeout, retries })发送请求,不得使用单独的 fetch 路径。test/adapters/encode-parity.test.ts对部分适配器进行了验证;请将新适配器也加入其中(目前尚未注册所有现有适配器,这是一个缺口,而不是跳过的理由)。 - 不得自行实现配置解析。 始终使用
resolveAdapterConfig。已弃用的别名(token→apiKey)通过applyDeprecatedAlias处理。 - 导出转换器。 如果服务需要特定的事件格式,请导出
to{Name}Event()/build{Name}Payload()辅助函数,以便单独测试。 - 兼容边缘运行时。 适配器运行于 Cloudflare Workers:不要使用
Buffer(请使用TextEncoder+btoa,参见loki.ts),也不要使用仅限 Node 的 API。如果某个运行时确实无法支持,请参照fs.ts中的isEdgeRuntime()防护模式。
步骤 2:构建配置
在 packages/evlog/tsdown.config.ts 中与现有适配器并列添加构建入口:
'adapters/{name}': 'src/adapters/{name}.ts',
遵循该文件中现有的排序方式。
步骤 3:包导出
在 packages/evlog/package.json 中添加两项(放在最后一个适配器之后,并检查当前列表,不要想当然):
在 exports 中:
"./{name}": {
"types": "./dist/adapters/{name}.d.mts",
"import": "./dist/adapters/{name}.mjs",
"default": "./dist/adapters/{name}.mjs"
}
在 typesVersions["*"] 中:
"{name}": [
"./dist/adapters/{name}.d.mts"
]
test/toolkit/entrypoint-registry.test.ts 会相互检查这三处注册,并且无需构建即可运行。如果 typesVersions["*"] 或 tsdown.config.ts 中缺少子路径,或者 import 和 default 未指向与 types 同目录下的 .mjs 文件,测试就会失败。没有导出项的 tsdown 入口不会被报告,因为内部代码块也会以这种方式构建。
第 4 步:单元测试
创建 packages/evlog/test/adapters/{name}.test.ts。阅读 references/test-template.md 查看完整的注释模板,并阅读 packages/evlog/test/README.md 了解整个仓库的约定。
测试 README 中的硬性要求:
- 使用
test/helpers/fetch.ts中的mockFetch()/getFetchCall/getFetchJson/getFetchHeaders,绝不要手动编写vi.spyOn(globalThis, 'fetch')样板代码。 - 在
afterEach中清理适配器读取的任何环境变量。 - 直接测试导出的纯函数辅助方法(
to{Name}Event、build{Name}Payload、URL 解析器),每个辅助方法各用一个describe。
必测类别:
- URL 构造(默认端点 + 自定义端点,兼容末尾斜杠)
- 请求头(身份验证、内容类型、服务专用请求头)
- 请求正文格式(JSON 结构与服务 API 匹配)
- 缺少
apiKey(或必填字段)时的跳过行为 - 批量操作(一次请求发送多个事件,空批次跳过 fetch)
- 已弃用的别名仍可用(如适用)
- 将适配器添加到
test/adapters/encode-parity.test.ts
第 5 步:端到端测试 + 沙盒
创建 packages/evlog/test/e2e/{name}.e2e.ts,并设置为仅在适配器环境变量存在时运行(缺失时跳过)。使用 pnpm test:e2e 运行。
如果该服务可自托管,请扩展本地沙盒,使适配器无需云凭据即可进行测试:
packages/evlog/test/e2e/docker-compose.yml:添加该服务packages/evlog/test/e2e/seed.mjs:让数据播种器也将数据写入新的后端packages/evlog/test/e2e/README.md:添加相关说明- 根目录
package.json中的sandbox:e2e脚本:如有需要,添加本地环境变量
请参考 Loki 和 ClickHouse 的配置。
第 6 步:适配器文档页面
修改 apps/docs/ 下的内容之前(第 6 至第 8 步),请先阅读 apps/docs/AGENTS.md。
适配器文档分为三类,位于 apps/docs/content/4.integrate/adapters/ 下:
| 类别 | 目录 | 示例 |
|---|---|---|
| 云端(仅 SaaS) | cloud/ | Axiom、PostHog、Sentry、Better Stack、Datadog |
| 云端或自托管 | hybrid/ | Loki、ClickHouse、OTLP、HyperDX |
| 自托管(仅本地) | self-hosted/ | FS、NuxtHub、Memory |
在相应类别中创建 {NN}.{name}.md,使用下一个可用编号。参考 Loki 页面(hybrid/01.loki.md)的 frontmatter、语气和章节结构。关键章节包括:简介、快速设置、配置(环境变量表 + 优先级)、高级用法、在目标服务中查询、故障排除、直接 API 用法、后续步骤。
重要:多框架示例。“快速开始”部分必须包含一个 ::framework-tabs 组,每个受支持的框架各有一个代码围栏(Nuxt、Nitro、Next.js、SvelteKit、TanStack Start、React Router、Hono、Express、Fastify、Elysia、NestJS、oRPC、Cloudflare Workers、Standalone),并在方括号标签中标明框架。复制 Axiom 页面中的组,并替换为对应的 drain 工厂。不要只展示 Nitro 示例,且组内只能包含代码围栏。
第 7 步:更新适配器概览页面
在 两 处编辑 apps/docs/content/4.integrate/adapters/01.overview.md(遵循现有适配器的模式):
- Frontmatter
links数组:添加包含图标和/integrate/adapters/{category}/{name}路径的链接条目,并按类别排序 ::card-group部分:在对应位置添加卡片块
第 8 步:更新公开 Skill
在 skills/review-logging-patterns/SKILL.md(通过 /.well-known/skills/ 发布于 evlog.dev)中:
- 在 Drain Adapters 表格中添加一行:
| {Name} | evlog/{name} | {NAME}_API_KEY, ... | - 将适配器名称添加到 YAML frontmatter 中的
description:行
第 9 步:Changeset
创建 .changeset/{name}-adapter.md,并设置 minor 版本升级。将其写成发布说明:说明适配器的功能、支持的部署模式、关键选项、环境变量和直接发送辅助方法。请参考 Loki/ClickHouse PR 中 .changeset 目录下的条目,了解所需的详细程度。
步骤 10:PR 范围
按字母顺序将 {name} 添加到 .github/workflows/semantic-pull-request.yml 中的 scopes 列表以及 .github/pull_request_template.md 的 Scopes 部分。请记住 PR 标题部分提到的时机注意事项:只有当 PR 的目标分支已包含此注册项时,该注册项才会对 PR 生效。
验证
完成全新安装后,先从仓库根目录生成工作区类型,然后运行软件包检查:
pnpm run dev:prepare
cd packages/evlog
pnpm run lint
pnpm run typecheck
pnpm run build # required before test — api-surface snapshot is gated on dist/
pnpm run test