- 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.
本篇技术指南聚焦 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 use
console.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 对应方法 }要点拆解:
- pino 核心:所有日志方法(
trace到fatal)最终都委托给内部的 pino logger,因此天然支持 JSON 结构化输出、多级过滤与低开销。 - pino-lambda 目标:日志目的地使用
pino-lambda的pinoLambdaDestination+StructuredLogFormatter,这是为 AWS Lambda 运行环境设计的日志管道(源码注释也说明pino-lambda目前是硬编码选择,原因是其初始化依赖 Lambda 函数上下文,后续若有更好的基础设施会重构)。 - 日志级别可配置:
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。
六、落地检查清单
在后端代码提交前,可对照以下清单自查:
- 代码中是否残留
console.log/console.warn/console.error?—— 应全部替换。 - 是否通过构造器注入了
Logger(来自@webiny/api-core/features/logger)?—— 应通过 DI 获取,而非new LoggerImpl()。 - 日志调用是否遵循
(objOrMsg, ...args)签名,把结构化上下文放第一个参数? - 是否需要输出
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.
相关推荐
Webiny api-core 与后端特性参考手册:基于 core-features-reference 的导入路径、抽象类型与实战用法全解析
Webiny api core 与后端特性参考手册:基于 core features reference 的导入路径、抽象类型与实战用法全解析 Webiny(w
CMS后端前端CocoaLumberjack 按 Logger 独立设置日志级别(Per-Logger Log Levels)实战指南
CocoaLumberjack 按 Logger 独立设置日志级别(Per Logger Log Levels)实战指南 导读 CocoaLumberjack
开发工具Webiny 后端开发指南:基于 Feature 的 Clean Architecture 与类型安全 DI 实践
Webiny 后端开发指南:基于 Feature 的 Clean Architecture 与类型安全 DI 实践 导读 本指南是 Webiny(开源、可自托管
CMS后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考