본문 바로가기
개발 일지/카카오VX

온체인마켓 LOG 적재 Apm+Kibana

by StelthPark 2024. 8. 16.

1.  작업 배경

NFT 거래시 API 로그를 남겨 유저가 어떤 행위를 한지 가늠하게 되며 차후 CS가 들어와도 처리하기 쉽고자 키바나에 로그가 적재 되도록 한다.

 

2.  작업 방향

nestJs로 개발된 NFT 마켓플레이스 백엔드 API 서비스에서 각 모듈 별 컨트롤러마다 인터셉터를 달게 된다. http요청 완료 되면 apmService를 통해 요청과 관련된 자세한 정보나 서버에서 발생한 에러를 기록하게 된다. 우선 apm 서비스가 필요했다.

 

@Injectable()
export class ApmService {
  private readonly apm: APM.Agent;

  constructor() {
    this.apm = apm;
  }

  // http 요청과 관련된 자세한 정보를 custom하여 저장
  setCustomContext(context: Record<string, unknown>): void {
    this.apm.setCustomContext(context);
  }

  // 서버에서 발생한 에러를 APM에 캡쳐해서 로깅할 때
  captureError(data: any): void {
    this.apm.captureError(data);
  }

  addLabels(context: Labels): void {
    this.apm.addLabels(context);
  }

  loadApmLog(params: ErrorExceptionDto) {
    try {
      this.setCustomContext({
        responseBody: params.requestData,
      });
      this.addLabels({
        exceptionErrorbacktrace: params.backtrace,
      });
    } catch (error) {
      this.captureError(error);
    }
  }
}

 

서버에서 성공적인 요청을 처리할 때와 실패했을 때 사용할 함수들을 생성해주었다. setCustomContext는 정상적으로 200요청에 맞춰 로그를 저장할 때 사용, captureError와 loadApmLog는 로그 적재중 오류 또는 요청 api 처리중 알수 없는 에러 등에 대해 로그 적재에 사용된다.

 

그렇다면 setCustomContent부터 어떻게 사용되는지 확인해보자. 우선 apmService를 주입받는 ApmInterceptor가 있어야 할 것이다. 이 인터셉터는 컨틀롤러 단 마다 @UseInterceptors(ApmInterceptor)를 사용해서 해당 컨트롤러 전역에서 적용되도록 하였다.

 

@UseInterceptors(ApmInterceptor)
export class MarketsController {
  constructor(private readonly marketsService: MarketsService) {}
  ...
 }

 

 

ApmInterceptor 내부에서는 context.switchToHttp().getRequest()를 통해 HTTP 요청 객체를 가져옵니다. 요청 객체에서 originalUrl, method, body를 추출하게 된다. 추출된 값들을 차례로 setCustomContext 파라미터로 날려주게 된다. 적재에 실패하게 된다면 captureError로 발생한 오류만 기록한다. 마지막으로 정상적으로 http요청이 완료되면 responseBody값들도 함께 로그를 적재한다.

    try {
      this.apmService.setCustomContext({
        requestBody: { originalUrl, method, body },
      });
    } catch (error) {
      this.apmService.captureError(error);
    }
    
    // http 요청이 완료 되면 다시 한번 response 정보를 저장
    return next.handle().pipe(
      map((data: any) => {
        this.apmService.setCustomContext({
          responseBody: data,
        });
        return data;
      }),
    );

 

그렇다면 this.apmService.loadApmLog(apmParams) 는 무슨역할을 할까? loadApmLog는 http-exception.filter 내부에 존재하게 된다. http 예외가 발생했을 때 관련 데이터를 포함해 apm 로그를 로드하게 된다.

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  constructor(private readonly apmService: ApmService) {}
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const apmParams: ErrorExceptionDto = { backtrace: exception.stack, requestData: request.params };
    this.apmService.loadApmLog(apmParams);
  }
}

 

 

3. REVIEW

정상적으로 처리된 로그는 어떻게 쌓이는지 개발 키바나에서 확인해본다.

 

자세히 보면 특정 유저가 13673번 NFT를 구매가능한지 확인하는 api를 요청했다. 이는 NFT 상세보기페이지로 접근하는 순간 요청되는 api이다. 이는 즉 유저가 어느 페이지에 접근하고 있는지도 추측이 가능해진다. 그 후 order number 903으로 보라에 구매요청양식 API를 날리게 된다. 이 2개의 api 요청 결과를 통해 13673 NFT 구매의 orderNumber는 903인것을 알 수 있게 된다. 

 

정상적으로 요청하고 처리됐다면 responseBody값이 실려있을 것이다. 다른 API도 JSON형태로 열어서 본다.

 

NFT구매를 한 뒤 블록체인에 처리된 데이터가 어떤것인지 받게 되었다. 받은 데이터 값을 조합 한다면, 이를 통해 퍼블릭한 네트워크인 블록체인 스코프에서 어떻게 NFT가 투명하게 처리되었는지도 그 자료로 유저도 확인할 수 있게 된다. NFT 구매/판매를 포함안 온체인마켓 거래에 대한 CS인입에 더욱 도움이 될 수 있었고 RDB에도 적재중인 히스토리 내역과 키바나, 그리고 블록체인 스코프까지 활용하여 보다 정확하게 유저CS에 대응할 수 있게 되었다.

댓글