适配器

HTTP 排水

与框架无关的 HTTP 日志传输,用于通过 fetch 或 sendBeacon 将客户端日志发送到服务器。不依赖任何供应商 SDK 或框架耦合。在浏览器或任何支持 fetch 的环境中工作。使用 `evlog/http` 入口点。

大多数可观测性工具专注于服务端日志。HTTP 排水提供了一种与框架无关的方式,通过浏览器将结构化日志发送到任意 HTTP 端点,无需任何供应商 SDK 或框架耦合。

evlog/browser 导入路径已弃用,并重新导出与 evlog/http 相同的 API。在下一个版本中将被移除。新代码建议优先使用 evlog/http

快速开始

app.ts
import { initLogger, log } from 'evlog'
import { createHttpLogDrain } from 'evlog/http'

const drain = createHttpLogDrain({
  drain: { endpoint: 'https://logs.example.com/v1/ingest' },
})
initLogger({ drain })

log.info({ action: 'page_view', path: location.pathname })

工作原理

  1. log.info() / log.warn() / log.error() 将事件推入内存缓冲区
  2. 事件按大小(默认 25)或时间间隔(默认 2 秒)进行批处理
  3. 批处理通过 fetch 发送,并设置 keepalive: true,确保请求在页面导航时仍能继续
  4. 页面变为隐藏(切换标签页或导航)时,缓存的事件会通过 navigator.sendBeacon 刷新作为后备
  5. 您的服务端端点接收一个 DrainContext[] JSON 数组,并按您希望的方式处理

两层 API

createHttpLogDrain(options)

高阶、预组合:创建带有批处理、重试和 visibilitychange 自动刷新的管道。直接返回一个 PipelineDrainFn<DrainContext>,可与 initLogger({ drain }) 配合使用。

app.ts
import { initLogger, log } from 'evlog'
import { createHttpLogDrain } from 'evlog/http'

const drain = createHttpLogDrain({
  drain: { endpoint: 'https://logs.example.com/v1/ingest' },
  pipeline: { batch: { size: 50, intervalMs: 5000 } },
})

initLogger({ drain })
log.info({ action: 'click', target: 'buy-button' })

createHttpDrain(config)

底层传输函数。当您希望完全控制管道配置时使用:

app.ts
import { createHttpDrain } from 'evlog/http'
import { createDrainPipeline } from 'evlog/pipeline'
import type { DrainContext } from 'evlog'

const transport = createHttpDrain({
  endpoint: 'https://logs.example.com/v1/ingest',
})
const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 100, intervalMs: 10000 },
  retry: { maxAttempts: 5 },
})

const drain = pipeline(transport)

配置参考

HttpDrainConfig

选项默认值说明
endpoint-(必需) 服务端摄入端点的完整 URL
headers-每个 fetch 请求发送的自定义头(例如 AuthorizationX-API-Key
timeout5000请求超时时间(毫秒)
useBeacontrue页面隐藏时使用 sendBeacon
credentials'same-origin'获取凭证模式('omit''same-origin''include')。对跨域端点设置为 'include'

HttpLogDrainOptions

选项默认值说明
drain-(必需) HttpDrainConfig 对象
pipeline{ batch: { size: 25, intervalMs: 2000 }, retry: { maxAttempts: 2 } }管道配置覆盖项
autoFlushtrue自动注册 visibilitychange 刷新监听器

sendBeacon 后备机制

当启用 useBeacon(默认)且页面变为隐藏时,排水机制会自动从 fetch 切换到 navigator.sendBeacon。这确保了即使用户关闭标签页或导航离开,日志也能送达,防止页面退出时数据丢失。

sendBeacon 有浏览器强制的负载限制(约 64 KB)。如果负载超过此限制,排水机制会抛出错误。请保持合理的批处理大小(默认的 25 在限制范围内)。

认证

通过传递自定义头信息来保护您的摄入端点:

app.ts
const drain = createHttpLogDrain({
  drain: {
    endpoint: 'https://logs.example.com/v1/ingest',
    headers: {
      'Authorization': 'Bearer ' + token,
    },
  },
})
headers 仅应用于 fetch 请求。当页面隐藏并使用 sendBeacon 时,该 API 不支持自定义头信息。如果您的端点需要认证,请考虑通过会话 Cookie 验证(对跨域端点设置 credentials: 'include',默认为 'same-origin')或禁用 sendBeaconuseBeacon: false)。

服务端端点

您的服务器需要一个接受 DrainContext[] JSON 体的 POST 端点。以下是常见框架的示例:

Express

server.ts
app.post('/v1/ingest', express.json(), (req, res) => {
  for (const entry of req.body) {
    console.log('[BROWSER]', JSON.stringify(entry))
  }
  res.sendStatus(204)
})

Hono

server.ts
app.post('/v1/ingest', async (c) => {
  const body = await c.req.json()
  for (const entry of body) {
    console.log('[BROWSER]', JSON.stringify(entry))
  }
  return c.body(null, 204)
})

完全控制

createHttpDraincreateDrainPipeline 结合使用以获得最大灵活性:

app.ts
import { initLogger, log } from 'evlog'
import type { DrainContext } from 'evlog'
import { createHttpDrain } from 'evlog/http'
import { createDrainPipeline } from 'evlog/pipeline'

const pipeline = createDrainPipeline<DrainContext>({
  batch: { size: 100, intervalMs: 10000 },
  retry: { maxAttempts: 5, backoff: 'exponential' },
  maxBufferSize: 500,
  onDropped: (events) => {
    console.warn(`Dropped ${events.length} client events`)
  },
})

const drain = pipeline(createHttpDrain({
  endpoint: 'https://logs.example.com/v1/ingest',
  timeout: 3000,
}))

initLogger({ drain })

log.info({ action: 'app_init' })

// 在页面卸载时刷新
window.addEventListener('beforeunload', () => drain.flush())
请参阅完整的 浏览器示例,了解一个可工作的 Hono 服务器 + 浏览器页面,展示从端到端的完整流程。
请参阅 Next.js 指南 了解可工作的实现。

下一步