create-evlog-framework-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCreate evlog Framework Integration
创建 evlog 框架集成
Add a new framework integration to evlog. Every integration follows the same architecture built on the shared utility. This skill walks through all touchpoints. Every single touchpoint is mandatory -- do not skip any.
createMiddlewareLogger为 evlog 新增一个框架集成。所有集成都基于共享的工具函数遵循相同的架构。本技能指南会覆盖所有需要修改的接触点。每一个接触点都是必填项——不要跳过任何步骤。
createMiddlewareLoggerPR Title
PR 标题
Recommended format for the pull request title:
feat({framework}): add {Framework} middleware integration拉取请求标题的推荐格式:
feat({framework}): add {Framework} middleware integrationTouchpoints Checklist
接触点检查清单
| # | File | Action |
|---|---|---|
| 1 | | Create integration source |
| 2 | | Add build entry + external |
| 3 | | Add |
| 4 | | Create tests |
| 5 | | Add framework section |
| 6 | | Create dedicated example page |
| 7 | | Add framework code snippet |
| 8 | | Add framework tab |
| 9 | | Add framework to integration section |
| 10 | | Create example app with test UI |
| 11 | | Add |
| 12 | | Create changeset ( |
| 13 | | Add |
| 14 | | Add |
Important: Do NOT consider the task complete until all 14 touchpoints have been addressed.
| # | 文件 | 操作 |
|---|---|---|
| 1 | | 创建集成源代码 |
| 2 | | 新增构建入口 + 外部依赖 |
| 3 | | 新增 |
| 4 | | 创建测试用例 |
| 5 | | 新增对应框架的文档章节 |
| 6 | | 创建专属的示例页面 |
| 7 | | 新增框架代码片段 |
| 8 | | 新增框架标签页 |
| 9 | | 在集成章节添加该框架 |
| 10 | | 创建带测试UI的示例应用 |
| 11 | 根目录 | 新增 |
| 12 | | 创建变更集( |
| 13 | | 新增 |
| 14 | | 新增 |
重要提示:只有完成所有14个接触点的修改,才能视为任务完成。
Naming Conventions
命名规范
Use these placeholders consistently:
| Placeholder | Example (Hono) | Usage |
|---|---|---|
| | Directory names, import paths, file names |
| | PascalCase in type/interface names |
请一致使用以下占位符:
| 占位符 | 示例(Hono) | 使用场景 |
|---|---|---|
| | 目录名、导入路径、文件名 |
| | 类型/接口名的大驼峰命名 |
Shared Utilities
共享工具函数
All integrations share the same core utilities. Never reimplement logic that exists in shared/.
| Utility | Location | Purpose |
|---|---|---|
| | Full lifecycle: logger creation, route filtering, tail sampling, emit, enrich, drain |
| | Convert Web API |
| | Convert Node.js |
| | Base options type with |
所有集成共享相同的核心工具函数。永远不要重写目录下已存在的逻辑。
shared/| 工具函数 | 位置 | 用途 |
|---|---|---|
| | 完整生命周期:日志创建、路由过滤、尾部采样、上报、信息丰富、排空 |
| | 将Web API |
| | 将Node.js |
| | 基础选项类型,包含 |
Test Helpers
测试辅助工具
| Utility | Location | Purpose |
|---|---|---|
| | Creates mock drain/enrich/keep callbacks |
| | Validates drain was called with expected event shape |
| | Validates enrich runs before drain |
| | Validates sensitive headers are excluded |
| | Validates standard wide event fields |
| 工具函数 | 位置 | 用途 |
|---|---|---|
| | 创建模拟的drain/enrich/keep回调 |
| | 验证drain被调用时传入的事件结构符合预期 |
| | 验证enrich在drain之前执行 |
| | 验证敏感头信息已被排除 |
| | 验证标准宽事件字段符合要求 |
Step 1: Integration Source
步骤1:集成源代码
Create .
packages/evlog/src/{framework}/index.tsThe 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创建。
packages/evlog/src/{framework}/index.ts集成文件应该尽可能精简——通常是50-80行框架专属的粘合代码。所有管道逻辑(enrich、drain、keep、头过滤)都由处理。
createMiddlewareLoggerTemplate Structure
模板结构
typescript
import { AsyncLocalStorage } from 'node:async_hooks'
import type { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from '../types'
import { createMiddlewareLogger } 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)
const storage = new AsyncLocalStorage<RequestLogger>()
export interface Evlog{Framework}Options {
include?: string[]
exclude?: string[]
routes?: Record<string, RouteConfig>
drain?: (ctx: DrainContext) => void | Promise<void>
enrich?: (ctx: EnrichContext) => void | Promise<void>
keep?: (ctx: TailSamplingContext) => void | Promise<void>
}
// 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 } }
/**
* Get the request-scoped logger from anywhere in the call stack.
* Uses AsyncLocalStorage — works across async boundaries.
*/
export function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {
const logger = storage.getStore()
if (!logger) {
throw new Error(
'[evlog] useLogger() was called outside of an evlog middleware context. '
+ 'Make sure the evlog middleware is registered before your routes.',
)
}
return logger as RequestLogger<T>
}
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())
}
}typescript
import { AsyncLocalStorage } from 'node:async_hooks'
import type { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from '../types'
import { createMiddlewareLogger } 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)
const storage = new AsyncLocalStorage<RequestLogger>()
export interface Evlog{Framework}Options {
include?: string[]
exclude?: string[]
routes?: Record<string, RouteConfig>
drain?: (ctx: DrainContext) => void | Promise<void>
enrich?: (ctx: EnrichContext) => void | Promise<void>
keep?: (ctx: TailSamplingContext) => void | Promise<void>
}
// 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 } }
/**
* 从调用栈的任意位置获取请求作用域的日志实例。
* 基于AsyncLocalStorage实现——可跨异步边界工作。
*/
export function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {
const logger = storage.getStore()
if (!logger) {
throw new Error(
'[evlog] useLogger() was called outside of an evlog middleware context. '
+ 'Make sure the evlog middleware is registered before your routes.',
)
}
return logger as RequestLogger<T>
}
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): — Web API Headers,
packages/evlog/src/hono/index.ts, wrapsc.set('log', logger)in try/catchnext() - Express (~80 lines): — Node.js headers,
packages/evlog/src/express/index.ts,req.log,res.on('finish')forAsyncLocalStorageuseLogger() - Elysia (~70 lines): — Web API Headers,
packages/evlog/src/elysia/index.tsplugin,derive()/onAfterHandle,onErrorforAsyncLocalStorageuseLogger()
- Hono(~40行):—— Web API Headers,
packages/evlog/src/hono/index.ts,在try/catch中包裹c.set('log', logger)next() - Express(~80行):—— Node.js headers,
packages/evlog/src/express/index.ts,req.log,基于res.on('finish')实现AsyncLocalStorageuseLogger() - Elysia(~70行):—— Web API Headers,
packages/evlog/src/elysia/index.ts插件,derive()/onAfterHandle,基于onError实现AsyncLocalStorageuseLogger()
Key Architecture Rules
核心架构规则
- Use — never call
createMiddlewareLoggerdirectlycreateRequestLogger - Use the right header extractor — for Web API
extractSafeHeaders,Headersfor Node.jsextractSafeNodeHeadersIncomingHttpHeaders - Spread user options into —
createMiddlewareLogger,drain,enrichare handled automatically bykeepfinish() - Store logger in the framework's idiomatic context (e.g., for Hono,
c.set()for Express,req.logfor Elysia).derive() - Export — backed by
useLogger()so the logger is accessible from anywhere in the call stackAsyncLocalStorage - Call in both success and error paths — it handles emit + enrich + drain
finish() - Re-throw errors after so framework error handlers still work
finish() - Export options interface with drain/enrich/keep for feature parity across all frameworks
- Export type helpers for typed context access (e.g., for Hono)
EvlogVariables - Framework SDK is a peer dependency — never bundle it
- Never duplicate pipeline logic — is internal to
callEnrichAndDraincreateMiddlewareLogger
- 使用—— 永远不要直接调用
createMiddlewareLoggercreateRequestLogger - 使用正确的头提取函数 —— Web API 用
Headers,Node.jsextractSafeHeaders用IncomingHttpHeadersextractSafeNodeHeaders - 将用户选项展开传入——
createMiddlewareLogger、drain、enrich会被keep自动处理finish() - 将日志实例存储到框架的惯用上下文对象中(例如Hono用,Express用
c.set(),Elysia用req.log).derive() - 导出—— 基于
useLogger()实现,因此可以在调用栈的任意位置访问日志实例AsyncLocalStorage - 在成功和错误路径中都调用—— 它会处理上报、信息丰富和排空逻辑
finish() - 在执行后重新抛出错误,保证框架的错误处理逻辑仍然可以正常工作
finish() - 导出选项接口,包含drain/enrich/keep,保证所有框架的功能一致性
- 导出类型辅助工具,用于类型化的上下文访问(例如Hono的)
EvlogVariables - 框架SDK作为对等依赖 —— 永远不要打包进产物
- 永远不要复制管道逻辑 —— 是
callEnrichAndDrain的内部逻辑createMiddlewareLogger
Framework-Specific Patterns
框架专属模式
Hono: Use return type, , for status, for headers.
MiddlewareHandlerc.set('log', logger)c.res.statusc.req.raw.headersExpress: Standard middleware, for response end, for . Type augmentation targets (NOT ). Error handler uses type.
(req, res, next)res.on('finish')storage.run(logger, () => next())useLogger()express-serve-static-coreexpressErrorRequestHandlerElysia: Return plugin, use to create logger and attach to context, for success path, for error path. Use in for support. Note: is fire-and-forget and may not complete before returns in tests — use instead.
new Elysia({ name: 'evlog' }).derive({ as: 'global' })logonAfterHandleonErrorstorage.enterWith(logger)deriveuseLogger()onAfterResponseapp.handle()onAfterHandleFastify: Use wrapper, , / hooks.
fastify-pluginfastify.decorateRequest('log', null)onRequestonResponseNestJS: with , / on observable, dynamic module.
NestInterceptorintercept()tap()catchError()forRoot()Hono:使用返回类型,,用获取状态码,获取请求头。
MiddlewareHandlerc.set('log', logger)c.res.statusc.req.raw.headersExpress:标准中间件,监听响应结束,实现支持。类型增强目标是(不是)。错误处理使用类型。
(req, res, next)res.on('finish')storage.run(logger, () => next())useLogger()express-serve-static-coreexpressErrorRequestHandlerElysia:返回插件,使用创建日志实例并将挂载到上下文,处理成功路径,处理错误路径。在中使用实现支持。注意:是 fire-and-forget 模式,在测试中可能在返回前未完成执行——请改用。
new Elysia({ name: 'evlog' }).derive({ as: 'global' })logonAfterHandleonErrorderivestorage.enterWith(logger)useLogger()onAfterResponseapp.handle()onAfterHandleFastify:使用包装,,/钩子。
fastify-pluginfastify.decorateRequest('log', null)onRequestonResponseNestJS:搭配,在observable上使用/,动态模块。
NestInterceptorintercept()tap()catchError()forRoot()Step 2: Build Config
步骤2:构建配置
Add a build entry in :
packages/evlog/tsdown.config.tstypescript
'{framework}/index': 'src/{framework}/index.ts',Place it after the existing framework entries (workers, next, hono, express).
Also add the framework SDK to the array:
externaltypescript
external: [
// ... existing externals
'{framework-package}', // e.g., 'elysia', 'fastify', 'express'
],在中新增构建入口:
packages/evlog/tsdown.config.tstypescript
'{framework}/index': 'src/{framework}/index.ts',放在已有的框架入口(workers、next、hono、express)之后。
同时将框架SDK添加到数组中:
externaltypescript
external: [
// ... existing externals
'{framework-package}', // e.g., 'elysia', 'fastify', 'express'
],Step 3: Package Exports
步骤3:包导出配置
In , add four entries:
packages/evlog/package.jsonIn (after the last framework entry):
exportsjson
"./{framework}": {
"types": "./dist/{framework}/index.d.mts",
"import": "./dist/{framework}/index.mjs"
}In :
typesVersions["*"]json
"{framework}": [
"./dist/{framework}/index.d.mts"
]In (with version range):
peerDependenciesjson
"{framework-package}": "^{latest-major}.0.0"In (mark as optional):
peerDependenciesMetajson
"{framework-package}": {
"optional": true
}In — add the framework name to the keywords array.
keywords在中新增四个配置项:
packages/evlog/package.json在中(放在最后一个框架入口之后):
exportsjson
"./{framework}": {
"types": "./dist/{framework}/index.d.mts",
"import": "./dist/{framework}/index.mjs"
}在中:
typesVersions["*"]json
"{framework}": [
"./dist/{framework}/index.d.mts"
]在中(带版本范围):
peerDependenciesjson
"{framework-package}": "^{latest-major}.0.0"在中(标记为可选):
peerDependenciesMetajson
"{framework-package}": {
"optional": true
}在中 —— 将框架名称添加到关键词数组。
keywordsStep 4: Tests
步骤4:测试用例
Create .
packages/evlog/test/{framework}.test.tsImport shared test helpers from :
./helpers/frameworktypescript
import {
assertDrainCalledWith,
assertEnrichBeforeDrain,
assertSensitiveHeadersFiltered,
createPipelineSpies,
} from './helpers/framework'Required test categories:
- Middleware creates logger — verify or
c.get('log')returns areq.logRequestLogger - 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 header is used when present
x-request-id - Context accumulation — verify data appears in emitted event
logger.set() - Drain callback — use helper
assertDrainCalledWith() - Enrich callback — use helper
assertEnrichBeforeDrain() - Keep callback — verify tail sampling callback receives context and can force-keep logs
- Sensitive header filtering — use helper
assertSensitiveHeadersFiltered() - 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 (or framework equivalent)
useLogger() === req.log - 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 , Express's , Fastify's ).
app.request()supertestinject()创建。
packages/evlog/test/{framework}.test.ts从导入共享测试辅助工具:
./helpers/frameworktypescript
import {
assertDrainCalledWith,
assertEnrichBeforeDrain,
assertSensitiveHeadersFiltered,
createPipelineSpies,
} from './helpers/framework'必填测试分类:
- 中间件创建日志实例 —— 验证或
c.get('log')返回req.log实例RequestLogger - 响应时自动上报 —— 验证事件包含状态码、请求方法、路径、耗时
- 错误处理 —— 验证错误被捕获,事件包含错误级别和错误详情
- 路由过滤 —— 验证跳过的路由不会创建日志实例
- 请求ID透传 —— 验证存在头时会被使用
x-request-id - 上下文累积 —— 验证的数据会出现在上报的事件中
logger.set() - Drain回调 —— 使用辅助工具验证
assertDrainCalledWith() - Enrich回调 —— 使用辅助工具验证
assertEnrichBeforeDrain() - Keep回调 —— 验证尾部采样回调收到上下文,并且可以强制保留日志
- 敏感头过滤 —— 使用辅助工具验证
assertSensitiveHeadersFiltered() - Drain/Enrich错误容错 —— 验证drain/enrich中的错误不会中断请求
- 跳过的路由不触发Drain/Enrich —— 验证被排除的路由不会调用drain/enrich
- 返回相同的日志实例 —— 验证
useLogger()(或对应框架的等价实现)useLogger() === req.log - 在上下文外调用会抛出错误 —— 验证未使用中间件时调用会抛出错误
useLogger() - 跨异步调用正常工作 —— 验证在异步服务函数中可以正常访问日志实例
useLogger()
如果框架有提供测试工具请使用(例如Hono的,Express的,Fastify的)。
app.request()supertestinject()Step 5: Installation Docs
步骤5:安装文档
Add a section in for the framework.
apps/docs/content/1.getting-started/2.installation.mdFollow the pattern of existing framework sections (Nuxt, Next.js, Nitro, Hono, Express). Include:
- One-liner description mentioning /
req.logandc.get('log')accessuseLogger() - Install command —
npm install evlog {framework} - Setup code — minimal working example with imports and configuration
- Usage in routes — how to access the logger in route handlers
- Full pipeline example — show drain + enrich + keep configuration
- Error handling — how structured errors work with the framework
- Callout linking to the dedicated example page
在中为该框架新增一个章节。
apps/docs/content/1.getting-started/2.installation.md遵循现有框架章节(Nuxt、Next.js、Nitro、Hono、Express)的模式,包含:
- 一行描述,说明/
req.log和c.get('log')的访问方式useLogger() - 安装命令 ——
npm install evlog {framework} - 配置代码 —— 包含导入和配置的最小可运行示例
- 路由中使用 —— 如何在路由处理函数中访问日志实例
- 完整管道示例 —— 展示drain + enrich + keep的配置
- 错误处理 —— 结构化错误如何与该框架配合工作
- 提示框,链接到专属的示例页面
Step 6: Example Docs Page
步骤6:示例文档页面
Create with a comprehensive guide.
apps/docs/content/6.examples/{N}.{framework}.mdFrontmatter:
yaml
---
title: {Framework}
description: Using evlog with {Framework} — automatic wide events, structured errors, drain adapters, enrichers, and tail sampling.
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 example pages):
- Setup — install + register middleware
- Wide Events — /
req.log.set()usagec.get('log').set() - useLogger() — accessing logger from services without passing req
- Error Handling — + framework error handler
createError() - Drain & Enrichers — middleware options
- Tail Sampling — callback
keep - Route Filtering — /
include/excluderoutes - Run Locally — clone +
bun run example:{framework} - Card group linking to GitHub source
创建,包含完整的使用指南。
apps/docs/content/6.examples/{N}.{framework}.mdFrontmatter:
yaml
---
title: {Framework}
description: 在{Framework}中使用evlog —— 自动宽事件、结构化错误、drain适配器、enricher和尾部采样。
navigation:
title: {Framework}
icon: i-simple-icons-{framework}
links:
- label: 源代码
icon: i-simple-icons-github
to: https://github.com/HugoRCD/evlog/tree/main/examples/{framework}
color: neutral
variant: subtle
---章节(遵循Express/Hono示例页面的结构):
- 配置 —— 安装 + 注册中间件
- 宽事件 —— /
req.log.set()的使用c.get('log').set() - —— 无需传递req即可在服务中访问日志实例
useLogger() - 错误处理 —— + 框架错误处理
createError() - Drain & Enrichers —— 中间件选项
- 尾部采样 —— 回调
keep - 路由过滤 —— /
include/excluderoutes - 本地运行 —— 克隆仓库 +
bun run example:{framework} - 卡片组,链接到GitHub源代码
Step 7: Landing Page
步骤7:首页
Add a code snippet in for the framework.
apps/docs/content/0.landing.mdFind the MDC component usage (the section with , , , , etc.) and add a new slot:
FeatureFrameworks#nuxt#nextjs#hono#expressmarkdown
#{framework}
```ts [src/index.ts]
// Framework-specific code example showing evlog usage
Place the snippet in the correct order relative to existing frameworks.在中为该框架新增代码片段。
apps/docs/content/0.landing.md找到 MDC组件的使用位置(包含、、、等的章节),新增一个插槽:
FeatureFrameworks#nuxt#nextjs#hono#expressmarkdown
#{framework}
```ts [src/index.ts]
// 展示evlog用法的框架专属代码示例
放在现有框架的正确排序位置。Step 8: FeatureFrameworks Component
步骤8:FeatureFrameworks组件
Update :
apps/docs/app/components/features/FeatureFrameworks.vue- Add the framework to the array with its icon and the next available tab index
frameworks - Add a with
<div v-show="activeTab === {N}">in the template<slot name="{framework}" /> - Increment tab indices for any frameworks that come after the new one
Icons use Simple Icons format: (e.g., , ).
i-simple-icons-{name}i-simple-icons-expressi-simple-icons-hono更新:
apps/docs/app/components/features/FeatureFrameworks.vue- 将框架添加到数组,配置对应的图标和下一个可用的标签索引
frameworks - 在模板中添加,内部包含
<div v-show="activeTab === {N}"><slot name="{framework}" /> - 为所有排在新框架之后的现有框架增加标签索引
图标使用Simple Icons格式:(例如、)。
i-simple-icons-{name}i-simple-icons-expressi-simple-icons-honoStep 9: Update AGENTS.md
步骤9:更新AGENTS.md
In the root file:
AGENTS.md- Add the framework to the "Framework Integration" section
- Add import path and basic setup example (showing both and
req.log)useLogger() - Show drain/enrich/keep usage
在根目录的文件中:
AGENTS.md- 将框架添加到**「框架集成」**章节
- 添加导入路径和基础配置示例(同时展示和
req.log的用法)useLogger() - 展示drain/enrich/keep的用法
Step 10: Example App
步骤10:示例应用
Create with a runnable app that demonstrates all evlog features.
examples/{framework}/The app must include:
- middleware with
evlog()(PostHog) anddraincallbacksenrich - Health route — basic usage
log.set() - Data route — context accumulation with user/business data, using in a service function
useLogger() - Error route — with status/why/fix/link
createError() - Error handler — framework's error handler with + manual
parseError()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 ( from ). The env var is already set in the root . This ensures every example tests a real external drain adapter.
createPostHogDrain()evlog/posthogPOSTHOG_API_KEY.envPretty printing should be enabled so the output is readable when testing locally.
Type the callback parameter explicitly — use from to avoid implicit :
enrichtype EnrichContextevloganytypescript
import { type EnrichContext } from 'evlog'
app.use(evlog({
enrich: (ctx: EnrichContext) => {
ctx.event.runtime = 'node'
},
}))创建目录,包含可运行的应用,展示所有evlog功能。
examples/{framework}/应用必须包含:
- 中间件,配置
evlog()(PostHog)和drain回调enrich - 健康检查路由 —— 基础的用法
log.set() - 数据路由 —— 结合用户/业务数据的上下文累积,在服务函数中使用
useLogger() - 错误路由 —— 包含状态/原因/修复方案/链接的
createError() - 错误处理 —— 框架的错误处理,搭配+ 手动
parseError()log.error() - 测试UI —— 部署在路径,是一个自包含的HTML页面,包含按钮可以访问每个路由并展示JSON响应
/
Drain必须使用PostHog(从导入)。根目录的中已经配置了环境变量。这保证每个示例都测试了真实的外部drain适配器。
evlog/posthogcreatePostHogDrain().envPOSTHOG_API_KEY应启用格式化输出,方便本地测试时阅读输出内容。
显式标注回调参数的类型 —— 使用从导入的类型,避免隐式:
enrichevlogEnrichContextanytypescript
import { type EnrichContext } from 'evlog'
app.use(evlog({
enrich: (ctx: EnrichContext) => {
ctx.event.runtime = 'node'
},
}))Test UI
测试UI
Every example must serve a test UI at — a self-contained HTML page (no external deps) that lets the user click routes and see responses without curl.
GET /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 file (
.ts) exporting asrc/ui.tsfunction returning an HTML stringtestUI() - The root route must be registered before the evlog middleware so it doesn't get logged
/
Reference: for the canonical pattern. Copy and adapt for each framework.
examples/hono/src/ui.ts每个示例都必须在路径提供测试UI —— 一个自包含的HTML页面(无外部依赖),用户可以点击路由查看响应,无需使用curl。
GET /UI必须满足:
- 列出所有可用路由,包含方法标识、路径、描述
- 点击时发送请求,展示带语法高亮的JSON响应
- 展示状态码(2xx/4xx/5xx对应不同颜色)和响应耗时
- 使用深色主题和等宽字体
- 是单个文件(
.ts),导出src/ui.ts函数返回HTML字符串testUI() - 根路由必须在evlog中间件之前注册,避免被日志记录
/
参考:是标准实现模式,可以复制适配到每个框架。
examples/hono/src/ui.tsRequired files
必填文件
| File | Purpose |
|---|---|
| App with all features demonstrated |
| Test UI — |
| |
| TypeScript config (if needed) |
| How to run + link to the UI |
| 文件 | 用途 |
|---|---|
| 展示所有功能的应用代码 |
| 测试UI —— |
| |
| TypeScript配置(如果需要) |
| 运行说明 + UI链接 |
Package scripts
包脚本
json
{
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts"
}
}json
{
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts"
}
}Step 11: Root Package Script
步骤11:根目录包脚本
Add a root-level script in the monorepo :
package.jsonjson
"example:{framework}": "dotenv -- turbo run dev --filter=evlog-{framework}-example"The prefix loads the root file (containing and other adapter keys) into the process before turbo starts. Turborepo does not load files — handles this at the root level so individual examples need no env configuration.
dotenv --.envPOSTHOG_API_KEY.envdotenv-cli在monorepo的根中新增根级脚本:
package.jsonjson
"example:{framework}": "dotenv -- turbo run dev --filter=evlog-{framework}-example"dotenv --.envPOSTHOG_API_KEY.envdotenv-cliStep 12: Changeset
步骤12:变更集
Create :
.changeset/{framework}-integration.mdmarkdown
---
"evlog": minor
---
Add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling support创建:
.changeset/{framework}-integration.mdmarkdown
---
"evlog": minor
---
Add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling supportStep 13 & 14: PR Scopes
步骤13 & 14:PR作用域
Add the framework name as a valid scope in both files so PR title validation passes:
.github/workflows/semantic-pull-request.yml{framework}scopesyaml
scopes: |
# ... existing scopes
{framework}.github/pull_request_template.md{framework}markdown
- {framework} ({Framework} integration)将框架名称作为有效作用域添加到两个文件中,保证PR标题校验通过:
.github/workflows/semantic-pull-request.yml{framework}scopesyaml
scopes: |
# ... existing scopes
{framework}.github/pull_request_template.md{framework}markdown
- {framework} ({Framework} integration)Verification
验证
After completing all steps, run from the repo root:
bash
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 errorsThen type-check the example:
bash
cd examples/{framework}
npx tsc --noEmit # Verify no TS errors in the example完成所有步骤后,在仓库根目录执行:
bash
cd packages/evlog
bun run build # 验证新增入口后构建成功
bun run test # 验证单元测试通过
bun run lint # 验证无lint错误然后对示例进行类型检查:
bash
cd examples/{framework}
npx tsc --noEmit # 验证示例中无TS错误