---
title: "自动脱敏"
description: "在控制台输出和传输之前自动清理事件中的个人身份信息（PII）。内置对信用卡、电子邮件、IP地址、电话号码、JWT等的智能屏蔽。"
canonical_url: "https://evlog.zhcndoc.com/learn/redaction"
---
# 自动脱敏

> 在控制台输出和传输之前自动清理事件中的个人身份信息（PII）。内置对信用卡、电子邮件、IP地址、电话号码、JWT等的智能屏蔽。

宽事件（Wide events）会捕获全面的上下文信息，这使得意外记录敏感数据变得很容易。自动脱敏会在事件**输出到控制台**以及**传输到任何接收器（drain）**之前，对事件中的个人身份信息（PII）进行清理。

**脱敏在生产环境中默认启用**（`NODE_ENV === 'production'`）。在开发环境中，脱敏处于关闭状态，因此你可以看到完整值以进行调试。无需配置，直接部署即可。

## 关闭它，或缩小范围

如果你需要在生产环境中禁用脱敏：

<framework-tabs>

```typescript [Nuxt]nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    redact: false,
  },
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'

export const { withEvlog, useLogger } = createEvlog({
  service: 'my-app',
  redact: false,
})
```

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

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

</framework-tabs>

你也可以通过设置 `redact: true` 在开发环境中显式启用脱敏。

<redaction-stream>



</redaction-stream>

## 充分屏蔽，同时保留调试所需信息

内置模式使用**部分屏蔽**，而不是简单地替换为 `[REDACTED]`，在保护实际数据的同时保留足够的上下文以进行调试。

<table>
<thead>
  <tr>
    <th>
      模式
    </th>
    
    <th>
      示例输入
    </th>
    
    <th>
      屏蔽输出
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        creditCard
      </code>
    </td>
    
    <td>
      <code>
        4111111111111111
      </code>
    </td>
    
    <td>
      <code>
        ****1111
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        email
      </code>
    </td>
    
    <td>
      <code>
        alice@example.com
      </code>
    </td>
    
    <td>
      <code>
        a***@***.com
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ipv4
      </code>
    </td>
    
    <td>
      <code>
        192.168.1.100
      </code>
    </td>
    
    <td>
      <code>
        ***.***.***.100
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        phone
      </code>
    </td>
    
    <td>
      <code>
        +33 6 12 34 56 78
      </code>
    </td>
    
    <td>
      <code>
        +33 ****5678
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        jwt
      </code>
    </td>
    
    <td>
      <code>
        eyJhbGciOiJIUzI1NiIs...
      </code>
    </td>
    
    <td>
      <code>
        eyJ***.***
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bearer
      </code>
    </td>
    
    <td>
      <code>
        Bearer sk_live_abc123...
      </code>
    </td>
    
    <td>
      <code>
        Bearer ***
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        iban
      </code>
    </td>
    
    <td>
      <code>
        FR76 3000 6000 0112 ...189
      </code>
    </td>
    
    <td>
      <code>
        FR76****189
      </code>
    </td>
  </tr>
</tbody>
</table>

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

`127.0.0.1` 和 `0.0.0.0` 从 IPv4 屏蔽中排除，因为它们不是真实的客户端地址。

</callout>

## 按键名识别密钥

密码或 API token 没有可识别的形状，因此内置的 `secrets` 会检查**键**而不是值。任何以 `password`、`passwd`、`secret` 或 `token` 结尾的键（不区分大小写，支持任意嵌套深度）都会将其值替换为 `replacement`，默认为 `[REDACTED]`：

<table>
<thead>
  <tr>
    <th>
      Key
    </th>
    
    <th>
      Redacted
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        password
      </code>
      
      , <code>
        newPassword
      </code>
      
      , <code>
        user_password
      </code>
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        secret
      </code>
      
      , <code>
        clientSecret
      </code>
      
      , <code>
        client_secret
      </code>
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        token
      </code>
      
      , <code>
        accessToken
      </code>
      
      , <code>
        refresh_token
      </code>
      
      , <code>
        API_TOKEN
      </code>
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        totalTokens
      </code>
      
      , <code>
        tokensPerSecond
      </code>
      
      , <code>
        tokenType
      </code>
    </td>
    
    <td>
      No
    </td>
  </tr>
</tbody>
</table>

密钥会被完整替换，而不会部分遮盖：与银行卡号不同，密码的任何部分都不适合或不需要保留。布尔值和 `null` 会保持不变，因此 `hasPassword: true` 这样的标志仍然可读。

它仍然只是安全网。请主动选择传给 `log.set()` 的字段，不要依赖它来替代选择。

## 配置

### 路径模式

使用单个 `paths` 数组，并配合点号表示法和通配符。像 `password` 这样的单独片段是 `**.password` 的简写，因此它会在**任意嵌套深度**屏蔽该键：

```typescript
evlog: {
  redact: {
    paths: [
      'password',              // 等同于 '**.password'
      '*_token',               // 任意深度的键名通配符
      'headers.x-forwarded-for', // 精确路径
      'user.*',                // user 下的所有直接字段
    ],
  }
}
```

<table>
<thead>
  <tr>
    <th>
      模式
    </th>
    
    <th>
      匹配项
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        user.email
      </code>
    </td>
    
    <td>
      仅精确路径
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        password
      </code>
      
       或 <code>
        **.password
      </code>
    </td>
    
    <td>
      任意深度的 <code>
        password
      </code>
      
       键
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        *_token
      </code>
    </td>
    
    <td>
      类似 <code>
        access_token
      </code>
      
      、<code>
        refresh_token
      </code>
      
       的键名
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        user.*
      </code>
    </td>
    
    <td>
      <code>
        user.email
      </code>
      
      、<code>
        user.password
      </code>
      
       等
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        audit.changes.*.password
      </code>
    </td>
    
    <td>
      精确匹配 + 通配符片段混合
    </td>
  </tr>
</tbody>
</table>

路径脱敏会将**整个值**（包括嵌套对象）替换为 `replacement`。当你需要对字段内的**字符串值**使用正则时，请使用 `patterns`。

这与 `auditDiff({ redactPaths: ['password'] })` 匹配，使用相同的 glob 语法，并在发射时全局应用。

### 选择性内置模式

仅选择你需要的模式：

```typescript
evlog: {
  redact: {
    builtins: ['email', 'creditCard'],
  }
}
```

### 自定义模式

添加你自己的正则表达式模式。这些使用简单的 `replacement` 字符串，而非智能屏蔽：

```typescript
evlog: {
  redact: {
    patterns: [/SECRET_\w+/g, /sk_live_\w+/g],
    replacement: '***',
  }
}
```

### 计算替换值

当替换内容必须根据被替换的值**派生**时，请传入函数而不是字符串。它会在与其他脱敏操作相同的阶段运行：在写入控制台之前、在任何 drain 之前。

常见的场景是在不暴露用于标识请求的凭据的情况下，保持请求之间的可关联性：

```typescript
initLogger({
  redact: {
    patterns: [/\/public\/claim\/([A-Za-z0-9._-]{12,})/g],
    replacement: (_match, ctx) => `/public/claim/[tok:${fingerprint(ctx.groups[0])}]`,
  },
})
// /public/claim/eyJhbGciOi...  →  /public/claim/[tok:9f3a1c]
```

该函数会接收匹配的值和一个上下文对象：

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        path
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      从事件根开始的点表示法路径（<code>
        user.email
      </code>
      
      、<code>
        items.0.token
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        key
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      字段的叶键（<code>
        email
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        groups
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      匹配 <code>
        patterns
      </code>
      
       条目的捕获组。仅对 <code>
        patterns
      </code>
      
       设置
    </td>
  </tr>
</tbody>
</table>

对于 `paths`，匹配的值是**整个字段值**，可以是任意类型，因为路径脱敏会替换整个子树。对于 `patterns`，匹配的值是匹配到的子字符串。

如果函数抛出异常或返回非字符串，脱敏会回退为 `[REDACTED]`，并记录此次失败。损坏的策略会退化为过度脱敏，而绝不会泄露它原本要清除的值。

### 条件策略

有些策略无法表示为路径列表：仅对特定租户屏蔽字段、仅当同级字段具有指定值时屏蔽字段，或者保留允许列表而不是拒绝列表。请使用 `transform`：

```typescript
initLogger({
  redact: {
    transform: (event) => {
      if (event.tenant === 'regulated') delete event.query
    },
  },
})
```

`transform` 会在 `paths`、`builtins` 和 `patterns` **之前**运行，因此它看到的是原始值，而声明式规则仍会应用于它留下的内容；如果钩子遗漏了某个字段，也不会成为你的最后一道防线。在原地修改事件；它已经是一个私有副本，因此你记录的对象不会被触碰。

它必须是同步的，因为它会在写入控制台之前、事件发出路径上运行。错误会像 drain 失败一样被捕获并报告：声明式阶段仍会运行，事件也仍会被记录。

<callout color="warning" icon="i-lucide-triangle-alert">

函数类型的 `replacement` 和 `transform` 不能在 `nuxt.config.ts` 或 Nitro 模块的选项中声明，因为该配置会在构建时序列化为 JSON，从而丢弃函数。请在 server plugin 中使用 `initLogger()`，或使用 `createEvlog()` 在运行时声明它们。如果仍然这样做，模块会发出构建时警告。

</callout>

### 禁用内置模式

如果你只想使用自定义脱敏：

```typescript
evlog: {
  redact: {
    builtins: false,
    paths: ['user.ssn'],
    patterns: [/INTERNAL_\w+/g],
  }
}
```

## 配置参考

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        redact
      </code>
    </td>
    
    <td>
      <code>
        boolean | RedactConfig
      </code>
    </td>
    
    <td>
      生产环境中为 <code>
        true
      </code>
    </td>
    
    <td>
      在生产环境中默认启用。设为 <code>
        false
      </code>
      
       可禁用。对象用于精细控制
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        paths
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      使用点号表示法并支持通配符的路径（<code>
        password
      </code>
      
      、<code>
        **.password
      </code>
      
      、<code>
        *_token
      </code>
      
      、<code>
        user.*
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        patterns
      </code>
    </td>
    
    <td>
      <code>
        RegExp[]
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      应用于字符串值的自定义正则表达式。使用统一的 <code>
        replacement
      </code>
      
       字符串
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        builtins
      </code>
    </td>
    
    <td>
      <code>
        false | string[]
      </code>
    </td>
    
    <td>
      全部启用
    </td>
    
    <td>
      设为 <code>
        false
      </code>
      
       可禁用内置规则。数组用于选择特定规则
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        replacement
      </code>
    </td>
    
    <td>
      <code>
        string | (matched, ctx) => string
      </code>
    </td>
    
    <td>
      <code>
        '[REDACTED]'
      </code>
    </td>
    
    <td>
      用于路径和自定义模式的替换内容。内置规则会改用智能遮蔽。函数根据匹配到的值计算替换内容
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        transform
      </code>
    </td>
    
    <td>
      <code>
        (event) => void
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      为条件式、租户范围或白名单式策略提供的后门。在声明式阶段之前运行
    </td>
  </tr>
</tbody>
</table>

可用的内置名称：`creditCard`、`email`、`ipv4`、`phone`、`jwt`、`bearer`、`iban`。

## 脱敏在管道中的运行位置

脱敏在发射管道中运行，在宽事件完全构建后、但在任何输出之前执行：

1. **转换**：如果存在 `transform` 钩子，它会首先看到原始事件
2. **路径脱敏**：将精确路径和通配符替换为 `[REDACTED]`
3. **智能屏蔽**：内置模式递归扫描所有字符串值，并进行部分屏蔽
4. **模式脱敏**：自定义正则模式扫描所有字符串值，并进行简单替换
5. **丰富化**：丰富化器将其字段添加到已脱敏的事件中
6. **控制台输出**：将事件打印到 stdout
7. **Drain**：将同一事件发送到外部服务

脱敏只运行一次，在丰富化器之前。丰富化器随后添加的字段不会再次脱敏，因此读取请求头或会话的丰富化器必须屏蔽其写入的内容。stdout 和 drain 接收同一事件，因此任何需要在输出前执行的操作都应放在 `transform` 或函数类型的 `replacement` 中。

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

脱敏在 HTTP 响应发送**之后**运行，因此不会增加 API 响应的延迟。

</callout>

## 一份可直接部署的配置

脱敏在生产环境中默认已启用。结合采样功能可以实现典型配置：

<framework-tabs>

```typescript [Nuxt]nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    env: { service: 'my-app' },
  },
  $production: {
    evlog: {
      sampling: {
        rates: { info: 10, debug: 0 },
        keep: [{ status: 400 }, { duration: 1000 }],
      },
    },
  },
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'

export const { withEvlog, useLogger } = createEvlog({
  service: 'my-app',
  sampling: {
    rates: { info: 10, debug: 0 },
    keep: [{ status: 400 }, { duration: 1000 }],
  },
})
```

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

initLogger({
  env: { service: 'my-app' },
  sampling: {
    rates: { info: 10, debug: 0 },
    keep: [{ status: 400 }, { duration: 1000 }],
  },
})
```

</framework-tabs>

## 查看已脱敏事件的样子

未启用脱敏时，敏感数据会进入你的日志和传输器：

```json
{
  "user": { "email": "alice@example.com", "ip": "192.168.1.42" },
  "payment": { "card": "4111111111111111" },
  "auth": "Bearer sk_live_abc123def456"
}
```

启用 `redact: true` 后：

```json
{
  "user": { "email": "a***@***.com", "ip": "***.***.***.42" },
  "payment": { "card": "****1111" },
  "auth": "Bearer ***"
}
```

相同的调试上下文，没有 PII 进入 Axiom/Datadog/Sentry。

## 下一步

- [最佳实践](https://evlog.zhcndoc.com/reference/best-practices) - 安全指南和生产检查清单
- [采样](https://evlog.zhcndoc.com/learn/sampling) - 控制生产环境中的日志量
- [配置](https://evlog.zhcndoc.com/reference/configuration) - 完整配置参考。

---

- [最佳实践](https://evlog.zhcndoc.com/reference/best-practices)
- [配置](https://evlog.zhcndoc.com/reference/configuration)


## Sitemap

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