---
title: "evlog 与 LogLayer 对比：API 层还是日志器"
description: "对比 LogLayer 的 transport、上下文和宽事件 mixin 与 evlog 请求事件，判断是否继续通过 LogLayer 使用现有日志器。"
canonical_url: "https://evlog.zhcndoc.com/compare/loglayer"
---
# evlog 与 LogLayer 对比：API 层还是日志器

> 对比 LogLayer 的 transport、上下文和宽事件 mixin 与 evlog 请求事件，判断是否继续通过 LogLayer 使用现有日志器。

你是想保留现有日志器，还是替换它？LogLayer 是一个 API 层：它为代码提供统一的流式 API，并通过 [transport](https://loglayer.dev/transports/) 将每条记录转发到 pino、winston、LogTape、consola 或 Datadog 等服务，因此底层库可以随时替换。evlog 本身就是日志器，会构建事件并通过 drain 适配器交付。

## 一览

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      LogLayer
    </th>
    
    <th>
      evlog
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      角色
    </td>
    
    <td>
      位于日志库或服务前的 API，每个实例可配置一个或多个 transport
    </td>
    
    <td>
      日志器本身，提供自己的输出和 drain 适配器
    </td>
  </tr>
  
  <tr>
    <td>
      结构化数据
    </td>
    
    <td>
      <code>
        withContext()
      </code>
      
       会跨记录保留上下文，<code>
        withMetadata()
      </code>
      
       和 <code>
        withError()
      </code>
      
       作用于单条记录
    </td>
    
    <td>
      <code>
        set()
      </code>
      
       会在操作 logger 上持续累积字段，直到调用 <code>
        emit()
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      宽事件
    </td>
    
    <td>
      使用 <code>
        @loglayer/mixin-wide-events
      </code>
      
      ：先调用 <code>
        withWideEvents()
      </code>
      
      ，再调用 <code>
        emitWideEvent()
      </code>
      
      ，并自行创建 <code>
        AsyncLocalStorage
      </code>
    </td>
    
    <td>
      内置支持：请求 logger 会累积字段，<a href="https://evlog.zhcndoc.com/integrate/frameworks/overview">
        框架集成
      </a>
      
      会在每个请求结束时发出事件
    </td>
  </tr>
  
  <tr>
    <td>
      框架集成
    </td>
    
    <td>
      Express、Fastify、Hono、Koa 和 Elysia：为请求创建作用域子 logger，并输出类似 pino-http 的 <code>
        incoming request
      </code>
      
       和 <code>
        request completed
      </code>
      
       记录
    </td>
    
    <td>
      每个请求一个事件，可通过 <code>
        useLogger()
      </code>
      
       和框架自身的 accessor 访问
    </td>
  </tr>
  
  <tr>
    <td>
      路由
    </td>
    
    <td>
      通过分组将记录发送到选定的 transport，并为每组设置级别
    </td>
    
    <td>
      不支持按子系统路由
    </td>
  </tr>
  
  <tr>
    <td>
      脱敏
    </td>
    
    <td>
      使用 <code>
        @loglayer/plugin-redaction
      </code>
      
      ：通过 fast-redact 配置路径
    </td>
    
    <td>
      调用 <code>
        initLogger()
      </code>
      
       后在生产环境中默认启用：遮蔽以 <code>
        password
      </code>
      
      、<code>
        passwd
      </code>
      
      、<code>
        secret
      </code>
      
       或 <code>
        token
      </code>
      
       结尾的键、邮箱和信用卡号等内置模式，以及已配置的路径
    </td>
  </tr>
  
  <tr>
    <td>
      采样
    </td>
    
    <td>
      采样插件，以及宽事件 mixin 上的速率、按级别、<code>
        shouldEmit
      </code>
      
       和 <code>
        forceKeep
      </code>
      
       选项
    </td>
    
    <td>
      按级别设置采样率，并通过 <code>
        keep
      </code>
      
       条件进行尾部采样
    </td>
  </tr>
  
  <tr>
    <td>
      测试
    </td>
    
    <td>
      <code>
        MockLogLayer
      </code>
      
      ，其方法均不执行操作的 logger
    </td>
    
    <td>
      捕获已发出事件的 memory drain
    </td>
  </tr>
</tbody>
</table>

资料来源：[LogLayer 介绍](https://loglayer.dev/introduction)、[宽事件 mixin](https://loglayer.dev/mixins/wide-events)、[脱敏插件](https://loglayer.dev/plugins/redaction)、[Express 集成](https://loglayer.dev/integrations/express)和[单元测试](https://loglayer.dev/logging-api/unit-testing)页面，查阅版本为 loglayer 9.4.0。

## 比较由谁发出宽事件

两个库都会累积字段，并为每次操作发出一条记录。差别在于由谁接好这个生命周期。LogLayer 的集成会写入两条请求记录，但不会发出宽事件。其[宽事件指南](https://loglayer.dev/guides/wide-events)要求应用编写 Express 中间件来运行 async context，并在响应结束时调用 `emitWideEvent()`。evlog 的集成会自行创建请求 logger，并在请求结束时发出事件。

在 `asyncContext.run()` 之外，LogLayer 不会保留任何内容：使用 1.4.0 版 mixin 时，`emitWideEvent()` 只会写入包含 message 的记录，也不会打印警告。请确保每个请求都在 context 中运行。

下面的示例会记录一次模拟结账及其结果。安装对应 package 后，使用 Node.js 24 运行文件。传入 `decline` 可测试错误路径。

LogLayer 会在 async context 中通过 mixin 累积字段：

```typescript [checkout-loglayer.ts]
import { AsyncLocalStorage } from 'node:async_hooks'
import { LogLayer, StructuredTransport, useLogLayerMixin } from 'loglayer'
import { createWideEventMixin } from '@loglayer/mixin-wide-events'

const asyncContext = new AsyncLocalStorage()
useLogLayerMixin(createWideEventMixin({ asyncContext }))
const log = new LogLayer({ transport: new StructuredTransport({ logger: console }) })

await asyncContext.run({}, async () => {
  log.withWideEvents({ orderId: 'order-123', amount: 2999 })
  try {
    if (process.argv.includes('decline')) throw new Error('Payment declined')
    log.withWideEvents({ outcome: 'paid' })
    log.emitWideEvent({ message: 'checkout' })
  } catch (error) {
    log.withWideEventError(error)
    log.withWideEvents({ outcome: 'declined' })
    log.emitWideEvent({ message: 'checkout', level: 'error' })
    process.exitCode = 1
  }
})
```

evlog 会在操作 logger 上累积字段，并在 `finally` 中发出事件：

```typescript [checkout-evlog.ts]
import { createLogger, initLogger } from 'evlog'

initLogger({ env: { service: 'checkout' }, pretty: false })
const log = createLogger({ orderId: 'order-123', amount: 2999 })
try {
  if (process.argv.includes('decline')) throw new Error('Payment declined')
  log.set({ outcome: 'paid' })
} catch (error) {
  log.error(error instanceof Error ? error : new Error(String(error)))
  log.set({ outcome: 'declined' })
  process.exitCode = 1
} finally {
  log.emit()
}
```

除非显式传入级别，否则 LogLayer 会以 `info` 级别发出记录，因此错误路径会传入 `level: 'error'`。调用 `log.error()` 后，evlog 会将事件级别提升为 `error`，并添加 `durationMs`。这些示例比较的是由谁负责发出事件，而不是输出格式或速度。

## 保留 LogLayer，还是规划迁移

如果你需要的正是 LogLayer 提供的能力，就继续使用它：通过统一 API 随时替换 pino 或 winston、使用分组将不同子系统路由到不同 transport，或将一条记录发送到多个目标。LogLayer 也可以放在你暂时不打算替换的 logger 前面。

If you migrate, plan these mappings before changing call sites:

<table>
<thead>
  <tr>
    <th>
      LogLayer
    </th>
    
    <th>
      evlog
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        withContext()
      </code>
    </td>
    
    <td>
      <code>
        createLogger()
      </code>
      
       的初始上下文，或请求 logger 上的 <code>
        set()
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      用 <code>
        withMetadata().info()
      </code>
      
       写入单条记录
    </td>
    
    <td>
      <a href="https://evlog.zhcndoc.com/learn/simple-logging">
        简单日志
      </a>
      
      中的 <code>
        log.info({ ... })
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      宽事件 mixin 和自定义中间件
    </td>
    
    <td>
      每个请求结束时发出事件的<a href="https://evlog.zhcndoc.com/integrate/frameworks/overview">
        框架集成
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      分组和每组级别
    </td>
    
    <td>
      没有直接对应项。请判断哪些记录仍需要单独路由
    </td>
  </tr>
  
  <tr>
    <td>
      脱敏插件路径
    </td>
    
    <td>
      <code>
        redact.paths
      </code>
      
      ，与生产环境默认脱敏规则共同使用
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        MockLogLayer
      </code>
    </td>
    
    <td>
      使用 <code>
        enabled: false
      </code>
      
       静默日志，或用 memory drain 断言事件
    </td>
  </tr>
</tbody>
</table>

## 测量将要部署的技术栈

LogLayer 调用的成本既包含 LogLayer 层，也包含 transport 后面的 logger，因此在 pino 上测量 LogLayer 并不等于只测量 LogLayer。[性能参考](https://evlog.zhcndoc.com/reference/performance)说明了 evlog 基准测试的边界。请使用计划部署的 transport、payload 和脱敏配置比较两者。

LogLayer 最常放在 pino 前面，相关对比见 [evlog 与 Pino 对比](https://evlog.zhcndoc.com/compare/pino)。其他选项见[日志器概览](https://evlog.zhcndoc.com/reference/vs-other-loggers)。

---

- [evlog 与 Pino 对比](https://evlog.zhcndoc.com/compare/pino)
- [evlog 与 LogTape 对比](https://evlog.zhcndoc.com/compare/logtape)
- [宽事件](https://evlog.zhcndoc.com/learn/wide-events)


## Sitemap

See the full [sitemap](https://evlog.zhcndoc.com/sitemap.md) for all pages.
