Skip to main content

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.

Ir a la instalación

Datos de origen

Repositorio
evloghq/evlog
Última actividad en el origen
10 de septiembre de 2026 a las 21:54
Idioma detectado de SKILL.md
inglés
Estrellas
1855
Forks
63

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
create-evlog-framework-integration
description
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.
metadata
{"internal":true}
# 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 | # | 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` + optional peer dep + keyword | | 4 | `packages/evlog/test/frameworks/{framework}.test.ts` | Create tests (real request driver + `describeStandardHttpMatrix`) | | 5 | `packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap` | Regenerated by `pnpm run build` + `pnpm test` | | 6 | `apps/docs/content/4.integrate/frameworks/{NN}.{framework}.md` | Create framework docs page | | 7 | `apps/docs/content/4.integrate/frameworks/00.overview.md` | Add table row + card | | 8 | `apps/docs/content/1.start/3.installation.md` | Add card in "Choose Your Framework" | | 9 | `apps/docs/content/0.landing.md` | Add framework code snippet slot | | 10 | `apps/docs/app/components/features/FeatureFrameworks.vue` | Add framework tab | | 11 | `skills/review-logging-patterns/SKILL.md` | Add framework setup section + update frontmatter description | | 12 | `packages/evlog/README.md` | Add framework section + row in the Framework Support table | | 13 | `examples/{framework}/` | Create example app with test UI (auto-discovered by `pnpm example {framework}` — no root script needed) | | 14 | `.changeset/{framework}-integration.md` | Create changeset (`minor`) | | 15 | `.github/workflows/semantic-pull-request.yml` + `.github/pull_request_template.md` | Register `{framework}` as a PR scope in both files | **Important**: Do NOT consider the task complete until all 15 touchpoints have been addressed. ## Naming Conventions | Placeholder | Example (Hono) | Usage | |-------------|----------------|-------| | `{framework}` | `hono` | Directory names, import paths, file names, PR scope | | `{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](https://evlog.dev/extend/custom-framework)). | Utility | Location | Purpose | |---------|----------|---------| | `defineFrameworkIntegration` | `../shared/integration` | Manifest factory — extract request, create logger, attach, run with ALS | | `createMiddlewareLogger` | `../shared/middleware` | Lower-level lifecycle (custom mode): logger creation, route filtering, tail sampling, emit, enrich, drain | | `BaseEvlogOptions` | `../shared/middleware` | Base 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/streamResponse` | Defer 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) ```typescript 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 - **Fastify**: `src/fastify/index.ts`. Fastify hooks (`onRequest` / `onResponse` / `onError`), `fastify-plugin` wrapper - **Elysia**: `src/elysia/index.ts`. Plugin with `.derive({ as: 'global' })`, `storage.enterWith`-style ALS, streaming deferral - **NestJS**: `src/nestjs/index.ts`. `EvlogModule.forRoot()` / `forRootAsync()` on top of the manifest - **oRPC**: `src/orpc/index.ts`. `evlog()` procedure middleware + `withEvlog(handler)` wrapper - **React Router**: `src/react-router/index.ts`. `loggerContext = createContext<AuditableLogger>()` - **SvelteKit**: `src/sveltekit/index.ts`. `evlog()` handle + `evlogHandleError()` + `createEvlogHooks()` - **Workers**: `src/workers/index.ts`. `defineWorkerFetch` / `withEvlog`, no ALS `useLogger()` (compat-flag constraint) For integrations that bind with `enterWith()`, use `createSharedEnterWithStorage` from `src/shared/asyncStorageScope.ts`. Its capability probe runs in a temporary scope with a distinct store, preserving the importer's async context on Bun and Node. ### Key Architecture Rules 1. **Prefer `defineFrameworkIntegration`**: it handles header normalization, request-id generation, ALS, fork attachment, and `waitUntil`. 2. **Status / error reporting stays framework-side**: call `finish({ status })` on success and `finish({ error })` on failure. `finish` runs emit + enrich + drain + plugin hooks. 3. **Re-throw errors** after `finish({ error })` so the framework's own error handler still runs. 4. **Streaming responses**: if the framework can return streaming bodies, defer the emit until the stream closes (`shouldDeferEmitForResponse`; see Hono and Elysia). 5. **Framework SDK is an optional peer dependency**: never bundle it. 6. **Never duplicate pipeline logic**: `runEnrichAndDrain` is internal to `createMiddlewareLogger`/`finish`. 7. **Export type helpers** for typed context access (e.g., `EvlogVariables` for Hono). ### When to fall back to custom mode Use `createMiddlewareLogger` directly (skipping `defineFrameworkIntegration`) when: - The middleware doesn't have a clear "request entry / response exit" pair (Next.js App Router server actions, Eve agent turns). - Logger creation spans multiple lifecycle phases owned by a module system (Nitro plugins + hooks). - The status is not knowable until after the response stream completes and the framework gives you no hook for it. ## Step 2: Build Config Add a build entry in `packages/evlog/tsdown.config.ts`: ```typescript '{framework}/index': 'src/{framework}/index.ts', ``` Also add the framework SDK to the `external` array (e.g., `'elysia'`, `'fastify'`). ## Step 3: Package Exports In `packages/evlog/package.json`: **In `exports`** (after the last framework entry): ```json "./{framework}": { "types": "./dist/{framework}/index.d.mts", "import": "./dist/{framework}/index.mjs" } ``` **In `typesVersions["*"]`**: `"{framework}": ["./dist/{framework}/index.d.mts"]` **In `peerDependencies`** (version range) + **`peerDependenciesMeta`** (`"optional": true`), and add the framework name to `keywords`. Exports without matching `tsdown.config.ts` entries fail `test/toolkit/api-surface.test.ts`. ## Step 4: Tests Create `packages/evlog/test/frameworks/{framework}.test.ts`. Read `packages/evlog/test/README.md` first, especially the **Framework runtime fidelity** table. Two non-negotiables: 1. **Real request driver.** Use the framework's own driver: supertest (Express/NestJS), `app.request()` (Hono), `app.inject()` (Fastify), `app.handle(new Request(...))` (Elysia). If no Node-friendly driver exists, call the user-facing contract directly with realistic input shapes (see the SvelteKit and React Router tests). Never extract internals to test a substitute. 2. **Wire the shared matrix.** Call `describeStandardHttpMatrix({ name, mount })` from `test/helpers/frameworkMatrix.ts`. It covers the standard sweep (event emission, `x-request-id`, route service) for every HTTP framework. On top of the matrix, cover the framework-specific surface: 1. Framework-native accessor returns the logger (`c.get('log')`, `req.log`, …) 2. Error handling. Errors captured, event has error level + details, error re-thrown 3. Route filtering. Skipped routes don't create a logger, skip drain/enrich 4. Context accumulation. `logger.set()` data appears in the emitted event 5. Drain / enrich / keep callbacks (use `createPipelineSpies()`, `assertHttpEventEmitted`, `waitForDrainCalls`, `findEventViaDrain` from `test/helpers/framework.ts`) 6. Drain/enrich error resilience. Errors there never break the request
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub