Skip to main content

create-evlog-adapter

Create a new built-in evlog adapter to send wide events to an external observability platform. Use when adding a new drain adapter (e.g., for Elasticsearch, Honeycomb, SigNoz, etc.) to the evlog package. Covers source code, build config, package exports, tests, e2e, and all documentation.

跳到安装

来源信息

仓库
evloghq/evlog
最近来源活动
2026年8月29日 16:04
检测到的 SKILL.md 语言
英语
星标
1,855
分支
63

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
3 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
create-evlog-adapter
description
Create a new built-in evlog adapter to send wide events to an external observability platform. Use when adding a new drain adapter (e.g., for Elasticsearch, Honeycomb, SigNoz, etc.) to the evlog package. Covers source code, build config, package exports, tests, e2e, and all documentation.
metadata
{"internal":true}
# Create evlog Adapter Add a new built-in adapter to evlog. Every adapter follows the same architecture and is built on the public toolkit primitives in `evlog/toolkit`, so a community adapter has the same shape as a built-in one. ## PR Title ``` feat({name}): add the {Name} drain adapter ``` Recent examples: `feat(loki): add the Grafana Loki drain adapter`, `feat(clickhouse): add the ClickHouse drain adapter`. Use the adapter name as the conventional-commit scope, and register that scope (see touchpoint 11). **Scope timing caveat**: the semantic PR check reads its scope list from the **base branch**, so a brand-new scope can't validate the very PR that introduces it. Either register the scope in a small preceding PR (the Loki/ClickHouse pattern), or use an unscoped title (`feat: add the {Name} drain adapter`) on the introducing PR. ## Touchpoints Checklist | # | File | Action | |---|------|--------| | 1 | `packages/evlog/src/adapters/{name}.ts` | Create adapter source (built on `defineHttpDrain` from `../shared/drain`) | | 2 | `packages/evlog/tsdown.config.ts` | Add build entry | | 3 | `packages/evlog/package.json` | Add `exports` + `typesVersions` entries | | 4 | `packages/evlog/test/adapters/{name}.test.ts` | Create unit tests (use `test/helpers/fetch.ts`) | | 5 | `packages/evlog/test/e2e/{name}.e2e.ts` | Create e2e test gated on env vars; extend the docker sandbox if self-hostable | | 6 | `packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap` | Regenerated by running the tests after a build (`pnpm run build` then `pnpm test`) | | 7 | `apps/docs/content/4.integrate/adapters/{category}/{NN}.{name}.md` | Create adapter doc page in the right category | | 8 | `apps/docs/content/4.integrate/adapters/01.overview.md` | Add adapter to overview (frontmatter link + card) | | 9 | `skills/review-logging-patterns/SKILL.md` | Add adapter row in the Drain Adapters table + frontmatter description | | 10 | `.changeset/{name}-adapter.md` | Create changeset (`minor`) describing the adapter | | 11 | `.github/workflows/semantic-pull-request.yml` + `.github/pull_request_template.md` | Register `{name}` as a PR scope in both files | **Important**: Do NOT consider the task complete until all 11 touchpoints have been addressed. ## Naming Conventions Use these placeholders consistently: | Placeholder | Example (Loki) | Usage | |-------------|----------------|-------| | `{name}` | `loki` | File names, import paths, env var suffix, PR scope | | `{Name}` | `Loki` | PascalCase in function/interface names | | `{NAME}` | `LOKI` | SCREAMING_CASE in env var prefixes | Standard option naming (use these exact names): | Concept | Standard option name | |---------|---------------------| | Bearer-style API secret | `apiKey` | | Base URL of the ingest API | `endpoint` | | Service identifier | `serviceName` | | Request timeout (ms) | `timeout` | | Retry attempts on transient failures | `retries` | If a service historically used a different name (`token`, `sourceToken`, …) keep it as a deprecated alias via `applyDeprecatedAlias`. See Axiom and Better Stack for the pattern. ## Step 1: Adapter Source: built on `defineHttpDrain` Create `packages/evlog/src/adapters/{name}.ts`. Read [references/adapter-template.md](references/adapter-template.md) for the full annotated template. `loki.ts` and `clickhouse.ts` are the most recent reference implementations. The contract is `defineHttpDrain<TConfig>({ name, label, resolve, encode })`. You only ship two pieces of logic: 1. **`resolve()`**: produce a fully-resolved config or `null` to skip. Use `resolveAdapterConfig` for the standard precedence (overrides → `runtimeConfig.evlog.{name}` → `runtimeConfig.{name}` → env vars). List `NUXT_{NAME}_*` before `{NAME}_*` in `ConfigField.env` for silent Nuxt compat; show only `{NAME}_*` in user-facing messages via `formatPublicEnvKeys`. 2. **`encode(events, config)`**: a private `encode{Name}Request(events, config): HttpDrainRequest` returning `{ url, headers, body }` for a batch. HTTP transport, identity headers, retries, timeout, and error logging are handled by `defineHttpDrain` (via `httpPost`). Key rules: - **Single factory.** Export one `create{Name}Drain(overrides?: Partial<{Name}Config>)`. No dual-API factories: if a service has multiple ingest modes (logs vs events), expose them via a `mode` option (see PostHog). - **No HTTP code in the adapter.** Never call `fetch` directly. If the service truly needs custom transport (binary envelopes, non-HTTP), use `defineDrain` from `../shared/drain` instead, see `fs.ts` and `memory.ts`. - **Encode parity.** The standalone `sendTo{Name}` / `sendBatchTo{Name}` helpers must reuse the same private `encode{Name}Request()` and go through `sendEncodedDrainRequest(request, { label, source, timeout, retries })`, never a separate fetch path. `test/adapters/encode-parity.test.ts` pins this for a subset of adapters; add the new one to it (not every existing adapter is registered there yet, and that is a gap, not a license to skip). - **No bespoke config resolution.** Always go through `resolveAdapterConfig`. Deprecated aliases (`token` → `apiKey`) go through `applyDeprecatedAlias`. - **Exported converters.** If the service needs a specific event shape, export `to{Name}Event()` / `build{Name}Payload()` helpers so they're testable independently. - **Edge-safe.** Adapters run on Cloudflare Workers: no `Buffer` (use `TextEncoder` + `btoa`, see `loki.ts`), no Node-only APIs. `fs.ts` shows the `isEdgeRuntime()` guard pattern when a runtime genuinely can't be supported. ## Step 2: Build Config Add a build entry in `packages/evlog/tsdown.config.ts` alongside the existing adapters: ```typescript 'adapters/{name}': 'src/adapters/{name}.ts', ``` Follow the existing ordering in that file. ## Step 3: Package Exports In `packages/evlog/package.json`, add two entries (after the last adapter, and check the current list rather than assuming): **In `exports`**: ```json "./{name}": { "types": "./dist/adapters/{name}.d.mts", "import": "./dist/adapters/{name}.mjs" } ``` **In `typesVersions["*"]`**: ```json "{name}": [ "./dist/adapters/{name}.d.mts" ] ``` Any export added to `package.json` without a matching `tsdown.config.ts` entry (and vice versa) fails `test/toolkit/api-surface.test.ts`, which is touchpoint 6. ## Step 4: Unit Tests Create `packages/evlog/test/adapters/{name}.test.ts`. Read [references/test-template.md](references/test-template.md) for the full annotated template, and `packages/evlog/test/README.md` for the repo-wide conventions. Non-negotiables from the test README: - Use `mockFetch()` / `getFetchCall` / `getFetchJson` / `getFetchHeaders` from `test/helpers/fetch.ts`, never hand-roll `vi.spyOn(globalThis, 'fetch')` boilerplate. - Clean up any env vars the adapter reads in `afterEach`. - Test the exported pure helpers (`to{Name}Event`, `build{Name}Payload`, URL resolvers) directly, one `describe` per helper. Required test categories: 1. URL construction (default + custom endpoint, trailing-slash tolerance) 2. Headers (auth, content-type, service-specific) 3. Request body format (JSON structure matches service API) 4. Skip behavior when `apiKey` (or required field) is missing 5. Batch operations (multiple events in one request, empty batch skips fetch) 6. Deprecated alias still works (when applicable) 7. Add the adapter to `test/adapters/encode-parity.test.ts` ## Step 5: E2E Test + Sandbox Create `packages/evlog/test/e2e/{name}.e2e.ts`, gated on the adapter's env vars (skipped when absent). Run with `pnpm test:e2e`. If the service is self-hostable, extend the local sandbox so the adapter can be exercised without cloud credentials: - `packages/evlog/test/e2e/docker-compose.yml`: add the service - `packages/evlog/test/e2e/seed.mjs`: fan the seeder out to the new backend - `packages/evlog/test/e2e/README.md`: document it - Root `package.json` `sandbox:e2e` script: add the local env var if needed See the Loki and ClickHouse setups as references. ## Step 6: Adapter Documentation Page Read `apps/docs/AGENTS.md` before touching anything under `apps/docs/` (steps 6 to 8). Adapter docs live in three categories under `apps/docs/content/4.integrate/adapters/`: | Category | Directory | Examples | |----------|-----------|----------| | Cloud (SaaS only) | `cloud/` | Axiom, PostHog, Sentry, Better Stack, Datadog | | Cloud or Self-Hosted | `hybrid/` | Loki, ClickHouse, OTLP, HyperDX | | Self-Hosted (local only) | `self-hosted/` | FS, NuxtHub, Memory | Create `{NN}.{name}.md` in the right category with the next available number. Use the Loki page (`hybrid/01.loki.md`) as a reference for frontmatter, tone, and sections. Key sections: intro, quick setup, configuration (env vars table + priority), advanced usage, querying in the target service, troubleshooting, direct API usage, next steps. **Important: multi-framework examples.** The Quick Start section must include a `::code-group` with tabs for the supported frameworks (Nuxt/Nitro, Hono, Express, Fastify, Elysia, NestJS, Standalone). Do not only show Nitro examples. ## Step 7: Update Adapters Overview Page Edit `apps/docs/content/4.integrate/adapters/01.overview.md` in **two** places (follow the pattern of existing adapters): 1. **Frontmatter `links` array**: add a link entry with icon and `/integrate/adapters/{category}/{name}` path, in category order 2. **`::card-group` section**: add a card block in the matching position ## Step 8: Update the Public Skill In `skills/review-logging-patterns/SKILL.md` (published on evlog.dev via `/.well-known/skills/`): 1. Add a row to the **Drain Adapters** table: `| {Name} | evlog/{name} | {NAME}_API_KEY, ... |` 2. Add the adapter name to the `description:` line in the YAML frontmatter ## Step 9: Changeset Create `.changeset/{name}-adapter.md` with a `minor` bump. Write it like a release note: what the adapter does, the deployment modes it covers, the key options, the env vars, and the direct-send helpers. See `.changeset` entries from the Loki/ClickHouse PRs for the expected depth. ## Step 10: PR Scopes Add `{name}` to the `scopes` list in `.github/workflows/semantic-pull-request.yml` **and** to the Scopes section of `.github/pull_request_template.md`, in alphabetical order. Remember the timing caveat from the PR Title section: this registration only takes effect for PRs whose base branch already contains it. ## Verification After a clean install, prepare generated workspace types from the repo root first, then run the package checks: ```bash 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 ```
在 GitHub 查看