☰
Webiny 后端日志规范:用 DI Logger 取代 console.*(`@webiny/api-core/features/logger` 实战指南)
2026/9/28 20:57:41 网站建设 项目流程
  • CMS
  • 后端
  • 前端

【免费下载链接】webiny-js

Open-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.

项目地址:https://gitcode.com/gh_mirrors/we/webiny-js
点击查看免费下载

本篇技术指南聚焦 Webiny 开源仓库中的一条核心编码规范——后端(api-*)代码严禁使用console.log/console.warn/console.error,必须通过依赖注入(DI)获取Logger并输出结构化日志。这条规范源自仓库 no-console-in-backend.md,适用于所有基于api-*系列包构建的 GraphQL API、事件处理器与后台任务。读完本文,你将掌握 DI Logger 的注入方式、pino 结构化日志的调用约定、日志级别与环境变量控制,并能直接在业务代码中替换掉所有console.*调用。

一、规范原文:为什么后端禁止console.*

仓库 no-console-in-backend.md 给出的规则非常明确:

Never useconsole.log/console.warn/console.errorin backend (api-*) code. Use the DI logger.

即:在任何api-*包(如api-core、api-headless-cms、api-website-builder、api-aco、api-file-manager等)的后端代码中,一律不得使用console.log/console.warn/console.error,取而代之的是通过依赖注入获得的Logger。该规则文件给出的正反示例是:

// Good —— 结构化上下文作为第一个参数 logger.warn({ error }, "message"); // Bad —— console 混入非结构化输出 console.warn("message", error);

需要说明的是,该规范约束的对象是后端api-*代码,这与仓库中另一条 no-console-in-backend 相关代码风格体系 所强调的“分层职责”一致:后端日志需要进入统一的日志管道(AWS Lambda + CloudWatch),而不是直接打印到进程标准输出。

二、DI Logger 从哪来:@webiny/api-core/features/logger的结构

规范指定的注入来源是@webiny/api-core/features/logger。从源码看,该模块位于 packages/api-core/src/features/logger,由四个文件组成,构成“抽象 + 实现 + 特性注册”的标准 DI 结构:

  • abstractions.ts:定义ILogger接口并导出抽象Logger(通过createAbstraction创建)。
  • LoggerService.ts:LoggerImpl实现类,底层封装 pino。
  • feature.ts:LoggerFeature,负责把Logger注册进 DI 容器。
  • index.ts:对外导出Logger抽象。

因此,规范中写的Inject Logger (from "@webiny/api-core/features/logger")指的正是导入 index.ts 导出的Logger抽象,并在构造函数参数中声明该依赖。

2.1 接口提供的完整日志级别

abstractions.ts 中ILogger接口定义了完整的日志方法,每个方法签名统一为(objOrMsg: object | string, ...args: any[]):

trace(objOrMsg, ...args); // 最细粒度,用于追踪 debug(objOrMsg, ...args); // 调试信息 info(objOrMsg, ...args); // 常规信息 warn(objOrMsg, ...args); // 警告 error(objOrMsg, ...args); // 错误 fatal(objOrMsg, ...args); // 致命错误 log(objOrMsg, ...args); // 通用日志,内部默认映射到 info

规范中强调的logger.info/warn/error(...)均在此列,且统一遵循“第一个参数可传结构化对象,后续参数为辅助信息”的 pino 约定。

2.2 注入方式:构造器依赖 + DI 容器注册

Logger通过createImplementation与createFeature接入 Webiny 的 DI 体系(见 LoggerService.ts 与 feature.ts):

// LoggerService.ts export const Logger = createImplementation({ abstraction: LoggerAbstraction, implementation: LoggerImpl, dependencies: [] });
// feature.ts export const LoggerFeature = createFeature({ name: "LoggerFeature", register(container) { container.register(Logger); } });

因此,在后端代码(如某个 Resolver、Service 或 Presenter 中)使用时,只需要在构造函数参数里声明Logger依赖即可:

import { Logger } from "@webiny/api-core/features/logger"; class MyService { constructor(private readonly logger: Logger) {} // 业务方法中直接调用 this.logger.info(...) / this.logger.warn(...) }

仓库中可找到真实的消费示例,例如 ApiKeyAuthenticator.ts 中即以依赖注入方式使用logger输出认证相关日志,印证了“通过构造器拿到 logger,再调用logger.info/warn/error”这一标准用法。

三、底层实现:pino 驱动的结构化日志

规范中提到It is pino-backed,这一点在 LoggerService.ts 中得到完整印证:

import { type Logger as PinoLogger, pino } from "pino"; import { pinoLambdaDestination, StructuredLogFormatter } from "pino-lambda"; export class LoggerImpl implements LoggerAbstraction.Interface { private pinoLogger: PinoLogger; constructor() { const level = this.getLogLevel(); const destination = pinoLambdaDestination({ formatter: new StructuredLogFormatter() }); this.pinoLogger = pino({ level }, destination); } // trace / debug / info / warn / error / fatal / log 均委托给 pinoLogger 对应方法 }

要点拆解:

  1. pino 核心:所有日志方法(trace到fatal)最终都委托给内部的 pino logger,因此天然支持 JSON 结构化输出、多级过滤与低开销。
  2. pino-lambda 目标:日志目的地使用pino-lambda的pinoLambdaDestination+StructuredLogFormatter,这是为 AWS Lambda 运行环境设计的日志管道(源码注释也说明pino-lambda目前是硬编码选择,原因是其初始化依赖 Lambda 函数上下文,后续若有更好的基础设施会重构)。
  3. 日志级别可配置:getLogLevel()读取环境变量WEBINY_API_LOG_LEVEL,缺省时回落到"info":
const DEFAULT_LOG_LEVEL = "info"; private getLogLevel() { return process.env.WEBINY_API_LOG_LEVEL || DEFAULT_LOG_LEVEL; }

这解释了“为什么默认看不到debug/trace日志”:默认级别是info。若需要更详细的后端日志,可通过部署环境变量WEBINY_API_LOG_LEVEL设置为debug或trace(可接受 pino 标准的级别值),而不必修改任何业务代码。

四、正确写法:结构化上下文作为第一个参数

规范给出的“Good / Bad”对比,本质上是 pino 的两种调用形式:

// Good:第一个参数传结构化对象 { error },pino 会将其序列化为 JSON 字段 logger.warn({ error }, "message"); // Bad:console 把对象塞进第二个位置,输出既非结构化也绕过了日志管道 console.warn("message", error);

在实际业务中,推荐把错误对象、请求 ID、租户 ID、资源 ID 等上下文放进第一个对象参数,人可读的说明文字放第二个字符串参数:

// 常规信息 logger.info({ tenant, entryId }, "Content entry published"); // 错误场景:同时携带错误对象与说明 logger.error({ error, entryId }, "Failed to publish content entry"); // 调试:需要时通过 WEBINY_API_LOG_LEVEL=debug 打开 logger.debug({ userId }, "Resolving user permissions");

这样每行日志都能被 CloudWatch / 日志平台按字段检索,而不是靠正则去抠字符串。

五、何时不受此规范约束

需要澄清边界:本规范针对的是后端api-*代码。前端/管理端应用(app-*系列包)或浏览器端代码并不在此规则管辖范围内,它们可以使用各自的日志机制。另外,仓库中与日志相关的其他基础设施,如packages/logger包,也面向不同场景,不应与本规范中的api-coreDI Logger 混为一谈。判断标准很简单:只要代码位于api-*包内,就用@webiny/api-core/features/logger的Logger。

六、落地检查清单

在后端代码提交前,可对照以下清单自查:

  1. 代码中是否残留console.log/console.warn/console.error?—— 应全部替换。
  2. 是否通过构造器注入了Logger(来自@webiny/api-core/features/logger)?—— 应通过 DI 获取,而非new LoggerImpl()。
  3. 日志调用是否遵循(objOrMsg, ...args)签名,把结构化上下文放第一个参数?
  4. 是否需要输出debug/trace级别?—— 不修改代码,直接通过环境变量WEBINY_API_LOG_LEVEL控制。

遵循这条规范,后端日志将统一进入 pino + pino-lambda 的结构化管道,级别可控、字段可查,也更利于在 AWS Lambda 环境下观测与排障——这正是 no-console-in-backend.md 想要达成的目标。

  • CMS
  • 后端
  • 前端

【免费下载链接】webiny-js

Open-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.

项目地址:https://gitcode.com/gh_mirrors/we/webiny-js
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询