适配器
大多数可观测性工具专注于服务端日志。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 })
工作原理
log.info()/log.warn()/log.error()将事件推入内存缓冲区- 事件按大小(默认 25)或时间间隔(默认 2 秒)进行批处理
- 批处理通过
fetch发送,并设置keepalive: true,确保请求在页面导航时仍能继续 - 页面变为隐藏(切换标签页或导航)时,缓存的事件会通过
navigator.sendBeacon刷新作为后备 - 您的服务端端点接收一个
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 请求发送的自定义头(例如 Authorization、X-API-Key) |
timeout | 5000 | 请求超时时间(毫秒) |
useBeacon | true | 页面隐藏时使用 sendBeacon |
credentials | 'same-origin' | 获取凭证模式('omit'、'same-origin'、'include')。对跨域端点设置为 'include' |
HttpLogDrainOptions
| 选项 | 默认值 | 说明 |
|---|---|---|
drain | - | (必需) HttpDrainConfig 对象 |
pipeline | { batch: { size: 25, intervalMs: 2000 }, retry: { maxAttempts: 2 } } | 管道配置覆盖项 |
autoFlush | true | 自动注册 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')或禁用 sendBeacon(useBeacon: 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)
})
完全控制
将 createHttpDrain 与 createDrainPipeline 结合使用以获得最大灵活性:
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 指南 了解可工作的实现。