evloghq/evlog1 file

Create Evlog Framework Integration

Create a new evlog framework integration to add automatic wide-event logging to an HTTP framework. Use when adding middleware/plugin support for a framework (e.g., Koa, H3 standalone, Deno Fresh, etc.) to the evlog package. Covers source code, build config, package exports, tests, example app, and all documentation.

Specification
Skill ID
evloghq/evlog/create-evlog-framework-integration
Publisher
evloghq
Repository
evlog
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

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-framework-integrationInstalls these files
  • SKILL.md

What this skill tells the agent

Create evlog Framework Integration

Add a new framework integration to evlog. The recommended path is the manifest mode built on defineFrameworkIntegration from evlog/toolkit, for any framework with a request/response middleware shape. For frameworks with a fundamentally different lifecycle you'll fall back to the lower-level createMiddlewareLogger.

Two paths

  • Manifest mode (preferred, ~30–80 lines of glue). Call defineFrameworkIntegration({ name, extractRequest, attachLogger, storage? }) once at module level, then write a tiny middleware that calls integration.start(ctx, options) and runs the framework's next() inside runWith. Reference implementations: all of packages/evlog/src/{hono,express,fastify,elysia,nestjs,orpc,react-router,sveltekit,workers}/index.ts use it.
  • Custom mode: use createMiddlewareLogger directly when the framework's lifecycle doesn't fit a standard middleware. Current custom-mode integrations: Next.js (src/next/), Nitro v2/v3 (src/nitro/, src/nitro-v3/), Eve (src/eve/).

Manifest mode now covers all classic HTTP frameworks. Use custom mode only when you can't extract a request synchronously at the start of the lifecycle (server actions, module-level hooks, agent turns).

Required API surface (from AGENTS.md)

Every framework integration must expose:

  1. evlog() middleware/plugin accepting the full BaseEvlogOptions (drain, enrich, keep, include, exclude, routes, plugins)
  2. useLogger() (ALS-backed). Workers is the one sanctioned exception (ALS needs a compat flag there; defineWorkerFetch attaches the logger instead)
  3. log.fork() support (automatic when storage is provided to the manifest)
  4. The framework-native accessor (c.get('log'), req.log, event.locals.log, …)

PR Title

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

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, or use an unscoped title (feat: add {Framework} middleware integration) on the introducing PR.

Touchpoints Checklist

#FileAction
1packages/evlog/src/{framework}/index.tsCreate integration source
2packages/evlog/tsdown.config.tsAdd build entry + external
3packages/evlog/package.jsonAdd exports + typesVersions + optional peer dep + keyword
4packages/evlog/test/frameworks/{framework}.test.tsCreate tests (real request driver + describeStandardHttpMatrix)
5packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snapRegenerated by pnpm run build + pnpm test
6apps/docs/content/4.integrate/frameworks/{NN}.{framework}.mdCreate framework docs page
7apps/docs/content/4.integrate/frameworks/00.overview.mdAdd table row + card
8apps/docs/content/1.start/3.installation.mdAdd card in "Choose Your Framework"
9apps/docs/content/0.landing.mdAdd framework code snippet slot
10apps/docs/app/components/features/FeatureFrameworks.vueAdd framework tab
11skills/review-logging-patterns/SKILL.mdAdd framework setup section + update frontmatter description
12packages/evlog/README.mdAdd framework section + row in the Framework Support table
13examples/{framework}/Create example app with test UI (auto-discovered by pnpm example {framework}, no root script needed)
14.changeset/{framework}-integration.mdCreate changeset (minor)
15.github/workflows/semantic-pull-request.yml + .github/pull_request_template.mdRegister {framework} as a PR scope in both files

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

Naming Conventions

PlaceholderExample (Hono)Usage
{framework}honoDirectory names, import paths, file names, PR scope
{Framework}HonoPascalCase 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).

UtilityLocationPurpose
defineFrameworkIntegration../shared/integrationManifest factory: extract request, create logger, attach, run with ALS
createMiddlewareLogger../shared/middlewareLower-level lifecycle (custom mode): logger creation, route filtering, tail sampling, emit, enrich, drain
BaseEvlogOptions../shared/middlewareBase user-facing options type with drain, enrich, keep, include, exclude, routes, plugins
createLoggerStorage../shared/storage (evlog/toolkit/storage)Factory returning { storage, useLogger } for AsyncLocalStorage-backed useLogger(). Prefer evlog/toolkit/storage on Workers / edge
shouldDeferEmitForResponse../shared/streamResponseDefer the wide event until a streaming body closes (see Hono/Elysia)

defineFrameworkIntegration automatically:

  • normalizes both Web Headers and Node IncomingHttpHeaders (so you don't need to pick a header extractor)
  • generates a requestId when none is present
  • calls createMiddlewareLogger and surfaces its { logger, finish, skipped, middlewareOptions }
  • attaches log.fork() automatically when storage is provided
  • exposes runWith(fn) to run downstream handlers inside the integration's ALS
  • forwards waitUntil when the runtime provides one (Workers, Hono on Workers)

Step 1: Integration Source

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

Template Structure (manifest mode)

import type { AuditableLogger } from '../audit'
import { defineFrameworkIntegration } from '../shared/integration'
import type { BaseEvlogOptions } from '../shared/middleware'
import { createLoggerStorage } from '../shared/storage'

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

export type Evlog{Framework}Options = BaseEvlogOptions
export { useLogger }

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

const integration = defineFrameworkIntegration<{Framework}Context>({
  name: '{framework}',
  extractRequest: (ctx) => ({
    method: /* ctx.method */,
    path: /* ctx.path */,
    headers: /* Web Headers OR Node headers OR plain object */,
    requestId: /* x-request-id header or undefined → auto-generated */,
  }),
  attachLogger: (ctx, logger) => {
    // Store in framework-idiomatic location:
    // - Hono:    c.set('log', logger)
    // - Express: req.log = logger
  },
  storage,
})

export function evlog(options: Evlog{Framework}Options = {}): FrameworkMiddleware {
  return async (ctx, next) => {
    const { skipped, finish, runWith } = integration.start(ctx, options)
    if (skipped) {
      await next()
      return
    }
    try {
      await runWith(() => next())
      await finish({ status: /* extract status from ctx */ })
    } catch (error) {
      await finish({ error: error as Error })
      throw error
    }
  }
}

Reference Implementations

  • Hono: src/hono/index.ts. c.set('log', logger) + ALS useLogger(), streaming deferral via shouldDeferEmitForResponse, waitUntil detection
  • Express: src/express/index.ts. req.log, ALS storage, res.on('finish') for terminal status

Use this skill in a project

Open any RiverX project, search the Skills panel for evloghq/evlog/create-evlog-framework-integration, and install it in one click.

Use this skill