Backend
日志
Malagu 默认提供的日志功能比较简单,底层是运行时 console 对象实现。
如果我们想要功能更为强大的日志,可以选择@malagu/logger
组件,该组件是基于 winston 库实现。当然,我们也可以很容集成其他第三方日志库。在 Serverless 场景,使用默认的日志功能实现就能满足需求,因为 Serverless 平台会自动帮我们收集日志,方便我们查看与分析应用。
当我们想要更为强大的日志功能的时候,可以选择@malagu/logger
组件,该组件提供强大日志格式自定义能力,同时支持日志输出到文件,以及日志文件按日期自动滚动更新。
我们在实现或者扩展日志模块的时候,最好能同时兼顾前后端日志的输出。
日志级别
框架的日志模块抽象了四种日志级别(由低到高):debug
、info
、warn
、error
。每个日志级别都对应着一个日志方法可以调用。框架提供了一个通用的属性配置用于配置日志级别,其他自定义的日志接口实现,强烈建议支持上述日志级别和属性配置。属性配置如下:
malagu.yml
malagu:
logger:
level: error
其中,框架默认日志级别为 info,后端默认日志级别为 error。
使用方式
import { Middleware } from "../middleware";
import { Context } from "../context";
import { Autowired, Component, Logger } from "@malagu/core";
import { HTTP_MIDDLEWARE_PRIORITY } from "./http-protocol";
@Component(Middleware)
export class HttpMiddleware implements Middleware {
/**
* 注入框架提供的日志服务类,该类为非单例对象
*/
@Autowired(Logger)
protected readonly logger: Logger;
async handle(ctx: Context, next: () => Promise<void>): Promise<void> {
const method = ctx.request.method;
const path = ctx.request.path;
// 记录 info 级别的日志
this.logger.info(`starting ${method} ${path}`);
const now = Date.now();
try {
await next();
const response = ctx.response;
if (!Context.isSkipAutoEnd() && !response.writableEnded) {
response.end(response.body);
}
} finally {
this.logger.info(`ending ${method} ${path} [${Date.now() - now}ms]`);
}
}
readonly priority = HTTP_MIDDLEWARE_PRIORITY;
}
自定义日志服务实现
我们可以实现以下日志接口,并以 Logger
为 ID 注入到 IoC 容器,并设置 rebind 为 true,即可替换框架默认的日志实现。
export interface Logger {
/*
* 设置日志对象级别的上下文信息,在打印日志的时候,都会带上该上下文
* 如果打印日志的时候额外制定了日志方法级别的上下文,则会覆盖日志对象级别的上下文
*/
setContext(context?: string): void;
info(message: any, context?: string): void;
error(message: any, context?: string): void;
warn(message: any, context?: string): void;
debug(message: any, context?: string): void;
}
自定义实现日志服务:
import { Logger, Component, Scope } from "@malagu/core";
import { AbstractLogger } from "./abstract-logger";
@Component({ id: Logger, rebind: true, scope: Scope.Transient })
export class LoggerImpl extends Logger {
setContext(context?: string): void {
// TODO
}
error(message: any, context?: string): void {
// TODO
}
info(message: any, context?: string): void {
// TODO
}
warn(message: any, context?: string): void {
// TODO
}
debug(message: any, context?: string): void {
// TODO
}
}
日志服务对象一般不能是单例,所以需要添加额外参数 scope 为 Scope.Transient。另外框架提供了一个日志服务抽象类 AbstractLogger,结合实际情况使用该抽象类简化自定义日志服务类的实现难度。
日志默认输出格式
2023-08-20T06:39:23.865Z [info] [trace: 96830560-6e5c-45cb-9a08-b25ec16b1ca4] starting GET /
2023-08-20T06:39:23.876Z [info] [trace: 96830560-6e5c-45cb-9a08-b25ec16b1ca4] ending GET / [10ms]
其中的 trace 部分的日志并不是总是存在,只有当检查到环境中存在 traceId 的时候,才会自动附加上。检查的逻辑通过 TraceIdProvider 接口提供。