使用 NestJS Dynamic Module 创建个性化的 Logger 实例

很久没写公众号了,比较忙。。。

今天分享一篇 NestJS Dynamic Module 实战示例,以加深自己对它的理解。


1背景

发现项目中使用 NestJS Logger 时都是这样用:

@Injectable()
class HelloService {
  private readonly logger = new Logger('HelloService');
}

@Injectable()
class WorldService {
  private readonly logger = new Logger('WorldService');
}

这种重复于多处的、手动的初始化代码让人感觉不舒服。于是想,有更方便的管理办法吗?比如,让 NestJS 管理这些 Logger 的初始化和注入?

2想要的效果

我们期望达到这种效果:

@Injectable()
class HelloService {
  constructor(@Logger('HelloService'private readonly logger: LoggerService) {}
}

即,当需要使用 logger 时,通过 @Logger 指令自动注入,并且,项目中不会出现 new Logger() 这类操作

(还有一小点要注意的是,这里我们使用的是 LoggerService ,而非 Logger,前者是接口,后者实现了前者,我们倾向于 面向接口编程。)

那么如何实现呢?

3如何实现自定义注入指令

通常我们使用 @Inject 来管理注入。但如果使用 @Inject('HelloService'),名为 'HelloService'logger 对象其实并不存在,而且它和 HelloService 这个 class 还会命名冲突。

鉴于此,我们打算基于 @Inject 指令,开发一个新的指令——就叫它 @Logger 吧!

@Logger 的代码如下:

import { Inject } from '@nestjs/common';

export const prefixesForLoggers: string[] = [];

export function Logger(prefix = ''{
  if (!prefixesForLoggers.includes(prefix)) {
    prefixesForLoggers.push(prefix);
  }
  return Inject(`${prefix}Logger`);
}

可以看到,@Logger 要做的是,当使用 @Logger('HelloService') 时,就是在使用 @Inject('HelloServiceLogger')

你可能会疑惑,名为 'HelloServiceLogger' provider 是在哪里注册的?别急,上面的 prefixesForLoggers 数组就是为了动态地注册这些 logger providers 做准备的。

4动态注册 logger providers

首先,由于每个使用到 logger 的组件都是用各自的 logger 实例(因为 logger 的名称(token)各不相同),所以我们需要先定义一个 ScopeTRANSIENT 的 logger provider:

文件:logger.provider.ts

import { prefixesForLoggers } from './logger.decorator';
import { Logger, Provider, Scope } from '@nestjs/common';

export const baseLoggerProvider = {
  provide: Logger,
  useClass: Logger,
  scope: Scope.TRANSIENT,
};

它的作用有2个,一是所有的 logger 实例都由它提供;二是每个组件拿到的都是新的 logger 实例。

接下来就是根据 @Logger 的调用信息,动态配置 baseLoggerProvider 生成的 logger 实例:

// 根据 logger 的 prefix 参数,动态生成 logger provider
function createLoggerProvider(prefix: string): Provider<Logger{
  const provider = {
    provide: `${prefix}Logger`,
    useFactory: (logger: Logger) => {
      if (prefix) {
        logger.setContext(prefix);
      }
      return logger;
    },
    inject: [Logger],
    scope: Scope.DEFAULT,
  };
  return provider;
}

export function createLoggerProviders(): Array<Provider<Logger>> {
  return prefixesForLoggers.map((prefix) => createLoggerProvider(prefix));
}

最后,我们用一个 LoggerModule 来集成上面的封装:

import { DynamicModule } from '@nestjs/common';
import { baseLoggerProvider, createLoggerProviders } from './logger.provider';

export class LoggerModule {
  static forRoot(): DynamicModule {
    const prefixedLoggerProviders = createLoggerProviders();
    return {
      module: LoggerModule,
      providers: [baseLoggerProvider, ...prefixedLoggerProviders],
      exports: [baseLoggerProvider, ...prefixedLoggerProviders],
    };
  }
}

注意,这里我们使用的是 DynamicModule。因为按照官方的说法[1],当模块间 被消费的一方(LoggerModule)的运行依赖于消费的一方(某个 import 了 LoggerModule 的模块)时,需要用 Dynamic modules 进行编写。换言之,当消费的一方需要向被消费的一方传递参数以帮助被消费一方完成初始化时,使用 DynamicModule 方式来编写。

(应用到本文,则是 LoggerModule 的运行需要收集 @Logger 调用的参数,以生成动态的 logger provider 定义,而这些参数很明显是消费方传递过来的,故而使用 DynamicModule)。

5使用

一个简单的使用示例如下:

HelloModule.ts:

import { Module } from '@nestjs/common';
import { HelloService } from './hello.service';
import { LoggerModule } from '../logger/logger.module';

HelloService.ts,我们注入2个 logger ,以观察是否符合预期:

import { Injectable, LoggerService } from '@nestjs/common';
import { Logger } from '../logger/logger.decorator';

export class HelloService {
  constructor(
    @Logger('Hello1Service'private readonly logger1: LoggerService,
    @Logger('Hello2Service'private readonly logger2: LoggerService,
  
) {}
}

调试代码,可以看到 2个logger 都被正确地创建了:

使用 NestJS Dynamic Module 创建个性化的 Logger 实例

6总结

休息,不总结了 🤣 。


参考资料

[1]

Dynamic modules: https://docs.nestjs.com/fundamentals/dynamic-modules

[2]

Advanced NestJS: How to build completely dynamic NestJS modules: https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370

[3]

Advanced NestJS: Dynamic Providers: https://dev.to/nestjs/advanced-nestjs-dynamic-providers-1ee


原文始发于微信公众号(背井):使用 NestJS Dynamic Module 创建个性化的 Logger 实例

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/246903.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!