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 Datadog, Sentry, Loki, Elasticsearch, etc.) to the evlog package. Covers source code, build config, package exports, tests, and all documentation.
Resources
1Install
npx skillscat add hugorcd/evlog/create-evlog-adapter Install via the SkillsCat registry.
Create evlog Adapter
Add a new built-in adapter to evlog. Every adapter follows the same architecture. This skill walks through all 8 touchpoints. Every single touchpoint is mandatory -- do not skip any.
PR Title
Recommended format for the pull request title:
feat: add {name} adapterThe exact wording may vary depending on the adapter (e.g., feat: add OTLP adapter, feat: add Axiom drain adapter), but it should always follow the feat: conventional commit prefix.
Touchpoints Checklist
| # | File | Action |
|---|---|---|
| 1 | packages/evlog/src/adapters/{name}.ts |
Create adapter source |
| 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 tests |
| 5 | apps/docs/content/3.adapters/{n}.{name}.md |
Create adapter doc page (before custom.md) |
| 6 | apps/docs/content/3.adapters/1.overview.md |
Add adapter to overview (links, card, env vars) |
| 7 | AGENTS.md |
Add adapter to the "Built-in Adapters" table |
| 8 | Renumber custom.md |
Ensure custom.md stays last after the new adapter |
Important: Do NOT consider the task complete until all 8 touchpoints have been addressed.
Naming Conventions
Use these placeholders consistently:
| Placeholder | Example (Datadog) | Usage |
|---|---|---|
{name} |
datadog |
File names, import paths, env var suffix |
{Name} |
Datadog |
PascalCase in function/interface names |
{NAME} |
DATADOG |
SCREAMING_CASE in env var prefixes |
Step 1: Adapter Source
Create packages/evlog/src/adapters/{name}.ts.
Read references/adapter-template.md for the full annotated template.
Key architecture rules:
- Config interface -- service-specific fields (API key, endpoint, etc.) plus optional
timeout?: number getRuntimeConfig()-- import from./_utils(shared helper, do NOT redefine locally)- Config priority (highest to lowest):
- Overrides passed to
create{Name}Drain() runtimeConfig.evlog.{name}runtimeConfig.{name}- Environment variables:
NUXT_{NAME}_*then{NAME}_*
- Overrides passed to
- Factory function --
create{Name}Drain(overrides?: Partial<Config>)returns(ctx: DrainContext) => Promise<void> - Exported send functions --
sendTo{Name}(event, config)andsendBatchTo{Name}(events, config)for direct use and testability - Error handling -- try/catch with
console.error('[evlog/{name}] ...'), never throw from the drain - Timeout --
AbortControllerwith 5000ms default, configurable viaconfig.timeout - Event transformation -- if the service needs a specific format, export a
to{Name}Event()converter
Step 2: Build Config
Add a build entry in packages/evlog/tsdown.config.ts alongside the existing adapters:
'adapters/{name}': 'src/adapters/{name}.ts',Place it after the last adapter entry (currently sentry at line 22).
Step 3: Package Exports
In packages/evlog/package.json, add two entries:
In exports (after the last adapter, currently ./posthog):
"./{name}": {
"types": "./dist/adapters/{name}.d.mts",
"import": "./dist/adapters/{name}.mjs"
}In typesVersions["*"] (after the last adapter):
"{name}": [
"./dist/adapters/{name}.d.mts"
]Step 4: Tests
Create packages/evlog/test/adapters/{name}.test.ts.
Read references/test-template.md for the full annotated template.
Required test categories:
- URL construction (default + custom endpoint)
- Headers (auth, content-type, service-specific)
- Request body format (JSON structure matches service API)
- Error handling (non-OK responses throw with status)
- Batch operations (
sendBatchTo{Name}) - Timeout handling (default 5000ms + custom)
Step 5: Adapter Documentation Page
Create apps/docs/content/3.adapters/{n}.{name}.md where {n} is the next number before custom.md (custom should always be last).
Use the existing Axiom adapter page (apps/docs/content/3.adapters/2.axiom.md) as a reference for frontmatter structure, 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.
Step 6: Update Adapters Overview Page
Edit apps/docs/content/3.adapters/1.overview.md to add the new adapter in three places (follow the pattern of existing adapters):
- Frontmatter
linksarray -- add a link entry with icon and path ::card-groupsection -- add a card block before the Custom card- Zero-Config Setup
.envexample -- add the adapter's env vars
Step 7: Update AGENTS.md
In the root AGENTS.md file, "Log Draining & Adapters" section:
- Add a row to the "Built-in Adapters" table
- Add a "Using {Name} Adapter" usage example block with
create{Name}Drain()and env vars
Follow the pattern of existing adapters in the file.
Step 8: Renumber custom.md
If the new adapter's number conflicts with custom.md, renumber custom.md to be the last entry. For example, if the new adapter is 5.{name}.md, rename 5.custom.md to 6.custom.md.
Verification
After completing all steps, run:
cd packages/evlog
bun run build # Verify build succeeds with new entry
bun run test # Verify tests pass