翻查日志并不是可观测性
那只是寄希望于运气.
一个现代化的 TypeScript 日志记录器,为你交付的一切而构建。简单日志、宽事件和结构化错误——一个 API,覆盖所有上下文。
Used by
简单的 API
设置上下文。
获取答案.
使用 log.set 累积上下文,使用 why 和 fix 抛出结构化错误,并在类型化目录中分组重复错误。一个宽事件即可捕获一切,无论请求成功还是失败。
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: user.id, plan: user.plan } })
log.set({ cart: { items: 3, total: 9999 } })
if (!charge.success) {
throw createError({
status: 402,
why: 'Card declined by issuer',
fix: 'Try a different card',
})
}
return { orderId: charge.id }
})✓ One log with full context
✓ Actionable error with context
代理就绪
为代理而构建.
结构化字段、可机读的上下文和可操作元数据,为 AI 代理提供诊断和解决问题所需的一切。启用文件系统排水以将 NDJSON 日志本地写入,并让代理直接读取它们。
Card declined by issuer — insufficient funds
Pro plan user (#1842) blocked on payment
Prompt for alternate payment method
stripe.com/docs/declines/codes
✓ Auto-created issue PAY-4521
Non-blocking
Pipeline runs in the background. Your response ships immediately.
Guaranteed delivery
Exponential backoff with jitter ensures logs reach every destination.
Bring your own drain
Write a simple function to send logs anywhere.
import { createDrainPipeline } from 'evlog/pipeline'
import { createAxiomDrain } from 'evlog/axiom'
import { createSentryDrain } from 'evlog/sentry'
const pipeline = createDrainPipeline({
drains: [
createAxiomDrain(),
createSentryDrain(),
],
batchSize: 50,
flushInterval: 5000,
})客户端日志
查看完整画面.
捕获浏览器事件并将其排水到服务器。自动批量、重试和页面感知的刷新,以及与服务器相同的客户端到服务器管道。
Automatic batching
Events are batched by size and time interval, reducing network overhead.
Page-aware delivery
Switches to sendBeacon when the page is hidden. No event left behind.
Server-side validation
Origin check, payload sanitization, and source tagging on every ingest.
import { createHttpLogDrain } from 'evlog/http'
const drain = createHttpLogDrain({
drain: {
endpoint: '/api/_evlog/ingest',
},
pipeline: {
batch: { size: 25, intervalMs: 2000 },
retry: { maxAttempts: 2 },
},
})
initLogger({ drain })BATCH · FLUSH
POST · BEACON
采样
保留重要内容.
两级过滤:头部采样按级别丢弃噪声,尾部采样挽救关键事件。永远不会错过错误、慢速请求或关键路径。
initLogger({
sampling: {
// Head: per-level rates
rates: {
info: 10, // keep 10%
warn: 50, // keep 50%
error: 100, // always
},
// Tail: force keep if match
keep: [
{ status: 400 },
{ duration: 1000 },
{ path: '/api/critical/**' },
]
}
})5 kept·3 dropped· noise reduced without data loss
审计日志
通过组合实现
符合合规要求.
一流的“谁在何时做了什么”轨迹,作为宽事件之上的一层薄封装。一个 enricher、一个 drain 包装器、一个 helper。防篡改哈希链、被拒绝的操作、可感知红action的差异、用于安全重试的幂等键,以及用于重构安全告警的类型化操作目录——全部来自主入口点,没有并行管道。
Reserved schema
Typed action, actor, target, outcome, changes, causation. No magic strings.
Tamper-evident
HMAC signatures or hash-chain integrity composable on any drain.
Safe retries
Deterministic idempotency keys auto-derived per audit event.
Compose, do not replace
Reuses your drains, enrichers, redact, sampling. No parallel pipeline.
log.audit({
action: 'invoice.refund',
actor: { type: 'user', id: user.id },
target: { type: 'invoice', id: 'inv_889' },
outcome: 'success',
reason: 'Customer requested refund',
})AI 可观测性
使 AI 调用
可观测.
你的 AI 端点如同黑盒。你无从知晓每个请求消耗了多少 token,模型调用了哪些工具,或是流式传输的速度如何。只需一行代码包装你的模型,每一次调用都会被捕获到宽事件中。成本估算、工具执行计时、流式性能、缓存命中、推理 token 以及多步代理分解。
Zero boilerplate
Wrap the model, done. No manual token tracking needed.
Works with everything
generateText, streamText, ToolLoopAgent, embed, multi-step agents.
Cost and performance
Token usage, cache hits, cost estimation, time to first chunk, tokens per second.
Telemetry integration
Per-tool execution timing, success/failure tracking, and total generation wall time.
const ai = createAILogger(log, {
cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },
})
const result = streamText({
model: ai.wrap('anthropic/claude-sonnet-4.6'),
messages,
experimental_telemetry: {
isEnabled: true,
integrations: [createEvlogIntegration(ai)],
},
})性能
添加日志记录,:br 而不是开销.
零依赖,gzip 后约 6 kB,每个请求约 3µs。与 pino、consola 和 winston 进行了基准测试。在宽事件模式下比 pino 快 7.7 倍(1 个关联事件 vs 4 条单独日志行),在其他所有路径上也具有竞争力。
ops/sec · higher is better · silent mode (no I/O)
ops/sec · higher is better · silent mode (no I/O)
ops/sec · higher is better · silent mode (no I/O)
1 event, not N log lines
Accumulate context, emit once. 75% less data downstream.
In-place mutations
No object spreads, no copies. Direct recursive merge.
Lazy allocation
Timestamps, sampling context — created only when needed.
No serialization until drain
Plain objects throughout. JSON.stringify runs once at the end.
Zero dependencies
No transitive deps. Nothing to audit, nothing to break.
Total overhead per request
create + 3x set + emit + sampling + enrichers
~3µs
0.003ms
export default defineEventHandler(async (event) => {
const log = useLogger(event)
const { cartId } = await readBody(event)
const cart = await db.findCart(cartId)
log.set({ cart: { items: cart.items.length, total: cart.total } })
const charge = await stripe.charge(cart.total)
log.set({ stripe: { chargeId: charge.id } })
if (!charge.success) {
throw createError({
status: 402,
message: '支付失败',
why: charge.decline_reason,
fix: '尝试其他支付方式',
})
}
return { orderId: charge.id }
})