翻查日志并不是可观测性
那只是寄希望于运气.
一个适用于你交付的一切的现代化 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
可观测性评分
在源代码中
找到日志记录缺口.
运行 evlog map,在受支持的 Nuxt、Nitro、Next.js App Router、TanStack Start 和 Hono 入口点中查找缺失的 evlog 模式。评分会汇总静态检查并建议修复方案。它不会运行你的处理程序,也不会验证它们生成的日志。
score
0/100
at risk
fix first
ANY/api/auth/:all*A — touches auth and logs nothing
server/api/auth/[...all].ts:1
POST/api/checkout$ — moves money with no audit trail
server/api/checkout.post.ts:14
POST/api/orders/:id/refund$ — throws plain errors with no why or fix
server/api/orders/[id]/refund.post.ts:22
nothing runs · nothing is instrumented · no traffic needed
evlog map --json
29 entry points scanned · 8 emit nothing at all
Three targets, with line numbers
Every verdict carries the file, the line, and the rule it broke
Fix, in the shape the report suggests
useLogger + log.set on two handlers, log.audit on the third
Run it again
Same code in, same verdict out — the score moved because the app did
evlog map --min-score 80
the score is the review comment nobody has to write
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
审计日志
通过组合实现
符合合规要求.
一流的“谁做了什么”追踪记录,作为宽事件之上的薄层。一个增强器、一个排水包装器、一个辅助函数。防篡改哈希链、被拒绝的操作、感知脱敏的差异、用于安全重试的幂等键,以及支持安全重构告警的类型化操作目录,全部来自主入口点,无需并行管道。
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,
telemetry: {
integrations: [createEvlogIntegration(ai)],
},
})性能
添加日志记录,:br 而不是开销.
核心包没有运行时依赖。累积上下文,并为每个操作发出一个事件。在估算生产成本之前,检查基准测试工作负载,并测量你的负载、输出和排水配置。
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
框架
您的堆栈已覆盖.
为 Nuxt、Next.js、SvelteKit、Hono、Express、Fastify 以及其他已记录堆栈提供集成。每份指南都介绍其设置方式和请求日志记录器的访问方式。对于 Vite 项目,添加 evlog/vite 即可实现自动初始化、调试代码移除和源代码位置记录。
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 }
})
常见问题
安装之前.
evlog map 会读取你的源代码,根据每个入口点在发生故障时能够提供给你的上下文进行评分,然后列出优先修复的入口点。它不需要流量、代理或部署,因此你可以在下一次事故发生之前,而不是事故处理中,获得这份清单。无论是上线第一天还是第三年,需要做的工作都是同一份清单。请参阅如何将它添加到已经在生产环境中运行的应用。log.set() 会向正在处理的事件添加上下文,createError() 则会为错误附加 why 和 fix,而不是只有一个裸堆栈。请参阅第一天需要使用其中多少内容,如果你想先了解其形态再看代码,也可以参阅evlog 是什么。::