0.0.3 • Published 11 months ago

@tecace/nest-opentelemetry v0.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

@tecace/nest-opentelemetry

description

基于官方 npm 封装 nest-opentelemetry 工具,无侵入式劫持 controller/provider,提供装饰器手动劫持非 nestjs 代码、忽略采集,自动上报 traces 和 metrics

usage

安装

npm install @tecace/nest-opentelemetry

使用

import { OTELModule } from '@tecace/nest-opentelemetry';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';

@Module({
  imports: [
    OTELModule.forRoot({
      serviceName: 'user-service',
      traceExporter: new OTLPTraceExporter({
        url: 'http://otelcol:4318/v1/traces', // 自搭建opentelemetry-collector
      }),
      metricReader: new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter({
          url: 'http://otelcol:4317/v1/metrics', // 自搭建opentelemetry-collector
        }),
        exportIntervalMillis: 5000,
      }),
    }),
  ]
})

initOptions

初始化选项在@opentelemetry/sdk-node 初始化选项 NodeSDKConfiguration 基础上扩展了一个 injectProviderPattern,其余选项均与@opentelemetry/sdk-node 初始化选项保持一致

选项说明默认
injectProviderPattern通过正则指定哪些 provider 需要追踪,,默认以 Service 结尾的 Provider class 都会被劫持injectProviderPattern: \/Service$\/
其余选项与 NodeSDKConfiguration 保持一致@opentelemetry/sdk-node 初始化选项https://www.npmjs.com/package/@opentelemetry/sdk-node

decorators

@CustomSpan() 独立 Span

适用于 Controller 或 Provider 类中的方法

import { OTELService, CustomSpan } from '@tecace/nest-opentelemetry';

@Controller()
export class UserController {
  constructor(
    private otelService: OTELService,
  ) {}

  @CustomSpan() // 如果需要独立span,必须使用@CustomSpan装饰
  async getUserInfoByName(data: any): SomeUserInfo {
    // 启动一个独立span
    const currentSpan = this.otelService.startSpan(
      'UserController.customGetUserInfo',
    );
    // 业务代码在放这里,业务结束后调用currentSpan.end()
    .......
    currentSpan.end();
    return someUserInfo;
  }
}

@TraceIgnore() 忽略追踪

适用于 Controller,Provider 类,或类中的方法

  • 装饰类的时候,整个类中的所有方法都不会追踪
  • 装饰方法的时候,不会追踪被装饰的方法
import { TraceIgnore } from '@tecace/nest-opentelemetry';

@TraceIgnore() // 忽略此控制器下所有方法的追踪
@Controller()
export class UserController {

  async getUserInfoByName(data: any): SomeUserInfo {
    // 业务代码
    .......
    return someUserInfo;
  }
}
import { TraceIgnore } from '@tecace/nest-opentelemetry';

@Controller()
export class UserController {
  @TraceIgnore() // 忽略getUserInfoByName方法的追踪
  async getUserInfoByName(data: any): SomeUserInfo {
    // 业务代码
    .......
    return someUserInfo;
  }
}

@CustomTrace() 用于非 nestjs 模块的追踪

非侵入式劫持 controller,provider 的原理是基于 nestjs 的依赖注入,获取到@Controller(),@Injectable()装饰的类及其对应的方法

如果我们封装了其他工具(例如基于 sequelize 封装的 dao 层),是不需要依赖注入的,那么就无法进行扫描,为了提供追踪功能,非 nestjs 模块的追踪需要实现以下条件

  • 使用@CustomTrace()装饰
  • 必须使用 class,并且实现单例模式,实现无参数的 static getInstance 方法

注意

如果一个工具(或模块)依赖于其他实例,同时其实例化要求实现一个不带参数的 static getInstance 方法,该怎么实现呢?

举一个例子,假设有一个 dao 工具,它是基于 model 实例封装的一层,其中 userDao.create 方法依赖于 userModel 实例的 create 方法。我们可以设计一个 init 方法,支持链式调用,以传入 userModel 实例并返回一个单例实例。

这种情况为什么会出现呢?在 NestJS 中,Sequelize 的 Model 可以直接在 provider 或 controller 中注入。而对于不使用 NestJS 的工具或模块,如果要基于 Model 封装一层,则需要将 Model 实例作为参数传递。

// user.dao.ts
import { CustomTrace } from '@tecace/nest-opentelemetry';

@CustomTrace() // 1.使用CustomTrace装饰
export class UserDao {
  userModelInstance!: typeof UsersModel;

  static instance: UserDao;
  // 2. 实现无参数的static getInstance方法
  static getInstance() {
    if (!this.instance) this.instance = new UserDao();
    return this.instance;
  }
  // 在工具外部就可以进行链式调用
  // const userDao = UserDao.getIntance().init(userModel)
  // userDao.create()
  init(userModelInstance: typeof UsersModel) {
    this.userModelInstance = userModelInstance;
    return UserDao.getInstance();
  }

  async create(data: any): Promise<any> {
    this.userModelInstance.create(data as Optional<any, any>);
    return await to(this.modelInstance.create(data as Optional<any, any>));
  }
}
// user.service.ts

import UserDao from './user.dao.ts';

@Injectable()
export class UserService {
  private readonly userDao!: UserDao;

  constructor(@InjectModel(UsersModel) private usersModel: typeof UsersModel) {
    this.userDao = UserDao.getInstance().init(this.usersModel); // 链式调用
  }

  async createUser(data: any): any {
    const [err, res] = await this.userDao.create(data);
    if (!err) return res;
  }
}