翻查日志并不是可观测性
那只是寄希望于运气.

一个现代化的 TypeScript 日志记录器,为你交付的一切而构建。简单日志、宽事件和结构化错误——一个 API,覆盖所有上下文。

request logs

Used by

设置上下文。
获取答案.

使用 log.set 累积上下文,使用 why 和 fix 抛出结构化错误,并在类型化目录中分组重复错误。一个宽事件即可捕获一切,无论请求成功还是失败。

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

通过组合实现
符合合规要求.

一流的“谁在何时做了什么”轨迹,作为宽事件之上的一层薄封装。一个 enricher、一个 drain 包装器、一个 helper。防篡改哈希链、被拒绝的操作、可感知红action的差异、用于安全重试的幂等键,以及用于重构安全告警的类型化操作目录——全部来自主入口点,没有并行管道。

log.audit() auditOnly() signed() auditDiff() mockAudit()
审计日志指南

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.

audit.jsonl
hash-chain
log.audit({
  action: 'invoice.refund',
  actor: { type: 'user', id: user.id },
  target: { type: 'invoice', id: 'inv_889' },
  outcome: 'success',
  reason: 'Customer requested refund',
})
INFOPOST/api/refund(82ms)
audit.action: "invoice.refund"
audit.actor: { type: "user", id: "u_42" }
audit.target: { type: "invoice", id: "inv_889" }
audit.outcome: "success"
audit.context: { requestId, traceId, ip, userAgent }
audit.idempotencyKey: "8f2c…"
audit.prevHash: "a1b2…"
audit.hash: "c3d4…"

使 AI 调用
可观测.

你的 AI 端点如同黑盒。你无从知晓每个请求消耗了多少 token,模型调用了哪些工具,或是流式传输的速度如何。只需一行代码包装你的模型,每一次调用都会被捕获到宽事件中。成本估算、工具执行计时、流式性能、缓存命中、推理 token 以及多步代理分解。

Token tracking Tool calls Streaming metrics Cost estimation Tool timing
AI SDK 集成

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.

wide event
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)],
  },
})
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.tools: [{name: "searchWeb", durationMs: 150, ...}]
ai.estimatedCost: 0.022
ai.msToFirstChunk: 234
ai.tokensPerSecond: 180

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

零依赖,gzip 后约 6 kB,每个请求约 3µs。与 pino、consola 和 winston 进行了基准测试。在宽事件模式下比 pino 快 7.7 倍(1 个关联事件 vs 4 条单独日志行),在其他所有路径上也具有竞争力。

Zero-alloc hot path CodSpeed CI Open source benchmarks
基准测试结果
benchmark
evlog
1.6M
consola
1.0M1.5x slower
pino
465K3.3x slower
winston
164K9.5x slower

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

evlog
1.7M
pino
845K2.0x slower
winston
430K4.0x slower
consola
280K6.1x slower

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

evlog
16.9M
pino
7.5M2.2x slower
winston
5.4M3.1x slower
consola
310K54.4x slower

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

0deps/~6 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 分钟即可完成设置。未来的你会感谢现在的你。