AppSignal APM을 NestJS와 통합하여 얻은 교훈

Superface에서는 백엔드에 NestJS 프레임워크를 사용하고 모니터링 및 오류 추적에 AppSignal APM을 사용합니다. AppSignal은 Node.js 통합을 제공하지만 NestJS로 시작하고 실행하는 것은 다소 까다로웠습니다.

이 블로그 게시물에서는 AppSignal이 NestJS와 작동하도록 관리하는 방법을 공유합니다.

이 블로그 게시물에 사용된 코드 스니펫은 example project 의 일부입니다.

AppSignal 초기화 및 구성



AppSignal은 Node.js 도구 및 프레임워크(Express, Koa, PostgreSQL, Redis 등)에 후크를 연결하고 호출할 특정 함수를 관찰하는 자동 계측을 사용합니다. 함수가 호출되면 계측은 애플리케이션을 대신하여 추적 범위를 자동으로 수집합니다.

AppSignal에는 자동 계측 작업을 수행하기 위한 다음 요구 사항(AppSignaldocs에서 가져옴)이 있습니다.

To auto-instrument modules, the Appsignal module must be both required and initialized before any other package.



NestJS에서 개체를 인스턴스화하는 표준 방법은 종속성 주입(DI) 컨테이너를 사용하는 것입니다.

요구 사항을 충족하기 위해 NestJS DI 컨테이너를 사용하여 AppSignal을 인스턴스화할 수 없습니다. AppSignal은 전역 변수로 인스턴스화되어야 합니다. 즉, NestJS를 활용할 수 없습니다ConfigModule.

환경 변수를 사용한 AppSignal 인스턴스화 및 구성의 예:

//source file: src/appsignal.ts

const name = process.env.APPSIGNAL_NAME;
const pushApiKey = process.env.APPSIGNAL_PUSH_API_KEY;
const active =
  process.env.APPSIGNAL_ACTIVE === '1' ||
  process.env.APPSIGNAL_ACTIVE === 'true';

export const appsignal = new Appsignal({
  active,
  name,
  pushApiKey,
});


source code

또한 NestJS 애플리케이션 부트스트랩 코드에서 Express를 초기화할 때 AppSignal 미들웨어를 등록해야 합니다.

//source file: src/main.ts

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  app.use(appsignalExpressMiddleware(appsignal));

  await app.listen(3000);
}
bootstrap();


source code

즉, APPSIGNAL_PUSH_API_KEY 환경 변수를 유효한 AppSignal 키로 설정하고 APPSIGNAL_NAME , APPSIGNAL_ACTIVE 환경 변수를 구성하면 AppSignal이 애플리케이션에서 처리하는 모든 HTTP 요청에서 메트릭 수집을 시작합니다.



오류 추적



Nest에는 애플리케이션 전체에서 처리되지 않은 모든 예외 처리를 담당하는 기본 제공 예외 레이어가 함께 제공됩니다. 자세한 내용은 NestException filters docs를 참조하십시오.

Nest 예외 필터에서 처리하는 오류를 추적하기 위해 NestAppsignalExceptionFilter 인터페이스를 구현하는ExceptionFilter을 만들었습니다.

//source file: src/exception_filters/appsignal_exception.filter.ts

@Catch()
export class AppsignalExceptionFilter<T extends Error>
  implements ExceptionFilter
{
  catch(error: T, _host: ArgumentsHost) {
    let status: number;
    const tracer = appsignal.tracer();

    if (!tracer) {
      return;
    }

    if (error instanceof HttpException) {
      status = error.getStatus();
    }

    if (error && (!status || (status && status >= 500))) {
      tracer.setError(error);
    }
  }
}


source code
AppsignalExceptionFilter는 상태 코드 5xx 및 기타 예외 유형이 있는 HttpException 예외를 추적합니다.

사용자 정의 예외 필터 구현에서 확장하여 AppsignalExceptionFilter를 사용하고 Nest 앱에서 예외 필터를 등록할 수 있습니다.

확장의 예AppsignalExceptionFilter :

//source file: src/exception_filters/all_exception.filter.ts

@Catch()
export class AllExceptionFilter extends AppsignalExceptionFilter<Error> {
  catch(error: Error, host: ArgumentsHost) {
    super.catch(error, host);

    const ctx = host.switchToHttp();
    const req = ctx.getRequest<Request>();
    const res = ctx.getResponse<Response>();

    const status = 500;

    const problem = {
      status,
      title: 'Internal server error',
      instance: req.path,
    };

    res.status(status).contentType('application/problem+json').json(problem);
  }
}


source code

전역 필터 등록의 예:

//source file: src/main.ts

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  app.use(appsignalExpressMiddleware(appsignal));
  app.useGlobalFilters(new AllExceptionFilter());

  await app.listen(3000);
}
bootstrap();


source code

@nestjs/bull 프로세스 모니터링



NestJS 외에도 백그라운드 작업 처리를 위해 Bull을 사용합니다. NestJS는 Bull의 래퍼로 @nestjs/bull 패키지를 제공합니다.

AppSignal은 Bull 작업을 자동으로 추적하지 않습니다. 다행스럽게도 Appsignal custom instrumentation을 사용하여 추적을 처리할 수 있습니다.

Bull 작업을 추적하기 위해 Bull 프로세스 데코레이터ProcessMonitor를 만들었습니다.

//source file: src/bull/process_monitor.decorator.ts

export function ProcessMonitor(): MethodDecorator {
  return function (
    target,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const method = descriptor.value;

    descriptor.value = async function (...args: any) {
      const tracer = appsignal.tracer();

      const span = tracer.createSpan({
        namespace: 'worker',
      });
      span.setName(`JOB ${this.constructor.name}.${propertyKey}`);
      span.setCategory('job.handler');

      const job = args[0];

      if (job) {
        span.setSampleData('custom_data', { jobId: job.id });
      }

      let result;
      await tracer.withSpan(span, async span => {
        try {
          result = await method.bind(this).apply(target, args);
        } catch (error) {
          span.setError(error);
          throw error;
        } finally {
          span.close();
        }
      });

      return result;
    };
  };
}


source code
ProcessMonitor 메서드 데코레이터는 worker 네임스페이스에 새 스팬을 만들고, 작업 ID를 수집하고, 예외가 발생할 경우 오류가 있는 스팬을 설정합니다.
ProcessMonitor 데코레이터를 코드 베이스에 추가하면 Bull 큐 프로세서 메서드를 데코레이션하여 사용을 시작합니다.

export const MAILING_QUEUE = 'mails';
export const SEND_EMAIL = 'send_email';

@Processor(MAILING_QUEUE)
export class MailingProcessor {

  @Process(SEND_EMAIL)
  @ProcessMonitor()
  async sendEmail(job: Job) {
    ...
  }
}


우아한 AppSignal 중지



기본적으로 @appsignal/nodejs는 Node.js V8 힙 통계를 추적하는 minutely probes을 시작합니다. 이 기능은 Node.js 내부에 대한 통찰력을 제공합니다.



아쉽게도 미세 프로브가 활성화된 경우 stop 메서드를 호출하여 명시적으로 프로브를 중지해야 합니다. 그렇지 않으면 지원 프로세스가 정상적으로 중지되지 않습니다.

Nest는 onApplicationShutdown lifecycle event 와 함께 제공되며 AppSignal 중지 방법을 호출하기에 적합한 위치입니다. 아래의 AppsignalShutdownService 구현 예를 참조하십시오.

//source file: src/appsignal_shutdown.service.ts

@Injectable()
export class AppsignalShutdownService implements OnApplicationShutdown {
  onApplicationShutdown(_signal: string) {
    appsignal.stop();
  }
}


source code

Nest 애플리케이션 모듈에 AppsignalShutdownService를 추가하는 것을 잊지 마십시오.

//source file: src/app.module.ts

@Module({
  providers: [AppsignalShutdownService],
})
export class AppModule {}


source code

좋은 웹페이지 즐겨찾기