e.getMessage() 만 찍는다// ❌ 발생 위치를 잃는다
log.error("처리 실패: " + e.getMessage());메시지만 남으면 어느 클래스, 어느 줄에서 예외가 났는지 알 수 없습니다. log.error("처리 실패", e) 처럼 Throwable 을 마지막 인자로 넘겨야 스택 트레이스가 함께 남습니다.
// ❌ 상위에서 또 잡아 또 찍으면 로그에 같은 장애가 여러 번 남는다
catch (Exception e) {
log.error("실패", e);
throw e;
}호출 스택 위쪽의 다른 catch 가 같은 예외를 또 잡아 또 로그를 남기면, 실제로는 한 번 난 장애가 2번, 3번으로 부풀려 보입니다. 예외는 최종 처리 지점 한 곳에서만 로그로 남깁니다.
System.out.println 을 로그로 쓴다레벨을 끄고 켤 수 없고, 파일 롤링도 없고, 어느 스레드·어느 요청인지 표시도 없습니다. 트래픽이 늘면 콘솔 출력 자체가 병목이 됩니다. 아무리 작은 도구성 코드라도 Logger 를 최소한으로 씁니다.
// ❌ DEBUG 가 꺼져 있어도 결합 자체는 실행된다
log.debug("상태: " + toJson(bigObject));레벨이 꺼져 있어도 + 연산과 toJson 호출은 먼저 일어납니다. log.debug("상태: {}", () -> toJson(bigObject)) 나 {} 자리표시자로 인자 생성을 레벨 검사 뒤로 미룹니다.
배치가 레코드 하나마다 INFO 를 찍으면 로그 파일이 순식간에 기가바이트 단위로 커집니다. 건별 로그는 DEBUG 로 낮추고, INFO 는 시작·종료·건수 요약 정도로만 남깁니다.
clear() 누락으로 다른 사용자 traceId 가 섞인다// ❌ finally 가 없으면 예외 시 clear 가 안 된다
Mdc.put("traceId", id);
service.doWork();
Mdc.clear();doWork() 에서 예외가 나면 clear() 줄까지 못 가고, 스레드 풀이 그 스레드를 재사용하면 다음 요청 로그에 이전 traceId 가 남습니다. put 다음은 항상 try/finally 로 감싸야 합니다.
@Async 로 넘어가면 traceId 가 사라진다ThreadLocal 기반 MDC 는 스레드를 넘지 못합니다. TaskDecorator 없이 @Async 를 쓰면 비동기 로그에서만 traceId 가 빠져 그 구간을 요청과 연결할 수 없습니다. 4.2 의 TaskDecorator 를 반드시 등록합니다.
toString() 에 비밀번호·주민번호가 그대로 나온다Lombok @Data 가 만든 toString() 은 모든 필드를 찍습니다. 그 DTO 를 로그에 통째로 남기면 민감 정보가 그대로 파일에 저장됩니다. @ToString.Exclude 로 민감 필드를 빼거나, 필요한 필드만 골라 메시지를 직접 만듭니다.
파일 크기·개수·총 용량 상한을 하나도 안 두면 로그가 무한히 쌓입니다. 디스크가 가득 차면 로그 쓰기뿐 아니라 애플리케이션 전체가 멈추는 사고로 이어집니다. maxHistory, totalSizeCap 은 처음 설정할 때부터 반드시 넣습니다.
장애 분석용으로 잠깐 DEBUG 를 켰다가 되돌리지 않으면, 그 이후로 로그량이 계속 많은 채로 남습니다. Actuator /loggers 로 되돌린 뒤에는 설정 파일도 같이 확인해 재기동 후에도 유지되지 않도록 합니다.