본문 바로가기
Delvelopment/Spring

[Spring, Java] SLF4J Logger 올바른 사용법. (log4j2, logback)

by 제제킴 2021. 11. 20.
반응형
반응형

 

새로운 직장으로 회사를 옮기면서 레거시 시스템을 보는일이 생기게 되었고, Logger를 세가지 방법으로 사용하는 것을 보고 공유가 필요하다고 느꼇다.
(정확히 레거시의 개발자는 누군지 모르겠다. 많은 사람들의 손을 탄거 같다.)

우선, Spring 에서 Logger를 세가지를 쓸 수 있다, Log4j2, log4j, logback 이다.
세개의 차이는 " " 에서 확인해 보면된다.

이 글은 어떤 라이브러리를 쓰든 공통으로 지켜야할 약속이라고 생각한다. (성능적으로 가독성 측면으로)

Log의 발생이 성능적으로 큰 이슈가 될 수 있음을 경험해 봤는데, 스트레스 테스트를 10만 건을 진행했을때 Logger를 어느정도 사용했냐 안했냐에 따라 성능적인 지표가 크게 차이났기 때문이다.

보통의 개발자는 신경쓰지 않지만 대량의 트래픽을 처리하는 개발자라면 고려해야할 사항 중 하나 일 것이다.
아래 나오는 예제 코드는 Spring 기반의 SLF4J(Simple Logging Facade For Java)를 이용해 로그를 남길 것입니다.

Best Case

우선 Best Case 를 알려드리고 왜 이렇게 사용해야할까를 설명하겠습니다.

String word = "World";
// 성능 상, 가독성 최상
logger.debug("Hello {}", word);

Log Level을 Info 레벨로 올리기되면 성능도 최상으로 개선효과가 일어납니다. (실제로 사용하는 로그라면)

왜?

logger.debug() 메소드가 실행 되기전 Log Level을 확인하기전 ("Hello {}", word) 의 문자열 연산을 하기 때문인데 해당 logger는 문자열 연산이 일어나지 않는다.

  • 중요한 점은 " + " 와 같은 연산이 없기 떄문에
  • 메소드 호출과 같은 추가 기능이 없기때문.
  • SLF4J의 치환문자 {} 를 활용해라.

 

@Getter
@ToString
class User{
	String name;
	String age;
	String job;
}

// 성능 중, 가독성 상
logger.debug("User name : {}, age : {}, job : {}", user.getName(), user.getAge(), user.getJob());
// 성능 상, 가독성 최상
logger.debug("User : {}", user);
  • 파라미터는 2개 이하로 사용하자. (3개 이상 사용하면 Object[]를 생성하기 때문이다)
  • Log Level 확인후 toString()이 일어나기 때문에 큰 비용이 소모되지 않는다.

 

String word = "World";

if(logger.isDebugEnabled()) {
	// 성능 최상, 가독성 중
	logger.debug("Hello {}", word);
}
  • 위와 같은 함수로 logger.debug() 메소드를 실행하지 않을 수도있다.
  • 성능은 최상이지만 소스가 길어지고 가독성은 안좋아 진다.
  • 성능은 좋지만 가독성인 측면 때문에 지양한다.

(다만 성능적으로 큰 퍼포먼스를 내야한다면 위 방법을 지향합니다.)

Worst Case

String word = "World";
// 성능 최악 , 가독성 최악
logger.debug("Hello {}" + word);

정말 피해야할 Log이다.
logger.debug() 메소드가 실행되기전 ("Hello {}" + word) 의 문자열 계산이 일어나게 되서 최악의 성능을 나타낼 수 있다.
예를 들어, Log Level이 INFO 레벨일때 위 문자열은 계산이 먼저되고 Debug 레벨이 아니기 떄문에 노출되지 않을 것입니다.
위 로그는 정말 간단하지만, 위와 같은 로그수가 많고 입출력이 많은 문자열이라면 시스템 성능 상 큰 문제를 발생하게 됩니다.
정말 피해야 하는 Logger 작성 법입니다.

 

Exception Best Case

try {
    throw new Exception("exception");
} catch(Exception e) {
    // 성능 상, 메시지 최상
		logger.warn("{} - id : {}", e.getMessage(), id);
}
  • logger에서 연산이 발생하지 않으며,
  • 로그 레벨보다 높으면 toString()이 일어나지 않는다.
  • Exception에서 getMessage()만 불러오기 때문에 성능적인 이점이 있다.
  • 파라미터를 추가해줌으로써 메시지의 활용이 크다.
  • 파라미터를 2개 이하로 설정한다.

 

Exception Worst Case

try {
    throw new Exception("exception");
} catch(Exception e) {
    // 성능 최악, 메시지 최악
    e.printStackTrace();
}

e.printStackTrace() 는 로그 레벨의 구분 없이 호출되며, 내부적으로 java.lang.System.err를 이용해서 로그를 남긴다.
즉, 로그레벨이 구분이 없으며, 커널 CPU를 많이 점유하기 때문에 성능이 떨어진다.
또한, 우리가 원하는 메시지를 표현할 수 없다.

 

 

이렇게 Logger의 올바른 사용법의 글을 마무리 한다.

Logger는 생각보다 성능적인 측면이 크니 적절하게 사용하길 바랍니다. :)

반응형

댓글