---
title: "简单日志"
description: "使用 evlog 记录单条结构化消息，使用与广泛事件相同的排水管道和脱敏功能，并调整现有日志记录器调用位置"
canonical_url: "https://evlog.zhcndoc.com/learn/simple-logging"
---
# 简单日志

> 使用 evlog 记录单条结构化消息，使用与广泛事件相同的排水管道和脱敏功能，并调整现有日志记录器调用位置

`log` API 是 evlog 的通用日志记录器。使用方式与 pino、consola 或 `console.log` 相同：每次调用都会通过与广泛事件相同的排水管道发出结构化事件。这两种模式可以共存；两者互不构成升级关系。

<callout color="neutral" icon="i-lucide-globe">

在 CLIs、库、任务和边缘环境中寻找相同的 API？请从 [Standalone TypeScript](https://evlog.zhcndoc.com/integrate/frameworks/standalone) 和 [Cloudflare Workers](https://evlog.zhcndoc.com/integrate/frameworks/cloudflare-workers) 开始。

</callout>

<callout color="info" icon="i-lucide-sparkles">

在 Nuxt 中，`log` 是 **自动导入** 的。不需要导入语句。

</callout>

## 设置

对于独立项目（非 Nuxt），请在启动时初始化一次：

```typescript [src/index.ts]
import { initLogger, log } from 'evlog'

initLogger({
  env: { service: 'my-app' },
})

log.info('app', '服务器已启动')
```

<callout color="info" icon="i-lucide-info">

如果未指定，`env.service` 默认值为 `'app'`。仅当你想使用自定义服务名称时才设置它。

</callout>

`log` 也可以完全不调用 `initLogger()`：每次调用都会使用默认值打印。这适合临时脚本，也是下面默认导入的行为。在生产环境中，这意味着没有脱敏和 drain，因此第一次发出的事件会记录一次性警告，指向 `initLogger({ drain })`。

## 两种调用方式

### 标签日志

传递一个标签和一条消息，以获得快速、可读的输出：

```typescript [src/index.ts]
import { log } from 'evlog'

log.info('auth', '用户已登录')
log.warn('cache', '键 user:42 的缓存未命中')
log.error('payment', 'Stripe webhook 失败')
log.debug('router', '匹配到路由 /api/checkout')
```

```bash [输出（美观格式）]
10:23:45.612 [auth] 用户已登录
10:23:45.613 [cache] 键 user:42 的缓存未命中
10:23:45.614 ERROR [payment] Stripe webhook 失败
10:23:45.615 [router] 匹配到路由 /api/checkout
```

### 在消息中插值

当消息包含 `%s`、`%d` 或 `%j` 且后面有额外参数时，evlog 会在发出前对其进行格式化。来自 pino 或 consola 的调用点可以保留 printf 风格，无需改成模板字符串：

```typescript [src/index.ts]
log.info('auth', 'User %s logged in from %s', 'alice', '10.0.0.7')
log.info('payment', 'Retrying charge for order %d', 4091)
log.info('debug', 'Payload %j', { orderId: 4091, total: 9999 })
```

```bash [输出（美化后）]
10:23:45.612 [auth] User alice logged in from 10.0.0.7
10:23:45.613 [payment] Retrying charge for order 4091
10:23:45.614 [debug] Payload {"orderId":4091,"total":9999}
```

不带标签时也可以使用相同形式：当第一个字符串包含格式说明符时，evlog 会将其视为消息，并将此次调用视为无标签调用。

```typescript [src/index.ts]
log.info('user %s logged in', 'alice') // emits [log] user alice logged in
```

<table>
<thead>
  <tr>
    <th>
      格式说明符
    </th>
    
    <th>
      参数格式
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        %s
      </code>
    </td>
    
    <td>
      字符串；对象和数组会作为 JSON 处理
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        %d
      </code>
    </td>
    
    <td>
      数字，使用 <code>
        Number()
      </code>
      
       强制转换；bigint 会显示为 <code>
        10n
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        %j
      </code>
    </td>
    
    <td>
      JSON；对于无法序列化的值回退为字符串
    </td>
  </tr>
</tbody>
</table>

只有存在额外参数时才会执行插值。包含 `%` 但没有参数的消息会原样记录；一旦传入参数，要表示字面量百分号请写成 `%%`。

<callout color="info" icon="i-lucide-info">

格式化只应用于消息字符串。结构化事件中的字段值永远不会被当作格式化输入。

</callout>

### 结构化事件

传递一个对象以生成丰富、可查询的事件，这些事件会通过排水管道传输：

```typescript [src/index.ts]
import { log } from 'evlog'

log.info({ action: 'user_login', userId: 42, method: 'oauth', provider: 'github' })
log.error({ action: 'sync_failed', source: 'postgres', target: 's3', error: 'connection_timeout' })
```

```bash [输出（美观格式）]
10:23:45.612 INFO [my-app]
  ├─ action: user_login
  ├─ userId: 42
  ├─ method: oauth
  └─ provider: github
```

<callout color="info" icon="i-lucide-info">

**标签日志** 针对控制台可读性进行了优化。**结构化事件**（对象形式）会生成完整的广泛事件，并通过排水管道传输到外部服务。

</callout>

## 选择级别及其代价

<table>
<thead>
  <tr>
    <th>
      级别
    </th>
    
    <th>
      方法
    </th>
    
    <th>
      使用场景
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        fatal
      </code>
    </td>
    
    <td>
      <code>
        log.fatal()
      </code>
    </td>
    
    <td>
      无法恢复的失败：请求或进程无法继续。即使采样会丢弃它，也始终保留
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        info
      </code>
    </td>
    
    <td>
      <code>
        log.info()
      </code>
    </td>
    
    <td>
      正常操作：启动、关闭、成功操作
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        warn
      </code>
    </td>
    
    <td>
      <code>
        log.warn()
      </code>
    </td>
    
    <td>
      可恢复但意外的情况：缓存未命中、重试、弃用
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        error
      </code>
    </td>
    
    <td>
      <code>
        log.error()
      </code>
    </td>
    
    <td>
      需要关注的失败：API 错误、超时、无效状态
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        debug
      </code>
    </td>
    
    <td>
      <code>
        log.debug()
      </code>
    </td>
    
    <td>
      仅开发环境的详细信息：SQL 查询、中间状态、路由
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        trace
      </code>
    </td>
    
    <td>
      <code>
        log.trace()
      </code>
    </td>
    
    <td>
      比 <code>
        debug
      </code>
      
       更细的详细信息。默认丢弃；使用 <code>
        sampling.rates.trace
      </code>
      
       选择加入
    </td>
  </tr>
</tbody>
</table>

<callout color="warning" icon="i-lucide-lightbulb">

可以使用 [Vite 插件](https://evlog.zhcndoc.com/reference/vite-plugin) 或 Nuxt 模块的 `strip` 选项，从生产构建中剥离 `log.debug()` 调用。

</callout>

## 值得复用的模式

### 应用程序生命周期

```typescript [src/index.ts]
import { log } from 'evlog'

log.info('app', 'Starting server on port 3000')
log.info({ action: 'db_connected', host: 'localhost', database: 'mydb', pool: 10 })
log.info('app', 'Ready to accept connections')
```

### 后台任务

```typescript [src/jobs/cleanup.ts]
import { log } from 'evlog'

log.info({ action: 'cron_started', job: 'cleanup', schedule: '0 */6 * * *' })
log.info({ action: 'cron_completed', job: 'cleanup', deleted: 42, duration: 1200 })
```

### 实用函数

```typescript [src/utils/webhook.ts]
import { log } from 'evlog'

function processWebhook(payload: WebhookPayload) {
  log.info({ action: 'webhook_received', type: payload.type, source: payload.source })

  if (!isValid(payload)) {
    log.warn({ action: 'webhook_invalid', type: payload.type, reason: 'missing_signature' })
    return
  }
}
```

## 将这些日志发送到排水管道

当使用对象形式时，事件会像广泛事件一样通过 [排水管道](https://evlog.zhcndoc.com/integrate/adapters/overview) 发送：

```typescript [src/index.ts]
import { initLogger, log } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

initLogger({
  env: { service: 'my-app' },
  drain: createAxiomDrain(),
})

log.info({ action: 'deploy', version: '1.2.3', region: 'us-east-1' })
```

## 从 console / pino / consola / winston 迁移

选择与你当前日志记录器对应的选项卡，查看 **迁移前** 的调用方式。下面的 **迁移后（evlog）** 代码片段无论你来自哪里都相同。

<code-group>

```typescript [pino]
import pino from 'pino'

const log = pino({ name: 'checkout' })

log.info({ event: 'checkout_started' })
log.info({ event: 'cart_loaded', items: 3, total: 9999 })
log.warn({ event: 'inventory_low', sku: 'SKU-42' })
log.error({ event: 'payment_failed', reason: 'card_declined' })
```

```typescript [winston]
import { createLogger, format, transports } from 'winston'

const log = createLogger({
  defaultMeta: { service: 'checkout' },
  format: format.json(),
  transports: [new transports.Console()],
})

log.info({ event: 'checkout_started' })
log.info({ event: 'cart_loaded', items: 3, total: 9999 })
log.warn({ event: 'inventory_low', sku: 'SKU-42' })
log.error({ event: 'payment_failed', reason: 'card_declined' })
```

```typescript [consola]
import { consola } from 'consola'

const log = consola.withTag('checkout')

log.info('开始结账')
log.info('购物车已加载', { items: 3, total: 9999 })
log.warn('库存不足', { sku: 'SKU-42' })
log.error('支付失败', { reason: 'card_declined' })
```

```typescript [console.log]
console.log('[checkout] 开始结账')
console.log('[checkout] 购物车已加载', { items: 3, total: 9999 })
console.warn('[checkout] 库存不足', { sku: 'SKU-42' })
console.error('[checkout] 支付失败', { reason: 'card_declined' })
```

</code-group>

这四种方式都会变成下面这样，无需配置格式化器、传输器或 peer-dep：

```typescript [迁移后（evlog）]
import { initLogger, log } from 'evlog'

initLogger({ env: { service: 'checkout' } })

log.info({ event: 'checkout_started' })
log.info({ event: 'cart_loaded', items: 3, total: 9999 })
log.warn({ event: 'inventory_low', sku: 'SKU-42' })
log.error({ event: 'payment_failed', reason: 'card_declined' })
```

`initLogger` 在启动时只需一行。排水管道、脱敏、采样、美观格式／JSON 切换和级别过滤默认都会完成配置：无需 `pino-pretty` peer dep，无需组装 winston 传输器，也无需设置 consola reporter。

如果你在评估 evlog 时希望获得类似 `import pino from 'pino'` 的即插即用体验，默认导出就是无需设置行的同一个 `log` API：

```typescript [src/index.ts]
import logger from 'evlog'

logger.info('checkout', 'Starting checkout')
logger.info({ event: 'checkout_started' })
```

每次调用都会立即打印。需要 drain、脱敏或宽事件时，添加 `initLogger` 行并继续调用相同的方法即可；默认导入和 `log` 是同一个对象，因此无需移动其他代码。

<callout color="neutral" icon="i-lucide-arrow-right">

想查看完整的并排对比（功能比较表、真实差距、逐项映射）？请参阅 [evlog vs pino, winston, consola](https://evlog.zhcndoc.com/reference/vs-other-loggers)。

</callout>

## 与广泛事件配对使用

`log` 和 `createLogger` 位于同一个日志记录器中。对于独立存在的事件（启动消息、临时警告、调试跟踪），使用 `log.*`；当你希望一个事件捕获整个操作时，使用 `createLogger`。它们共享全局排水管道、脱敏和类型，因此可以按调用选择。

```typescript [scripts/sync-data.ts]
import { initLogger, log, createLogger } from 'evlog'

initLogger({ env: { service: 'sync-worker' } })

log.info('sync', 'Worker starting')

const run = createLogger({ source: 'postgres', target: 's3' })
try {
  const records = await fetchRecords()
  run.set({ found: records.length })

  for (const record of records) {
    await syncOne(record)
    log.debug({ event: 'record_synced', id: record.id })
  }

  run.set({ status: 'complete', synced: records.length })
} catch (err) {
  log.error({ event: 'sync_failed' })
  run.error(err as Error)
  throw err
} finally {
  run.emit()
}

log.info('sync', 'Worker finished')
```

`log.*` 调用会在开发环境中为你提供实时轨迹；`createLogger` 块会让你的仪表盘每次运行都得到一行可查询记录。两者都会通过同一个排水管道。

## 下一步

- [广泛事件](https://evlog.zhcndoc.com/learn/wide-events)：累积上下文并发出完整事件
- [结构化错误](https://evlog.zhcndoc.com/learn/structured-errors)：抛出带有 `why`、`fix` 和 `link` 的错误
- [配置](https://evlog.zhcndoc.com/reference/configuration)：所有 `initLogger` 选项
- [适配器](https://evlog.zhcndoc.com/integrate/adapters/overview)：将事件发送到 Axiom、Sentry、PostHog 等
- [Standalone TypeScript](https://evlog.zhcndoc.com/integrate/frameworks/standalone)：无需 Web 框架的脚本、工作进程和库
- [evlog 与其他日志记录器的对比](https://evlog.zhcndoc.com/reference/vs-other-loggers)：与 pino、winston、consola 并排对比

---

- [广泛事件](https://evlog.zhcndoc.com/learn/wide-events)
- [配置](https://evlog.zhcndoc.com/reference/configuration)


## Sitemap

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