Dlaczego nie używać Javy?util.wycinka drzew?

Po raz pierwszy w życiu znalazłem się w sytuacji, w której piszę Java API, które będzie open source. Mam nadzieję, że zostanie włączony do wielu innych projektów.

Do logowania ja (i rzeczywiście ludzie, z którymi pracuję) zawsze używałem JUL (java.util.logowania) i nigdy nie miał z tym żadnych problemów. Jednak teraz muszę bardziej szczegółowo zrozumieć, co powinienem zrobić dla mojego rozwoju API. Poszperałem trochę na ten temat i z informacjami, które mam, jestem po prostu bardziej zdezorientowany. Stąd ten post.

Ponieważ pochodzę z JUL jestem stronniczy w tym. Moja wiedza o reszcie nie jest aż tak duża.

Z badań, które zrobiłem, wymyśliłem te powody, dla których ludzie nie lubią JUL:

  1. "zacząłem się rozwijać w Javie na długo przed wydaniem Sun JUL i łatwiej było mi kontynuować logging-framework-X, niż nauczyć się czegoś nowego" . Hmm. Nie żartuję, ludzie tak mówią. Z tym argumentem możemy wszyscy be doing COBOL. (jednak z pewnością mogę odnieść się do tego, że sam jestem leniwy)

  2. "nie podoba mi się nazwa poziomów logowania w JUL" . Ok, poważnie, to nie jest wystarczający powód, aby wprowadzić nową zależność.

  3. "nie podoba mi się standardowy format wyjścia z JUL" . Hmm. To tylko konfiguracja. Nie musisz nawet robić nic kodowego. (to prawda, w dawnych czasach być może trzeba było stworzyć własne Formatter klasy, aby uzyskać to dobrze).

  4. "używam innych bibliotek, które również używają logging-framework-X, więc pomyślałem, że łatwiej będzie użyć właśnie tej" . To cykliczna kłótnia, prawda ? Dlaczego 'wszyscy' używają logging-framework-X, a nie JUL?

  5. "wszyscy inni używają logging-framework-X" . Dla mnie jest to tylko szczególny przypadek powyższego. Większość nie zawsze ma rację.

Więc prawdziwe pytanie brzmi dlaczego nie JUL?. Co mnie ominęło ? Raison d ' être for logging facades (SLF4J, JCL) jest to, że wiele implementacji loggingu istniało historycznie, a powód tego naprawdę sięga epoki przed lip, jak to widzę. Gdyby JUL był idealny to nie istniałyby Elewacje, czy co? Nie powinniśmy raczej pytać, dlaczego były one konieczne? (i sprawdź, czy te powody nadal istnieją)

Ok, moje dotychczasowe badania doprowadziły do kilku rzeczy to widzę może być prawdziwe problemy Z JUL:

  1. Wydajność . Niektórzy mówią, że wydajność w SLF4J jest lepsza od reszty. Wydaje mi się, że jest to przypadek przedwczesnej optymalizacji. Jeśli musisz rejestrować setki megabajtów na sekundę, nie jestem pewien, czy i tak jesteś na dobrej drodze. JUL również ewoluował i testy, które zrobiłeś na Javie 1.4, mogą już nie być prawdziwe. Możesz przeczytać o tym tutaj i ta poprawka trafiła do Java 7. Wielu też mówi o napowietrzności konkatenacji łańcuchów w metodach logowania. Jednak logowanie oparte na szablonach pozwala uniknąć tego kosztu i istnieje również w lipcu. Osobiście nigdy nie piszę logowania opartego na szablonach. Za leniwy na to. Na przykład, jeśli zrobię to z JUL:

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

    Mój IDE ostrzega mnie i prosi o pozwolenie, aby zmienić go na:

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

    .. co oczywiście zaakceptuję. Udzielam pozwolenia ! Dziękuję za pomoc.

    Więc sam nie piszę takich wypowiedzi, robi to IDE.

    Podsumowując kwestię wydajności nie znalazłem nic, co sugerowałoby, że wydajność JUL nie jest ok w porównaniu z konkurencją.

  2. Konfiguracja z classpath . Po wyjęciu z pudełka JUL nie może załadować pliku konfiguracyjnego ze ścieżki klasowej. Jest to kilka linijek kodu , aby to zrobić. Rozumiem, dlaczego może to być irytujące, ale rozwiązanie jest krótkie i proste.

  3. Dostępność urządzeń obsługujących wyjścia . JUL jest wyposażony w 5 uchwytów wyjściowych po wyjęciu z pudełka: konsolę, strumień plików, gniazdo i pamięć. Można je rozszerzyć lub napisać nowe. Może to być na przykład zapis do UNIX / Linux Syslog i Windows Event Log. Osobiście nigdy nie miałem tego wymogu ani nie widziałem go używanego, ale z pewnością mogę odnieść się do tego, dlaczego może to być przydatna funkcja. Logback jest dostarczany z appenderem na przykład dla Syslog. Mimo to argumentowałbym, że

    1. 99,5% zapotrzebowania na miejsca wyjściowe są objęte tym, co jest w JUL out-of-the-box.
    2. specjalne potrzeby mogą być zaspokajane przez niestandardowych opiekunów na górze JUL, a nie na górze czegoś innego. Nie ma dla mnie nic, co sugerowałoby, że napisanie obsługi wyjścia Syslog dla JUL zajmuje więcej czasu niż dla innego frameworka logowania.
Naprawdę martwię się, że coś przeoczyłem. Wykorzystanie elewacji zrębowych i realizacji zrębowych innych niż JUL jest tak powszechne, że muszę dojść do wniosku, że to ja po prostu nie rozumiem. Obawiam się, że to nie byłby pierwszy raz. :-)

Więc co powinienem zrobić z moim API? Chcę, żeby to się powiodło. Mogę oczywiście po prostu "iść z prądem" i zaimplementować SLF4J (który wydaje się najbardziej popularny w dzisiejszych czasach), ale dla własnego dobra muszę dokładnie zrozumieć, co jest nie tak z dzisiejszym JUL, który gwarantuje wszystkie fuzz? Czy wybierając JUL do swojej biblioteki ?

Testowanie wydajności

(dział dodany przez nolan600 dnia 07-JUL-2012)

Jest odniesienie poniżej z Ceki o parametryzacji SLF4J jest 10 razy lub więcej szybciej niż JUL. więc zacząłem robić kilka prostych testów. Na pierwszy rzut oka twierdzenie jest z pewnością poprawne. Oto wstępne wyniki (ale czytaj dalej!):

  • Czas wykonania SLF4J, backend Logback: 1515
  • Czas wykonania SLF4J, backend JUL: 12938
  • Czas wykonania Lip: 16911

Powyższe liczby to msecs, więc mniej znaczy lepiej. Więc 10 razy różnica wydajności jest na początku całkiem blisko. Moja początkowa reakcja: to dużo !

Oto rdzeń testu. Jak widać liczba całkowita i łańcuch jest konstruowany w pętli, która jest następnie używana w instrukcji log:

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

(chciałem, aby Instrukcja log miała zarówno prymitywny typ danych (w tym przypadku int), jak i bardziej złożony typ danych (w tym przypadku String). Nie jasne, że to ma znaczenie, ale masz to.)

Deklaracja log dla SLF4J:

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

Wypowiedź dziennika dla JUL:

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

JVM został "rozgrzany" tym samym testem wykonanym raz przed wykonaniem rzeczywistego pomiaru. Java 1.7.03 została użyta w systemie Windows 7. Zastosowano najnowsze wersje SLF4J (v1.6.6) i Logback (v1.0.6). Stdout i stderr zostały przekierowane do urządzenia null.

Jednak teraz ostrożnie, okazuje się, że JUL spędza większość czasu w getSourceClassName(), ponieważ JUL domyślnie wypisuje nazwę klasy źródłowej na wyjściu, podczas gdy Logback nie. porównujemy więc jabłka i pomarańcze. Muszę zrobić test jeszcze raz i skonfigurować implementacje logowania w podobny sposób, aby faktycznie generowały te same rzeczy. Podejrzewam jednak, że SLF4J + Logback nadal wyjdzie na wierzch, ale daleko od początkowych numerów, jak podano powyżej. Zostańcie z nami.

Btw: w teście po raz pierwszy pracowałem z SLF4J lub Logback. Przyjemne doświadczenie. JUL jest z pewnością dużo mniej przyjazna, gdy zaczynasz.

Testowanie wydajności (część 2)

(dział dodany przez nolan600 dnia 08-JUL-2012)

Jak się okazuje nie ma znaczenia dla wydajności jak skonfigurujesz swój wzorzec w JUL, tzn. czy zawiera nazwę źródłową czy nie. Próbowałem z bardzo prostym wzorem:

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

I to wcale nie zmieniło powyższych czasów. Mój profiler ujawnił, że rejestrator nadal spędził dużo czasu w połączenia do getSourceClassName(), nawet jeśli to nie było częścią mojego wzorca. Wzór nie ma znaczenia.

W związku z tym kończę w kwestii wydajności, że przynajmniej w przypadku testowanego Oświadczenia dziennika opartego na szablonie wydaje się być mniej więcej czynnikiem 10 w rzeczywistej różnicy wydajności między JUL (slow) i SLF4J+Logback (quick). Tak jak powiedział Ceki.

Widzę też inną rzecz, a mianowicie, że połączenie SLF4J jest dużo droższe niż Jul ditto. (95 ms vs 0.3 ms jeśli mój profiler jest dokładne). To ma sens. SLF4J musi odsiedzieć trochę czasu na powiązaniu podstawowej implementacji logowania. To mnie nie przeraża. Wywołania te powinny być dość rzadkie w okresie użytkowania aplikacji. Szybkość powinna być w rzeczywistych wywołaniach dziennika.

Ostateczna konkluzja

(dział dodany przez nolan600 dnia 08-JUL-2012)

Dziękuję za wszystkie odpowiedzi. Wbrew temu, co początkowo myślałem, że zdecydowałem się na użycie SLF4J dla mojego API. Jest to oparte na liczbie rzeczy i twój wkład:

  1. Daje to elastyczność wyboru wdrożenia dziennika w czasie wdrażania.

  2. Problemy z brakiem elastyczności konfiguracji JUL podczas uruchamiania wewnątrz serwera aplikacji.

  3. SLF4J jest z pewnością o wiele szybszy, jak opisano powyżej, w szczególności, jeśli połączysz go z Logbackiem. Nawet jeśli to był tylko szorstki test mam powody sądzić, że dużo więcej wysiłku włożyłem w optymalizację na SLF4J + Logback niż na JUL.

  4. Dokumentacja. Dokumentacja SLF4J jest po prostu dużo bardziej obszerna i precyzyjna.

  5. Elastyczność wzoru. Podczas testów postanowiłem, że JUL naśladuje domyślny wzór z Logbacka. Ten wzór zawiera nazwę wątku. Okazuje się, że JUL nie może tego zrobić po wyjęciu z pudełka. Ok, Nie przegapiłem tego do tej pory, ale myślę, że nie jest to rzecz, której powinno brakować w log frameworku. Kropka!

  6. Większość (lub wiele) projektów Java używa dziś Mavena, więc dodanie zależności nie jest aż tak wielką rzeczą, zwłaszcza jeśli zależność ta jest raczej stabilna, tzn. nie zmienia ciągle swojego API. To wydaje się być prawdą dla SLF4J. również SLF4J jar i przyjaciele są małe.

Więc dziwną rzeczą, która się wydarzyła, było to, że naprawdę zdenerwowałem się na JUL po pracy trochę z SLF4J. nadal żałuję, że tak musi być z JUL. JUL jest daleki od ideału, ale wykonuje swoją pracę. Po prostu nie dość dobrze. To samo można powiedzieć o Properties jako przykład, ale nie myślimy o abstrakcji, aby ludzie mogli podłączyć własną bibliotekę konfiguracji i co masz. Myślę, że powodem jest to, że Properties pojawia się tuż nad poprzeczką, podczas gdy przeciwieństwo jest prawdziwe dla JUL dzisiaj ... a w przeszłości była zerowa, bo nie istniała.

Author: peterh, 2012-07-06

5 answers

Disclaimer: jestem założycielem projektów log4j, SLF4J i logback.

Istnieją obiektywne powody, dla których preferuje się SLF4J. po pierwsze, daje użytkownikowi końcowemu swobodę wyboru podstawowej struktury logowania. Ponadto użytkownicy savvier preferują logback, który oferuje możliwości wykraczające poza log4j , A j. u. L pozostaje w tyle. Funkcjonalność j.u. l może być wystarczająca dla niektórych użytkowników, ale dla wielu innych po prostu nie jest. w skrócie, jeśli logowanie jest ważne, aby Ty, chciałbyś użyć SLF4J z logback jako podstawowej implementacji. Jeśli logowanie jest nieistotne, J. U. l jest w porządku.

Jednak, jako programista oss, musisz wziąć pod uwagę preferencje swoich użytkowników, a nie tylko własne. Wynika z tego, że powinieneś przyjąć SLF4J nie dlatego, że ty jesteś przekonany, że SLF4J jest lepszy niż j.u.L, ale dlatego, że większość programistów Javy obecnie (lipiec 2012) preferuje SLF4J jako swój logging API. Jeśli ostatecznie zdecydujesz się nie dbać o popularne opinia, należy wziąć pod uwagę następujące fakty:

    Ci, którzy wolą J. U. L, robią to z wygody, ponieważ J. U. l jest w pakiecie z JDK. Według mojej wiedzy nie ma innych obiektywnych argumentów przemawiających za j. u. L.
  1. twoje własne preferencje dla j.U.L są takie, preferencje .

Zatem trzymanie "twardych faktów" nad opinią publiczną, choć pozornie odważne, jest logicznym błędem w tym przypadku.

Jeśli nadal nie przekonany, JB Nizet sprawia, że dodatkowy i mocny argument:

Z wyjątkiem tego, że użytkownik końcowy mógł już zrobić to dostosowanie dla swojego własny kod lub inną bibliotekę używającą log4j lub logback. j. u. L jest extensible, ale trzeba rozszerzyć logback, j. u. L, log4j i tylko God wie, który inny Framework logowania, ponieważ używa czterech bibliotek, które używanie czterech różnych frameworków logowania jest uciążliwe. Używając SLF4J, możesz pozwól mu skonfigurować frameworki logowania, które chce, a nie ten wybrałeś. pamiętaj, że typowy projekt wykorzystuje miriady biblioteki, i nie tylko Twoje .

Jeśli z jakiegoś powodu nienawidzisz API SLF4J i korzystanie z niego pozbawi Cię frajdy z pracy, to zdecydowanie idź na j. u. L.W końcu są sposoby na przekierowanie j.U.L do SLF4J.

Nawiasem mówiąc, parametryzacja j.U.L jest co najmniej 10 razy wolniejsza niż SLF4J, co kończy się zauważalną różnicą.

 165
Author: Ceki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:46
  1. java.util.logging został wprowadzony w Javie 1.4. Wcześniej były zastosowania do logowania, dlatego istnieje wiele innych interfejsów API logowania. Te interfejsy API, które były mocno używane przed Javą 1.4, miały więc świetny marketshare, który nie spadł tylko do 0, gdy Wersja 1.4 została wydana.

  2. JUL nie zaczynał tak dobrze, wiele z rzeczy, o których wspomniałeś, gdzie dużo gorzej w 1.4 i tylko lepiej w 1.5 (i chyba w 6 też, ale nie jestem zbyt pewien).

  3. JUL nie pasuje dla wielu aplikacji o różnych konfiguracjach w tym samym JVM (pomyśl o wielu aplikacjach internetowych, które nie powinny wchodzić w interakcje). Tomcat musi przeskoczyć przez kilka obręczy, aby to działało (skutecznie ponownie wdrażając JUL, jeśli dobrze zrozumiałem).

  4. Nie zawsze możesz wpływać na to, jakiego frameworka logowania używają Twoje biblioteki. Dlatego użycie SLF4J (który jest w rzeczywistości tylko bardzo cienką warstwą API ponad innymi bibliotekami) pomaga utrzymać nieco spójny obraz całego logging world (dzięki czemu możesz decydować o podstawowej strukturze logowania, mając jednocześnie bibliotekę logującą się w tym samym systemie).

  5. Biblioteki nie mogą się łatwo zmienić. Jeśli poprzednia wersja biblioteki używała logging-library-X, nie może łatwo przełączyć się na logging-library-Y (na przykład JUL), nawet jeśli ta ostatnia jest wyraźnie nadrzędna: każdy użytkownik tej biblioteki musiałby nauczyć się nowego frameworka logging i (przynajmniej) zmienić swoją konfigurację logowania. To wielkie Nie-Nie, zwłaszcza gdy nie przynosi widocznego zysku większości ludzi.

Po tym wszystkim, co myślę JUL jest przynajmniej ważną alternatywą dla innych frameworków do logowania w dzisiejszych czasach.

 26
Author: Joachim Sauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-09 08:37:43

IMHO, główną zaletą korzystania z logowania fasadowego takiego jak slf4j jest to, że pozwalasz użytkownikowi końcowemu biblioteki wybrać konkretną implementację logowania, zamiast narzucać swój wybór użytkownikowi końcowemu.

Może zainwestował czas i pieniądze w Log4j lub LogBack (specjalne formatery, appendery itp.) i woli nadal używać Log4j lub LogBack, zamiast konfigurować jul. Nie ma problemu: slf4j na to pozwala. Czy warto używać Log4j zamiast jul? Może, może nie. Ale nie obchodzi cię to. Niech użytkownik końcowy wybierze to, co woli.

 24
Author: JB Nizet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-06 09:19:45

Zacząłem, tak jak ty podejrzewam, używać JUL, ponieważ najłatwiej było zacząć od razu. Z biegiem lat jednak żałuję, że nie spędziłem trochę więcej czasu na wyborze.

Moim głównym problemem jest teraz to, że mamy znaczną ilość kodu 'bibliotecznego', który jest używany w wielu aplikacjach i wszystkie używają JUL. Ilekroć używam tych narzędzi w aplikacji typu web-service, logowanie po prostu znika lub przechodzi w nieprzewidywalne lub dziwne miejsce.

Naszym rozwiązaniem było dodanie fasadę do kodu biblioteki, co oznaczało, że wywołania dziennika biblioteki nie zmieniły się, ale były dynamicznie przekierowywane do dowolnego dostępnego mechanizmu logowania. Gdy są zawarte w narzędziu POJO są one kierowane do JUL, ale po wdrożeniu jako aplikacja internetowa są przekierowywane do LogBack.

Nasz żal-oczywiście-jest taki, że kod biblioteki nie używa parametryzowanego logowania, ale teraz Można go doposażyć w razie potrzeby.

Do budowy elewacji użyliśmy slf4j.

 4
Author: OldCurmudgeon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-06 13:42:03

Uruchomiłem jul przeciwko slf4j-1.7.21 nad logback-1.1.7, wyjście na SSD, Java 1.8, Win64

Jul uruchomił 48449 ms, logback 27185 ms dla pętli 1M.

Mimo to trochę więcej szybkości i trochę ładniejsze API nie jest dla mnie warte 3 bibliotek i 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);
    }
}

I

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);
    }

}
 1
Author: weberjn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-25 13:24:59