자체 제작 logger를 피할 수 없을 때, 정말 필요한 최소 요건은 무엇인가: 실무 요건과 통합 테스트 관점
기성 logging framework를 쓸 수 있다면 그쪽이 안전합니다. 그래도 애플리케이션 쪽 제약이나 운영상의 사정으로 자체 제작 logger를 피할 수 없는 장면이 있습니다. 거기서 처음에 고민하기 쉬운 점은, 어디까지 구현하면 「너무 거칠지도 않고, 너무 무겁지도 않은」 설계가 되는가 하는 점입니다.
이 글에서는 대상을 장애 조사용 애플리케이션 로그에 한정합니다. 감사 증적, 분산 트레이싱, 메트릭 기반, 클라우드 집약까지 한 번에 떠안지 않고, 우선 현장에서 도움이 되는 최소 구성을 정의한 뒤, 그 구성을 정말로 신뢰할 수 있게 하기 위한 통합 테스트 관점을 정리합니다.
먼저 결론
첫 버전에서 잡고 싶은 요점은 다음과 같습니다.
- 형식은
UTF-8의JSON Lines로 한다 - 1 레코드 1 행을 무너뜨리지 않는다
- 필수 항목은
시각,레벨,카테고리,메시지,구조화 fields,sessionId,processId - 기본은
1 프로세스 1 파일 - 부하가 낮으면 동기 쓰기, 많이 나오면
single writer + bounded queue Error/Critical과 세션 시작·종료는 동기 flush한다- 순환과 보관은 v1부터 넣는다
- 저장 위치를 쓸 수 없을 때 몰래 다른 곳으로 도망가지 않는다
이 정도까지 좁히면 구현과 운영 양쪽에서 파탄이 잘 나지 않습니다.
우선 대상 범위를 좁힌다
자체 제작 logger가 어려워지기 쉬운 이유는, 처음부터 뭐든지 다루려고 하기 때문입니다. 진단 로그, 감사 로그, 성능 측정, 분산 트레이스, 사용자 행동 분석을 하나의 구조로 묶으려 하면 요건이 단번에 늘어납니다.
이번 대상은 애플리케이션 장애를 구분하기 위한 진단 로그입니다. 즉, 「언제」, 「어느 처리에서」, 「무엇이 일어났고」, 「그때 어떤 문맥이었는지」를 나중에 따라갈 수 있는 것을 우선합니다. 여기로 좁히는 것만으로도 첫 설계 판단이 꽤 편해집니다.
최소한으로 필요한 요건
1. 형식은 UTF-8 JSON Lines
평문 텍스트를 이어 붙이는 식으로도 로그를 남길 수는 있지만, 나중에 기계적으로 다루기가 어려워집니다. 반대로 처음부터 무거운 독자 바이너리 형식으로 만들면 운영 시 가시성이 떨어집니다.
그 중간으로 다루기 편한 것이 UTF-8의 JSON Lines입니다. 1 행이 1 레코드이면 텍스트로서도 읽기 쉽고, 나중에 스크립트나 도구로 분석하기도 쉬워집니다. 중간에 쓰기가 끊겨도 어느 행이 깨졌는지 구분하기 쉬운 점도 실무에 맞습니다.
2. 필수 항목을 처음에 고정한다
최소한 갖춰 두고 싶은 항목은 다음 7개입니다.
timestamplevelcategorymessagefieldssessionIdprocessId
message만의 문자열 로그로 만들면, 나중에 검색 조건이 늘어났을 때 곤란해집니다. 반대로 항목을 너무 늘리면 호출 쪽의 부담이 급격히 올라갑니다. 처음에는 이 정도로 고정하고, 추가는 정말로 필요해진 다음에 검토하는 것이 안전합니다.
3. 1 프로세스 1 파일을 기본으로 한다
여러 프로세스에서 같은 파일에 추가 쓰기를 하게 하는 설계는 겉보기 이상으로 사고 요인이 많아집니다. 배타 제어, 부분 쓰기, 순환 타이밍, 비정상 종료 시 처리가 단번에 어려워지기 때문입니다.
우선은 1 프로세스 1 파일을 기본으로 하세요. 여러 프로세스를 묶고 싶다면 후단에서 집약하거나, 전용 집약 프로세스를 명시적으로 세우는 편이 안전합니다.
4. 쓰기 전략은 부하로 나눈다
로그 양이 적은 단계에서는 동기 쓰기 쪽이 알기 쉽고, 장애 조사도 편합니다. 억지로 비동기화하면 종료 직전의 로그를 잃거나, 예외 시 flush 조건이 애매해지기 쉽습니다.
한편으로 로그 양이 많아져 동기 I/O가 병목이 되면 single writer + bounded queue를 채택합니다. 이때 중요한 것은 큐가 넘쳤을 때의 방침을 먼저 정해 두는 것입니다. 오래된 로그를 버릴지, 새로운 로그를 떨굴지, 경고를 낼지를 애매하게 두지 마세요.
5. flush 조건을 정한다
Error와 Critical, 그리고 세션 시작·종료 로그는 동기 flush해 두면 장애 조사에 도움이 됩니다. 평소의 Info까지 전부 flush하면 느려지므로, 전부를 같은 취급으로 두지 않는 것이 현실적입니다.
6. 순환과 보관은 v1부터 넣는다
순환은 「나중에 넣으면 된다」고 생각되기 쉽지만, 운영에 들어가면 갑자기 곤란해지는 기능입니다. 크기, 일일, 기동마다 등 방식은 뭐든 좋으니, 적어도 「무한히 늘어나지 않는 것」과 「몇 개를 남길 것인가」가 정해진 상태로 두어야 합니다.
7. 저장 실패 시에 멋대로 대체 저장을 하지 않는다
로그 저장 위치를 쓸 수 없을 때, 몰래 다른 곳에 쓰는 설계는 나중에 조사를 어렵게 합니다. 운영 담당이 「있어야 할 곳」에 로그가 없다는 것만으로 장애 대응의 초동이 늦어집니다.
저장할 수 없다면 앱 쪽 알림, 이벤트 로그, 표준 오류 등 명시적으로 알 수 있는 수단으로 실패를 드러내세요. 적어도 「어디로 갔는지 모르는」 상태는 피해야 합니다.
v1의 최소 구성 이미지
첫 버전에서는 다음 정도로 충분한 경우가 많습니다.
UTF-8 JSON Lines- 1 프로세스 1 파일
- 세션 단위의 파일명
- 크기 기반 또는 기동 단위의 순환
- 보관 개수의 상한
Error/Critical의 동기 flush- 구조화된
fields를 받을 수 있는 API
이 이상의 기능은 실제 운영에서 「정말로 곤란했던 일」이 보인 다음에 붙이는 편이, 결과적으로 유지보수하기 쉬워집니다.
흔한 NG
피하고 싶은 전형적인 예도 들어 둡니다.
message문자열에 전부 욱여넣는다- 여러 프로세스에서 같은 파일을 공유한다
- flush 조건을 정하지 않고 전면 비동기화한다
- 순환과 보관을 뒤로 미룬다
- 저장 실패 시 몰래 다른 폴더로 도망간다
- 네트워크 전송이나 로컬 DB 저장까지 v1에 넣는다
어느 것이나 겉보기에 편리해 보이지만, 구분이나 운영을 무겁게 만들기 쉬운 항목입니다.
통합 테스트는 실제 파일·실제 스레드·실제 프로세스로 생각한다
logger는 유닛 테스트만으로는 안심하기 어려운 부품입니다. 문자열 정형이나 JSON화만 검증해도, 실제 운영에서 문제가 되는 것은 I/O, 병행성, 순환, 종료 시 flush, 권한 오류 쪽이기 때문입니다.
그래서 통합 테스트에서는 실제 파일, 실제 스레드, 필요하다면 실제 프로세스를 써서 확인하는 것이 중요합니다. 적어도 「평소에는 통과하지만 장애 시에는 신뢰할 수 없다」는 상태는 피하고 싶은 부분입니다.
통과시켜 두고 싶은 통합 테스트 항목
단일 쓰기의 건전성
- 1 행이 1 JSON 레코드로 되어 있는가
UTF-8로 다시 읽을 수 있는가- 필수 항목이 매번 들어가 있는가
- 개행 혼입으로 여러 줄로 깨져 있지 않은가
같은 프로세스 내의 병행 실행
- 여러 스레드에서 동시에 써도 레코드가 깨지지 않는가
- 레코드 수의 과부족이 없는가
- queue 이용 시에 순서나 누락 방침이 사양대로인가
flush와 종료 시의 동작
Error/Critical가 즉시 반영되는가- 정상 종료 시에 queue 안이 비워지는가
- 예외 종료에 가까운 경로에서도 필요한 종료 로그가 남는가
순환과 보관
- 순환 조건을 충족하면 새로운 파일로 전환되는가
- 보관 상한을 넘은 오래된 파일이 사양대로 삭제되는가
- 순환 직전·직후에도 JSON 행이 깨지지 않는가
이상계
- 저장 위치 디렉토리가 존재하지 않을 때의 처리
- 쓰기 권한이 없을 때의 처리
- 디스크 풀 상당으로 실패했을 때의 알림이나 반환값
- queue 넘침 시의 동작
여러 프로세스의 처리
만약 사양이 1 프로세스 1 파일이라면, 다른 프로세스가 같은 파일에 들어가려 하지 않는 것 자체를 확인 대상으로 삼을 수 있습니다. 반대로 집약 프로세스 방식이라면 그쪽으로의 전달 실패까지 포함해 확인이 필요합니다.
v1에서 최소한 통과시키고 싶은 개수
처음에 전부 하려고 하면 테스트가 너무 무거워집니다. v1에서 최소한 통과시키고 싶은 것은 다음 6개 정도입니다.
- 단일 스레드에서의 정상 쓰기
- 여러 스레드 동시 쓰기
Error/Critical의 flush- 순환과 보관
- 저장 위치 이상 시의 실패 알림
- 정상 종료 시의 drain과 최종 flush
이 6개가 통과되기만 해도, 「문자열은 나오지만 운영에서 신뢰할 수 없는 logger」에서는 꽤 멀어질 수 있습니다.
정리
자체 제작 logger의 첫 목표는 고기능화가 아니라 「장애 시에 믿을 수 있는 것」입니다. 그러기 위해서는 형식을 UTF-8 JSON Lines로 고정하고, 필수 항목을 좁히고, 1 프로세스 1 파일을 기본으로 하며, flush·순환·보관·실패 시 동작을 일찌감치 정해 두는 것이 유효합니다.
그리고 그 설계가 정말로 기능하는지는 실제 파일·실제 스레드·실제 프로세스를 쓰는 통합 테스트에서 확인할 필요가 있습니다. 구현을 키우기 전에 우선 최소 구성과 최소한의 테스트 세트를 먼저 굳혀 두면, 나중에 무리 없이 키우기 쉬워집니다.
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
어디까지를 유닛 테스트로 검증하고 어디부터를 결합 테스트로 검증해야 하는가 - 경계를 긋는 방법과 실무 판단표
유닛 테스트와 결합 테스트의 경계를 어디에 그어야 할지 망설이는 분을 위해, 순수 로직과 접속·배선·환경 차이라는 축으로 분류하고 실무에서 바로 쓸 수 있는 판단표와 사례로 정리합니다. 테스트가 빠르고 망가지기 어려워집니다.
상정하지 않은 예외가 발생했을 때의 체크리스트 - 앱을 종료시킬지 계속할지, 먼저 보는 판단표
상정 외 예외 시 앱을 종료할지 계속할지를 실패 단위 격리·공유 상태 회복·외부 부작용 설명·네이티브 경계 건전성의 네 축으로 판단하는 흐름을 표와 플로차트로 정리한 글입니다. 독자는 catch 가능 여부가 아니라 불변 조건 회복 가능성으로 가르...
COM 컴포넌트나 OCX / ActiveX 개발에서 빠지기 쉬운 것 - Visual Studio의 32bit / 64bit, 등록, 관리자 권한의 덫을 정리
COM 컴포넌트와 ActiveX, OCX 개발에서 자주 만나는 0x80040154나 0x80070005를 비트 수, 등록 방식, HKCU와 HKLM 스코프, 관리자 권한이라는 네 축으로 풀어 Visual Studio 2022의 64bit화 시대에...
ClickOnce란 무엇인가 - 구조, 업데이트, 어울리는 장면・어울리지 않는 장면을 실무 시점에서 정리
ClickOnce가 무엇이고 매니페스트, 캐시, 업데이트, 서명이 어떻게 맞물려 동작하는지를 Mermaid 그림과 함께 정리하고, 사내용 .NET 데스크톱 앱 배포에서 어울리는 안건과 어울리지 않는 안건을 실무 시점에서 판단할 수 있도록 도와드립니다.
Windows 샌드박스로 Windows 앱 개발의 검증을 빠르게 하는 방법 - 관리자 권한 문제, 클린 환경, 권한 부족・리소스 부족의 재현을 실무용으로 정리
Windows Sandbox로 Windows 앱의 클린 환경 검증을 빠르게 하는 실무 노하우를 정리합니다. .wsb 파일을 용도별로 나누고, 입력은 읽기 전용・출력만 쓰기 가능으로 분리하며, 표준 사용자나 메모리 부족, GPU 없는 상태의 재현까...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.