Почему бы не использовать java.util.logging?
Впервые в жизни я нахожусь в положении, когда я пишу Java API, который будет с открытым исходным кодом. Надеюсь, будет включен во многие другие проекты.
Для ведения журнала я (и, действительно, люди, с которыми я работаю) всегда использовал JUL (java.util.logging) и никогда не имел с этим проблем. Однако теперь мне нужно более подробно понять, что я должен делать для разработки своего API. Я провел некоторое исследование по этому вопросу, и с информацией, которую я получил, я просто запутался. Отсюда и этот пост.
Так как я приехал из июля, я склонен к этому. Мои знания об отдыхе не так уж велики.
Из проведенного мною исследования я пришел к выводу, что людям не нравится JUL:
"Я начал разрабатывать на Java задолго до того, как Sun выпустила JUL, и мне было проще перейти на logging-framework-X, чем изучать что-то новое". Хм. Я не шучу, это на самом деле то, что говорят люди. С этим аргументом мы все могли бы делать COBOL. (однако я, конечно, могу относиться к тому, что я ленивый чувак)
"Мне не нравятся названия уровней логирования в JUL". Хорошо, серьезно, этого просто недостаточно для введения новой зависимости.
"Мне не нравится стандартный формат вывода из JUL". Хм. Это просто конфигурация. Вам даже не нужно ничего делать по коду. (правда, в старые времена вам, возможно, приходилось создавать собственный класс Formatter, чтобы сделать это правильно).
"Я использую другие библиотеки, которые также используют logging-framework-X, поэтому я подумал, что проще использовать эту". Это циклический аргумент, не так ли? Почему "все" используют logging-framework-X, а не JUL?
"Все остальные используют logging-framework-X". Для меня это просто особый случай из вышесказанного. Большинство не всегда верно.
Так что настоящий большой вопрос - почему не JUL?, Что я пропустил? Смысл существования каркасов фасадов (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 не очень хорошая по сравнению с конкурентами.
Конфигурация из classpath. Готовый JUL не может загрузить файл конфигурации из пути к классам. Это несколько строк кода, чтобы сделать это. Я понимаю, почему это может раздражать, но решение является коротким и простым.
Наличие обработчиков вывода. JUL поставляется с 5 встроенными обработчиками вывода: консоль, файловый поток, сокет и память. Они могут быть расширены или могут быть написаны новые. Например, это может быть запись в системный журнал UNIX/Linux и журнал событий Windows. Лично у меня никогда не было этого требования, и я не видел, чтобы оно использовалось, но я определенно могу понять, почему оно может быть полезным. Logback поставляется с приложением для Syslog, например. Тем не менее я бы сказал, что
- 99,5% потребностей в конечных пунктах назначения покрываются за счет того, что имеется в JUL "из коробки".
- Особые потребности могут удовлетворяться пользовательскими обработчиками поверх JUL, а не поверх чего-то еще. Для меня нет ничего, что предполагало бы, что для написания обработчика вывода Syslog для JUL требуется больше времени, чем для другой среды ведения журналов.
Я действительно обеспокоен тем, что есть кое-что, что я пропустил. Использование каркасов фасадов и каротажных реализаций, отличных от JUL, настолько широко распространено, что я должен прийти к выводу, что это я просто не понимаю. Боюсь, это будет не в первый раз.:-)
Так что мне делать с моим API? Я хочу, чтобы это стало успешным. Я, конечно, могу просто "плыть по течению" и внедрить SLF4J (который кажется самым популярным в наши дни), но ради себя, я все еще должен точно понять, что не так с сегодняшним JUL, который оправдывает весь пух? Смогу ли я саботировать себя, выбрав JUL для своей библиотеки?
Тестирование производительности
(раздел добавлен nolan600 07 июля 2012 г.)
Ниже приведена ссылка Ceki на то, что параметризация SLF4J в 10 или более раз быстрее, чем в JUL. Итак, я начал делать несколько простых тестов. На первый взгляд претензия, безусловно, верна. Вот предварительные результаты (но читайте дальше!):
- Время выполнения SLF4J, выход из базы данных: 1515
- Время исполнения SLF4J, бэкэнд, JUL: 12938
- Время выполнения ИЮЛЬ: 16911
Числа выше - это мсек, поэтому чем меньше, тем лучше. Так что разница в производительности в 10 раз на первый взгляд довольно близка. Моя первоначальная реакция: это много!
Вот суть теста. Как видно, целое число и строка строятся в цикле, который затем используется в операторе log:
for (int i = 0; i < noOfExecutions; i++) {
for (char x=32; x<88; x++) {
String someString = Character.toString(x);
// here we log
}
}
(Я хотел, чтобы оператор 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 все равно выйдет на первое место, но далеко от первоначальных чисел, как указано выше. Оставайтесь в курсе.
Кстати: тест был первый раз, когда я фактически работал с SLF4J или Logback. Приятный опыт. JUL, конечно, гораздо менее гостеприимный, когда вы начинаете.
Тестирование производительности (часть 2)
(раздел добавлен nolan600 08 июля 2012 г.)
Как оказалось, для производительности не имеет значения, как вы конфигурируете свой шаблон в JUL, то есть, содержит ли он имя источника или нет. Я попробовал с очень простым шаблоном:
java.util.logging.SimpleFormatter.format="%4$s: %5$s [%1$tc]%n"
и это не изменило вышеупомянутые сроки вообще. Мой профилировщик показал, что регистратор все еще проводил много времени в звонках getSourceClassName()
даже если это не было частью моей картины. Шаблон не имеет значения.
Поэтому я делаю вывод о проблеме производительности, согласно которой, по крайней мере, для протестированного оператора журнала на основе шаблонов реальная разница в производительности между JUL (медленным) и SLF4J+Logback (быстрым), по-видимому, примерно в 10 раз. Как сказал Цеки.
Я также вижу еще одну вещь, а именно, что SLF4J getLogger()
звонок намного дороже, чем то же самое в JUL. (95 мс против 0,3 мс, если мой профилировщик точен). Это имеет смысл. SLF4J нужно некоторое время связать с базовой реализацией ведения журнала. Это не пугает меня. Эти вызовы должны быть несколько редкими в течение жизни приложения. Стойкость должна быть в реальном журнале звонков.
Окончательный вывод
(раздел добавлен nolan600 08 июля 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 сегодня все наоборот... и в прошлом он пришел в ноль, потому что его не было.
6 ответов
Отказ от ответственности: я являюсь основателем проектов log4j, SLF4J и logback.
Есть объективные причины для предпочтения SLF4J. С одной стороны, SLF4J позволяет конечному пользователю свободно выбирать базовую структуру ведения журналов. Кроме того, более опытные пользователи, как правило, предпочитают logback, который предлагает возможности, выходящие за пределы log4j, а jul отстает. Для некоторых пользователей может быть достаточно функционального джула, но для многих других его просто нет. Короче говоря, если ведение журнала важно для вас, вы можете использовать SLF4J с logback в качестве базовой реализации. Если регистрация не важна, с Джулом все в порядке.
Однако, как разработчик oss, вы должны учитывать предпочтения своих пользователей, а не только свои собственные. Из этого следует, что вы должны использовать SLF4J не потому, что вы убеждены, что SLF4J лучше, чем jul, а потому, что большинство разработчиков Java (в настоящее время (июль 2012 г.)) предпочитают SLF4J в качестве своего API для ведения журнала. Если в конечном итоге вы решили не заботиться о распространенном мнении, учтите следующие факты:
- те, кто предпочитают джул, делают это из-за удобства, потому что джул в комплекте с JDK. Насколько мне известно, других объективных аргументов в пользу юля нет.
- Ваше собственное предпочтение джулу - это просто предпочтение.
Таким образом, удержание "неопровержимых фактов" над общественным мнением, хотя и кажется смелым, в данном случае является логической ошибкой.
Если все еще не убежден, JB Nizet приводит дополнительный и убедительный аргумент:
За исключением того, что конечный пользователь мог уже выполнить эту настройку для своего собственного кода или другой библиотеки, которая использует log4j или logback. jul является расширяемым, но необходимость расширять logback, jul, log4j, и Бог знает только, какие другие каркасы журналирования, потому что он использует четыре библиотеки, которые используют четыре разные каркасы журналирования, громоздка. Используя SLF4J, вы позволяете ему настраивать необходимые каркасы логирования, а не ту, которую вы выбрали. Помните, что типичный проект использует множество библиотек, а не только вашу.
Если по какой-либо причине вы ненавидите SLF4J API и используете его, вы потеряете удовольствие от своей работы, тогда непременно идите на июль. В конце концов, есть способы перенаправить джул на SLF4J.
Кстати, джул-параметризация, по крайней мере, в 10 раз медленнее, чем у SLF4J, что в итоге дает заметную разницу.
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 является, по крайней мере, допустимой альтернативой другим системам журналирования в наши дни.
ИМХО, главное преимущество использования фасада ведения журнала, такого как slf4j, заключается в том, что вы позволяете конечному пользователю библиотеки выбирать, какую конкретную реализацию ведения журнала он хочет, вместо того, чтобы навязывать свой выбор конечному пользователю.
Возможно, он вложил время и деньги в Log4j или LogBack (специальные средства форматирования, приложения и т. Д.) И предпочитает продолжать использовать Log4j или LogBack, а не настраивать jul. Нет проблем: slf4j позволяет это. Разумно ли использовать Log4j вместо jul? Может быть, а может и нет. Но тебе все равно. Позвольте конечному пользователю выбрать то, что он предпочитает.
Я начал, как и вы, я подозреваю, использовать JUL, потому что это было легче всего начать немедленно. Однако за эти годы мне стало жаль, что я потратил немного больше времени на выбор.
Теперь моя главная проблема заключается в том, что у нас есть значительный объем библиотечного кода, который используется во многих приложениях, и все они используют JUL. Всякий раз, когда я использую эти инструменты в приложении типа веб-службы, регистрация просто исчезает или уходит в непредсказуемое или странное состояние.
Наше решение состояло в том, чтобы добавить фасад к библиотечному коду, что означало, что вызовы журнала библиотеки не изменились, а были динамически перенаправлены на любой доступный механизм ведения журнала. Когда они включены в инструмент POJO, они направляются в JUL, но при развертывании в виде веб-приложения они перенаправляются в LogBack.
Мы, конечно, сожалеем о том, что код библиотеки не использует параметризованное ведение журнала, но теперь его можно модифицировать по мере необходимости.
Мы использовали slf4j, чтобы построить фасад.
Я запускал jul против slf4j-1.7.21 через logback-1.1.7, вывод на SSD, Java 1.8, Win64
jul пробежал 48449 мс, выход из системы 27185 мс для цикла 1M.
Тем не менее, немного больше скорости и немного приятнее 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);
}
}
Причины, почему не JUL:
Неудобный для пользователя дизайн API - неуклюжий синтаксис аргументов сообщений, странное определение уровней логирования... Список можно продолжить.
Это реализация ведения журнала, а не Фасад.
Как указано в цитируемом сообщении в блоге, целью Java 9 System.Logger НЕ является определение универсального интерфейса для ведения журнала. И SLF4J, и Apache Commons Logging пытались достичь такой цели Facade, но IMHO оба потерпели неудачу, предоставив слишком много реализации вместо того, чтобы придерживаться простой цели четко определенного фасада ведения журнала. Например, реальный фасад ведения журнала никогда не будет сравниваться с любой реализацией ведения журнала по «производительности», потому что сам фасад не должен вести журнал.
Отказ от ответственности: для фасада ведения журнала я заново изобретаю колесо и использую свое собственное - https://github.com/elf4j/elf4j/