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.
- Skill ID
- evloghq/evlog/create-evlog-adapter
- Publisher
- evloghq
- Repository
- evlog
- Installs
- 0
- Files
- 3
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
evloghq/evlog/create-evlog-adapterInstalls these files- SKILL.md
- references/adapter-template.md
- references/test-template.md
What this skill tells the agent
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 adapterRecent 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 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:
- `resolve()`: produce a fully-resolved config or
nullto skip. UseresolveAdapterConfigfor the standard precedence (overrides →runtimeConfig.evlog.{name}→runtimeConfig.{name}→ env vars). ListNUXT_{NAME}_*before{NAME}_*inConfigField.envfor silent Nuxt compat; show only{NAME}_*in user-facing messages viaformatPublicEnvKeys. - `encode(events, config)`: a private
encode{Name}Request(events, config): HttpDrainRequestreturning{ url, headers, body }for a batch. HTTP transport, identity headers, retries, timeout, and error logging are handled bydefineHttpDrain(viahttpPost).
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 amodeoption (see PostHog). - No HTTP code in the adapter. Never call
fetchdirectly. If the service truly needs custom transport (binary envelopes, non-HTTP), usedefineDrainfrom../shared/draininstead, seefs.tsandmemory.ts. - Encode parity. The standalone
sendTo{Name}/sendBatchTo{Name}helpers must reuse the same privateencode{Name}Request()and go throughsendEncodedDrainRequest(request, { label, source, timeout, retries }), never a separate fetch path.test/adapters/encode-parity.test.tspins 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 throughapplyDeprecatedAlias. - 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(useTextEncoder+btoa, seeloki.ts), no Node-only APIs.fs.tsshows theisEdgeRuntime()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:
'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`:
"./{name}": {
"types": "./dist/adapters/{name}.d.mts",
"import": "./dist/adapters/{name}.mjs"
}*In `typesVersions[""]`**:
"{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 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/getFetchHeadersfromtest/helpers/fetch.ts, never hand-rollvi.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, onedescribeper helper.
Required test categories:
- URL construction (default + custom endpoint, trailing-slash tolerance)
- Headers (auth, content-type, service-specific)
- Request body format (JSON structure matches service API)
- Skip behavior when
apiKey(or required field) is missing - Batch operations (multiple events in one request, empty batch skips fetch)
- Deprecated alias still works (when applicable)
- 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 servicepackages/evlog/test/e2e/seed.mjs: fan the seeder out to the new backendpackages/evlog/test/e2e/README.md: document it- Root
package.jsonsandbox:e2escript: add the local env var if needed
More from evloghq
Use this skill in a project
Open any RiverX project, search the Skills panel for evloghq/evlog/create-evlog-adapter, and install it in one click.
