Backend

日志

Malagu 默认提供的日志功能比较简单,底层是运行时 console 对象实现。

如果我们想要功能更为强大的日志,可以选择@malagu/logger组件,该组件是基于 winston 库实现。当然,我们也可以很容集成其他第三方日志库。在 Serverless 场景,使用默认的日志功能实现就能满足需求,因为 Serverless 平台会自动帮我们收集日志,方便我们查看与分析应用。

当我们想要更为强大的日志功能的时候,可以选择@malagu/logger组件,该组件提供强大日志格式自定义能力,同时支持日志输出到文件,以及日志文件按日期自动滚动更新。

我们在实现或者扩展日志模块的时候,最好能同时兼顾前后端日志的输出。

日志级别

框架的日志模块抽象了四种日志级别(由低到高):debuginfowarnerror。每个日志级别都对应着一个日志方法可以调用。框架提供了一个通用的属性配置用于配置日志级别,其他自定义的日志接口实现,强烈建议支持上述日志级别和属性配置。属性配置如下:

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 接口提供。


Copyright © 2024 Zero (github@groupguanfang) 粤ICP备2023102563号