[java] java.util.logging을 사용하지 않는 이유는 무엇입니까?

내 인생에서 처음으로 오픈 소스가 될 Java API를 작성하는 위치에 있습니다. 많은 다른 프로젝트에 포함되기를 바랍니다.

로깅을 위해 (그리고 실제로 작업하는 사람들) 항상 JUL (java.util.logging)을 사용했으며 아무런 문제가 없었습니다. 그러나 이제 API 개발을 위해해야 ​​할 일을 더 자세하게 이해해야합니다. 나는 이것에 대해 약간의 연구를 해 왔으며 내가 얻은 정보로 더 혼란스러워합니다. 따라서이 게시물.

내가 JUL에서 왔기 때문에 나는 그것에 편견이 있습니다. 나머지에 대한 나의 지식은 그렇게 크지 않습니다.

내가 한 연구에서 사람들이 JUL을 좋아하지 않는 이유를 생각해 냈습니다.

  1. “Sun이 JUL을 출시하기 훨씬 전에 Java로 개발을 시작했으며 새로운 것을 배우기보다는 logging-framework-X를 계속 사용하는 것이 더 쉬웠습니다 . 흠. 농담이 아니에요. 사람들이 실제로하는 말입니다. 이 논쟁으로 우리 모두는 COBOL을 할 수 있습니다. (그러나 나는 이것이 게으른 친구임을 분명히 말할 수있다)

  2. “JUL의 로깅 수준 이름이 마음에 들지 않습니다 . 진지하게, 이것은 새로운 의존성을 도입할만한 이유가 충분하지 않습니다.

  3. “JUL의 표준 출력 형식이 마음에 들지 않습니다 . 흠. 이것은 단지 구성입니다. 코드 단위로 아무것도 할 필요조차 없습니다. (사실 옛날에는 포맷터 클래스를 직접 만들어야했습니다.)

  4. “저는 logging-framework-X를 사용하는 다른 라이브러리를 사용하기 때문에 그 라이브러리를 사용하는 것이 더 쉽다고 생각했습니다 . 이것은 원형의 주장이지 않습니까? 왜 ‘모두’가 JUL이 아닌 logging-framework-X를 사용합니까?

  5. “다른 사람이 logging-framework-X를 사용하고 있습니다” . 나에게 이것은 위의 특별한 경우입니다. 대다수가 항상 옳은 것은 아닙니다.

그래서 진짜 큰 질문은 왜 JUL이 아닌가? . 내가 놓친 것이 무엇입니까? 외관 로깅 (SLF4J, JCL)에 대한 단점은 역사적으로 여러 로깅 구현이 존재했으며 그 이유는 내가 볼 때 JUL 이전으로 거슬러 올라갑니다. 만약 JUL이 완벽했다면, 정면의 로깅은 존재하지 않습니까? 문제를 더 혼란스럽게 만드는 JUL은 어느 정도 파사드 자체이므로 처리기, 포맷터 및 심지어 LogManager를 바꿀 수 있습니다.

똑같은 일을하는 여러 가지 방법 (로깅)을 포용하는 대신, 왜 그들이 왜 필요한지 의심해서는 안 되는가? (그리고 그 이유가 여전히 존재하는지 확인하십시오)

좋아, 지금까지의 연구는 JUL과 관련 하여 실제로 문제 가 될 수있는 몇 가지 사실을 발견했다 .

  1. 성능 . 어떤 사람들은 SLF4J의 성능이 다른 것보다 우수하다고 말합니다. 이것은 나에게 조기 최적화의 경우 인 것 같습니다. 초당 수백 메가 바이트를 기록 해야하는 경우 어쨌든 올바른 경로에 있는지 잘 모르겠습니다. JUL도 발전했으며 Java 1.4에서 수행 한 테스트가 더 이상 사실이 아닐 수도 있습니다. 여기에서 읽을 수 있으며이 수정으로 Java 7로 변경되었습니다. 많은 사람들이 로깅 메소드에서 문자열 연결의 오버 헤드에 대해서도 이야기합니다. 그러나 템플리트 기반 로깅은이 비용을 피하며 JUL에도 존재합니다. 개인적으로는 템플릿 기반 로깅을 작성하지 않습니다. 너무 게으르다. 예를 들어 JUL로 이것을하면 :

    log.finest("Lookup request from username=" + username
       + ", valueX=" + valueX
       + ", valueY=" + valueY));

    내 IDE가 경고하고 다음과 같이 변경해야한다는 권한을 요청합니다.

    log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}",
       new Object[]{username, valueX, valueY});

    .. 물론 받아 들일 것입니다. 권한 부여! 도와 주셔서 감사합니다.

    따라서 실제로 그러한 진술을 직접 작성하지는 않습니다. IDE에서 수행합니다.

    성능 문제에 대한 결론에서 나는 JUL의 성능이 경쟁사에 비해 좋지 않다는 것을 암시하는 것을 찾지 못했습니다.

  2. 클래스 경로에서 구성 . 기본 JUL은 클래스 경로에서 구성 파일을로드 할 수 없습니다. 그렇게 하는 것은 몇 줄의 코드 입니다. 왜 이것이 귀찮은 지 알 수 있지만 해결책은 짧고 간단합니다.

  3. 출력 핸들러의 가용성 . JUL에는 콘솔, 파일 스트림, 소켓 및 메모리 등 5 가지 출력 핸들러가 기본 제공됩니다. 이것들은 확장되거나 새로운 것을 쓸 수 있습니다. 예를 들어, UNIX / Linux Syslog 및 Windows 이벤트 로그에 쓰는 중일 수 있습니다. 나는 개인적 으로이 요구 사항을 갖지 못했거나 사용 된 것을 보지 못했지만 그것이 왜 유용한 기능 일 수 있는지 분명히 알 수 있습니다. 로그 백에는 Syslog의 어 펜더가 포함되어 있습니다. 여전히 나는 그것을 주장 할 것이다

    1. 출력 대상에 대한 요구의 99.5 %는 기본적으로 JUL에 포함되어 있습니다.
    2. 특별한 요구 사항은 다른 것보다는 JUL 위의 사용자 지정 처리기에 의해 제공 될 수 있습니다. JUL 용 Syslog 출력 핸들러를 다른 로깅 프레임 워크보다 작성하는 데 시간이 더 걸린다는 제안은 없습니다.

내가 간과 한 것이 있다는 것이 정말 걱정입니다. JUL 이외의 로깅 파사드와 로깅 구현의 사용은 너무나 널리 퍼져 있으므로 이해하지 못하는 사람이 나라는 결론에 도달해야합니다. 처음이 아니에요, 두렵습니다. 🙂

API로 무엇을해야합니까? 나는 그것이 성공하기를 원합니다. 물론 “흐름을 따라가”고 SLF4J (요즘 가장 인기있는 것)를 구현할 수는 있지만, 내 자신을 위해 오늘의 JUL에 무엇이 잘못되었는지 정확히 이해해야합니까? 내 라이브러리에 JUL을 선택하여 스스로 방해 할 수 있습니까?

테스트 성능

(2012 년 7 월 7 일에 nolan600에 의해 추가 된 섹션)

Ceki에서 SLF4J의 매개 변수화가 JUL보다 10 배 이상 빠르다는 내용이 아래에 나와 있습니다. 그래서 간단한 테스트를 시작했습니다. 언뜻보기에 주장은 정확합니다. 예비 결과는 다음과 같습니다 (그러나 계속 읽어보십시오!).

  • 실행 시간 SLF4J, 백엔드 로그 백 : 1515
  • 실행 시간 SLF4J, 백엔드 JUL : 12938
  • 실행 시간 JUL : 16911

위의 숫자는 msec이므로 적을수록 좋습니다. 따라서 성능 차이의 10 배는 실제로는 실제로 거의 비슷합니다. 내 초기 반응 : 그것은 많이입니다!

테스트의 핵심은 다음과 같습니다. 알 수 있듯이 정수와 문자열은 루프로 해석되어 로그 문에 사용됩니다.

    for (int i = 0; i < noOfExecutions; i++) {
        for (char x=32; x<88; x++) {
            String someString = Character.toString(x);
            // here we log 
        }
    }

(로그 진술에 기본 데이터 유형 (이 경우 int)과 더 복잡한 데이터 유형 (이 경우 String)이 필요했습니다. 확실하지 않지만 거기에 있습니다.)

SLF4J에 대한 로그 설명 :

logger.info("Logging {} and {} ", i, someString);

JUL에 대한 로그 설명 :

logger.log(Level.INFO, "Logging {0} and {1}", new Object[]{i, someString});

실제 측정을 수행하기 전에 동일한 테스트를 한 번 실행하여 JVM이 ‘워밍업’되었습니다. Java 1.7.03은 Windows 7에서 사용되었습니다. 최신 버전의 SLF4J (v1.6.6) 및 Logback (v1.0.6)이 사용되었습니다. Stdout 및 stderr이 널 장치로 경로 재 지정되었습니다.

그러나 JUL은 getSourceClassName()기본적으로 소스 클래스 이름을 출력에 출력하지만 Logback은 출력하지 않기 때문에 JUL이 대부분의 시간을 소비하고 있음을 조심 하십시오. 우리는 사과와 오렌지를 비교하고 있습니다. 테스트를 다시 수행하고 로깅 구현을 비슷한 방식으로 구성하여 실제로 동일한 내용을 출력해야합니다. 그러나 SLF4J + Logback은 여전히 ​​위에 나와 있지만 초기 숫자와는 거리가 멀다고 생각합니다. 계속 지켜봐 주시기 바랍니다.

Btw : 테스트는 실제로 SLF4J 또는 Logback으로 작업 한 것이 처음입니다. 즐거운 경험. JUL은 시작할 때 훨씬 덜 환영합니다.

테스트 성능 (2 부)

(2012 년 7 월 8 일에 nolan600에 의해 추가 된 섹션)

JUL에서 패턴을 구성하는 방법, 즉 소스 이름 포함 여부에 관계없이 성능에는 문제가되지 않습니다. 나는 매우 간단한 패턴으로 시도했다.

java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"

그리고 그것은 위의 타이밍을 전혀 바꾸지 않았습니다. 내 프로파일 러는 로거 getSourceClassName()가 내 패턴의 일부가 아니더라도 호출에 많은 시간을 보냈다고 밝혔다 . 패턴은 중요하지 않습니다.

따라서 테스트 된 템플릿 기반 로그 문에 대해 JUL (느린)과 SLF4J + 로그 백 (빠른)의 실제 성능 차이에 대략 10의 요인이있는 것으로 보이는 성능 문제에 대해 결론을 내 렸습니다. Ceki가 말한 것처럼.

또한 SLF4J의 getLogger()통화가 JUL의 통화보다 훨씬 비싸다는 또 다른 것을 볼 수 있습니다 . (내 프로파일 러가 정확하면 95ms 대 0.3ms). 이것은 말이됩니다. SLF4J는 기본 로깅 구현의 바인딩에 약간의 시간을 할애해야합니다. 이것은 나를 두려워하지 않습니다. 이러한 호출은 응용 프로그램 수명 동안 다소 드 물어야합니다. 견뢰도는 실제 로그 호출에 있어야합니다.

최종 결론

(2012 년 7 월 8 일에 nolan600에 의해 추가 된 섹션)

모든 답변에 감사드립니다. 처음에 생각했던 것과는 달리 API에 SLF4J를 사용하기로 결정했습니다. 이것은 많은 것들과 당신의 입력을 기반으로합니다 :

  1. 배포시 로그 구현을 유연하게 선택할 수 있습니다.

  2. 응용 프로그램 서버 내에서 실행될 때 JUL 구성의 유연성이 부족한 문제

  3. SLF4J는 Logback과 결합하면 특히 위에서 설명한 것처럼 훨씬 빠릅니다. 이것이 거친 테스트 일지라도 JUL보다 SLF4J + Logback에서 훨씬 많은 노력이 최적화되었다고 생각할 이유가 있습니다.

  4. 선적 서류 비치. SLF4J에 대한 문서는 훨씬 포괄적이고 정확합니다.

  5. 패턴 유연성. 테스트를 수행하면서 JUL이 Logback의 기본 패턴을 모방하도록 설정했습니다. 이 패턴에는 스레드 이름이 포함됩니다. JUL 은이 작업을 즉시 수행 할 수 없습니다. 좋아, 나는 지금까지 그것을 놓치지 않았다. 그러나 나는 그것이 로그 프레임 워크에서 없어야한다고 생각하지 않는다. 기간!

  6. 오늘날 대부분의 (또는 많은) Java 프로젝트는 Maven을 사용하므로 종속성을 추가하는 것이 특히 그 종속성이 다소 안정적인 경우, 즉 API를 지속적으로 변경하지 않는 경우 큰 문제는 아닙니다. 이것은 SLF4J에 해당되는 것 같습니다. 또한 SLF4J 항아리와 친구는 크기가 작습니다.

그래서 이상한 일이 실제로 SLF4J와 조금 일한 후 JUL에 대해 화가 났었습니다. JUL과 함께 해야하는 것이 여전히 유감입니다. JUL은 완벽하지는 않지만 그 일을합니다. 충분하지 않습니다. Properties예를 들어 똑같이 말할 수는 있지만 사람들이 자신의 구성 라이브러리에 플러그인 할 수 있다고 추상화하지는 않습니다. 나는 그 이유가 Properties바로 막대 바로 위에 오는 반면 오늘 JUL의 반대는 사실 이라고 생각합니다 … 그리고 과거에는 존재하지 않았기 때문에 0에 도달했습니다.



답변

면책 조항 : 저는 log4j, SLF4J 및 logback 프로젝트의 창립자입니다.

SLF4J를 선호하는 객관적인 이유가 있습니다. 우선 SLF4J를 사용하면 최종 사용자가 기본 로깅 프레임 워크를 선택할 수 있습니다 . 또한 숙련 된 사용자는 jul이 뒤쳐져 log4j 이외의 기능을 제공 하는 로그 백 을 선호하는 경향이 있습니다. 기능별 jul은 일부 사용자에게는 충분할 수 있지만 다른 사용자에게는 충분하지 않습니다. 간단히 말해서, 로깅이 중요한 경우 SLF4J를 기본 구현으로 로그 백과 함께 사용하려고합니다. 로깅이 중요하지 않으면 jul이 좋습니다.

그러나 OS 개발자는 자신 만이 아니라 사용자의 선호도를 고려해야합니다. 이 때문에하지 SLF4J을 채택해야 다음 당신은 SLF4J 더 7월 이상하지만 것을 확신 현재 대부분의 자바 개발자 (2012 년 7 월) 자신의 로깅 API로 SLF4J를 선호하기 때문이다. 궁극적으로 대중 의견에 신경 쓰지 않기로 결정한 경우 다음 사실을 고려하십시오.

  1. jul을 선호하는 사람들은 jul이 JDK와 번들로 제공되므로 편리하지 않습니다. 내 지식으로는 줄을 찬성하는 다른 객관적인 주장은 없다
  2. jul에 대한 당신의 선호는 바로 그 선호 입니다.

따라서, 용감한 것 같지만 여론보다 “고단 사실”을 유지하는 것은이 경우 논리적 오류입니다.

여전히 확신이 없다면 JB Nizet 는 추가적이고 강력한 논쟁을합니다.

최종 사용자는 자신의 코드 또는 log4j 또는 logback을 사용하는 다른 라이브러리에 대해이 사용자 정의를 이미 수행했을 수 있습니다. jul은 확장 가능하지만 로그 백, jul, log4j를 확장해야하며 하나님은 다른 로깅 프레임 워크를 사용하는 것은 4 개의 서로 다른 로깅 프레임 워크를 사용하는 4 개의 라이브러리를 사용하기 때문에 번거 롭습니다. SLF4J를 사용하면 선택한 프레임 워크가 아닌 원하는 로깅 프레임 워크를 구성 할 수 있습니다. 전형적인 프로젝트는 당신뿐만 아니라 수많은 라이브러리를 사용한다는 것을 기억하십시오 .

어떤 이유로 든 SLF4J API를 싫어하고 그것을 사용하여 작업에서 재미를 빼앗는다면 반드시 jul을 찾으십시오. 결국 jul을 SLF4J리디렉션 하는 수단이 있습니다 .

그런데 jul 매개 변수화는 SLF4J보다 적어도 10 배 느리므로 눈에 띄는 차이가 있습니다.


답변

  1. java.util.loggingJava 1.4에 도입되었습니다. 그 전에 로깅에 대한 사용이 있었기 때문에 다른 많은 로깅 API가 존재합니다. Java 1.4 이전에 많이 사용 된 API는 1.4가 출시되었을 때 0으로 떨어지지 않은 시장 점유율을 보였습니다.

  2. JUL은 1.4에서 훨씬 더 나쁘고 1.5에서만 나아진 곳에서 언급 한 많은 것을 시작하지 않았습니다 (6도 추측하지만 너무 확실하지는 않습니다).

  3. JUL은 동일한 JVM에서 서로 다른 구성을 가진 여러 응용 프로그램에 적합하지 않습니다 (상호 작용하지 않아야하는 여러 웹 응용 프로그램 생각). Tomcat은 그 일을하기 위해 몇 가지 농구를 뛰어 넘어야합니다 (정확하게 이해하면 효과적으로 JUL을 다시 구현).

  4. 라이브러리가 사용하는 로깅 프레임 워크에 항상 영향을 줄 수는 없습니다. 따라서 SLF4J (실제로 다른 라이브러리보다 매우 얇은 API 레이어)를 사용하면 전체 로깅 세계에 대해 다소 일관된 그림을 유지할 수 있습니다 (따라서 동일한 시스템에서 라이브러리 로깅을 유지하면서 기본 로깅 프레임 워크를 결정할 수 있음).

  5. 라이브러리는 쉽게 변경할 수 없습니다. logging-library-X를 사용하는 데 사용 된 이전 버전의 라이브러리 인 경우 logging-library-Y (예 : JUL)로 쉽게 전환 할 수 없습니다. 새로운 로깅 프레임 워크와 (적어도) 로깅을 재구성합니다. 그것은 대부분의 사람들에게 명백한 이익을 가져 오지 않을 때 특히 그렇습니다.

나는 7 월이 생각하는 모든 것을 미루어 보아 적어도 다른 로깅 프레임 워크 이러한 일에 대한 유효한 대안.


답변

slf4j와 같은 로깅 파사드를 사용할 때의 주요 이점은 라이브러리의 최종 사용자가 최종 사용자에게 선택을 강요하기보다는 원하는 구체적인 로깅 구현을 선택할 수 있다는 것입니다.

어쩌면 그는 Log4j 또는 LogBack (특수 포맷터, 어 펜더 등)에 시간과 돈을 투자했으며 jul을 구성하는 대신 Log4j 또는 LogBack을 계속 사용하는 것을 선호합니다. 문제 없습니다 : slf4j가 허용합니다. Jul보다 Log4j를 사용하는 것이 현명한 선택입니까? 그럴 수도 있고 아닐 수도 있고. 그러나 당신은 상관하지 않습니다. 최종 사용자가 원하는 것을 선택하게하십시오.


답변

JUL을 사용하는 것이 가장 쉬운 방법 이었기 때문에 JUL을 사용하기 시작했습니다. 그러나 지난 몇 년 동안, 나는 조금 더 많은 시간을 보냈 으면 좋겠다.

내 주요 문제는 이제 많은 응용 프로그램에서 사용되는 상당한 양의 ‘라이브러리’코드가 있으며 모두 JUL을 사용한다는 것입니다. 웹 서비스 유형 앱에서 이러한 도구를 사용할 때마다 로깅이 사라지거나 예측할 수 없거나 이상한 곳으로갑니다.

우리의 해결책은 라이브러리 로그 호출이 변경되지 않았지만 사용 가능한 로깅 메커니즘으로 동적으로 리디렉션되는 라이브러리 코드에 외관을 추가하는 것이 었습니다. POJO 도구에 포함되면 JUL로 연결되지만 웹 응용 프로그램으로 배포되면 LogBack으로 리디렉션됩니다.

우리의 후회는 물론 라이브러리 코드가 매개 변수화 된 로깅을 사용하지 않지만 이제 필요할 때마다 개장 할 수 있다는 것입니다.

우리는 slf4j를 사용하여 정면을 만들었습니다.


답변

logback-1.1.7을 통해 slf4j-1.7.21에 대해 jul을 실행하고 SSD, Java 1.8, Win64로 출력

jul은 1M 루프에 대해 48449ms, 로그 백 27185ms를 실행했습니다.

여전히 조금 더 빠른 속도와 조금 더 멋진 API는 3 개의 라이브러리와 800K의 가치가 없습니다.

package log;

import java.util.logging.Level;
import java.util.logging.Logger;

public class LogJUL
{
    final static Logger logger = Logger.getLogger(LogJUL.class.getSimpleName());

    public static void main(String[] args)
    {
        int N = 1024*1024;

        long l = System.currentTimeMillis();

        for (int i = 0; i < N; i++)
        {
            Long lc = System.currentTimeMillis();

            Object[] o = { lc };

            logger.log(Level.INFO,"Epoch time {0}", o);
        }

        l = System.currentTimeMillis() - l;

        System.out.printf("time (ms) %d%n", l);
    }
}

package log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogSLF
{
    static Logger logger = LoggerFactory.getLogger(LogSLF.class);


    public static void main(String[] args)
    {
        int N = 1024*1024;

        long l = System.currentTimeMillis();

        for (int i = 0; i < N; i++)
        {
            Long lc = System.currentTimeMillis();

            logger.info("Epoch time {}", lc);
        }

        l = System.currentTimeMillis() - l;

        System.out.printf("time (ms) %d%n", l);
    }

}


답변