Почему бы не использовать java.util.logging?

Впервые в жизни я оказался в положении, когда я пишу Java API, который будет открыт. Надеемся, что мы будем включены во многие другие проекты.

Для ведения журнала я (и действительно люди, с которыми я работаю) всегда использовал JUL (java.util.logging) и никогда не имел с ним никаких проблем. Однако теперь мне нужно более подробно понять, что я должен сделать для разработки моего API. Я провел некоторое исследование по этому вопросу и с информацией, которую у меня есть, я просто больше смущен. Отсюда этот пост.

Поскольку я родом из JUL, я склонен к этому. Мои знания об остальном не так уж велики.

Из исследования, которое я сделал, я придумал эти причины, почему люди не любят JUL:

  • "Я начал развиваться в Java задолго до того, как Sun выпустил JUL, и мне было проще продолжить работу с logging-framework-X, а не изучать что-то новое". Хм. Я не шучу, это на самом деле то, что люди говорят. С этим аргументом мы все могли бы сделать COBOL. (однако я, безусловно, могу сказать, что это тоже ленивый чувак)

  • "Мне не нравятся имена уровней ведения журнала в JUL". Хорошо, серьезно, этого просто недостаточно, чтобы представить новую зависимость.

  • "Мне не нравится стандартный формат вывода из JUL". Хм. Это просто конфигурация. Вы даже не должны делать что-то по коду. (правда, еще в старые времена вам, возможно, пришлось создать свой собственный класс Formatter, чтобы получить его право).

  • "Я использую другие библиотеки, которые также используют logging-framework-X, поэтому я подумал, что просто просто использовать этот". Это циклический аргумент, не так ли? Почему "все" используют logging-framework-X, а не JUL?

  • "Все остальные используют logging-framework-X". Это для меня просто особый случай из вышеперечисленного. Большинство не всегда правильно.

Итак, реальный большой вопрос: почему не JUL?. Что я пропустил? Raison d'être для протоколирования фасадов (SLF4J, JCL) заключается в том, что множество реализаций регистрации существовали исторически, и причина этого действительно восходит к эпохе до JUL, как я ее вижу. Если бы JUL был идеален, то каротажные фасады не существовали бы или что? Вместо того, чтобы обнимать их, мы не должны ставить под вопрос, почему они необходимы в первую очередь? (и посмотрите, сохранились ли эти причины)

Хорошо, мои исследования до сих пор привели к двум вещам, которые, как я могу видеть, могут быть реальными проблемами с помощью JUL:

  • Производительность. Некоторые говорят, что производительность в 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 не соответствует по сравнению с конкурентами.

  • Конфигурация из пути к классам. Из-за коробки JUL не удается загрузить файл конфигурации из пути к классам. Это несколько строк кода, чтобы сделать это. Я могу понять, почему это может раздражать, но решение короткое и простое.

  • Доступность обработчиков вывода. JUL поставляется с 5 выходными обработчиками из коробки: консоль, файловый поток, сокет и память. Они могут быть расширены или могут быть записаны новые. Это может быть, например, запись в Syslog UNIX/Linux и журнал событий Windows. У меня лично никогда не было этого требования, и я не видел его, но я, безусловно, могу сказать, почему это может быть полезной функцией. Например, в журнале регистрации есть приложение для Syslog. Тем не менее я бы сказал, что

    • 99,5% потребностей в адресах вывода покрываются тем, что находится в JUL из коробки.
    • Особые потребности могут обслуживаться пользовательскими обработчиками поверх JUL, а не поверх чего-то другого. Мне ничего не кажется, что требуется больше времени для написания обработчика вывода Syslog для JUL, чем для другой структуры ведения журнала.

Я действительно обеспокоен тем, что кое-что я забыл. Использование журнальных фасадов и реализаций каротажа, кроме JUL, настолько широко распространено, что я должен прийти к выводу, что это я, который просто не понимает. Боюсь, это будет не первый раз.: -)

Итак, что мне делать с моим API? Я хочу, чтобы он стал успешным. Я могу, конечно, просто "пойти с потоком" и реализовать SLF4J (который кажется самым популярным в наши дни), но для меня самого я все равно должен точно понять, что не так с JUL сегодняшнего дня, который гарантирует весь пух? Смогу ли я саботировать себя, выбрав JUL для своей библиотеки?

Производительность тестирования

(раздел, добавленный nolan600 07-JUL-2012)

Здесь приведена ссылка ниже от Ceki о параметризации SLF4J в 10 раз или быстрее, чем у JUL. Поэтому я начал делать простые тесты. На первый взгляд претензия, безусловно, правильная. Вот предварительные результаты (но читайте дальше!):

  • Время выполнения SLF4J, backend Logback: 1515
  • Время выполнения SLF4J, backend JUL: 12938
  • Время выполнения JUL: 16911

Цифры выше - это msecs, поэтому лучше. Таким образом, разница в производительности в 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(), потому что JUL по умолчанию печатает имя исходного кода в выходном файле, а Logback - нет. Поэтому мы сравниваем яблоки и апельсины. Я должен снова выполнить тест и настроить реализации ведения журнала таким же образом, чтобы они фактически выводили один и тот же материал. Тем не менее, я подозреваю, что SLF4J + Logback все равно будет отображаться сверху, но далеко не так, как указано выше. Оставайтесь с нами.

Btw: Тест был первый раз, когда я работал с SLF4J или Logback. Приятный опыт. JUL, безусловно, гораздо менее приветствуется, когда вы начинаете.

Производительность тестирования (часть 2)

(раздел, добавленный nolan600 от 08-JUL-2012)

Как оказалось, для производительности не имеет большого значения, как вы настраиваете свой шаблон в JUL, то есть, включает ли оно имя источника или нет. Я пробовал с очень простым шаблоном:

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

и это не изменило приведенные выше тайминги. Мой профилировщик показал, что регистратор все еще проводил много времени при вызовах getSourceClassName(), даже если это не было частью моего шаблона. Шаблон не имеет значения.

Поэтому я делаю вывод о производительности, которая, по крайней мере, для тестового заявления на основе шаблонов на основе шаблонов, по-видимому, составляет примерно 10 раз в реальной разнице в производительности между JUL (медленным) и SLF4J + Logback (быстрый). Так же, как сказал Чеки.

Я также вижу, что вызов SLF4J getLogger() намного дороже, чем JUL. (95 мс против 0,3 мс, если мой профилировщик является точным). Это имеет смысл. SLF4J должен сделать некоторое время на привязке основной реализации каротажа. Это меня не пугает. Эти вызовы должны быть несколько редкими в течение срока действия приложения. Быстрота должна быть в фактических вызовах журнала.

Окончательное заключение

(раздел, добавленный nolan600 от 08-JUL-2012)

Спасибо за все ваши ответы. Вопреки тому, что я изначально думал, что я решил использовать SLF4J для моего API. Это основано на ряде вещей и ваших данных:

  • Это дает гибкость для выбора реализации журнала во время развертывания.

  • Проблемы с отсутствием гибкости конфигурации JUL при запуске внутри сервера приложений.

  • SLF4J, безусловно, намного быстрее, как описано выше, в частности, если вы связываете его с Logback. Даже если это было просто грубым тестом, у меня есть основания полагать, что в SLF4J + Logback больше усилий было потрачено на оптимизацию, чем на JUL.

  • Документация

    . Документация для SLF4J просто намного более полная и точная.

  • Гибкость шаблонов. Поскольку я сделал тесты, я решил, что JUL имитирует шаблон по умолчанию из Logback. Этот шаблон содержит название потока. Оказывается, JUL не может сделать это из коробки. Хорошо, я до сих пор не пропустил его, но я не думаю, что это должно быть недостающим в структуре журнала. Период!

  • Большинство (или многих) Java-проектов сегодня используют Maven, поэтому добавление зависимости не так уж и важно, особенно если эта зависимость довольно стабильна, т.е. не постоянно меняет свой API. Это похоже на SLF4J. Также баночка SLF4J и друзья небольшого размера.

Итак, случилось странное, что я действительно расстроился с JUL, немного поработав с SLF4J. Я до сих пор сожалею, что так должно быть с JUL. JUL далек от совершенства, но отчасти это делает. Просто не совсем хорошо. То же самое можно сказать и о Properties, но мы не думаем об абстрагировании, чтобы люди могли подключить свою собственную библиотеку конфигурации и что у вас есть. Я думаю, что причина в том, что Properties входит чуть выше бара, в то время как противоположное верно для JUL сегодняшнего дня... и в прошлом он пришел на ноль, потому что его не было.

Ответ 1

Отказ от ответственности. Я являюсь основателем проектов log4j, SLF4J и logback.

Есть объективные причины для предпочтения SLF4J. Во-первых, он предоставляет конечному пользователю свободу выбора базовой структуры ведения журнала. Кроме того, более здравомыслящие пользователи предпочитают logback, который предлагает возможности за пределами log4j, а j.u.l отстает. Для некоторых пользователей может быть достаточно j.u.l, но для многих других это просто не так. В двух словах, если регистрация важна для вас, вы хотели бы использовать SLF4J с логином в качестве основной реализации. Если запись неважно, j.u.l в порядке.

Однако, как разработчик oss, вам нужно учитывать предпочтения ваших пользователей, а не только ваши собственные. Из этого следует, что вы должны принять SLF4J не потому, что убеждены, что SLF4J лучше, чем j.u.l, но поскольку большинство разработчиков Java в настоящее время (июль 2012) предпочитают SLF4J в качестве их API протоколирования. Если в конечном итоге вы решите не заботиться о народном мнении, рассмотрите следующие факты:

  • те, кто предпочитает j.u.l, делают это из удобства, потому что j.u.l связан с JDK. Насколько я знаю, нет никаких других объективных аргументов в пользу j.u.l.
  • Ваше предпочтение j.u.l - это просто предпочтение.

Таким образом, проведение "жестких фактов" над общественным мнением, в то время как, казалось бы, смелое, является логическим ошибочным в этом случае.

Если все еще не убежден, JB Nizet дает дополнительный и мощный аргумент:

За исключением того, что конечный пользователь уже мог выполнить эту настройку для своего собственный код или другую библиотеку, использующую log4j или logback. j.u.l является расширяемый, но имеющий расширение logback, j.u.l, log4j и только Бог знает, какая другая система ведения журнала, потому что он использует четыре библиотеки, которые использование четырех различных фреймворков регистрации громоздко. Используя SLF4J, вы позволяют ему настраивать рамки ведения журнала, которые он хочет, а не тот ты выбрал. Помните, что в типичном проекте используются мириады библиотеки, а не только ваши.

Если по какой-то причине вы ненавидите API SLF4J и используете его, вы будете вымарывать удовольствие от своей работы, тогда непременно отправляйтесь на j.u.l. В конце концов, есть средства перенаправить j.u.l на SLF4J.

Кстати, параметризация j.u.l по меньшей мере в 10 раз медленнее SLF4J, что приводит к заметной разнице.

Ответ 2

  • java.util.logging был введен в Java 1.4. Раньше использовались для ведения журнала, поэтому многие другие API протоколирования существуют. Эти API, где он использовался до Java 1.4, и, таким образом, имели отличную рыночную цену, которая не просто снижалась до 0, когда был выпущен 1.4.

  • JUL не начинал все это замечательно, многие из тех вещей, о которых вы упомянули, где намного хуже в 1.4, и только улучшились в 1.5 (и я думаю, что в 6 тоже, но я не слишком уверен).

  • JUL не подходит для нескольких приложений с разными конфигурациями в одной JVM (думаю, что несколько веб-приложений, которые не должны взаимодействовать). Tomcat должен проскользнуть через некоторые обручи, чтобы получить эту работу (эффективно перепрограммировать JUL, если я это правильно понял).

  • Вы не всегда можете влиять на структуру журналов, используемую вашими библиотеками. Поэтому использование SLF4J (которое на самом деле является очень тонким слоем API над другими библиотеками) помогает сохранять несколько согласованную картину всего мира протоколирования (поэтому вы можете решить основную структуру ведения журнала, сохраняя при этом библиотеку в той же системе).

  • Библиотеки не могут легко измениться. Если предыдущая версия библиотеки, используемая для использования logging-library-X, она не может легко переключиться на logging-library-Y (например, JUL), даже если последняя явно внушительна: любой пользователь этой библиотеки должен будет изучить новые рамки ведения журнала и (по крайней мере) перенастроить их протоколирование. Это большой нет-нет, особенно когда это не приносит никакого очевидного выигрыша для большинства людей.

Сказав все, что, по моему мнению, JUL является по крайней мере действительной альтернативой другим фреймворкам каротажа в наши дни.

Ответ 3

IMHO, основное преимущество в использовании логического фасада, такого как slf4j, заключается в том, что вы позволяете конечному пользователю библиотеки выбирать, какую конкретную реализацию ведения журнала он хочет, а не навязывать свой выбор конечному пользователю.

Возможно, он вложил время и деньги в Log4j или LogBack (специальные форматы, приставки и т.д.) и предпочитает продолжать использовать Log4j или LogBack, а не настраивать jul. Нет проблем: slf4j позволяет это. Правильно ли использовать Log4j над jul? Может быть, может и нет. Но тебе все равно. Пусть конечный пользователь выбирает то, что он предпочитает.

Ответ 4

Я начал, как и я, подозреваю, используя JUL, потому что это было самое легкое, что нужно было немедленно сделать. На протяжении многих лет, однако, я пришел, чтобы пожелать, чтобы я потратил немного больше времени на выбор.

Моя главная проблема сейчас в том, что у нас есть значительное количество "библиотечного" кода, который используется во многих приложениях, и все они используют JUL. Всякий раз, когда я использую эти инструменты в приложении типа веб-сервиса, ведение журнала просто исчезает или выходит куда-то непредсказуемым или странным.

Наше решение состояло в том, чтобы добавить фасад к библиотечному коду, что означало, что вызовы журнала библиотеки не менялись, а динамически перенаправлялись на любой механизм ведения журнала. Когда они включены в инструмент POJO, они направляются в JUL, но при развертывании в качестве веб-приложения они перенаправляются в LogBack.

Наше сожаление - конечно, состоит в том, что в библиотечном коде не используется параметризованное ведение журнала, но теперь его можно модифицировать по мере необходимости.

Мы использовали slf4j для построения фасада.

Ответ 5

Я запускал jul против slf4j-1.7.21 над logback-1.1.7, выводя на SSD, Java 1.8, Win64

jul пробежал 48449 мс, logback 27185 мс для цикла 1М.

Тем не менее, немного больше скорости и немного лучшего 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);
    }

}