create-evlog-framework-integration

安装量: 149
排名: #5790

安装

npx skills add https://github.com/hugorcd/evlog --skill 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/evlog/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/
.
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
(Hono, Elysia, etc.)
extractSafeNodeHeaders
../shared/headers
Convert Node.js
IncomingHttpHeaders
→ filtered
Record
(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 : - label : Source Code icon : i - simple - icons - github to : https : //github.com/HugoRCD/evlog/tree/main/examples/ { framework } color : neutral variant : subtle


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} ```ts [src/index.ts] // 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 <div v-show="activeTab === {N}"> with <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/evlog/SKILL.md

In skills/evlog/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}** |withimport { evlog } from 'evlog/{framework}'` (example) | 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 & 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

返回排行榜