[Nest.js] 로깅은 왜 중요하고, 어떻게 찍히는 것일까? 내장 로거를 직접 만들어보며 알아보자!

초이지수·2023년 7월 1일
1

Nest.js

목록 보기
5/14

Nest.js로 프로젝트를 하던 중, 365/24로 관리하는 시스템은 로그가 중요하다고 하는데, 왜 중요한 것인지궁금해졌다.

로그가 왜 중요한 것인지, Nest.js에서 기본적으로 제공하는 내장 Logger는 어떻게 구성이 되어 있는지 직접 만들어 보며 알아보기로 했다!


📌 1. 로그(Log)와 로깅(Logging)이란?

✔️ 로그

프로그램 개발이나 운영 시 발생하는 문제점을 추적하거나 운영 상태를 모니터링하기 위한 텍스트

✔️ 로깅

로그를 생성하고 기록하는 작업이나 프로세스

로깅을 통해 개발자는 개발 과정 or 개발 후에 발생할 수 있는 예상치 못한 애플리케이션의 문제를 진단할 수 있고, 다양한 정보를 수집할 수 있다.

또, 유저의 사용 패턴을 분석(어떤 기능이 많이 사용되는지)하는 데에도 로그를 활용할 수 있다.


📎 1-2. 로깅을 사용하는 이유

1. 디버깅 및 문제 해결 - 버그 수정과 문제 해결에 용이
로깅은 애플리케이션의 동작을 추적하고 디버깅하는데 도움을 준다. 실행 중인 애플리케이션에서 발생하는 문제를 파악하고 오류 메시지, 예외 스택 트레이스 등을 통해 문제의 원인을 분석할 수 있다.

2. 성능 모니터링
실행 시간, 리소스 사용량, 네트워크 요청, 데이터베이스 쿼리 등의 정보를 로그로 기록해 성능 문제를 식별하고 최적화 할 수 있다.

3. 보안 감시
로그를 통해 악의적인 행위나 보안 위협을 탐지하고, 이를 조기에 대응할 수 있다.
로그인 실패, 권한 부여 실패, 비정상적인 액세스 시도 등의 이벤트를 기록하여 보안 사고를 탐지할 수 있다.

4. 운영 및 모니터링
로그는 애플리케이션의 운영과 모니터링에 필수적이다. 로그를 통해 애플리케이션의 상태, 실행 흐름, 작업 완료 여부 등을 추적할 수 있고, 이를 통해 애플리케이션 운영에 필요한 조치를 취할 수 있다.


🤔 그렇다면, 로그는 많으면 많을 수록 좋은 것일까?

365/24로 관리하는 시스템에서 로그는 굉장히 중요하지만, 로그가 중요하다는 생각에 무분별하게 남기는 것은 좋지 않다. 적절한 수준의 로그 기록 기준을 잡지 못하면 방대한 양의 로그 파일이 생성되고, 의미 있는 로그를 쌓지 못하는 경우가 발생할 수 있다.

예를 들어 예외 상황이 발생하면 ERROR 레벨로 로그를 남기는 경우, 보통의 서비스에서는 시간 내 에러 로그가 일정 수치 이상 쌓이면 알람을 발생시키도록 구성한다.

정상적이지 않은 모든 상황에서 전부 ERROR 레벨로 처리하게 되면, 불필요하게 많은 알람들로 인해 정작 확인해야 할 심각한 에러 로그들을 놓칠 수 있다!

그래서 적정 수준에서 로그 레벨을 구분해 알람 경보 수준을 구분하는 것이 필요하다.

(로그 레벨은 4-1의 isLogLevelEnabled에서 더 깊게 알아보자!)


📌 2. 내장 Logger 클래스를 이용해 HTTP 요청, 응답에 대한 로깅을 처리해보자!

Nest.js에서 제공하는 내장 Logger 클래스는 @nest/common 패키지로 제공된다.

Nest.js에서 로깅을 사용하려면 Logger 모듈을 Nest.js 애플리케이션에 주입하고, 해당 모듈에서 logger 객체를 사용해 로그를 기록 할 수 있다.

이렇게 간단하게 사용할 수 있도록 Logger모듈을 어떻게 구성해 놓았는지, 어떤 코드로 작성되어있는지 더 자세히 알아보도록 하자!


📎 2-1. 기본적으로 세팅되어있는 logging 코드

일단 Nest.js 공식문서에 나와있는 명령어로 프로젝트를 실행했다.

$ nest new 프로젝트이름

프로젝트를 실행하니, 기본적으로 세팅된 코드에서 로그가 찍혔다.

Nest 애플리케이션의 진입점인 main.ts 파일에 로깅이 설정되어 활성화 되고 있었다.


  • src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

로깅은 NestFactory.create(AppModule)을 통해 애플리케이션 인스턴스를 생성할 때부터 시작된다.

🖇 인스턴스
객체 지향 프로그래밍에서 클래스를 기반으로 생성된 구체적인 실체
클래스는 객체를 정의하는 템플릿이라고 볼 수 있다.
이를 이용해 인스턴스를 생성하면 메모리에 실제로 데이터를 담을 수 있는 객체가 만들어진다!


🖇 터미널에서 기본으로 세팅된 내장 로그를 찍어본 모습

  1. Nest.js 애플리케이션의 시작을 나타내는 로깅
    (NestFactory의 내부 코드에서 출력)

  2. AppModule의 종속성이 초기화되었음을 나타내는 로깅
    Nest.js의 의존성 주입 시스템을 통해 AppModule에서 사용되는 다른 모듈, 서비스, 컴포넌트 등의 종속성이 준비되고 초기화 되었다는 것을 의미한다.
    (InstanceLoader의 내부 코드에서 출력)

  3. 루트 경로를 처리하는 AppController가 생성되었음을 나타내는 로깅
    (RoutesResolver의 내부 코드에서 출력)

  4. 루트 경로에 대한 GET 요청을 처리할 수 있음을 나타내는 로깅
    (RouterExplorer의 내부 코드에서 출력)

  5. Nest.js 애플리케이션이 성공적으로 시작되었음을 나타내는 로깅
    (NestApplication의 내부 코드에서 출력)


위의 코드들은 Nest.js 프레임워크의 핵심 패키지인 @nestjs/core 에 작성되어있다.

Logger 클래스에 대한 코드를 이해하는 것이 목적이기 때문에 (Nest.js의 핵심 패키지를 살펴보는 것 보단) HTTP 요청과 응답에 대한 로깅을 처리해주는 코드를 작성하고, 해당 코드가 @nestjs/common 패키지의 Logger 클래스에서 어떻게 처리되고 있는지 확인해보기로 했다!


📎 2-2. Logger 클래스를 사용해 HTTP 요청과 응답에 대한 로깅 처리를 해주는 코드를 작성해보자!

Nest.js 에서는 인터셉터를 사용해 전역적으로 로깅 작업을 수행할 수 있다.

app.useGlobalInterceptors()를 사용해, 애플리케이션 전체에서 HttpLoggingInterceptor를 적용해 request, response 로깅을 처리해주는 코드를 작성해보도록 하자!


  • Nest.js 프로젝트 파일 구성

  • src/main.ts

  • src/common/http-logging.interceptor.ts

1. Logger 인스턴스 생성

  • private logger = new Logger(HttpLoggingInterceptor.name) 에서 Logger 클래스를 사용해, HttpLoggingInterceptor 클래스 내에서 logger 인스턴스를 생성하고 있다. 이를 통해 로깅 작업을 수행할 수 있다.

2. 로그 기록

  • tap 연산자를 사용해 HTTP 요청 및 응답 정보를 가로채서 로깅 작업을 수행하는 역할을 한다. logger.log() 메서드를 호출해 로그를 기록한다.

  • 로그에는 요청의 메서드, URL, 쿼리 매개변수, 경로 매개변수, 본문 등의 정보와
    응답의 상태 코드, 데이터가 포함된다.


🖇 postman으로 get요청을 보낸 후 찍힌 로그


📌 3. Logger 클래스를 직접 만들어 보자!

Nest.js에서 로그를 기록할 때 사용되는 @nestjs/common 패키지Logger 클래스 를 직접 만들어보며 이해해보자!


logger.service.ts 파일에는

import { Injectable, Optional } from '../decorators/core';
import { isObject } from '../utils/shared.utils';
import { ConsoleLogger } from './console-logger.service';
import { isLogLevelEnabled } from './utils';

이와 같이 import가 되어 있는데
ConsoleLogger, Injectable, Optional는 @nestjs/common 패키지에서 가져오기로 하고, 추후에 이 클래스들도 직접 만들어 보기로 하자 지금은 Logger에 직접적으로 관련된 코드에 집중
isLogLevelEnabled, isObject 클래스는 직접 만들어 보기로 했다.


isLogLevelEnabled로그 레벨에 관련된 함수이고,
isObject객체가 맞는지 확인 해주는 함수이다.

isLogLevelEnabled 함수부터 만들어보자!


📎 3-1. isLogLevelEnabled

로깅 시스템에서는 로그 레벨을 사용해 로그 메시지를 분류하고 필터링 한다.


🤔 로그 레벨이란?

  • 로그 레벨 : 해당 로그 메시지가 얼마나 중요한지 알려주는 정보

로그 레벨은 담당 개발자가 밤에 계속 잠을 잘 수 있는지, 당장 침대에서 튕겨져나와 노트북과의 진솔한 시간을 가져야 하는지 구분할 수 있게 해준다.오열


로깅 프레임워크들마다 지원하는 로그 레벨들이 조금씩 다른데, Nest.js에 있는 레벨은 다음과 같다.

1. VERBOSE

가장 낮은 로그 레벨
상세한 디버깅 정보를 포함한 모든 로그 메시지를 기록

2. DEBUG

  • 개발 or 테스트 단계에서 해당 기능들이 올바르게 작동하는지 확인하기 위한 로그 레벨
    ex) 데이터베이스 연결이 생성되거나 해제되는 시점 혹은 함수 호출에 사용된 인자와 반환 값 등을 남겨서 개발 및 테스트 과정에서 문제를 추적하고 해결하는데 도움을 준다.

  • 다른 레벨들과 달리 운영 환경에서는 남기고 싶지 않은 로그 메시지를 위한 레벨

3. LOG(INFO)

  • 애플리케이션에서 정상 작동에 대한 정보
    = 어떤 일이 발생했음을 나타내는 표준 로그 레벨

  • 시스템을 파악하는데 유익한 정보여야만함. (무의미한 정보를 남길 필요 없음)

  • 애플리케이션 상태, 설정 또는 외부 리소스와의 상호 작용과 같은 상태 확인을 위한 이벤트를 나타냄
    ex) 인증 API의 백엔드 시스템에서 인증이 성공했는지 여부에 따라 사용자가 인증을 요청한 정보가 남음
    애플리케이션이 시작되거나 종료되는 시점. 중요한 작업이 완료되거나 실행되는 시점(ex) 배치 작업, 정기 작업)

4. WARN

  • 애플리케이션에서 잠재적으로 문제가 될 수 있는 상황일 때 남기는 로그 레벨
    : 개발자가 조취를 취할 수 있도록 주의를 기울일 필요가 있는 상황

  • 예를 들어,
    - 사용자가 로그인에 실패하는 것은 ID, password를 잘 못 입력해 언제든 발생할 수 있는 일반적인 문제 상황. -> WARN 레벨로 책정
    - 로그인이 5번 연속 실패하는 등 특정 기준치를 넘길 경우 -> ERROR 레벨로 책정

  • 이런 경우 사용자에게 노출되는 메시지에 상세한 가이드가 필요한 것이지, 로그 레벨이 ERROR일 필요가 없다.

5. ERROR

애플리케이션에서 발생한 심각한 오류나 예외 상황을 나타내는 로그 레벨

기능 자체가 제대로 작동하지 못하는 문제일 때 남겨야 하며, 즉시 조치가 필요한 상황을 의미한다.
ex) 데이터베이스 연결이 실패한 경우, 내부 시스템의 문제로 결제가 실패하는 경우


자! 이제 isLogLevelEnabled 코드를 작성해보자!

위의 코드에서 로그 레벨이 활성화 되어있다(= targetLevel이 logLevels에 포함되어있다)는 뜻은, 해당 로그 레벨에 대한 로그 메시지가 적절하게 처리되는 상태 라는 뜻이다.

즉, 해당 로그 레벨에 대한 로그 메시지가 설정된 로그 레벨 이상으로 설정 되어 있어야 로그가 기록되거나 처리된다는 의미이다


🤔 isLogLevelEnabled 함수는 어떤 역할을 하고 있나요?

주어진 비교 대상 로그 레벨(targetLevel)이 활성화되어 있는지 확인하는 함수이다.

isLogLevelEnabled 함수에는 두 개의 매개변수가 등장한다.

1. targetLeve
비교 대상 로그 레벨(logLevels 배열에 포함되어 있는지 확인하려는 값)

2. logLevels
활성화된 로그 레벨의 배열


targetLevelValue >= highestLogLevelValue

  • 비교 대상 로그 레벨이 활성화된 로그 레벨보다 우선순위가 높거나 같다는 의미
  • 대상 로그 레벨은 활성화되어 있는 것으로 간주하고 true를 반환한다.

targetLevelValue < highestLogLevelValue

  • 비교 대상 로그 레벨은 활성화된 로그 레벨보다 우선순위가 낮다
  • 대상 로그 레벨은 활성화되지 않은 것으로 간주하고 false 반환한다.

로그 레벨을 비교해 비교 대상 로그 레벨이 최소한의 로그 레벨보다 우선순위가 높은지 확인하는 것이다. 이 함수로 인해, 더 낮은 우선순위의 로그 레벨을 필터링하고 필요한 로깅 작업을 수행할 수 있다!


📎 3-2. 유틸리티 함수 작성 (isObject)

로그와 직접적으로 관련된 코드는 아니지만, Logger 클래스에서 overrideLogger 메서드를 호출 할 때 전달되는 logger 매개변수 값이 객체인지 확인해주는 isUndefined, isObject, isPlainObject, isNull 함수 도 함께 작성해보기로 했다.

shared.utils.ts Nest.js 프로젝트에서 공유되는 다양한 유틸리티 함수들을 정의해놓은 파일에 작성되어 import 되고 있었다.


###$ ✔️ utils란?
utilities의 축약어로 유틸리티 함수를 포함하는 파일이나 모듈

유틸리티 함수는 주로 재사용 가능한 기능을 제공하거나, 특정 작업을 수행하는데 도움을 주는 함수이다. 이러한 함수들은 코드의 간결성, 재사용성, 가독성을 향상시키기 위해 사용된다.


🖇 isUndefined, isObject, isPlainObject, isNull 함수


📎 3-3. Logger 클래스 만들어보기!

Nest.js 애플리케이션에서 로깅 기능을 쉽게 구현할 수 있도록 해주는 Logger 클래스를 만들어보자!


Logger 클래스는 Logger 인스턴스를 주입받아서 사용하거나, 정적 메서드를 통해 전역적으로 로깅 기능을 사용할 수 있다. Logger 클래스는 LoggerService 인터페이스를 구현하며, ConsoleLogger(실제 로그를 콘솔에 출력하는 역할)를 기본 로거로 사용한다.

클래스 내에는 정적 멤버와 인스턴스 멤버가 모두 정의 되어있다. 정적 멤버는 로깅 기능을 위한 메서드를 제공하고, 인스턴스 멤버는 로거 인스턴스를 생성하고 로그 메서드를 호출하는 역할을 한다.

로그 버퍼링 기능도 제공된다. 버퍼링이 활성화되면 로그는 버퍼에 저장되고, 버퍼가 비활성화되면 버퍼에 저장된 로그들이 순차적으로 출력된다.


🖇 클래스 vs 인스턴스 vs 인터페이스

  • 클래스 : 객체를 생성하기 위한 템플릿 ex) 사람, 자동차
  • 인스턴스 : 클래스를 바탕으로 실제로 생성된 객체 ex) 최지수, 팰리세이드
  • 인터페이스 : 객체의 구조 or 동작에 대한 계약을 정의 ex) 운전 가능

ex) '사람' 클래스와 '자동차' 클래스 간의 상호작용을 '운전 가능' 인터페이스를 정의해 '사람' 클래스와 '자동차' 클래스가 이 인터페이스를 구현하도록 코드를 작성할 수 있다. ('운전'이라는 메서드를 구현)
이를 통해 '사람' 객체가 '자동차' 를 운전할 수 있고, '자동차' 객체는 '사람' 에게 운전을 요청할 수 있다.


✔️ LoggerService 인터페이스

LoggerService 인터페이스는 로깅 서비스에 필요한 메서드들을 정의하는 역할을 한다

1. log, error, warn, debug, verbose 메서드
각각의 로그 레벨이 log, error, warn, debug, verbose 인 로그를 기록한다. message 파라미터는 로그 메시지를 나타내고, optionalParams는 추가적인 파라미터를 전달할 수 있다.

2. setLogLevels 메서드
로그 레벨을 설정한다.
levels 파라미터는 LogLevel배열로 설정할 로그 레벨들을 전달한다.

물음표는 TypeScript에서 선택적 속성을 나타낸다. (선택사항이라는 것을 의미)


🤔 로그 버퍼란?

로그 메시지를 임시로 저장하는 메모리 영역
로그는 버퍼에 쌓이고, 버퍼가 가득차면 로그 관리자가 이를 처리해 영구적인 저장소(ex) 로그파일)에 기록한다.

큰 버퍼 크기는 많은 양의 로그를 처리할 수 있게 해주지만, 시스템의 메모리 사용량을 늘릴 수도 있다. 따라서 로그 버퍼의 크기는 신중하게 설정해야한다.


✔️ LogBufferRecord 인터페이스

LogBufferRecord는 로그 버퍼에 저장되는 개별 로그 항목을 나타내는 인터페이스 이다. 로그 버퍼에 저장된 각 로그 항목에 대한 정보를 포함하는 객체를 정의하는 역할을 하고 있다.

methodRef 속성을 사용해 로그 버퍼에 저장된 로그 항목을 처리할 때, 해당 로그 항목에 저장된 함수를 호출하게 된다. methodRef를 사용해서 저장된 함수에 arguments를 전달해 메서드를 실행할 수 있다.


✔️ ConsoleLogger

ConsoleLogger는 LoggerService 인터페이스를 구현하면서, 그 안에 정의된 메서드들(log, error, warn, debug, verbose)을 사용해 로그를 콘솔에 출력한다.

@nestjs/common 패키지의 ConsoleLogger가 사용되었다.


✔️ Intl.DateTimeFormat

날짜와 시간을 형식화하는 객체를 생성해준다.


DateTimeFormat객체는 Intl객체에 내장된 날짜 및 시간 형식화 기능을 사용한다.

Intl.DateTimeFormat 생성자의 첫 번째 매개변수는 로케일(locale) 을 나타낸다. 위의 코드에서는 undefined로 설정되어 있으므로 현재 실행 환경의 기본 로케일을 사용한다.
두 번째 매개변수는 형식화 옵션을 지정하는 객체이다. year, hour, minute, second, day, month 속성을 설정해 형식화할 요소를 지정한다.

형식화된 날짜와 시간은 해당 지역 or 언어의 관행에 맞게 표시된다.

  • 미국 로케일: "7/4/2023, 10:30:15 AM"
  • 영국 로케일: "04/07/2023, 10:30:15"

✔️ Logger 클래스

Logger 클래스는 LoggerService를 구현하며, @Injectable() 데코레이터를 사용해 주입 가능한 클래스를 나타낸다.


🖇 protected

Logger 클래스 내부에서만 접근 가능하며, 해당 클래스를 상속받은 서브 클래스에서도 접근 가능한 멤버나 속성을 선언할 때 사용된다.


🖇 WrapBuffer

이 메서드 데코레이터는 Logger.isBufferAttached 가 참인 경우에만 원래 메서드의 호출을 Logger.logBuffer에 추가하는 것! 이다. 이를 통해 메서드 호출 정보를 기록하고 추후에 사용할 수 있다.

Logger.logBufferAttached 가 거짓인 경우, 메서드 호출은 로그 없이 실행되고, 로그 버퍼에는 아무런 변화가 없다. 로그 기록은 생략되고, 이전에 저장된 호출 정보는 유지된다.


🖇 Logger 클래스 생성자 정의

로거 인스턴스를 초기화 하기 위해 생성자를 정의한다. 이 생성자를 통해 로거 인스턴스를 생성하고, 로거 인스턴스의 동작을 커스터마이즈 할 수 있는 옵션을 제공한다.

1. context 매개변수 : 로그 메시지에 추가적인 컨텍스트 정보를 제공할 수 있다.
2. options 매개변수 : 로거 인스턴스의 동작을 설정하는 옵션을 제공한다. 위 코드에서는 timestamp 라는 프로퍼티를 가지는 객체를 받아온다. timestamp 값을 true 로 설정하면 로그에 시간 정보가 추가된다.


🖇 localInstance 게터 메서드

localInstance 게터를 통해 현재 사용 중인 로거 인스턴스를 얻을 수 있다. 이를 통해 로그 레벨에 맞는 로그 메시지를 생성하고 로깅 서비스를 활용할 수 있다.

localInstance 게터와 registerLocalInstanceRef 메서드는 Logger 클래스의 인스턴스에 대한 로컬 LoggerService 인스턴스를 관리하는 데 사용된다.

localInstance 게터는 LoggerService 인터페이스의 인스턴스를 반환한다. 이 게터의 구현에서는 Logger.staticInstanceRef 값을 확인하고 다음 세 가지 경우 중 하나에 따라 registerLocalInstanceRef 메서드를 호출하여 로컬 인스턴스를 생성하고 반환한다.


  1. staticInstanceRef가 DEFAULT_LOGGER와 같으면 로컬 인스턴스를 등록한다. (기본 로거 인스턴스인 ConsoleLogger 를 인스턴스로 사용하기 위함)

  2. staticInstanceRef가 Logger 클래스의 인스턴스인 경우, 프로토타입 체인을 통해 클래스의 생성자(Logger)와 비교한다.
    생성자가 Logger 클래스와 일치한다면, registerLocalInstanceRef 메서드를 호출해 로컬 인스턴스를 등록한다. (Logger 클래스의 인스턴스가 자기 자신을 참조하는 것을 방지하기 위한 조건)

  3. 위의 조건에 해당하지 않는 경우, 현재 로거 인스턴스(Logger.staticInstanceRef)를 반환한다.


🖇 로그 메서드

error, log, warn, debug, verbose 메서드는 각 로그 레벨에 해당하는 로그를 작성한다.

각 메서드는 Logger.WrapBuffer 데코레이터로 감싸져 있고, 이를 통해 로그 버퍼가 활성화된 경우 로그를 버퍼에 저장하고, 그렇지 않은 경우에는 기존 동작을 수행한다.


🖇 정적 로그 메서드

정적 로그 메서드가 있는 이유는 Logger 클래스를 인스턴스화 하지 않아도 로깅 기능을 사용할 수 있도록 하기 위해서이다. 정적 로그 메서드를 사용하면 Logger 클래스의 인스턴스를 생성하지 않고도 바로 로그를 기록할 수 있다.

  • 일반 로그 메서드 : 인스턴스화된 Logger 객체를 통해 로그를 기록하는데 사용된다. 해당 객체에 대한 로그 레벨, 컨텍스트 등을 구성할 수 있다.
  • 정적 로그 메서드 : Logger 클래스 자체에 대해 로그를 기록하는데 사용된다. 인스턴스화 없이도 바로 로그를 기록할 수 있다.

일반 로그 메서드는 로거 인스턴스에 대한 로그 기록 및 설정을 관리하는데 사용되고,
정적 로그 메서드는 클래스 수준에서 로그를 기록하는데 사용된다.


🖇 flush 메서드

flush 메서드는 버퍼에 저장된 로그 메시지를 출력(로그메시지를 기반으로한 로그 메서드 호출을 통해)하고, 버퍼를 비운다.


🖇 attachBuffer 와 datachBuffer 메서드

attachBuffer
로그 버퍼를 연결하는 역할을 한다. 로그 버퍼를 연결하면 로그 메서드가 호출될 때 실제 로깅이 실행되지 않고, 로그 버퍼에 로그 데이터가 저장된다.

detachBuffer
로그 버퍼 연결을 해제하는 역할을 한다. 저장된 로그들이 한 번에 로깅되게 된다.

이렇게 로그 버퍼를 사용하면, 실제 로깅이 발생하는 시점을 조절할 수 있다. 로그 버퍼를 연결한 상태에서 로그 메서드를 여러 번 호출해도 실제로 로깅이 발생하지 않아 성능상의 이점이 있고, 이후에 로그 버퍼를 해제하면 저장된 로그들이 한 번에 로깅되므로 로깅 횟수를 줄이고 로깅 작업의 오버헤드를 최소화할 수 있다.

- 오버헤드
특정 기능을 수행하는데 드는 간접적인 시간, 메모리 등 자원을 뜻한다.
예를 들어, 10초 걸리는 기능이 간접적인 원인으로 20초 걸린다면 오버헤드는 10초가 되는 것이다.


기타 보조 메서드

🖇 getTimestamp

현재 시간을 포맷팅해 반환하는 역할을 한다.


🖇 overrideLogger

로거를 재정의하는 역할을 한다. 로거를 동적으로 재정의 함으로써 애플리케이션에서 사용하는 로깅 동작을 변경하고 제어할 수 있게 한다.

Nest v9 이전의 버전에서는 Logger 클래스를 상속해 확장하는 것을 허용하지 않는 경고 메시지를 띄운다.


🖇 isLevelEnabled

isLevelEnabled 메서드는 주어진 로그 레벨을 isLogLevelEnabled() 함수에 전달해 해당 로그 레벨이 활성화되어 있는지 여부를 확인하고 그 결과를 반환한다.


🖇 registerLocalInstanceRef

registerLocalInstanceRef 메서드는 localInstanceRef가 존재하지 않을 때에만 호출된다.

localInstanceRef가 이미 존재한다면, 그것을 그대로 반환한다. localInstanceRef가 존재하지 않는다면, context, options, 로그 레벨을 이용하여 ConsoleLogger 클래스의 새로운 인스턴스를 생성한다.그리고 이 새로운 인스턴스를 localInstanceRef에 할당하고 반환한다.


localInstance 게터는 Logger.staticInstanceRef에 기반하여 로컬 LoggerService 인스턴스를 관리하는 것이고, registerLocalInstanceRef 메서드실제로 로컬 인스턴스를 생성하고 반환한다.


💡 프로젝트를 하며 배운 점

로그를 디버깅용으로만 사용했기 때문에 로그가 디버깅과 문제 해결만을 위해 중요한 줄 알았는데, 로깅을 통해 성능 모니터링, 보안 감시, 사용자의 패턴 분석등 다양한 면으로 개발에 도움을 주는 중요한 기능이라는 것을 알게 되었다.

로그를 파일로 출력하거나 데이터베이스에 저장해서 모니터링, 분석 등을 할 수 있다는 것도 이번에 알았기 때문에 로그의 양에 대해서 생각해본 적이 없었다. 로그를 무작정 다 파일로 출력하거나 데이터베이스에 저장하는 것이 아니라, 중요도에 따라 로그 레벨로 구분 한 뒤 정말 필요한 로그 메시지를 개발자가 확인하고, 처리를 할 수 있도록 하는 것이 중요하다는 것도 알게 되었다. 역시 개발의 모든 기능들은 양보다는 질과 어떻게 효율적으로 사용할 수 있는지 고민하는 것이 중요하다!

Logger 클래스 내부를 살펴보니 다양한 클래스, 함수, 인터페이스들로 구성되어 있었다. 추후에는 로그 메시지의 형식을 변경해보거나 (ex)타임스탬프 설정) 로그 레벨 조정, 로그 필터링(@nestjs/logger 패키지를 사용해 특정 사용자, 특정 예외 상황 등을 필터링) 로그 보안(로그 레벨 메서드 내에서 로그 메시지가 출력되기 전에 필요한 마스킹 로직을 적용 or 암호화 / 특정조건에따라 로그 메시지를 필터링), 로그 저장소 등 필요에 따라 로그를 커스터마이징해 관리할 수 있겠다고 생각했다

또! 로그를 디버깅용(에러 발생시에 바로 출력)으로만 사용해보았으니 당연히 로그는 바로 출력되는 것이라고 생각했는데, 로그 버퍼를 통해 로깅이 발생하는 시점을 조절할 수 있다는 것도 새로웠다.
Nest.js 내장 Logger에는 로그 레벨 메서드별로 Logger.WrapBuffer 데코레이터가 감싸져 로그 버퍼가 활성화된 경우 로그를 버퍼에 저장하고, detachBuffer 메서드가 로그 버퍼 연결을 해제하면 저자왼 로그들이 한 번에 로깅되게 된다.
로그 버퍼를 사용하면 로깅 횟수를 줄이고 오버헤드를 최소화시켜 성능을 향상시키고 데이터베이스와의 I/O(데이터베이스와 연결 했을 경우)작업 비용을 절감할 수 있다. 여기서도 느낀 것은 역시 효율화! 다.

그리고 로그 레벨도 동적 로그 레벨 메서드와 정적 로그 레벨 메서드를 분리해서 작성하는 것도 생각하지 못했던 부분이라 아차 싶었다.
Logger 클래스의 인스턴스를 생성해 사용하는 경우, 해당 인스턴스의 로그 레벨을 동적으로 설정할 수 있어야 한다. 예를 들어, 특정 모듈이나 컨트롤러에서는 log 레벨로 출력하고, 다른 모듈에서는 debug 레벨로 로그를 출력하고 싶을 수 있다. (setLogLevels 메서드를 통해 인스턴스의 로그 레벨을 설정할 수 있다.)
당연히 컨트롤러마다 로그 레벨 및 로깅 설정이 다를 수 있는 건데! Logger 클래스의 인스턴스를 생성해 사용한다는 것을 놓쳤다... 이로써 Nest.js 뿐만 아니라 자바스크립트 기본기 공부도 소홀히 하면 안 되겠다고 생각했다.

그리고! 정말 신기했던 것!!!!!!!!
평소에 디버깅할 때 사용하던 console.log도 콘솔의 로그니까 console.log()인 줄 알았는데 로그 레벨의 log 였다는 것도 놀라웠다!!!!!!! console.warn() 이런식으로도 사용할 수 있다니!

어떤 개발을 하든 중요한 것은 어떻게 하면 조금 더 효율적으로 처리를 할 수 있는지, 안정적으로 데이터를 기록하고 원하는 방식으로 보내줄 수 있는 지에 대한 고민이라고 느꼈다.
한 문장으로 정리하면 간단해보이지만 개발을 할 때는 기능 구현에 급급해 질 수도 있고, 고려해야되는 여러가지 사항들(자원, 고객 요구사항 등)에 포커스가 쏠릴 수도 있다.

결국 개발자는 선택하는 사람이라고 생각한다. 어떤 로직으로 어떤 패키지, 데이터베이스등을 골라 자신이 생각한 최적의 방법으로 개발을 하는지, 그 최적의 방법을 이끌어내려면 더 더 더욱 많은 공부와 경험을 해보아야 할 것 이다.

결론. 다양하고 깊게! 공부를 더하고 이것 저것 많이 만들어보자!


참고했던 사이트

profile
닫혀 있어서 벽인 줄 알고 있지만, 사실은 문이다.

0개의 댓글