log4j에서 로깅 전에 isDebugEnabled를 확인하면 성능이 향상됩니까?


207

로깅을 위해 응용 프로그램에서 Log4J 를 사용 하고 있습니다. 이전에는 다음과 같은 디버그 호출을 사용하고있었습니다.

옵션 1:

logger.debug("some debug text");

그러나 일부 링크는 isDebugEnabled()다음과 같이 먼저 확인하는 것이 좋습니다 .

옵션 2 :

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text");
}

제 질문은 " 옵션 2가 어떤 식 으로든 성능을 향상 시킵니까? "입니다.

어쨌든 Log4J 프레임 워크는 debugEnabled에 대해 동일한 검사를 수행하기 때문입니다. 옵션 2의 경우 프레임 워크가 isDebugEnabled()여러 번 (각 호출마다) 메소드 를 호출 할 필요가없는 단일 메소드 또는 클래스에서 다중 디버그 명령문을 사용하는 것이 좋습니다 . 이 경우 isDebugEnabled()메소드를 한 번만 호출 하고 Log4J가 레벨을 디버그하도록 구성된 경우 실제로 isDebugEnabled()메소드를 두 번 호출합니다 .

  1. debugEnabled 변수에 값을 할당하는 경우
  2. 실제로는 logger.debug () 메소드에 의해 호출됩니다.

옵션 1에 따라 logger.debug()메소드 또는 클래스에서 여러 명령문을 작성하고 메소드를 호출 debug()하면 옵션 2와 비교하여 Log4J 프레임 워크에 대한 오버 헤드 가 아니라고 생각합니다 isDebugEnabled(). 매우 작은 메소드 (코드 측면에서)이므로 인라인을위한 좋은 후보자가 되십시오.

답변:


247

이 경우 옵션 1이 더 좋습니다.

가드 문 (확인 isDebugEnabled())은 toString()다양한 객체 의 메소드를 호출 하고 결과를 연결하는 경우 로그 메시지의 잠재적으로 비싼 계산을 방지하기 위해 존재 합니다.

주어진 예에서, 로그 메시지는 상수 문자열이므로 로거가 로그를 버릴 수 있도록하면 로거의 사용 가능 여부를 확인하는 것만 큼 효율적이며 분기가 적기 때문에 코드의 복잡성이 줄어 듭니다.

더 좋은 방법은 로그 문이 형식 스펙과 로거가 대체 할 인수 목록을 취하는 로거가 사용 가능한 경우에만 "lazily"인 최신 로깅 프레임 워크를 사용하는 것입니다. 이것이 slf4j가 취하는 접근법 입니다.

자세한 정보는 log4j로 이와 같은 작업을 수행하는 예제와 관련 질문에 대한 답변을 참조하십시오 .


3
log5j는 slf4j와 거의 같은 방식으로 log4j를 확장합니다
Bill Michell

이것도 java.util.Logging의 접근법입니다.
Paul

@Geek 로그 수준이 높게 설정되어 로그 이벤트가 비활성화 된 경우 더 효율적입니다. 내 답변에서
erickson

1
이것이 log4j 2에서 변경 되었습니까?
SnakeDoc

3
@SnakeDoc 아니요. 메소드 호출의 기본입니다. 메소드 arg-list의 표현식은 호출 전에 효과적으로 평가됩니다. 이러한 표현식이 a) 비싸고 b) 특정 조건에서만 (예 : 디버그가 활성화 된 경우) 원하는 경우 호출 주위에 조건 확인을하는 것이 유일한 선택이며 프레임 워크는이를 수행 할 수 없습니다. 포맷터 기반 로그 방법의 장점은 일부 객체 (기본적으로 무료 임)를 전달할 수 있으며 toString()필요한 경우에만 로거가 호출 한다는 것입니다.
SusanW

31

옵션 1에서 메시지 문자열은 상수이므로 조건으로 로깅 명령문을 랩핑하는 데 절대적으로 이득이 없습니다. 반대로, 로그 명령문이 디버그 가능하면 isDebugEnabled()메소드 에서 한 번, 두 번 평가 됩니다. debug()방법. 호출 비용은 isDebugEnabled()5 ~ 30 나노초 정도이며 대부분의 실제 목적에는 무시할 수 있습니다. 따라서 옵션 2는 코드를 오염시키고 다른 이득을 제공하지 않기 때문에 바람직하지 않습니다.


17

를 사용하면 isDebugEnabled()문자열을 연결하여 로그 메시지를 작성할 때 사용 됩니다.

Var myVar = new MyVar();
log.debug("My var is " + myVar + ", value:" + myVar.someCall());

그러나 귀하의 예에서는 문자열을 로깅하고 연결과 같은 작업을 수행하지 않으므로 속도가 향상되지 않습니다. 따라서 코드에 부풀림을 추가하고 읽기가 더 어려워집니다.

개인적으로 다음과 같이 String 클래스에서 Java 1.5 형식 호출을 사용합니다.

Var myVar = new MyVar();
log.debug(String.format("My var is '%s', value: '%s'", myVar, myVar.someCall()));

나는 많은 최적화가 있는지 의심하지만 읽기가 더 쉽다.

대부분의 로깅 API는 기본적으로 다음과 같은 형식을 제공합니다. 예를 들어 slf4j는 다음을 제공합니다.

logger.debug("My var is {}", myVar);

훨씬 더 읽기 쉽습니다.


8
String.format (...)을 사용하면 로그 줄을 더 쉽게 읽을 수 있지만 실제로 성능에 나쁜 영향을 줄 수 있습니다. 이를 수행하는 SLF4J 방식은 매개 변수를 logger.debug 메소드로 전송 하고 문자열이 생성 되기 전에 isDebugEnabled의 평가가 수행됩니다 . String.format (...)을 사용하여 logger.debug에 대한 메서드 호출이 수행되기 전에 문자열이 생성되므로 디버그 수준이더라도 문자열 작성에 대한 패널티를 지불하게됩니다. 활성화되지 않았습니다. 니트 따기 미안, 그냥 초보자에 대한 혼란을 피하려고 노력 ;-)
StFS

2
String.format은 concat보다 40 배 느리며 slf4j는 2 개의 매개 변수로 제한됩니다. 여기의 숫자를 참조하십시오 : stackoverflow.com/questions/925423/… 프로덕션 시스템이 작동 할 때 디버그 명령문에서 형식화 작업이 낭비되는 프로파일 러 그래프가 많이 있습니다. INFO 또는 ERROR의 로그 수준에서 실행
AztecWarrior_25


8

짧은 버전 : 부울 isDebugEnabled () 검사도 수행 할 수 있습니다.

이유 :
1- 복잡한 논리 / 문자열이 연결된 경우. 디버그 문에 추가되어 이미 체크인했습니다.
2- "복잡한"디버그 명령문에 대한 명령문을 선택적으로 포함 할 필요는 없습니다. 모든 진술은 그런 식으로 포함됩니다.
3- log.debug를 호출하면 로깅 전에 다음을 실행합니다.

if(repository.isDisabled(Level.DEBUG_INT))
return;

이것은 기본적으로 호출 로그와 동일합니다. 또는 고양이. isDebugEnabled ().

하나! 이것이 log4j 개발자들이 생각하는 것입니다 (javadoc에 있으므로 아마도 그것을 따라야합니다).

이것은 방법입니다

public
  boolean isDebugEnabled() {
     if(repository.isDisabled( Level.DEBUG_INT))
      return false;
    return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
  }

이것은 그것을위한 javadoc입니다

/**
*  Check whether this category is enabled for the <code>DEBUG</code>
*  Level.
*
*  <p> This function is intended to lessen the computational cost of
*  disabled log debug statements.
*
*  <p> For some <code>cat</code> Category object, when you write,
*  <pre>
*      cat.debug("This is entry number: " + i );
*  </pre>
*
*  <p>You incur the cost constructing the message, concatenatiion in
*  this case, regardless of whether the message is logged or not.
*
*  <p>If you are worried about speed, then you should write
*  <pre>
*    if(cat.isDebugEnabled()) {
*      cat.debug("This is entry number: " + i );
*    }
*  </pre>
*
*  <p>This way you will not incur the cost of parameter
*  construction if debugging is disabled for <code>cat</code>. On
*  the other hand, if the <code>cat</code> is debug enabled, you
*  will incur the cost of evaluating whether the category is debug
*  enabled twice. Once in <code>isDebugEnabled</code> and once in
*  the <code>debug</code>.  This is an insignificant overhead
*  since evaluating a category takes about 1%% of the time it
*  takes to actually log.
*
*  @return boolean - <code>true</code> if this category is debug
*  enabled, <code>false</code> otherwise.
*   */

1
JavaDoc을 포함 해 주셔서 감사합니다. 나는 어딘가 에서이 조언을 보았고 확실한 참조를 찾으려고 노력하고 있음을 알고있었습니다. 이것은 결정적이지는 않지만 적어도 아주 잘 알려졌습니다.
Simon Peter Chappell

7

다른 사람들이 guard 문을 사용하여 언급했듯이 문자열을 만드는 것이 시간이 많이 걸리는 호출 인 경우에만 실제로 유용합니다. 이것의 구체적인 예는 문자열을 만들 때 게으른로드를 트리거하는 것입니다.

Java 또는 Simple SLF4J ( http://www.slf4j.org/manual.html )를 사용하여이 문제점을 피할 수 있습니다 . 이를 통해 다음과 같은 메소드 호출이 가능합니다.

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

디버그가 활성화 된 경우 전달 된 매개 변수 만 문자열로 변환합니다. 이름에서 알 수 있듯이 SLF4J는 외관 만이며 로깅 호출은 log4j로 전달 될 수 있습니다.

이 버전의 "자신의 롤"버전을 쉽게 만들 수도 있습니다.

도움이 되었기를 바랍니다.


6

옵션 2가 더 좋습니다.

그 자체로는 성능이 향상되지 않습니다. 그러나 성능이 저하되지 않도록합니다. 방법은 다음과 같습니다.

일반적으로 logger.debug (someString);

그러나 일반적으로 응용 프로그램이 커짐에 따라 많은 손이 바뀌고 초보자 개발자는 eSP를 볼 수 있습니다.

logger.debug (str1 + str2 + str3 + str4);

등.

로그 레벨이 ERROR 또는 FATAL로 설정되어 있어도 문자열 연결이 발생합니다! 응용 프로그램에 문자열 연결과 함께 많은 DEBUG 수준 메시지가 포함되어 있으면 특히 jdk 1.4 이하에서 성능이 저하됩니다. (최신 버전의 jdk internall이 stringbuffer.append ()를 수행하는지 확실하지 않습니다).

이것이 옵션 2가 안전한 이유입니다. 문자열 연결조차도 발생하지 않습니다.


3

@erickson처럼 그것은 달려 있습니다. 내가 기억한다면, Log4j isDebugEnableddebug()방법으로 이미 빌드되었습니다 .
객체에 대한 루프, 계산 수행 및 문자열 연결과 같이 디버그 문에서 비싼 계산을 수행하지 않는 한 내 의견으로는 괜찮습니다.

StringBuilder buffer = new StringBuilder();
for(Object o : myHugeCollection){
  buffer.append(o.getName()).append(":");
  buffer.append(o.getResultFromExpensiveComputation()).append(",");
}
log.debug(buffer.toString());

로 더 나은 것

if (log.isDebugEnabled(){
  StringBuilder buffer = new StringBuilder();
  for(Object o : myHugeCollection){
    buffer.append(o.getName()).append(":");
    buffer.append(o.getResultFromExpensiveComputation()).append(",");
  }
  log.debug(buffer.toString());
}

3

A에 대한 한 줄 , 나는이 연결을하지 이런 식으로, 메시지 로깅의 원 내부를 사용 :

ej :

logger.debug(str1 + str2 + str3 + str4);

나는한다:

logger.debug(logger.isDebugEnable()?str1 + str2 + str3 + str4:null);

그러나 여러 줄 의 코드

ej.

for(Message mess:list) {
    logger.debug("mess:" + mess.getText());
}

나는한다:

if(logger.isDebugEnable()) {
    for(Message mess:list) {
         logger.debug("mess:" + mess.getText());
    }
}

3

많은 사람들이 log4j2를 검색 할 때이 답변을보고있을 수 있으며 거의 ​​모든 현재 답변은 log4j2 또는 최근 변경 사항을 고려하지 않기 때문에 질문에 대답해야합니다.

log4j2는 Supplier를 지원합니다 (현재는 자체 구현이지만 문서에 따르면 버전 3.0에서 Java의 Supplier 인터페이스를 사용할 계획입니다). 매뉴얼 에서 이것에 대해 조금 더 읽을 수 있습니다 . 이를 통해 고가의 로그 메시지 작성을 공급 업체에 넣을 수 있습니다.

LogManager.getLogger().debug(() -> createExpensiveLogMessage());

2

디버그 텍스트에서 문자열을 연결하는 것이 일반적이기 때문에 속도가 향상됩니다.

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text" + someState);
}

1
jdk 1.5 이상을 사용하는 경우 연결 문자열에 아무런 차이가 없습니다.
Silent Warrior

어떻게 오세요? JDK5는 어떻게 다르게 할 것입니까?
javashlook

1
jdk 1.5에서 단일 문으로 문자열을 연결하면 내부적으로 StringBuffer.append () 메서드 만 사용합니다. 따라서 성능에 영향을 미치지 않습니다.
Silent Warrior

2
문자열 연결은 의심의 여지없이 시간이 걸립니다. 그러나 나는 그것을 '비싸다'고 묘사할지 확실하지 않습니다. 위의 예제에서 얼마나 많은 시간이 절약됩니까? 주변 코드가 실제로하는 것과 비교하여? (예 : 데이터베이스 읽기 또는 메모리 내 계산). 이런 종류의 진술은 자격이 필요하다고 생각합니다.
Brian Agnew

1
JDK 1.4조차도 간단한 문자열 연결로 새 String 객체를 만들지 않습니다. 문자열이 전혀 표시되지 않으면 StringBuffer.append ()를 사용하면 성능이 저하됩니다.
javashlook

1

이후 의 Log4j 버전 2.4(또는 slf4j-api 2.0.0-alpha1)가 훨씬 더 사용하기의 유창 API (또는 자바 게으른 로깅을위한 8 람다 지원 ), 지원 Supplier<?>에 의해 제공 될 수 로그 메시지 인수에 대한 람다 :

log.debug("Debug message with expensive data : {}", 
           () -> doExpensiveCalculation());

또는 slf4j API를 사용하는 경우 :

log.atDebug()
            .addArgument(() -> doExpensiveCalculation())
            .log("Debug message with expensive data : {}");

0

옵션 2를 사용하면 부울 검사가 빠릅니다. 옵션 1에서 메소드 호출 (스택에 물건을 밀어 넣음)을 수행 한 다음 여전히 빠른 부울 검사를 수행합니다. 내가 보는 문제는 일관성입니다. 디버그 및 정보 문 중 일부가 랩핑되고 일부는 랩핑되지 않은 경우 일관된 코드 스타일이 아닙니다. 또한 나중에 누군가가 연결 문자열을 포함하도록 디버그 문을 변경할 수 있습니다. 대규모 응용 프로그램에서 디버그 및 정보 문을 래핑하고 프로파일 링하면 성능에서 몇 퍼센트 포인트를 절약했습니다. 그다지 많지 않지만 그만한 가치가 있습니다. 이제 IntelliJ에서 랩핑 된 디버그 및 정보 문을 자동으로 생성하기 위해 몇 가지 매크로를 설정했습니다.


0

옵션 2는 실제로 비싸지 않기 때문에 대부분의 경우 옵션 2를 사용하는 것이 좋습니다.

사례 1 : log.debug ( "one string")

사례 2 : log.debug ( "1 문자열"+ "2 문자열"+ object.toString + object2.toString)

이들 중 하나가 호출 될 때 log.debug 내의 매개 변수 문자열 (Case 1 또는 Case2)이 평가되어야합니다. 그것이 모두가 '비싸다'는 의미입니다. 'isDebugEnabled ()'이전에 조건이있는 경우 성능이 저장되는 위치를 평가할 필요가 없습니다.


0

2.x부터 Apache Log4j에는이 확인 기능이 내장되어 있으므로 isDebugEnabled()더 이상 필요하지 않습니다. a를 수행하면 debug()활성화되지 않은 경우 메시지가 표시되지 않습니다.


-1

Log4j2를 사용하면 매개 변수를와 유사한 메시지 템플리트로 형식화 할 수 String.format()있으므로 수행 할 필요가 없습니다 isDebugEnabled().

Logger log = LogManager.getFormatterLogger(getClass());
log.debug("Some message [myField=%s]", myField);

간단한 log4j2.properties 샘플 :

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d %-5p: %c - %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.