create-evlog-framework-integration

Create evlog Framework Integration

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "create-evlog-framework-integration" with this command: npx skills add hugorcd/evlog/hugorcd-evlog-create-evlog-framework-integration

Create evlog Framework Integration

Add a new framework integration to evlog. Every integration follows the same architecture built on the shared createMiddlewareLogger utility. This skill walks through all touchpoints. Every single touchpoint is mandatory -- do not skip any.

PR Title

Recommended format for the pull request title:

feat({framework}): add {Framework} middleware integration

Touchpoints Checklist

File Action

1 packages/evlog/src/{framework}/index.ts

Create integration source

2 packages/evlog/tsdown.config.ts

Add build entry + external

3 packages/evlog/package.json

Add exports

  • typesVersions
  • peer dep + keyword

4 packages/evlog/test/{framework}.test.ts

Create tests

5 apps/docs/content/2.frameworks/{NN}.{framework}.md

Create framework docs page

6 apps/docs/content/2.frameworks/00.overview.md

Add card + table row

7 apps/docs/content/1.getting-started/2.installation.md

Add card in "Choose Your Framework"

8 apps/docs/content/0.landing.md

Add framework code snippet

9 apps/docs/app/components/features/FeatureFrameworks.vue

Add framework tab

10 skills/review-logging-patterns/SKILL.md

Add framework setup section + update frontmatter description

11 packages/evlog/README.md

Add framework section + add row to Framework Support table

12 examples/{framework}/

Create example app with test UI

13 package.json (root) Add example:{framework} script

14 .changeset/{framework}-integration.md

Create changeset (minor )

15 .github/workflows/semantic-pull-request.yml

Add {framework} scope

16 .github/pull_request_template.md

Add {framework} scope

Important: Do NOT consider the task complete until all 16 touchpoints have been addressed.

Naming Conventions

Use these placeholders consistently:

Placeholder Example (Hono) Usage

{framework}

hono

Directory names, import paths, file names

{Framework}

Hono

PascalCase in type/interface names

Shared Utilities

All integrations share the same core utilities. Never reimplement logic that exists in shared/. These are also publicly available as evlog/toolkit for community-built integrations (see Custom Integration docs).

Utility Location Purpose

createMiddlewareLogger

../shared/middleware

Full lifecycle: logger creation, route filtering, tail sampling, emit, enrich, drain

extractSafeHeaders

../shared/headers

Convert Web API Headers → filtered Record<string, string> (Hono, Elysia, etc.)

extractSafeNodeHeaders

../shared/headers

Convert Node.js IncomingHttpHeaders → filtered Record<string, string> (Express, Fastify, NestJS)

BaseEvlogOptions

../shared/middleware

Base user-facing options type with drain , enrich , keep , include , exclude , routes

MiddlewareLoggerOptions

../shared/middleware

Internal options type extending BaseEvlogOptions with method , path , requestId , headers

createLoggerStorage

../shared/storage

Factory returning { storage, useLogger } for AsyncLocalStorage -backed useLogger()

Test Helpers

Utility Location Purpose

createPipelineSpies()

test/helpers/framework

Creates mock drain/enrich/keep callbacks

assertDrainCalledWith()

test/helpers/framework

Validates drain was called with expected event shape

assertEnrichBeforeDrain()

test/helpers/framework

Validates enrich runs before drain

assertSensitiveHeadersFiltered()

test/helpers/framework

Validates sensitive headers are excluded

assertWideEventShape()

test/helpers/framework

Validates standard wide event fields

Step 1: Integration Source

Create packages/evlog/src/{framework}/index.ts .

The integration file should be minimal — typically 50-80 lines of framework-specific glue. All pipeline logic (enrich, drain, keep, header filtering) is handled by createMiddlewareLogger .

Template Structure

import type { RequestLogger } from '../types' import { createMiddlewareLogger, type BaseEvlogOptions } from '../shared/middleware' import { extractSafeHeaders } from '../shared/headers' // for Web API Headers (Hono, Elysia) // OR import { extractSafeNodeHeaders } from '../shared/headers' // for Node.js headers (Express, Fastify) import { createLoggerStorage } from '../shared/storage'

const { storage, useLogger } = createLoggerStorage( 'middleware context. Make sure the evlog middleware is registered before your routes.', )

export interface Evlog{Framework}Options extends BaseEvlogOptions {}

export { useLogger }

// Type augmentation for typed logger access (framework-specific) // For Express: declare module 'express-serve-static-core' { interface Request { log: RequestLogger } } // For Hono: export type EvlogVariables = { Variables: { log: RequestLogger } }

export function evlog(options: Evlog{Framework}Options = {}): FrameworkMiddleware { return async (frameworkContext, next) => { const { logger, finish, skipped } = createMiddlewareLogger({ method: /* extract from framework context /, path: / extract from framework context /, requestId: / extract x-request-id or crypto.randomUUID() /, headers: extractSafeHeaders(/ framework request Headers object */), ...options, })

if (skipped) {
  await next()
  return
}

// Store logger in framework-specific context
// e.g., c.set('log', logger) for Hono
// e.g., req.log = logger for Express

// Wrap next() in AsyncLocalStorage.run() for useLogger() support
// Express: storage.run(logger, () => next())
// Hono: await storage.run(logger, () => next())

} }

Reference Implementations

  • Hono (~40 lines): packages/evlog/src/hono/index.ts — Web API Headers, c.set('log', logger) , wraps next() in try/catch

  • Express (~80 lines): packages/evlog/src/express/index.ts — Node.js headers, req.log , res.on('finish') , AsyncLocalStorage for useLogger()

  • Elysia (~70 lines): packages/evlog/src/elysia/index.ts — Web API Headers, derive() plugin, onAfterHandle /onError , AsyncLocalStorage for useLogger()

Key Architecture Rules

  • Use createMiddlewareLogger — never call createRequestLogger directly

  • Use the right header extractor — extractSafeHeaders for Web API Headers , extractSafeNodeHeaders for Node.js IncomingHttpHeaders

  • Spread user options into createMiddlewareLogger — drain , enrich , keep are handled automatically by finish()

  • Store logger in the framework's idiomatic context (e.g., c.set() for Hono, req.log for Express, .derive() for Elysia)

  • Export useLogger() — backed by AsyncLocalStorage so the logger is accessible from anywhere in the call stack

  • Call finish() in both success and error paths — it handles emit + enrich + drain

  • Re-throw errors after finish() so framework error handlers still work

  • Export options interface with drain/enrich/keep for feature parity across all frameworks

  • Export type helpers for typed context access (e.g., EvlogVariables for Hono)

  • Framework SDK is a peer dependency — never bundle it

  • Never duplicate pipeline logic — callEnrichAndDrain is internal to createMiddlewareLogger

Framework-Specific Patterns

Hono: Use MiddlewareHandler return type, c.set('log', logger) , c.res.status for status, c.req.raw.headers for headers.

Express: Standard (req, res, next) middleware, res.on('finish') for response end, storage.run(logger, () => next()) for useLogger() . Type augmentation targets express-serve-static-core (NOT express ). Error handler uses ErrorRequestHandler type.

Elysia: Return new Elysia({ name: 'evlog' }) plugin, use .derive({ as: 'global' }) to create logger and attach log to context, onAfterHandle for success path, onError for error path. Use storage.enterWith(logger) in derive for useLogger() support. Note: onAfterResponse is fire-and-forget and may not complete before app.handle() returns in tests — use onAfterHandle instead.

Fastify: Use fastify-plugin wrapper, fastify.decorateRequest('log', null) , onRequest /onResponse hooks.

NestJS: NestInterceptor with intercept() , tap() /catchError() on observable, forRoot() dynamic module.

Step 2: Build Config

Add a build entry in packages/evlog/tsdown.config.ts :

'{framework}/index': 'src/{framework}/index.ts',

Place it after the existing framework entries (workers, next, hono, express).

Also add the framework SDK to the external array:

external: [ // ... existing externals '{framework-package}', // e.g., 'elysia', 'fastify', 'express' ],

Step 3: Package Exports

In packages/evlog/package.json , add four entries:

In exports (after the last framework entry):

"./{framework}": { "types": "./dist/{framework}/index.d.mts", "import": "./dist/{framework}/index.mjs" }

In typesVersions["*"] :

"{framework}": [ "./dist/{framework}/index.d.mts" ]

In peerDependencies (with version range):

"{framework-package}": "^{latest-major}.0.0"

In peerDependenciesMeta (mark as optional):

"{framework-package}": { "optional": true }

In keywords — add the framework name to the keywords array.

Step 4: Tests

Create packages/evlog/test/{framework}.test.ts .

Import shared test helpers from ./helpers/framework :

import { assertDrainCalledWith, assertEnrichBeforeDrain, assertSensitiveHeadersFiltered, createPipelineSpies, } from './helpers/framework'

Required test categories:

  • Middleware creates logger — verify c.get('log') or req.log returns a RequestLogger

  • Auto-emit on response — verify event includes status, method, path, duration

  • Error handling — verify errors are captured and event has error level + error details

  • Route filtering — verify skipped routes don't create a logger

  • Request ID forwarding — verify x-request-id header is used when present

  • Context accumulation — verify logger.set() data appears in emitted event

  • Drain callback — use assertDrainCalledWith() helper

  • Enrich callback — use assertEnrichBeforeDrain() helper

  • Keep callback — verify tail sampling callback receives context and can force-keep logs

  • Sensitive header filtering — use assertSensitiveHeadersFiltered() helper

  • Drain/enrich error resilience — verify errors in drain/enrich do not break the request

  • Skipped routes skip drain/enrich — verify drain/enrich are not called for excluded routes

  • useLogger() returns same logger — verify useLogger() === req.log (or framework equivalent)

  • useLogger() throws outside context — verify error thrown when called without middleware

  • useLogger() works across async — verify logger accessible in async service functions

Use the framework's test utilities when available (e.g., Hono's app.request() , Express's supertest , Fastify's inject() ).

Step 5: Framework Docs Page

Create apps/docs/content/2.frameworks/{NN}.{framework}.md with a comprehensive, self-contained guide.

Use zero-padded numbering ({NN} ) to maintain correct sidebar ordering. Check existing files to determine the next number.

Frontmatter:


title: {Framework} description: Using evlog with {Framework} — automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in {Framework} applications. navigation: title: {Framework} icon: i-simple-icons-{framework} links:


Sections (follow the Express/Hono/Elysia pages as reference):

  • Quick Start — install + register middleware (copy-paste minimum setup)

  • Wide Events — progressive log.set() usage

  • useLogger() — accessing logger from services without passing req

  • Error Handling — createError()

  • parseError()
  • framework error handler
  • Drain & Enrichers — middleware options with inline example

  • Pipeline (Batching & Retry) — createDrainPipeline example

  • Tail Sampling — keep callback

  • Route Filtering — include / exclude / routes

  • Client-Side Logging — browser drain (only if framework has a client-side story)

  • Run Locally — clone + bun run example:{framework}

  • Card group linking to GitHub source

Step 6: Overview & Installation Cards

In apps/docs/content/2.frameworks/00.overview.md :

  • Add a row to the Overview table with framework name, import, type, logger access, and status

  • Add a :::card in the appropriate section (Full-Stack or Server Frameworks) with color: neutral

In apps/docs/content/1.getting-started/2.installation.md :

  • Add a :::card in the "Choose Your Framework" ::card-group with color: neutral

  • Place it in the correct order relative to existing frameworks (Nuxt, Next.js, SvelteKit, Nitro, TanStack Start, NestJS, Express, Hono, Fastify, Elysia, CF Workers)

Step 7: Landing Page (unchanged)

Add a code snippet in apps/docs/content/0.landing.md for the framework.

Find the FeatureFrameworks MDC component usage (the section with #nuxt , #nextjs , #hono , #express , etc.) and add a new slot:

#{framework}

// Framework-specific code example showing evlog usage

Place the snippet in the correct order relative to existing frameworks.

## Step 8: FeatureFrameworks Component

Update `apps/docs/app/components/features/FeatureFrameworks.vue`:

1. Add the framework to the `frameworks` array with its icon and the **next available tab index**
2. Add a `&#x3C;div v-show="activeTab === {N}">` with `&#x3C;slot name="{framework}" />` in the template
3. **Increment tab indices** for any frameworks that come after the new one

Icons use Simple Icons format: `i-simple-icons-{name}` (e.g., `i-simple-icons-express`, `i-simple-icons-hono`).

## Step 9: Update `skills/review-logging-patterns/SKILL.md`

In `skills/review-logging-patterns/SKILL.md` (the public skill distributed to users):

1. Add `### {Framework}` in the **"Framework Setup"** section, after the last existing framework entry and before "Cloudflare Workers"
2. Include:
 - Import + `initLogger` + middleware/plugin setup
 - Logger access in route handlers (`req.log`, `c.get('log')`, or `{ log }` destructuring)
 - `useLogger()` snippet with a short service function example
 - Full pipeline example showing `drain`, `enrich`, and `keep` options
3. Update the `description:` line in the YAML frontmatter to mention the new framework name

## Step 10: Update `packages/evlog/README.md`

In the root `packages/evlog/README.md`:

1. Add a `## {Framework}` section after the Elysia section (before `## Browser`), with a minimal setup snippet and a link to the example app
2. Add a row to the **"Framework Support"** table:

```markdown
| **{Framework}** | `{registration pattern}` with `import { evlog } from 'evlog/{framework}'` ([example](./examples/{framework})) |

Keep the snippet short — just init, register/use middleware, and one route handler showing logger access. No need to repeat drain/enrich/keep here.

Step 11: Example App

Create examples/{framework}/
with a runnable app that demonstrates all evlog features.

The app must include:

- evlog()
middleware with drain
(PostHog) and enrich
callbacks

- Health route — basic log.set()
usage

- Data route — context accumulation with user/business data, using useLogger()
in a service function

- Error route — createError()
with status/why/fix/link

- Error handler — framework's error handler with parseError()
+ manual log.error()

- Test UI — served at /
, a self-contained HTML page with buttons to hit each route and display JSON responses

Drain must use PostHog (createPostHogDrain()
from evlog/posthog
). The POSTHOG_API_KEY
env var is already set in the root .env
. This ensures every example tests a real external drain adapter.

Pretty printing should be enabled so the output is readable when testing locally.

Type the enrich
callback parameter explicitly — use type EnrichContext
from evlog
to avoid implicit any
:

import { type EnrichContext } from 'evlog'

app.use(evlog({
enrich: (ctx: EnrichContext) => {
  ctx.event.runtime = 'node'
},
}))

Test UI

Every example must serve a test UI at GET /
— a self-contained HTML page (no external deps) that lets the user click routes and see responses without curl.

The UI must:

- List all available routes with method badge + path + description

- Send the request on click and display the JSON response with syntax highlighting

- Show status code (color-coded 2xx/4xx/5xx) and response time

- Use a dark theme with monospace font

- Be a single .ts
file (src/ui.ts
) exporting a testUI()
function returning an HTML string

- The root /
route must be registered before the evlog middleware so it doesn't get logged

Reference: examples/hono/src/ui.ts
for the canonical pattern. Copy and adapt for each framework.

Required files

File
Purpose

src/index.ts

App with all features demonstrated

src/ui.ts

Test UI — testUI()
returning self-contained HTML

package.json

dev
and start
scripts

tsconfig.json

TypeScript config (if needed)

README.md

How to run + link to the UI

Package scripts

{
"scripts": {
  "dev": "bun --watch src/index.ts",
  "start": "bun src/index.ts"
}
}

Step 12: Root Package Script

Add a root-level script in the monorepo package.json
:

"example:{framework}": "dotenv -- turbo run dev --filter=evlog-{framework}-example"

The dotenv --
prefix loads the root .env
file (containing POSTHOG_API_KEY
and other adapter keys) into the process before turbo starts. Turborepo does not load .env
files — dotenv-cli
handles this at the root level so individual examples need no env configuration.

Step 13: Changeset

Create .changeset/{framework}-integration.md
:

---
"evlog": minor
---

Add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling support

Step 15 &#x26; 16: PR Scopes

Add the framework name as a valid scope in both files so PR title validation passes:

.github/workflows/semantic-pull-request.yml
— add {framework}
to the scopes
list:

scopes: |
# ... existing scopes
{framework}

.github/pull_request_template.md
— add {framework}
to the Scopes section:

- {framework} ({Framework} integration)

Verification

After completing all steps, run from the repo root:

cd packages/evlog
bun run build    # Verify build succeeds with new entry
bun run test     # Verify unit tests pass
bun run lint     # Verify no lint errors

Then type-check the example:

cd examples/{framework}
npx tsc --noEmit  # Verify no TS errors in the example

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

review-logging-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

create-evlog-enricher

No summary provided by upstream source.

Repository SourceNeeds Review
General

create-evlog-adapter

No summary provided by upstream source.

Repository SourceNeeds Review
General

analyze-logs

No summary provided by upstream source.

Repository SourceNeeds Review