停止在混乱中搜索
.

宽事件和结构化错误,用于 TypeScript。每条请求一个日志,完整上下文,解释原因和修复方法的错误。

request logs

设置上下文。
获取答案.

使用 log.set 累积上下文,使用 why 抛出结构化错误和修复方法。一个宽事件捕获一切,无论请求成功或失败。

Wide events Root cause Fix suggestion
快速入门指南
checkout.post.ts
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 }
})
output
INFOPOST/api/checkout(234ms)
user: { id: 1842, plan: "pro" }
cart: { items: 3, total: 9999 }
status: 200
requestId: "req_8f2k..."

One log with full context

ERRORPOST/api/checkout402
message: "Payment failed"
why: "Card declined by issuer"
fix: "Try a different card"
user: { id: 1842, plan: "pro" }

Actionable error with context

为代理而构建.

结构化字段、可机读的上下文和可操作元数据,为 AI 代理提供诊断和解决问题所需的一切。启用文件系统排水以将 NDJSON 日志本地写入,并让代理直接读取它们。

Structured context Machine-parseable Local NDJSON files
代理技能设置
outputERROR
ERRORPOST/api/payment402
message: "Payment processing failed"
why: "Card issuer declined: insufficient funds"
fix: "Retry with a different payment method"
user: { id: 1842, plan: "pro" }
links: ["stripe.com/docs/declines"]
AI Agent analyzing
Reading structured error context...
Root cause

Card declined by issuer — insufficient funds

User impact

Pro plan user (#1842) blocked on payment

Suggested fix

Prompt for alternate payment method

Documentation

stripe.com/docs/declines/codes

Auto-created issue PAY-4521

发送到任何地方.

批量写入、自动重试和扇出到多个目标。日志通过永不阻塞响应的管道流动。

Batching Retry & backoff Fan-out
探索适配器

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.

evlog-drain.ts
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,
})
evlog
BATCH · RETRY · FANOUT
Axiom
OTLP
Sentry
PostHog
Better Stack
+ File System·Custom drains·and more

查看完整画面.

捕获浏览器事件并将其排水到服务器。自动批量、重试和页面感知的刷新,以及与服务器相同的客户端到服务器管道。

Auto-batch sendBeacon Origin validation
客户端日志指南

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.

http-drain.ts
import { createHttpLogDrain } from 'evlog/http'

const drain = createHttpLogDrain({
  drain: {
    endpoint: '/api/_evlog/ingest',
  },
  pipeline: {
    batch: { size: 25, intervalMs: 2000 },
    retry: { maxAttempts: 2 },
  },
})

initLogger({ drain })
BrowserEVENTS

BATCH · FLUSH

PipelineRETRY · BACKOFF

POST · BEACON

ServerVALIDATE · DRAIN
auto-flush on page visibility change

保留重要内容.

两级过滤:头部采样按级别丢弃噪声,尾部采样挽救关键事件。永远不会错过错误、慢速请求或关键路径。

Head sampling Tail sampling Per-level rates
采样指南
evlog.config.ts
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/**' },
    ]
  }
})
log stream
HEADTAIL
INFO/api/users45ms
INFO/api/orders120ms
INFO/api/health12ms
WARN/api/payment340ms
INFO/api/search1240ms
ERROR/api/checkout450ms
INFO/api/feed32ms
INFO/api/critical/alert55ms

5 kept·3 dropped· noise reduced without data loss

使 AI 调用
可观测.

您的 AI 端点是黑盒。您不知道每个请求消耗多少令牌,模型调用了哪些工具,或者流的速度有多快。用一行代码包装您的模型,每个调用都将被捕获到宽事件中。成本跟踪、工具可见性、流式性能、缓存命中、推理令牌。

Token tracking Tool calls Streaming metrics
AI SDK 集成

Zero boilerplate

Wrap the model, done. No manual token tracking needed.

Works with everything

generateText, streamText, ToolLoopAgent, generateObject.

Cost and performance

Token usage, cache hits, time to first chunk, tokens per second.

wide event
const ai = createAILogger(log)

const result = streamText({
  model: ai.wrap('anthropic/claude-sonnet-4.6'),
  messages,
})
INFOPOST/api/chat(4.5s)
status: 200
requestId: "req_8f2k..."
Which model? How many tokens? What did it cost?
INFOPOST/api/chat(4.5s)
status: 200
ai.model: "claude-sonnet-4.6"
ai.inputTokens: 3312
ai.outputTokens: 814
ai.reasoningTokens: 225
ai.toolCalls: ["searchWeb", "queryDB"]
ai.steps: 3
ai.msToFirstChunk: 234
ai.tokensPerSecond: 180

添加日志记录,:br 而不是开销.

零依赖,5.2 kB gzip,约每个请求 3µs。在 pino、consola 和 winston 之间进行了基准测试。在宽事件场景中比 pino 快 8 倍,同时产生更丰富、更有用的输出。

Zero-alloc hot path CodSpeed CI Open source benchmarks
基准测试结果
benchmark
evlog
1.8M
consola
1.0M1.7x slower
pino
508K3.4x slower
winston
203K8.6x slower

ops/sec · higher is better · silent mode (no I/O)

evlog
1.9M
pino
871K2.1x slower
winston
569K3.3x slower
consola
272K6.8x slower

ops/sec · higher is better · silent mode (no I/O)

evlog
20.5M
pino
7.4M2.8x slower
winston
5.4M3.8x slower
consola
299K68.6x slower

ops/sec · higher is better · silent mode (no I/O)

0deps/5.2 kBgzip/12frameworks/tree-shakeable
why it's fast

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

您的堆栈已覆盖.

每个主流框架的本地集成。一次导入,零配置,到处相同的 API。Vite 插件添加了自动初始化、调试剥离和源位置,适用于任何基于 Vite 的堆栈。

框架集成
Vite
server/api/checkout.post.ts
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 }
})

更好的日志
今晚就能交付 今晚.

宽事件、结构化错误,简单易设置。在 10 分钟内设置 evlog。未来的你会感谢现在的你。