@ddd-ts-kit/winston

中文文档
0.106.0发布于 2026/11/01MITTypeScriptES Module构建来源已验证

温斯顿内核记录器端口的适配器,每行都有跟踪和执行关联。

中文使用文档
文档目录9 节
  1. 作文根
  2. 发展模式
  3. 隐私
  4. 水平
  5. 冲洗
  6. 表演
  7. 哪一个
  8. NestJS
  9. 在测试中

英语 · 法语

@ddd-ts-kit /温斯顿

内核 Logger 港口 温斯顿的JSON行 标准输出,每行正在进行的工作的相关字段,凭据 已编辑,关闭的冲洗。中没有 domain/ 或者 application/ 进口这 包,或记录任何东西:组合根绑定适配器和基础设施, 表示和方面通过端口编写。

npm i @ddd-ts-kit/winston winston

对温斯顿3.19写的。

域和处理程序不记录任何内容:它们必须记录的是事件或错误。这个 适配器的 infrastructure/的控制者 presentation/ 这些方面(logged, loggingMiddleware(作业调度器)接收内核 Logger,和组成根 结合 WinstonLogger 一次。记录日志的代码与 @ddd-ts-kit/pino 或者 StdoutLogger:看 从适配器进行日志记录.

作文根

// platform/observability/create-logger.ts
import type { ExecutionScope, LogLevel } from '@ddd-ts-kit/kernel';
import { traceLogFields } from '@ddd-ts-kit/tracing';
import { createWinstonLogger, type WinstonLogger } from '@ddd-ts-kit/winston';

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): WinstonLogger {
  return createWinstonLogger({ ...settings, fields: () => traceLogFields(scope) });
}
// 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: that ends the winston logger
  await tracing.shutdown();
}

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

应用程序的一行,如下所示:

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

The path of one call to logger.info through winston: below the threshold the adapter stops after one comparison; otherwise it describes the errors among the fields and keeps level and message out of them; winston merges the fields of the line, the fields of the children and its defaultMeta; then the formats run in order: correlationFields fills the keys nobody gave from fields(), redactCredentials hides the credential keys, isoTime writes time, json or the pretty line serializes; each transport writes the line: the console by default, or the files, syslog or stream you give

相关性。 fields 按温斯顿格式在每一行读取:no call 站点传递一个id。 traceLogFields(scope) 回答跨度内的跟踪和跨度id,以及 在执行范围内,关联id、因果id、起源、参与者和 租户。这个包不依赖于跟踪包:any () => LogFields 合适, () => logFieldsOf(scope.current()) 包括在内。抛出的函数不给出字段,永远不会 失去了线。这条线的田地赢得了孩子们的田地,孩子们赢得了 fields: 格式只能填满没人给的钥匙。 StdoutLogger 和 @ddd-ts-kit/pino 意见不同 第二步(这里) 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

一个你自己的例子。 已经配置了winston的项目会继续这样做 接受它想要的格式:

import { CREDENTIAL_KEYS, type ExecutionScope } from '@ddd-ts-kit/kernel';
import { traceLogFields } from '@ddd-ts-kit/tracing';
import winston from 'winston';
import { WinstonLogger, correlationFields, redactCredentials } from '@ddd-ts-kit/winston';

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

const instance = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    correlationFields(() => traceLogFields(scope)),
    redactCredentials([...CREDENTIAL_KEYS, 'iban']), // after the formats that add fields
    winston.format.timestamp(),
    winston.format.json(),
  ),
  transports: [new winston.transports.Console()],
});

export const logger = new WinstonLogger(instance);

createWinstonLogger 写下下面的瞬间 time,通过其名称和消息获取级别 下 message,比如 StdoutLogger 方面的包和喜欢 @ddd-ts-kit/pino: 一个仪表板上写着3。一行的键被排序出来:winston的JSON格式 使用稳定的stringifier进行序列化。

你自己的交通工具 不需要自己配置温斯顿: transports 取代了 控制台,所以当行仍然必须到达标准输出时也命名它。

import { createWinstonLogger } from '@ddd-ts-kit/winston';
import winston from 'winston';

export const logger = createWinstonLogger({
  service: 'shop-api',
  transports: [
    new winston.transports.Console(), // the standard output, as by default
    new winston.transports.File({ filename: 'logs/errors.log', level: 'error' }), // errors only, kept on disk
  ],
});

每个传输都有自己的过滤器 level 格式运行后:上面的文件接收到 error 行,控制台的每一行从日志记录器的阈值开始。

发展模式

createWinstonLogger({ level: 'debug', format: 'pretty', fields: () => traceLogFields(scope) });
10:00:00.000 info order shipped { context: 'shipping', service: 'shop-api', correlationId: '9f2c' }

这个 pretty 格式是由winston自己的组合子(colorize, printf),在… 处理:没有其他的包,当地的时间,在颜色的水平,领域作为节点 在几行上打印一个对象,一个堆栈。从配置中选择格式 (LOG_FORMAT=pretty 在 .env, json 其他地方)。

隐私

日志线是个人数据泄露的地方:它们被写在任何地方,每个人都可以阅读并保存 太久。在适配器中完成了三件事,因此调用站点不必记住它们。

凭据已编辑。 通过温斯顿格式,在行,子和的字段中 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

redact: ['iban', 'ssn'] 添加到列表中。无论名字是什么情况,都是匹配的 (Authorization, X-Api-Key),如 isCredentialKey 果仁做了,却什么也没看 对于一个以上的层次,也不是在一个列表中:记录你想要的部分,而不是整个请求。这个 您传递的对象永远不会写入:当必须隐藏密钥时,将复制嵌套对象。

作为字段传递的错误 (logger.error('payment failed', { error }): LogValue 的 该港口允许船舶驶入。 Error)写为 describeError 内核的描述,是一样的 从端口的每个记录器中:它的类为 type,它的 code 当 这是一个 DomainError,它的 message,它自己 stack,那么…… cause 同样的方法,直到 八个深。链的每个错误都写一次。的字段中查找错误 行,不在对象内嵌套。 CREDENTIAL_KEYS, REDACTED 和 SCRUBBED_MESSAGE 是 内核的也一样。

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

createWinstonLogger({ scrubMessages: true }); // or new WinstonLogger(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 }).

水平

港口温斯顿(npm 水平)
debugdebug在它下面,温斯顿也有 silly
infoinfo默认阈值; http 和 verbose 站之间 info 和 debug
warnwarn
errorerror

名字是一样的 cli 的水平。的实例。 syslog 水平没有 warn: 说出它去了哪里,否则这行就被删掉了,因为温斯顿的写作水平和它不一样 不知道。

import { WinstonLogger } from '@ddd-ts-kit/winston';
import winston from 'winston';

const instance = winston.createLogger({
  levels: winston.config.syslog.levels, // emerg, alert, crit, error, warning, notice, info, debug
  transports: [new winston.transports.Console()],
});

export const logger = new WinstonLogger(instance, { levels: { warn: 'warning' } }); // logger.warn() writes at 'warning'

低于阈值的一行需要进行一次比较:适配器请求 isLevelEnabled 首先, 因为温斯顿一个人就能在传送机下线前运行所有格式。这个 运行时更改实例的阈值(instance.level = 'debug')后面跟着the 适配器和它的孩子。 isLevelEnabled(level) 的端口回答了同样的问题 映射的关卡:在建造需要花费的领域之前询问它。一旦记录器被刷新 答案 false 对于每个关卡。

child(fields) 是温斯顿的孩子吗?田野被捆绑一次,温斯顿就把它们合并在一起 每行的字段。当温斯顿被直接召唤时,有三件事与他不同 目的:一个名为 level 或者 message 永远不要替换级别或消息的 行;消息永远不会作为格式字符串(%s); 和 createWinstonLogger 一个 字段命名 time 被该行的瞬间覆盖。有一件事是温斯顿的: service, environment 和 base 是它的 defaultMeta,赢得了一场比赛 相同的名字在一条线上或在一个孩子,其中其他两个记录器的工具包让线和 孩子赢了。

冲洗

flush(): Promise<void> 结束Winston记录器,并在其每个传输完成后进行解析 写的是递给他们的 finish 事件)。它从不拒绝,第二个电话会接 同样的承诺。 app.stop() 最后在模块停止和错误报告器之后调用它 已被刷新:也在停止日志。没有应用di包,您可以在 那个地方。

后 flush() 解决@ddd-ts-kit/winston@ddd-ts-kit/pino
记录一行删除,没有错误写好了,像以前一样
isLevelEnabled(level)false 对于每个关卡门槛,和以前一样
第二个 flush()同样的承诺新面孔

端口在关闭时调用一次刷新,并且不指定之后记录的内容。 这里的刷新是最终的:温斯顿记录器不会重新打开,所以之后记录的内容, 通过这个记录器或它的子记录器之一,在没有错误的情况下删除 isLevelEnabled 答案 false. 关于关机的任何必须记录的内容都在之前记录了 app.stop() 的回报。在你自己开的小溪上运输 (一个套接字,一个文件流给 Stream 运输部已经把自己的生产线交给了这条小溪 当 flush() 解析:结束该流是你的。

表演

winston通过Node流、格式链及其传输写入每一行 而在调用过程中:大约7微秒一行在哪里 @ddd-ts-kit/pino 需要3 (节点24,一个子节点,四个相关字段,三个字段,一个丢弃的接收器)等等 在这里,每行需要一个字段的副本,每个级别需要一个合并 子,格式阅读 fields,编校传递顶级值,即时, 和 JSON.stringify 通过安全序列化器。对于每行几百行,这算不了什么 第二,可以看到数以万计:在那里,降低水平或采取 @ddd-ts-kit/pino.

适配器不会缓冲任何东西,因此在传输之外的崩溃中不会丢失任何东西 它自己没有写。日志调用永远不会抛出无法读取或序列化的字段(a Getter(抛出)给出没有它们的行,标记 logError: "unserializable fields", 和 createWinstonLogger 将其格式保存为流内相同的规则。

哪一个

采取 StdoutLogger 标准输出中的JSON行都是您的 需要:不依赖,线条形状相同。采取 @ddd-ts-kit/pino 当日志量很重要时 或者当框架已经说pino (fasttify)时。球队已经跑了,带上温斯顿 它的传输和格式(文件与旋转,syslog,一个供应商传输),并希望保持 他们。这三个实现一个端口,并由一个合约套件持有:交换是其中的一行 组成根。

NestJS

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

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 });

nest-winston 当Nest类记录大量日志时,另一种选择是:给它一个winston实例 构建并封装同一个实例 new WinstonLogger(instance) 为了工具箱。

在测试中

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

import { Writable } from 'node:stream';
import { beforeEach, describe, expect, it } from 'vitest';
import { LOG_LEVELS } from '@ddd-ts-kit/kernel';
import { loggerContract, type LoggedLine } from '@ddd-ts-kit/testing/contracts';
import { createWinstonLogger } from '@ddd-ts-kit/winston';
import winston from 'winston';

loggerContract({ describe, it, expect, beforeEach }, {
  name: 'WinstonLogger',
  createLogger: (setup) => { // { level, fields, scrubMessages }
    const written: string[] = [];
    const stream = new Writable({
      write: (chunk, _encoding, done) => {
        written.push(String(chunk));
        done();
      },
    });
    const logger = createWinstonLogger({ ...setup, transports: [new winston.transports.Stream({ stream })] });

    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/winston 共发布 63 个版本,首次发布于 2026/10/11;过去 12 个月发布了 63 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.106.0latest2026/11/012 天前
0.105.02026/10/312 天前
0.104.02026/10/312 天前
0.103.02026/10/313 天前
0.102.02026/10/313 天前
0.101.02026/10/303 天前

关于 @ddd-ts-kit/winston 的常见问题

@ddd-ts-kit/winston 是什么?

温斯顿内核记录器端口的适配器,每行都有跟踪和执行关联。

如何安装 @ddd-ts-kit/winston?

在项目目录运行 npm install @ddd-ts-kit/winston,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:winston(^3.19.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@ddd-ts-kit/winston 支持 TypeScript 吗?

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

@ddd-ts-kit/winston 支持 ESM 还是 CommonJS?

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

@ddd-ts-kit/winston 需要什么版本的 Node.js?

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

@ddd-ts-kit/winston 可以商用吗?

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

@ddd-ts-kit/winston 还在维护吗?

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