Loading...
Loading...
Compare original and translation side by side
createMiddlewareLoggercreateMiddlewareLoggerfeat({framework}): add {Framework} middleware integrationfeat({framework}): add {Framework} middleware integration| # | 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 |
| # | 文件 | 操作 |
|---|---|---|
| 1 | | 创建集成源代码 |
| 2 | | 新增构建入口 + 外部依赖 |
| 3 | | 新增 |
| 4 | | 创建测试用例 |
| 5 | | 新增对应框架的文档章节 |
| 6 | | 创建专属的示例页面 |
| 7 | | 新增框架代码片段 |
| 8 | | 新增框架标签页 |
| 9 | | 在集成章节添加该框架 |
| 10 | | 创建带测试UI的示例应用 |
| 11 | 根目录 | 新增 |
| 12 | | 创建变更集( |
| 13 | | 新增 |
| 14 | | 新增 |
| Placeholder | Example (Hono) | Usage |
|---|---|---|
| | Directory names, import paths, file names |
| | PascalCase in type/interface names |
| 占位符 | 示例(Hono) | 使用场景 |
|---|---|---|
| | 目录名、导入路径、文件名 |
| | 类型/接口名的大驼峰命名 |
| 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 |
| | 基础选项类型,包含 |
| 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之前执行 |
| | 验证敏感头信息已被排除 |
| | 验证标准宽事件字段符合要求 |
packages/evlog/src/{framework}/index.tscreateMiddlewareLoggerpackages/evlog/src/{framework}/index.tscreateMiddlewareLoggerimport { 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())
}
}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())
}
}packages/evlog/src/hono/index.tsc.set('log', logger)next()packages/evlog/src/express/index.tsreq.logres.on('finish')AsyncLocalStorageuseLogger()packages/evlog/src/elysia/index.tsderive()onAfterHandleonErrorAsyncLocalStorageuseLogger()packages/evlog/src/hono/index.tsc.set('log', logger)next()packages/evlog/src/express/index.tsreq.logres.on('finish')AsyncLocalStorageuseLogger()packages/evlog/src/elysia/index.tsderive()onAfterHandleonErrorAsyncLocalStorageuseLogger()createMiddlewareLoggercreateRequestLoggerextractSafeHeadersHeadersextractSafeNodeHeadersIncomingHttpHeaderscreateMiddlewareLoggerdrainenrichkeepfinish()c.set()req.log.derive()useLogger()AsyncLocalStoragefinish()finish()EvlogVariablescallEnrichAndDraincreateMiddlewareLoggercreateMiddlewareLoggercreateRequestLoggerHeadersextractSafeHeadersIncomingHttpHeadersextractSafeNodeHeaderscreateMiddlewareLoggerdrainenrichkeepfinish()c.set()req.log.derive()useLogger()AsyncLocalStoragefinish()finish()EvlogVariablescallEnrichAndDraincreateMiddlewareLoggerMiddlewareHandlerc.set('log', logger)c.res.statusc.req.raw.headers(req, res, next)res.on('finish')storage.run(logger, () => next())useLogger()express-serve-static-coreexpressErrorRequestHandlernew Elysia({ name: 'evlog' }).derive({ as: 'global' })logonAfterHandleonErrorstorage.enterWith(logger)deriveuseLogger()onAfterResponseapp.handle()onAfterHandlefastify-pluginfastify.decorateRequest('log', null)onRequestonResponseNestInterceptorintercept()tap()catchError()forRoot()MiddlewareHandlerc.set('log', logger)c.res.statusc.req.raw.headers(req, res, next)res.on('finish')storage.run(logger, () => next())useLogger()express-serve-static-coreexpressErrorRequestHandlernew Elysia({ name: 'evlog' }).derive({ as: 'global' })logonAfterHandleonErrorderivestorage.enterWith(logger)useLogger()onAfterResponseapp.handle()onAfterHandlefastify-pluginfastify.decorateRequest('log', null)onRequestonResponseNestInterceptorintercept()tap()catchError()forRoot()packages/evlog/tsdown.config.ts'{framework}/index': 'src/{framework}/index.ts',externalexternal: [
// ... existing externals
'{framework-package}', // e.g., 'elysia', 'fastify', 'express'
],packages/evlog/tsdown.config.ts'{framework}/index': 'src/{framework}/index.ts',externalexternal: [
// ... existing externals
'{framework-package}', // e.g., 'elysia', 'fastify', 'express'
],packages/evlog/package.jsonexports"./{framework}": {
"types": "./dist/{framework}/index.d.mts",
"import": "./dist/{framework}/index.mjs"
}typesVersions["*"]"{framework}": [
"./dist/{framework}/index.d.mts"
]peerDependencies"{framework-package}": "^{latest-major}.0.0"peerDependenciesMeta"{framework-package}": {
"optional": true
}keywordspackages/evlog/package.jsonexports"./{framework}": {
"types": "./dist/{framework}/index.d.mts",
"import": "./dist/{framework}/index.mjs"
}typesVersions["*"]"{framework}": [
"./dist/{framework}/index.d.mts"
]peerDependencies"{framework-package}": "^{latest-major}.0.0"peerDependenciesMeta"{framework-package}": {
"optional": true
}keywordspackages/evlog/test/{framework}.test.ts./helpers/frameworkimport {
assertDrainCalledWith,
assertEnrichBeforeDrain,
assertSensitiveHeadersFiltered,
createPipelineSpies,
} from './helpers/framework'c.get('log')req.logRequestLoggerx-request-idlogger.set()assertDrainCalledWith()assertEnrichBeforeDrain()assertSensitiveHeadersFiltered()useLogger() === req.logapp.request()supertestinject()packages/evlog/test/{framework}.test.ts./helpers/frameworkimport {
assertDrainCalledWith,
assertEnrichBeforeDrain,
assertSensitiveHeadersFiltered,
createPipelineSpies,
} from './helpers/framework'c.get('log')req.logRequestLoggerx-request-idlogger.set()assertDrainCalledWith()assertEnrichBeforeDrain()assertSensitiveHeadersFiltered()useLogger()useLogger() === req.loguseLogger()useLogger()app.request()supertestinject()apps/docs/content/1.getting-started/2.installation.mdreq.logc.get('log')useLogger()npm install evlog {framework}apps/docs/content/1.getting-started/2.installation.mdreq.logc.get('log')useLogger()npm install evlog {framework}apps/docs/content/6.examples/{N}.{framework}.md---
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
---req.log.set()c.get('log').set()createError()keepincludeexcluderoutesbun run example:{framework}apps/docs/content/6.examples/{N}.{framework}.md---
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
---req.log.set()c.get('log').set()useLogger()createError()keepincludeexcluderoutesbun run example:{framework}apps/docs/content/0.landing.mdFeatureFrameworks#nuxt#nextjs#hono#express #{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.mdFeatureFrameworks#nuxt#nextjs#hono#express #{framework}
```ts [src/index.ts]
// 展示evlog用法的框架专属代码示例
放在现有框架的正确排序位置。apps/docs/app/components/features/FeatureFrameworks.vueframeworks<div v-show="activeTab === {N}"><slot name="{framework}" />i-simple-icons-{name}i-simple-icons-expressi-simple-icons-honoapps/docs/app/components/features/FeatureFrameworks.vueframeworks<div v-show="activeTab === {N}"><slot name="{framework}" />i-simple-icons-{name}i-simple-icons-expressi-simple-icons-honoAGENTS.mdreq.loguseLogger()AGENTS.mdreq.loguseLogger()examples/{framework}/evlog()drainenrichlog.set()useLogger()createError()parseError()log.error()/createPostHogDrain()evlog/posthogPOSTHOG_API_KEY.envenrichtype EnrichContextevloganyimport { type EnrichContext } from 'evlog'
app.use(evlog({
enrich: (ctx: EnrichContext) => {
ctx.event.runtime = 'node'
},
}))examples/{framework}/evlog()drainenrichlog.set()useLogger()createError()parseError()log.error()/evlog/posthogcreatePostHogDrain().envPOSTHOG_API_KEYenrichevlogEnrichContextanyimport { type EnrichContext } from 'evlog'
app.use(evlog({
enrich: (ctx: EnrichContext) => {
ctx.event.runtime = 'node'
},
}))GET /.tssrc/ui.tstestUI()/examples/hono/src/ui.tsGET /.tssrc/ui.tstestUI()/examples/hono/src/ui.ts| File | Purpose |
|---|---|
| App with all features demonstrated |
| Test UI — |
| |
| TypeScript config (if needed) |
| How to run + link to the UI |
| 文件 | 用途 |
|---|---|
| 展示所有功能的应用代码 |
| 测试UI —— |
| |
| TypeScript配置(如果需要) |
| 运行说明 + UI链接 |
{
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts"
}
}{
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts"
}
}package.json"example:{framework}": "dotenv -- turbo run dev --filter=evlog-{framework}-example"dotenv --.envPOSTHOG_API_KEY.envdotenv-clipackage.json"example:{framework}": "dotenv -- turbo run dev --filter=evlog-{framework}-example"dotenv --.envPOSTHOG_API_KEY.envdotenv-cli.changeset/{framework}-integration.md---
"evlog": minor
---
Add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling support.changeset/{framework}-integration.md---
"evlog": minor
---
Add {Framework} middleware integration (`evlog/{framework}`) with automatic wide-event logging, drain, enrich, and tail sampling support.github/workflows/semantic-pull-request.yml{framework}scopesscopes: |
# ... existing scopes
{framework}.github/pull_request_template.md{framework}- {framework} ({Framework} integration).github/workflows/semantic-pull-request.yml{framework}scopesscopes: |
# ... existing scopes
{framework}.github/pull_request_template.md{framework}- {framework} ({Framework} integration)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 errorscd examples/{framework}
npx tsc --noEmit # Verify no TS errors in the examplecd packages/evlog
bun run build # 验证新增入口后构建成功
bun run test # 验证单元测试通过
bun run lint # 验证无lint错误cd examples/{framework}
npx tsc --noEmit # 验证示例中无TS错误