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.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
evloghq/evlog
آخر نشاط في المصدر
١٠ سبتمبر ٢٠٢٦ في ٢١:٥٤
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٬٨٥٥
التفرعات
٦٣

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub