# @ddd-ts-kit/pino 中文文档

规范地址：https://npmcn.com/package/%40ddd-ts-kit/pino/

版本：0\.106\.0
npm 元数据更新时间：2026-10-31T21:43:50.241Z
中文译文更新时间：2026-10-31T23:36:57.290Z
软件包许可：MIT

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40ddd-ts-kit/pino
原文对照：https://npmcn.com/package/%40ddd-ts-kit/pino/?lang=original
项目主页：https://gbourgeat.github.io/ddd-ts-kit/packages/pino/
源码仓库：https://github.com/gbourgeat/ddd-ts-kit

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：71.1 kB，36 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.12\.0
- 运行依赖：1 个
- peer 依赖：pino ^10\.4\.0，pino-pretty ^13\.2\.0
- 首次发布：2026-10-11T09:41:28.122Z
- 最新版本发布：2026-10-31T21:43:49.900Z
- 已发布版本数：63（近 12 个月 63 个）

## 中文简介

内核记录器端口在pino上的适配器，每行都有跟踪和执行关联。

## 安装

```sh
npm install @ddd-ts-kit/pino
```

也可以使用：`pnpm add @ddd-ts-kit/pino`、`yarn add @ddd-ts-kit/pino`、`bun add @ddd-ts-kit/pino`

## README

**英语** · [法语](https://npmcn.com/package/%40ddd-ts-kit/pino/README.fr.md)

# @ddd-ts-kit /皮诺

内核 `Logger` 港口 [皮诺](https://getpino.io/)：标准输出上的JSON行 相关字段的每一行正在进行的工作，凭据编校，刷新为 关闭。中没有 `domain/` 或者 `application/` 导入此包，或记录任何内容 组合根绑定适配器，基础设施、表示和方面写入 通过港口。

```
npm i @ddd-ts-kit/pino pino
npm i -D pino-pretty        # only for the pretty format
```

对着pino 10写的。 `pino-pretty` 13是可选对等体，只有当 `pretty` 格式要求。

![Where the pino logger sits in an application: the domain and the handlers log nothing; the adapters of infrastructure, the controllers of presentation and the aspects write through the kernel Logger port; the composition root binds PinoLogger to that port; pino writes JSON lines to the standard output, which the platform collects](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/pino-place-in-an-application.svg)

域和处理程序通过事件和错误说明发生了什么，而不是使用日志行。 写在它们周围的是：的适配器 `infrastructure/`的控制者 `presentation/`，以及各方面(`logged`， `loggingMiddleware`（作业调度器）。他们都 接收内核 `Logger`;组合根绑定 `PinoLogger` 一次。更换皮诺 后来更改了该绑定，而不是调用站点。

## 作文根

```ts
// platform/observability/create-logger.ts
import type { ExecutionScope, LogLevel } from '@ddd-ts-kit/kernel';
import { createPinoLogger, type PinoLogger } from '@ddd-ts-kit/pino';
import { traceLogFields } from '@ddd-ts-kit/tracing';

export interface LogSettings {
  readonly level: LogLevel; // LOG_LEVEL
  readonly format: 'json' | 'pretty'; // LOG_FORMAT
  readonly service: string;
  readonly environment: string;
  readonly scrubMessages: boolean; // LOG_SCRUB_MESSAGES
}

export function createLogger(scope: ExecutionScope, settings: LogSettings): PinoLogger {
  return createPinoLogger({ ...settings, fields: () => traceLogFields(scope) });
}
```

```ts
// main.ts
import { tracing } from '@/platform/observability/tracing'; // first
import { createLogger } from '@/platform/observability/create-logger';
import { AsyncLocalStorageExecutionScope } from '@ddd-ts-kit/kernel';
import { kernelModule } from '@ddd-ts-kit/di';
import { createApplication } from '@ddd-ts-kit/di/tsyringe';

const scope = new AsyncLocalStorageExecutionScope();
const logger = createLogger(scope, config.log);

const app = createApplication({
  modules: [kernelModule({ executionScope: scope, logger }), ...contexts],
});

process.once('SIGTERM', () => void shutdown());

async function shutdown(): Promise<void> {
  await app.stop(); // flushes the error reporter, then the logger, last
  await tracing.shutdown();
}
```

`init --logger pino` 写这个 `create-logger.ts` (它回答了。 `NoopLogger` 当给出no时 设置（这是工具和测试保持沉默的方式）； `platform/composition/lay-foundations.ts` 调用一次，然后 `composeApplication` 将记录器交给 `kernelModule({ logger })`.

应用程序的一行，如下所示：

```json
{"level":"info","time":"2026-03-01T10:00:00.000Z","service":"shop-api","environment":"production","correlationId":"9f2c","origin":"http","actorId":"user-1","tenantId":"acme","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7","context":"shipping","message":"order shipped"}
```

![The path of one call to logger.info: below the threshold it stops after one comparison; otherwise the adapter describes the errors among the fields and drops the level and time keys; pino writes the header, then service, environment and base, the fields of the children, what fields() answers and the fields of the line; it replaces the value of every credential key by \[redacted\]; then it hands the JSON line to its destination, at once on the standard output, through a 4 KiB buffer with asynchronous, or to the pino-pretty worker](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/pino-path-of-a-line.svg)

**相关性。** `fields` 在写的每一行读取，通过pino `mixin`:不 调用站点传递一个id。 `traceLogFields(scope)` 回答span内的trace和span id， 在执行范围内，关联id，因果id，起源，行动者和 租户。这个包不依赖于跟踪包：any `() => LogFields` 合适, `() => logFieldsOf(scope.current())` 包括在内。抛出的函数不给出字段，永远不会 失去了线。这条线的田地越积越多 `fields`，它胜过了孩子的声音：pino 首先写入子节点的绑定，然后再写入mixin的响应，因此给出一个键 by both写入两次，JSON解析器读取其中一个 `fields`. `StdoutLogger` 订单 它们都是一样的； `@ddd-ts-kit/winston` 难道没有一个孩子赢得别人的好感吗 `fields`）. 不 在子节点中绑定一个键 `fields` 的答案。

![Which value a key gets when it is given twice, from the weakest to the strongest: with pino and StdoutLogger, service, environment and base, then the children, then fields(), then the line, which wins; with winston, fields(), then the children, then the line, then service, environment and base, which win](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/pino-which-value-wins.svg)

**一个你自己的例子。** 已经配置了pino的项目会继续这样做 拿走它想要的东西：

```ts
import { CREDENTIAL_KEYS, type ExecutionScope } from '@ddd-ts-kit/kernel';
import { pino } from 'pino';
import { PinoLogger, correlationMixin, credentialRedaction } from '@ddd-ts-kit/pino';
import { traceLogFields } from '@ddd-ts-kit/tracing';

declare const scope: ExecutionScope; // the one the composition root creates

const instance = pino({
  mixin: correlationMixin(() => traceLogFields(scope)),
  redact: credentialRedaction([...CREDENTIAL_KEYS, 'iban']),
});

export const logger = new PinoLogger(instance);
```

`createPinoLogger` 写 `level` 通过它的名字和下面的信息 `message`，比如 `StdoutLogger` 方面的包和喜欢 `@ddd-ts-kit/winston`：一个仪表板显示 三。你自己的实例保存着你给它的钥匙(`msg` 默认情况下为数字级别)。

## 从适配器进行日志记录

适配器接收 `Logger` 内核的构造函数，就像任何其他端口一样，和 永远不要导入这个包。在子节点中绑定一次适配器的每行所携带的内容；把 在每一行中，从一个调用到下一个调用的变化。相关字段已经存在： `fields` 他们补充道。

```ts
// infrastructure/carrier/carrier-gateway.ts
import type { Logger } from '@ddd-ts-kit/kernel';

interface Carrier {
  readonly name: string;
  book(orderId: string, weightGrams: number): Promise<string>; // the tracking number
}

export class CarrierGateway {
  private readonly logger: Logger;

  constructor(
    private readonly preferred: Carrier,
    private readonly fallback: Carrier,
    logger: Logger,
  ) {
    this.logger = logger.child({ context: 'shipping' }); // on every line of this adapter
  }

  async book(orderId: string, weightGrams: number): Promise<string> {
    try {
      return await this.preferred.book(orderId, weightGrams);
    } catch (error) {
      // Handled here, not rethrown: worth a line, with the error described by the logger.
      this.logger.warn('carrier unavailable, booking with the fallback', {
        orderId,
        carrier: this.preferred.name,
        error: error instanceof Error ? error : String(error),
      });

      return this.fallback.book(orderId, weightGrams);
    }
  }
}
```

消息是常量，标识符是字段：仪表板按行计数 消息时，搜索根据字段查找订单，不会在文本中插入任何个人数据。

```ts
import type { Logger } from '@ddd-ts-kit/kernel';

declare const logger: Logger;
declare const order: { readonly id: string; readonly customerEmail: string; readonly lines: readonly unknown[] };

// Instead of: one distinct message per order, an email in clear text, a search that cannot filter.
logger.info(`order ${order.id} shipped to ${order.customerEmail}`);

// Write: the same message every time, identifiers as fields, nothing personal.
logger.info('order shipped', { orderId: order.id });

// Fields that cost something to build are built only when the line is written.
if (logger.isLevelEnabled('debug')) {
  logger.debug('order shipped, detail', { orderId: order.id, lineCount: order.lines.length });
}
```

代码释放的错误（它抛出或重新抛出）不会在途中记录：总线 路由或工作报告给 `ErrorReporter` 一次，并且方面记录其类型及其 代码。只在错误被处理和停止的地方记录错误，就像上面的回退一样。

## 发展模式

```ts
createPinoLogger({ level: 'debug', format: 'pretty', fields: () => traceLogFields(scope) });
```

```
[10:00:00.000] INFO: order shipped
    service: "shop-api"
    correlationId: "9f2c"
    context: "shipping"
```

这个 `pretty` 格式是运输pino文件的要求 `pino-pretty`：在worker中运行 线程，所以它接受一个文件描述符或路径作为 `destination`，不是一条小溪，而且 `flush()` 等待工人。对于您自己的流中的漂亮行，在 `pino-pretty` 用作溪流， `pino(options, pretty({ destination }))`，然后把它包起来 `new PinoLogger(instance)`． 不要在生产环境中使用：从配置中选择格式 （`LOG_FORMAT=pretty` 在 `.env`， `json` 其他地方)。

## 隐私

日志线是个人数据泄露的地方：它们被写在任何地方，每个人都可以阅读并保存 太久。在适配器中完成了三件事，因此调用站点不必记住它们。这个 凭据密钥列表、错误描述和两个标记(`CREDENTIAL_KEYS`， `describeError`， `REDACTED`， `SCRUBBED_MESSAGE`)是内核的，由每个内核共享 端口记录器。

**凭据已编辑。** 皮诺自己的 `redact`，在田野里的一条线，一个孩子和 `fields` 一样的。这些键的值被替换为 `[redacted]`，在一行的顶端 下一层(`headers.authorization`， `body.password`):

`password`， `passwd`， `authorization`， `cookie`， `set-cookie`， `token`， `secret`， `apiKey`， `api_key`， `x-api-key`， `access_token`， `refresh_token`， `id_token`， `accessToken`， `refreshToken`， `idToken`， `client_secret`， `clientSecret`， `client-secret`， `private_key`， `privateKey`

这些名字无论在什么情况下都是匹配的`Authorization`， `X-Api-Key`), `isCredentialKey` 核的。 `redact: ['iban', 'ssn']` 添加到列表中：添加的名称为 匹配，因为它写在一行的顶部，无论它的情况下一层，自pino 在顶部按其路径命名键。没有比一层更深的东西，也没有在一个 列表：记录你想要的部分，而不是整个请求。

**作为字段传递的错误** （`logger.error('payment failed', { error })`: `LogValue` 的 该港口允许船舶驶入。 `Error`)写为 `describeError` 内核的描述，是一样的 从端口的每个记录器中：它的类为 `type`,它的 `code` 当 这是一个 `DomainError`,它的 `message`，它自己 `stack`，那么…… `cause` 同样的方法，直到 八个深。链的每个错误都写入一次：pino的标准序列化器 `err` 关键 是否将每个原因折叠到消息和第一个原因的堆栈中，以便适配器将其关闭 对于通过港口的线路。在行字段中查找错误，而不是 在嵌套对象内部。

**意外错误的消息可以被丢弃。** 错误的消息是第一位的 个人数据出现的地方：司机引用了他拒绝的那一行。默认保留它，因为 一条日志线上面写着 `[message removed]` 花费了所有的调查。只有一个选择 这个项目自己写的信息保存了下来，那些是它的 `DomainError`年代;类、代码、堆栈和 Cause为其他的保留，堆栈失去它的第一行，这重复了这个消息：

```ts
createPinoLogger({ scrubMessages: true }); // or new PinoLogger(instance, { scrubMessages: true })
```

这是 `scrubMessages` 端口的每个记录器(`StdoutLogger`皮诺，温斯顿， `InMemoryLogger`)和的一个 `@ddd-ts-kit/sentry`，用同样的规则，同样的 `SCRUBBED_MESSAGE` 同样的权衡：当日志离开你的手或没有时打开它 操作符应该读取那里的数据。日志线本身的信息是你的： 写 `order shipped` 并将标识符放在字段中。

该工具包本身从不向记录器传递错误 `logged` 方面, `loggingMiddleware` 和 作业调度器写入 `errorType` 和 `errorCode` (`identifyError` （内核），永远不会 一个信息，随便什么 `scrubMessages` 说，错误会出现在 `ErrorReporter`。这个 上面的描述是针对您自己的代码选择记录的错误： `logger.error('payment failed', { error })`.

## 水平

| 港口 | 皮诺 |  |
| --- | --- | --- |
| `debug` | `debug` （20) |  |
| `info` | `info` (30) | 默认阈值 |
| `warn` | `warn` (40) |  |
| `error` | `error` （50) |  |

港口没有 `trace` 也没有 `fatal`没有任何通过它写的东西使用它们。下一条线 Threshold需要进行一次比较：它的字段不被读取，并且 `fields` 未被调用。这个 运行时更改实例的阈值(`instance.level = 'debug'`)后面跟着the 适配器和它的孩子。 `isLevelEnabled(level)` 回答这个门槛：提前问 建造需要花费的田地。

`child(fields)` 是一个pino子节点：当子节点被创建时，字段被序列化一次， 而且每行不需要任何成本。一个键被绑定两次（被子键和父键绑定，被子键和父键绑定） `fields`（或通过它们中的任何一个和通过行）在一行上写两次，就像pino一样；一个 JSON解析器读取最后一个，按以下顺序： `service`， `environment` 和 `base`, 从最大的孩子到最亲的孩子， `fields`，行。 `level` 和 `time` 是 异常：该名称的字段被删除， 因此，没有字段伪造一行的标题。

## 冲洗

`flush(): Promise<void>` 一旦将pino缓冲的内容传递到目的地，就进行解析 传输的工作线程，或异步目的地的缓冲区。它从不排斥。虽然它 运行时，它使进程保持活动状态，最多10秒：pino不让工作线程运行 运输是一个过程，所以那个工人的回答可能会来得太晚 进程没有其他事情可做，并且在刷新后关闭所做的操作将永远不会运行。 `app.stop()` 在模块停止并刷新错误报告器之后，最后调用它： 停止日志。没有应用di包，在那个地方自己调用它。

端口在关闭时调用一次刷新，并且不指定之后记录的内容。 在这里，日志记录器保持可用：在刷新之后记录的一行与其他行一样写入，并且 与 `asynchronous: true` 它在缓冲区中等待pino的退出钩子。

## 表演

默认情况下，在调用返回之前将每行写入标准输出。没有什么是 一旦丢失，标准输出的缓慢读取会减慢应用程序的速度。

`asynchronous: true` 缓存4kib并写入热路径(`pino.destination({ sync: false })`)，这使得pino在负载下速度很快。代价是一个僵死的进程所没有的 但是写的。 `flush()` 写入缓冲区，pino自己的exit钩子也写入缓冲区 `process.exit` 以及未捕获的异常。没有处理程序的进程结束的信号(`SIGKILL`, 内存不足杀手，a `SIGTERM` 没有人听)最近损失了4千兆字节 诗句，那些可以解释它的诗句；在第一时间退出也是如此，在 目的地是开放的。保持默认值，除非日志记录出现在概要文件中。

每行花费它的字段的一个对象副本（错误描述，标题键被删除），然后pino： 大约3微秒 `@ddd-ts-kit/winston` 取7（节点24，子节点，4） 相关字段，三个字段，一个接收器，丢弃)。 编校是由pino编译的：在一行的顶部查找每个凭据密钥，并且一个 通配符通过以下级别，其审查器查看每个密钥的名称。通配符路径 每个键将是很明显的书写方式，并且花费十倍：在pino 10上测量， 其中有13个从1微秒到9微秒不等。日志调用永远不会抛出无法读取或序列化的字段 （抛出的getter）给出没有它们的行，标记 `logError: "unserializable fields"`.

## 哪一个

采取 `StdoutLogger` 标准输出中的JSON行都是您的 需要：不依赖，线条形状相同。当日志量很重要时使用pino 框架已经说pino (fasttify)，或者当您需要它的传输时。采取 `@ddd-ts-kit/winston` 当团队已经运行温斯顿传输和格式。这三个 实现一个端口并由一个契约套件保持：交换是组合的一行 根。

## NestJS

Nest也有自己的 `LoggerService`，由框架用于其自己的消息，并由 `new Logger(context)` 在Nest类中。该工具包的应用程序代码从不需要它：处理程序和 适配器接收内核 `Logger`． 要通过同一个实例发送Nest自身记录的内容， 给它一座几行字的桥；这个工具包没有发货，因为它会将这个包绑定到Nest；

```ts
const nest = logger.child({ context: 'nest' });
const bridge: LoggerService = {
  log: (message: unknown) => nest.info(String(message)),
  warn: (message: unknown) => nest.warn(String(message)),
  error: (message: unknown) => nest.error(String(message)),
  debug: (message: unknown) => nest.debug(String(message)),
};

const app = await NestFactory.create(AppModule, { logger: bridge });
```

`nestjs-pino` 当Nest类记录大量日志时，另一种选择是：给它一个pino实例 ，并将相同的实例封装在 `new PinoLogger(instance)` 为了工具箱。

## 在测试中

这个包没有任何内容：测试应用程序绑定 `InMemoryLogger` 核函数的 `entries` 是数据。要使端口的另一个适配器具有相同的行为，请运行合约：

```ts
import { beforeEach, describe, expect, it } from 'vitest';
import { LOG_LEVELS } from '@ddd-ts-kit/kernel';
import { createPinoLogger } from '@ddd-ts-kit/pino';
import { loggerContract, type LoggedLine } from '@ddd-ts-kit/testing/contracts';

loggerContract({ describe, it, expect, beforeEach }, {
  name: 'PinoLogger',
  createLogger: (setup) => { // { level, fields, scrubMessages }
    const written: string[] = [];
    const logger = createPinoLogger({ ...setup, destination: { write: (line) => written.push(line) } });

    return { logger, lines: () => written.map((line) => lineOf(JSON.parse(line))) };
  },
});

/** A written line as the contract reads it: the level by its name, the message, the other fields. */
function lineOf({ level, message, time: _time, ...fields }: Record<string, unknown>): LoggedLine {
  const known = LOG_LEVELS.find((one) => one === level);

  if (known === undefined) {
    throw new Error(`Not a level of the port: ${String(level)}`);
  }

  return { level: known, message: String(message), fields };
}
```

## 常见问题

### @ddd-ts-kit/pino 是什么？

内核记录器端口在pino上的适配器，每行都有跟踪和执行关联。

### 如何安装 @ddd-ts-kit/pino？

在项目目录运行 npm install @ddd-ts-kit/pino，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：pino（^10\.4\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @ddd-ts-kit/pino 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @ddd-ts-kit/pino 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @ddd-ts-kit/pino 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22\.12\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @ddd-ts-kit/pino 可以商用吗？

@ddd-ts-kit/pino 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @ddd-ts-kit/pino 还在维护吗？

最新版本 0\.106\.0 发布于 2026/11/01（2 天前）；至今共发布 63 个版本，其中过去 12 个月发布了 63 个；首次发布于 2026/10/11。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

