자바를 처음 배울 때 콘솔에 값을 찍는 용도로 제일 먼저 쓰는 API가 System.out.println()이다. 동작 확인할 때도 쓰고, 에러 원인 찾는 디버깅 용도로도 엄청 많이 쓴다. 초보일수록 “일단 찍어보자”가 습관처럼 나온다.
근데 이 방식은 실제 애플리케이션에서는 문제가 된다. 성능 때문이다.
System.out.println()이 왜 성능에 안 좋은가
System.out.println()로 디버깅 메시지를 출력하면 결국 어딘가로 “출력”을 해야 한다. 환경에 따라 콘솔/파일/스트림 등으로 나가는데, 중요한 건 이 출력 작업 자체가 꽤 비싼 작업이라는 점이다. 특히 많은 요청이 오고, 그때마다 print가 찍히기 시작하면 애플리케이션이 쓸데없는 I/O 비용을 계속 치르게 된다.
실제로 현업에서는 System.out.println()이 너무 많이 들어가 있으면:
- 성능이 떨어지고
- 로그가 난장판이 되고
- 배포 전에 System.out.println()을 지우거나 주석 처리해야 하는 일이 생긴다
근데 이 “삭제/주석 처리”도 비용이다. 나중에 또 디버깅해야 하면 다시 원복하고, 또 배포 전에 지우고… 이런 흐름이 반복된다.
그래서 결론은 이거다.
System.out.println()은 학습용으로는 편하지만, 서비스 코드에서는 유지보수/성능 측면에서 최악이다.
그래서 등장한 게 “로깅 라이브러리”다
이 단점을 해결하기 위해 나온 게 로깅 라이브러리이다. 앞으로는 System.out.println()은 잊고, 로깅으로 메시지를 남기는 습관을 들이는 게 맞다.
자바 진영에서 많이 쓰는 로깅 구현체 중 하나가 Logback이다.
그런데 여기서 또 문제가 하나 있다.
- 로깅 구현체는 종류가 많다 (Log4J, Logback, 기타 등등)
- 더 좋은 구현체가 나오면 바꾸고 싶어진다
- 근데 코드 전체가 특정 구현체에 종속되어 있으면 교체가 너무 힘들다
이 문제를 해결하려고 보통 SLF4J를 사용한다.
SLF4J는 “인터페이스”, Logback은 “구현체”이다
SLF4J는 “로깅 API(추상화)”이다.
실제로 로그를 출력하는 역할은 Logback 같은 구현체가 담당한다.
즉 구조는 이렇게 된다.
- 코드에서는 SLF4J로만 로그를 작성한다
- 실제 출력은 Logback이 한다 (pom.xml에 추가된 dependency가 그 역할)
그래서 프로젝트 코드를 보면 이런 import를 쓴다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
즉, Logback을 코드에서 직접 부르지 않는다.
대신 pom.xml에는 Logback 구현체가 들어있다.
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
이렇게 해두면 나중에 더 좋은 구현체가 나오면?
- 코드 수정 없이
- pom.xml에서 구현체만 바꾸면 된다
이게 SLF4J를 쓰는 핵심 이유이다.
로그 레벨로 “출력되는 양”을 조절한다
로깅 라이브러리는 메시지 출력 여부를 로그 레벨로 관리한다.
대표적으로 많이 쓰는 레벨은 이거다.
`trace`, `debug`, `info`, `warn`, `error`
그리고 레벨의 “높고 낮음”은 보통 이렇게 이해하면 된다.
`trace` < `debug` < `info` < `warn` < `error`
- 레벨이 낮을수록: 더 많은 로그를 찍는다 (세세한 정보까지)
- 레벨이 높을수록: 더 중요한 로그만 찍는다 (경고/에러 위주)
예를 들어:
- 설정이 `warn`이면 → `warn`, `error`만 출력된다
- 설정이 `debug`이면 → `debug`, `info`, `warn`, `error`가 출력된다
그리고 로그 레벨은 메시지를 찍는 코드에서 결정된다.
예를 들어 `RequestHandler`에서 `log.debug(...)`를 쓰면 그 메시지는 `debug` 레벨 메시지이다.
로그를 “문자열 더하기(+)”로 만들면 성능이 떨어질 수 있다
여기서 또 중요한 성능 포인트가 하나 나온다.
예를 들어 로그를 이렇게 만들면:
log.debug("New Client Connect! Connected IP : " + connection.getInetAddress() + ", Port : " + connection.getPort());
겉보기엔 문제 없어 보이지만, 설정 레벨이 info나 warn라서 debug 로그가 출력되지 않는 상황이어도 문제가 생긴다.
왜냐하면…
- debug()가 실제로 출력하지 않더라도
- debug()에 넘길 인자를 만들기 위해
"문자열 + 값 + 문자열 + 값" 계산은 먼저 실행된다
즉 출력도 안 할 로그인데도, 불필요한 문자열 연산을 매번 하게 된다.
자바에서 문자열 더하기는 생각보다 비용이 크고, 요청이 많아지면 이게 누적되어 성능을 갉아먹는다.
SLF4J의 `{ }` 포맷을 쓰면 이 문제를 피할 수 있다
SLF4J는 이 문제를 해결하려고 파라미터 바인딩 방식을 제공한다.
이렇게 쓰는 방식이다.
log.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(), connection.getPort());
이 방식의 장점은:
- debug 레벨이 꺼져 있으면
- 굳이 메시지를 만들기 위한 문자열 결합을 하지 않는다
- 필요할 때만 포맷팅이 실행된다
즉 “동적인 로그”를 찍으면서도 불필요한 연산을 줄여서 성능을 지킨다.
결론은 이거다.
로깅은 + 문자열 결합이 아니라 {}
패턴을 쓰는 게 기본이다.
출처 : 《자바 웹 프로그래밍 Next Step》, 박재성, 로드북
'Book > 자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
| 5장_ : Servlet으로 Hello World 출력하기 (0) | 2026.02.05 |
|---|---|
| 5장_: 임베디드 톰캣으로 웹 서버 띄우기 (0) | 2026.02.05 |
| 3장_ 요구사항 4: 302 status code 적용 (0) | 2026.02.01 |
| 3장_ 요구사항3: POST방식으로 회원가입 (0) | 2026.02.01 |
| 3장_ 메모: 요청 메시지의 형태 (0) | 2026.01.31 |