扩展
definePlugin 是 evlog 的规范扩展点——可以从 setup、onRequestStart、enrich、keep、drain、onRequestFinish、onClientLog、extendLogger 中选择任意子集,并将其整合为一个统一的对象。
request lifecycle
IN PIPELINE
  1. filterroute allowed

    /api/checkout matches include

  2. create loggerrequestId · startTime

    POST · /api/checkout · req_8a2c

  3. handlerlog.set() x3

    context accumulates

  4. tail sampleevlog:emit:keep

    no rule matched

  5. head sampleinfo: 100% kept

    random < rate

  6. emitWideEvent built

    logger sealed · ready to ship

  7. enrichevlog:enrich

    + userAgent · + geo

  8. drainevlog:drain

    → axiom · → fs

context
$POST/api/checkout
log.set(user:{ id: 1, plan: "pro" })
log.set(cart:{ items: 3, total: 9999 })
log.set(payment:{ method: "card", status: "ok" })
wide event
{
  level:    "info",
  method:   "POST",
  path:     "/api/checkout",
  duration: 234,
  status:   200,
  user:     { id: 1, plan: "pro" },
  cart:     { items: 3, total: 9999 },
  payment:  { method: "card", status: "ok" },
  userAgent: { browser: "chrome" },
  geo:      { country: "FR" }
}

definePlugin() 是 evlog 的规范扩展点。Drains 和 enrichers 是插件的特殊情况,但单个插件可以同时选择接入多个钩子——这对于任何需要混合多个职责的非平凡扩展来说,都是合适的形态(例如:对每个事件进行 enrich + 在 drain 时产生副作用 + 在尾部采样时做 keep 决策,并且都读取同一份共享状态)。

当扩展只做一件事时,优先使用单一用途的 enricherPlugin() / drainPlugin() 包装器。只有当多个钩子共享状态时,才使用 definePlugin

构建一个多钩子的 evlog 插件

最小示例

import { definePlugin } from 'evlog'

export const tenantPlugin = definePlugin({
  name: 'tenant',
  onRequestStart({ logger, headers }) {
    const tenantId = headers?.['x-tenant-id']
    if (tenantId) logger.set({ tenant: { id: tenantId } })
  },
  enrich({ event }) {
    event.region = process.env.REGION
  },
})

在你引导 evlog 的地方注册该插件。具体形态取决于你的运行时:

import { initLogger } from 'evlog'
import { tenantPlugin } from './plugins/tenant'

initLogger({ plugins: [tenantPlugin] })

钩子

Hook何时用途
setup(ctx)注册时只执行一次读取 env,建立共享状态
onRequestStart(ctx)每个请求,在任何处理程序运行之前将 header 中的值提取到 logger
enrich(ctx)每个事件,在 drain 之前添加派生字段(地理位置、部署 ID……)
keep(ctx)尾部采样决策基于结果强制保留(status >= 400duration > 500,……)
drain(ctx)每个已发出的事件副作用:告警、镜像到队列等
onRequestFinish(ctx)响应后每请求后的后处理
onClientLog(ctx)浏览器提交的事件命中摄取端点时观察 / 拒绝客户端流量
extendLogger(logger)每个请求添加自定义方法(例如 logger.audit.refund()

每个钩子都是可选的。一个插件可以实现任意子集。完整类型位于 packages/evlog/src/shared/plugin.ts

一个多钩子示例

当多个职责共享状态时,插件就会大放异彩。这里,单个 request-metrics 插件通过 setuponRequestStartdrain 跟踪每个请求的时序:

import { definePlugin } from 'evlog/toolkit'

export const requestMetricsPlugin = definePlugin({
  name: 'request-metrics',

  setup({ env }) {
    statsd.init({ service: env.service })
  },

  enrich({ event }) {
    event.tier = event.duration && event.duration > 1000 ? 'slow' : 'fast'
  },

  drain({ event }) {
    statsd.timing('http.request', event.duration as number, { path: event.path as string })
  },

  onRequestStart({ logger, request }) {
    logger.set({ trace: { startedAt: Date.now() } })
  },

  onRequestFinish({ event, durationMs }) {
    if (event && (event.level === 'error' || durationMs > 5000)) {
      // 告警 / 转发 / 等等。
    }
  },
})

便捷插件

对于单钩子扩展,工具包提供了 drainPlugin()enricherPlugin() 包装器:

import { drainPlugin, enricherPlugin } from 'evlog/toolkit'

const drainOnly = drainPlugin('axiom', createAxiomDrain())
const enricherOnly = enricherPlugin('user-agent', createUserAgentEnricher())

当意图很明显时,这些写法等价于 definePlugin({ name, drain | enrich }) 的形式,但可读性更强。

常见陷阱

  • 不要在钩子里抛出错误。 插件运行器会捕获错误并记录插件名称,但从 enrich 抛出的错误不会将事件向下游传递。请让钩子具备防御性。
  • drain 会对每个事件运行——而不只是每个请求。如果你只关心每请求生命周期,请改用 onRequestFinish
  • extendLogger 会修改 logger 对象——请在 .d.ts 中为 RequestLogger 做增强,这样 useLogger(event) 就能在 TypeScript 中暴露新方法。参见 typed fields
  • 插件会按 name 去重。使用相同的 name 重新注册会替换之前的版本(最后一次注册生效)。

下一步