365/24 로 관리하는 시스템에서 로그는 굉장히 중요하다.
하지만 로그가 중요하다는 생각에 무분별하게 남기는 것은 좋지 않다.
대표적인 예로 습관적으로 예외 상황이 발생하면 ERROR 레벨로 로그를 남기는 경우이다.
보통의 서비스에서는 시간 내 에러 로그가 일정 수치 이상 쌓이면 알람을 발생시키도록 구성한다.
정상적이지 않은 모든 상황에서 전부 ERROR 레벨로 처리하게 되면 불필요하게 많은 알람들로 인해 정작 봐야할 심각한 에러 로그들도 놓칠 수 있다.
그래서 적정 수준에서 로그 레벨을 구분하여 알람 경보 수준도 구분하는 것이 필요하다.
1. 로그 레벨
로그 레벨은 해당 로그 메세지가 얼마나 중요한지를 알려주는 정보이다.
로그 레벨의 중요도는 담당 개발자가 밤에 계속 잠을 잘 수 있는지, 즉시 침대에서 일어나 노트북을 켜야하는지를 구분할 수 있게 해준다.
로그 수준은 시스템 상태에 대한 중요한 정보와 순전히 유익한 정보를 필터링하는 방법으로 생각할 수 있다.
로그 수준은 정보 노이즈 및 경고 피로를 줄이는 데 도움이 될 수 있다.
로깅 프레임워크들마다 지원하는 로그 레벨들이 조금씩 다르지만, 공통적으로 가지고 있는 레벨은 다음과 같다.
DEBUG
- 개발 혹은 테스트 단계에서 해당 기능들이 올바르게 작동하는지 확인하기 위한 로그 레벨이다.
- 예를 들어 데이터베이스 연결이 생성되거나 해제되는 시점 혹은 함수 호출에 사용된 인자와 반환 값 등을 남겨서 개발 및 테스트 과정에서 문제를 추적하고 해결하는 데 도움을 주도록 한다.
- 요즘은 워낙 IDE의 디버거가 좋아져서 로컬 개발 단계에서는 활용할일이 적지만, Dev/QA 등 환경에서의 문제들을 파악해야할때 남기면 좋다.
- 본인이 여전히
console.log
혹은System.out.println
으로 로컬에서 문제를 확인한다면 IDE의 디버거를 필수로 익히고, 그게 당장은 어렵다면log.debug
로 로그를 남기는 것을 추천한다.
- 본인이 여전히
- 다른 레벨들과 달리 운영 환경에서는 남기고 싶지 않은 로그 메세지를 위한 레벨이다.
INFO
- 애플리케이션에서 정상 작동에 대한 정보 즉, 어떤 일이 발생했음을 나타내는 표준 로그 레벨
- 애플리케이션 상태, 설정 또는 외부 리소스와의 상호 작용과 같은 상태 확인을 위한 이벤트를 나타낸다.
- 예를 들어, 인증 API의 백엔드 시스템에서 인증이 성공했는지 여부에 따라 사용자가 인증을 요청한 정보가
INFO
레벨의 로그로 남긴다. - 애플리케이션이 시작되거나 종료되는 시점, 중요한 작업이 완료되거나 실행되는 시점 (예: 배치 작업, 정기 작업) 등의 경우에도 남길 수 있다.
- 예를 들어, 인증 API의 백엔드 시스템에서 인증이 성공했는지 여부에 따라 사용자가 인증을 요청한 정보가
INFO
로그는 시스템을 파악하는데 유익한 정보여야만 한다.- 무의미한 정보를 남길 필요가 없다.
- 운영 환경에서도 사용할 수 있다.
WARN
- 애플리케이션에서 잠재적으로 문제가 될 수 있는 상황일때 남기는 로그 레벨
- 예를 들어 사용자가 로그인에 실패하는 것은 ID, PW 등을 오입력하여 언제든 발생할 수 있는 일반적인 문제 상황이다.
- 이럴때는
WARN
레벨로 책정하고, 로그인이 5번 연속 실패하는 등 특정 기준치를 넘길 경우ERROR
레벨로 남기는 것이 좋다. - 이와 유사하게 예상치 못한 입력 (사용자가 유효하지 않은 입력을 제공한 경우 - 예: 이메일 형식이 아닌 입력), 리소스 제한 (파일 업로드 사이즈 초과 등)과 같은 경고를 기록할 수 있다.
- 이런 경우 사용자에게 노출되는 메세지에 상세한 가이드가 필요한 것이지, 로그 레벨이 ERROR일 필요가 없다.
- 이 레벨의 메세지는 개발자가 조치를 취할 수 있도록 주의를 기울일 필요가 있는 상황을 나타낸다.
- 운영 환경에서도 사용할 수 있다.
ERROR
- 애플리케이션에서 발생한 심각한 오류나 예외 상황을 나타내는 로그 레벨
- 기능 자체가 제대로 작동하지 못하는 문제일때 남겨야 하며 즉시 조치가 필요할때를 의미한다.
- 예를 들어 데이터베이스 연결이 실패한 경우, 내부 시스템의 문제로 결제가 실패하는 경우 등일 경우엔
ERROR
로 남기며 즉시 조치를 취해야 한다.
- 예를 들어 데이터베이스 연결이 실패한 경우, 내부 시스템의 문제로 결제가 실패하는 경우 등일 경우엔
- 운영 환경에서도 사용할 수 있다.
이 중에서 운영에서 필요한 것은 WARN 레벨과 ERROR 레벨을 구분하는 것이다.
어떨때 WARN
을 써야하는지 알아보자.
2. WARN 레벨이 어울리는 상황
예외 상황, 오류 상황이 모두 ERROR
일 수는 없다.
일반적이지 않은 예외 상황이 발생했더라도, 그 상황이 개발자가 제어할 수 없는 상황이라면 이는 WARN
으로 두는 것이 좋다.
2-1. 외부 API 연동
사내 서비스가 아닌 외부 API 서비스의 호출을 100% 성공하도록 관리하는 것은 불가능하다.
따라서 외부 API 호출의 일정 비율이 실패하는 것이 일상인 것을 어느정도 전제해야 한다.
예를 들어 다음과 같이 API 연동이 있다고 가정해보자.
function get(url): string {
try {
return api.getCompanyInfo (url);
} catch (e) {
log.error("Error occurred", e);
return "response";
}
}
분당 1만건의 트래픽이 발생하는 API라면 대략 외부 API의 실패율이 0.1%
(성공율 99.9%
) 만 되어도 분당 10개의 ERROR
로그가 발생한다.
분당 10개의 ERROR
로그는 일반적인 시스템에서는 모니터링 알람이 발생할 수 있는 수치이다.
(물론 외부 API의 SLA가 0.1%보다 높을 수도 있다.)
1분마다 에러 알람이 발생한다면 종국에는 알람이 와도 제대로 확인하지 않게 된다.
이럴 경우 다음과 같이 WARN
레벨로 수정한다.
function get(url): string {
try {
return api.getCompanyInfo (url);
} catch (e) {
log.warn("Error occurred", e);
return "response";
}
}
외부 API 의 에러 발생율을 우리가 어떻게 할 수는 없다.
한 두번의 실패가 큰 영향을 끼치는 상황이 아니라면 외부 API는 WARN
으로 레벨을 두자.
물론 다음과 같은 경우에는 ERROR
레벨로 둬야한다.
- 결제 등 비즈니스상 치명적인 API의 경우
- 단, 잔고 부족, 인증 실패 등 고객의 실수인 경우는
WARN
으로 둔다. - 그 외 외부 결제사의 문제라면 이는
ERROR
로 로그를 남겨 빠르게 인지후, 해당 결제사가 정상이 될때까지는 미노출시키는 등의 작업을 해야만 한다.
- 단, 잔고 부족, 인증 실패 등 고객의 실수인 경우는
- 재시도 전략이 없는 상태에서 하루 1번, 한달에 1번 정도로 요청 자체가 주기적으로 적은 횟수만 수행하는 경우
- Retry 호출, 메세지큐 등 재시도 전략이 있는게 아니면서 딱 1번씩만 호출하는 경우 재시도를 해야하기 때문에
ERROR
로 로그를 남긴다.
- Retry 호출, 메세지큐 등 재시도 전략이 있는게 아니면서 딱 1번씩만 호출하는 경우 재시도를 해야하기 때문에
2-2. 사용자의 입력
사용자의 입력으로 인한 오류 역시 개발자가 제어할 수 없으면서 빈번하게 발생할 수 있는 예외 상황이다.
예를 들어 다음과 같이 id,pw가 일치하지 않는 경우를 검증하는 함수가 있다고 해보자.
export async function validateSignIn(id: string, password: string) {
const matchResult = await userRepository.validateSignIn(id, password);
if(!matchResult.isMatch) {
log.error(`id=${id} 의 로그인 정보가 정확하지 않습니다.`);
throw new Error('로그인 정보가 정확하지 않습니다.');
}
if(matchResult.failCount > 5) {
log.error(`id=${id} 의 로그인 실패 횟수가 5회를 초과했습니다.`);
throw new Error('로그인 실패 횟수 초과로 30분간 로그인을 시도할 수 없습니다.');
}
}
로그인 실패와 5회이상 연속된 로그인 실패 둘 다 ERROR
레벨이라서 사용자가 조금만 입력 실수를 하면 수많은 에러 알람들을 마주치게 된다.
이런 경우 역시 WARN
, ERROR
를 구분하면 좋다.
export async function validateSignIn(id: string, password: string) {
const matchResult = await userRepository.validateSignIn(id, password);
if(!matchResult.isMatch) {
// ERROR -> WARN
log.warn(`id=${id} 의 로그인 정보가 정확하지 않습니다.`);
throw new Error('로그인 정보가 정확하지 않습니다.');
}
if(matchResult.failCount > 5) {
log.error(`id=${id} 의 로그인 실패 횟수가 5회를 초과했습니다.`);
throw new Error('로그인 실패 횟수 초과로 30분간 로그인을 시도할 수 없습니다.');
}
}
이 외에도 다양한 경우가 포함된다.
- 입력 항목에서 필수 항목을 누락하고 요청한 경우
- 형식에 맞지 않는 (Email, 전화번호 등) 값으로 요청한 경우
이런 상황은 서비스에서 빈번하게 발생한다.
이들 역시 모두 ERROR
잡을 수는 없으며, WARN
로 로그를 남기고, WARN
로그들은 좀 더 낮은 기준으로 알람을 발송하도록 한다.
3. 마무리
로그 레벨 중 WARN
과 ERROR
을 구분하면 진짜 봐야할 중요한 문제들만 볼 수 있다.
둘의 레벨을 구분하면 아래와 같이 모니터링 기준을 가져갈 수 있다.
INFO
: 기존대비 +-50%이상 차이날 경우 알람WARN
: 분당 20개이상일 경우 알람ERROR
: 분당 5개이상일 경우 알람
별도로 관리하는 로그레벨과 모니터링 알람 기준은 개발자들에게 모니터링 알람에 대한 경각심을 유지시켜줄 수 있다.
너무 빈번한 에러 알람은 더이상 개발자들이 에러 알람의 중요성을 가지지 못하게 한다.
빈번한 에러 알람은 더이상 알람이 예외적인 상황이 아닌 것으로 인식하게 만든다.
봐야할 에러들에만 집중하기 위해서는 적절하게 로그 레벨을 구분할 수 있어야 한다.