Agent Skills

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.

Install

npx skills add https://github.com/evloghq/evlog --skill create-evlog-adapter
SKILL.md

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 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:

'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 / 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:

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

Related skills

azure-diagnosticsmicrosoft608KDebug Azure production issues on Azure using AppLens, Azure Monitor, resource health, and safe triage. WHEN: debug production issues, troubleshoot app service, app service high CPU, app service deployment failure, troubleshoot container apps, troubleshoot functions, troubleshoot AKS, VM RDP, Linux SSH, VM black screen, can't connect to VM, reset VM password, NSG or firewall blocking, kubectl cannot connect, kube-system/CoreDNS failures, pod pending, crashloop, node not ready, upgrade failures, aazure-preparemicrosoft608KPrepare azd-based Azure projects for deployment: generates azure.yaml, infrastructure (Bicep/Terraform), and Dockerfiles for the Azure Developer CLI (azd) workflow. USE ONLY when the user explicitly wants to use azd as the deployment tool, or the project already has an azure.yaml file. DO NOT USE FOR: non-azd deployments, Python App Service code-only deploys (use python-appservice-deploy), or cross-cloud migration (use azure-cloud-migrate). WHEN: prepare app for azd, create azure.yaml, set up azazure-aimicrosoft608KUse for Azure AI: Search, Speech, OpenAI, Document Intelligence. Helps with search, vector/hybrid search, speech-to-text, text-to-speech, transcription, OCR. WHEN: AI Search, query search, vector search, hybrid search, semantic search, speech-to-text, text-to-speech, transcribe, OCR, convert text to speech.azure-deploymicrosoft607KExecute Azure deployments for ALREADY-PREPARED applications that have existing .azure/deployment-plan.md and infrastructure files. DO NOT use this skill when the user asks to CREATE a new application — use azure-prepare instead. This skill runs azd up, azd deploy, terraform apply, and az deployment commands with built-in error recovery. Requires .azure/deployment-plan.md from azure-prepare and validated status from azure-validate. WHEN: \"run azd up\", \"run azd deploy\", \"execute deployment\",

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers