공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 10 / 22

로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그

섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 로깅 구조 4요소

모든 로깅 프레임워크는 같은 4단계로 동작합니다. Logger 는 이름 트리로 어디서 로그가 났는지 식별하고, Level 은 그 로그를 남길지 걸러냅니다. 통과한 로그는 Handler(콘솔·파일·소켓)로 나가고, Formatter 가 한 줄의 모양을 정합니다.

JUL 용어 SLF4J/Logback 용어
Logger Logger
Level (FINE) Level (DEBUG)
Handler Appender
Formatter Layout·Encoder
Filter Filter
없음 MDC

SLF4J 는 로깅 API 를 감싼 파사드일 뿐 자체 구현이 없고, Spring Boot 는 기본으로 Logback 을 구현체로 붙입니다. 코드는 SLF4J 인터페이스만 보고 짜므로, 나중에 Log4j2 로 구현체를 바꿔도 코드 수정이 없습니다.

오래된 라이브러리 중에는 JUL 이나 commons-logging 으로 직접 로그를 찍는 것들이 있습니다. 이런 라이브러리를 그대로 두면 로그가 두 갈래로 나뉘어 한 파일에서 요청을 추적할 수 없습니다. jul-to-slf4j, jcl-over-slf4j 브리지를 붙이면 이 로그도 SLF4J 경로로 흘러들어와 한곳에서 관리할 수 있습니다.

2.2 레벨과 계층

로거 이름은 보통 클래스의 FQCN(com.company.repo.MemberRepo)이고, 점(.)으로 트리를 이룹니다. 로거 자신에게 레벨이 없으면 가장 가까운 조상에서 상속받는데, 이를 유효 레벨이라 부릅니다. 데모 [1]에서 app.service 는 자기 레벨이 null 이라 조상 app 의 INFO 를 그대로 씁니다.

레벨 선택 기준입니다.

레벨 의미
ERROR 즉시 조치가 필요한 실패
WARN 조치가 필요할 수 있는 이상 신호
INFO 업무 이벤트 한 줄 요약
DEBUG 개발·장애 분석용 상세
TRACE 루프 내부 등 아주 상세

운영 서버는 INFO 를 기본으로 두고, 의심되는 패키지만 좁게 DEBUG 로 올립니다. Spring Boot 설정은 logging.level.com.company.repo=DEBUG 한 줄로 이 패키지 이하 전체의 레벨을 바꿉니다. 전체를 DEBUG 로 켜면 로그량이 폭증해 오히려 원인을 찾기 어려워집니다.

2.3 MDC 로 요청 추적

MDC(Mapped Diagnostic Context)는 요청마다 traceId, userId 같은 값을 스레드에 심어 두고, 포맷터가 모든 로그 줄에 자동으로 붙여 주는 장치입니다. 요청 시작에 put, 끝에 clear 를 호출하며 서블릿 필터나 인터셉터가 이 자리를 맡습니다.

clear 를 빠뜨리면 사고가 납니다. 스레드 풀은 스레드를 재사용하므로, 이전 요청의 traceId 가 지워지지 않은 채 다음 요청 로그에 섞여 나옵니다. 데모 [2]는 finally 블록에서 Mdc.clear() 를 호출해 이 문제를 막습니다.

@Async, CompletableFuture, 스케줄러로 작업이 다른 스레드로 넘어가면 ThreadLocal 기반 MDC 는 끊깁니다. 데모 [3]처럼 넘기기 전에 값을 복사해 새 스레드에 다시 심어야 하고, Spring 은 TaskDecorator 로 이 복사를 자동화합니다.

Spring Boot 3 의 Micrometer Tracing 은 traceId·spanId 를 자동으로 MDC 에 넣어 줍니다. %X{traceId} 패턴 한 줄만 추가하면 별도 코드 없이 바로 찍을 수 있습니다.

응답 헤더에 같은 traceId 를 돌려주면 사용자가 문의할 때 그 값으로 로그를 바로 찾을 수 있습니다. 문의 접수 시 화면 캡처에 헤더 값이 남아 있으면 장애 분석 시간이 크게 줄어듭니다.

2.4 예외 로깅 규칙

예외를 로그로 남길 때는 Throwable 을 마지막 인자로 넘겨야 스택 트레이스가 보존됩니다. e.getMessage() 만 문자열에 붙이면 메시지는 남지만 발생 위치를 잃습니다. 데모 [4]가 이 차이를 보여줍니다.

"잡아서 로그 찍고 다시 던지기"는 금지된 안티패턴입니다. 호출 스택 위쪽에서 같은 예외를 또 잡아 또 로그를 찍으면, 같은 장애가 2번, 3번 중복으로 남아 로그만 보고는 실제 발생 건수를 알 수 없습니다. 예외는 한 번만, 최종 처리 지점에서 찍습니다.

최종 처리 지점은 웹 계층이면 @RestControllerAdvice, 배치면 최상단 실행 루프입니다. 원인 체인(Caused by)은 마지막까지 보존해야 근본 원인을 알 수 있습니다.

로그 메시지에는 "무슨 입력이었는가" 같은 컨텍스트를 함께 남겨야 재현이 쉬워집니다. 예외 메시지만으로는 어떤 사용자, 어떤 요청에서 났는지 알 수 없어 MDC 의 traceId 와 함께 봐야 완전한 그림이 나옵니다.

2.5 성능

레벨이 꺼져 있어도 로그 메서드에 넘길 인자를 만드는 계산은 먼저 실행됩니다. 문자열 결합이나 배열 순회가 들어 있으면 그 비용을 그대로 냅니다. 데모 [5]는 20만 번 반복에서 결합 방식이 Supplier 방식보다 수십 배 느린 것을 보여줍니다.

SLF4J 의 log.debug("상태 {}", i) 같은 {} 자리표시자, JUL 의 Supplier 인자, isDebugEnabled() 검사는 모두 "인자 만들기 자체를 레벨 검사 뒤로 미루는" 방법입니다. 인자를 만드는 비용이 크지 않다면 굳이 안 써도 되지만, 루프 안이나 직렬화가 섞이면 반드시 씁니다.

동기 파일 로깅은 디스크 I/O 가 끝날 때까지 요청 스레드가 기다립니다. 트래픽이 몰리면 로깅 자체가 응답 지연의 원인이 됩니다.

AsyncAppender 는 로그를 큐에 넣고 별도 스레드가 파일에 쓰게 해 이 지연을 없앱니다. 큐가 가득 찼을 때 오래된 로그를 버릴지 요청을 막을지 정하는 유실 정책과, 종료 시 큐에 남은 로그를 다 쓰고 끝내는 flush 를 함께 설정해야 합니다. 이 정책이 없으면 서버가 죽을 때 마지막 몇 줄이 통째로 사라져 정작 필요한 장애 직전 로그를 잃습니다.

루프 안에서 매 건마다 로그를 남기는 대신, 집계해서 한 줄로 요약하는 것도 성능에 도움이 됩니다. 예를 들어 10만 건 배치라면 "처리 완료 10만건" 한 줄이 "1건 처리" 10만 줄보다 낫습니다.

2.6 마스킹과 보안

로그 파일은 데이터베이스보다 접근 통제가 느슨한 경우가 많아, 개인정보보호법상 사고가 나기 쉬운 지점입니다. 주민번호·카드번호·전화번호·이메일·비밀번호·인증 토큰(Authorization 헤더)은 로그에 아예 남기지 않거나 마스킹해야 합니다.

데모 [6]은 정규식 기반 MaskingFilter 로 로그 메시지가 핸들러에 도달하기 직전에 값을 바꿔치기합니다. Logback 에서는 커스텀 Converter 나 MaskingPatternLayout 이 같은 역할을 맡습니다.

흔한 실수는 DTO 를 통째로 toString() 해서 찍는 것입니다. 필드 하나에 비밀번호나 주민번호가 있으면 그 값이 그대로 로그에 남습니다.

요청·응답 본문 전체를 로깅하는 것도 위험합니다. 필요한 필드만 화이트리스트로 골라 찍거나, 아예 로깅 대상에서 빼는 것이 안전합니다.

2.7 파일 롤링과 보관

로그 파일은 크기나 날짜 기준으로 롤링하고, 보관 개수·총 용량에 상한을 둬야 합니다. 오래된 파일은 압축하고, 감사 요건에 맞춰 보관 기간을 정합니다.

데모 [7]은 JUL FileHandler 의 limit(바이트), count(파일 개수)로 이 롤링을 재현합니다. Logback 은 SizeAndTimeBasedRollingPolicy 의 maxFileSize, maxHistory, totalSizeCap 세 속성으로 같은 일을 합니다.

속성 역할
maxFileSize 파일 하나의 최대 크기
maxHistory 보관할 최대 일수
totalSizeCap 전체 로그가 차지할 총 용량 상한

이 상한을 안 두면 디스크가 가득 차 애플리케이션 전체가 멈추는 사고가 실제로 일어납니다. 로그 경로는 애플리케이션 설치 폴더 밖(예: /var/log/app)에 두어, 배포 때 폴더를 통째로 지우거나 덮어써도 로그가 살아남게 합니다.

같은 서버에 인스턴스를 여러 대 띄우면 파일명에 포트나 인스턴스 식별자를 넣어야 서로 덮어쓰지 않습니다. 실무 배치 레슨에서 다룬 다중 인스턴스 배포와 이 규칙은 항상 같이 챙겨야 합니다.

2.8 구조화 로그와 런타임 레벨 변경

ELK·Loki 같은 수집기가 있으면 JSON 한 줄 포맷이 검색·집계에 유리합니다. 데모 [8]이 이 포맷을 보여줍니다. 수집기가 없으면 사람이 읽는 한 줄 포맷에 grep 을 쓰는 편이 낫습니다. 두 포맷 모두 traceId 는 반드시 포함해야 나중에 요청을 추적할 수 있습니다.

Spring Boot 3.4 이상은 logging.structured.format.console=ecs 설정 한 줄로 구조화 로그를 켤 수 있습니다. 그 이전 버전은 logstash-logback-encoder 를 붙입니다.

재기동 없이 레벨을 바꾸는 방법도 두 가지입니다. Actuator 를 켰다면 /actuator/loggers/{name} 에 POST 하고, Logback 은 설정 파일에 scan="true" 를 두면 파일이 바뀔 때 자동 재적용합니다. 장애 분석 중에만 DEBUG 를 켜고, 원인을 찾으면 반드시 원래 레벨로 되돌려야 합니다.

핵심 원리
  • 2.1 로깅 구조 4요소
  • 2.2 레벨과 계층
  • 2.3 MDC 로 요청 추적
  • 2.4 예외 로깅 규칙
  • 2.5 성능
  • 2.6 마스킹과 보안
  • 2.7 파일 롤링과 보관
  • 2.8 구조화 로그와 런타임 레벨 변경
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제