Как правильно обрабатывать отладочный вывод в Java?

32

По мере того, как мои текущие Java-проекты становятся все больше и больше, я чувствую также растущую потребность вставлять отладочный вывод в несколько пунктов моего кода.

Чтобы включить или отключить эту функцию соответствующим образом, в зависимости от открытия или закрытия тестовых сессий, я обычно помещаю private static final boolean DEBUG = falseв начало классов, которые проверяют мои тесты, и тривиально использую ее таким образом (например):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

и тому подобное.

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

И наоборот, я (как и многие другие) не хотел бы переводить все приложение в режим отладки, поскольку объем выводимого текста может быть чрезмерным.

Итак, есть ли правильный способ архитектурно обработать такую ​​ситуацию или самый правильный способ - использовать член класса DEBUG?

Федерико Занкан
источник
14
в Java правильный путь - НЕ использовать доморощенный код для ведения журнала. Выберите установленные рамки, не изобретайте велосипед
комнат
Я использую логическое DEBUG в некоторых из моих более сложных классов по той же причине, о которой вы говорили. Я обычно не хочу отлаживать приложение целиком, только класс, доставляющий мне проблемы. Привычка пришла из моих дней на языке COBOL, где операторы DISPLAY были единственной доступной формой отладки.
Гилберт Ле Блан
1
Я также рекомендовал бы больше полагаться на отладчик, когда это возможно, и не засорять ваш код операторами отладки.
Эндрю Т Финнелл
1
Практикуете ли вы Test Driven Development (TDD) с модульными тестами? Как только я начал это делать, я заметил значительное сокращение «кода отладки».
JW01

Ответы:

52

Вы хотите взглянуть на каркас каротажа и, возможно, каркас фасада каротажа.

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

Каркасы

Некоторые каркасы

  • Java Logging Framework (часть JDK),
  • Apache Log4J (немного старый, но все еще крепкий и активно поддерживаемый),
  • LogBack (созданный для обеспечения более современного подхода, чем Log4J, одним из создателей Log4J ).

Некоторые лесозаготовительные фасады

использование

Основной пример

Большинство этих фреймворков позволит вам написать что-то в форме (здесь используется slf4j-apiи logback-core):

package chapters.introduction;

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

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

Обратите внимание на использование текущего класса для создания выделенного регистратора, который позволил бы SLF4J / LogBack отформатировать вывод и указать, откуда пришло сообщение регистрации.

Как отмечено в руководстве по SLF4J , типичным шаблоном использования в классе обычно является:

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

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

Но на самом деле, еще более часто объявлять регистратор в форме:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

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

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

Logger logger = LoggerFactory.getLogger("MyModuleName");

Уровни отладки

Уровни отладки варьируются от одного фреймворка к другому, но наиболее распространенными являются (в порядке критичности, от доброкачественного до плохого дерьма и, вероятно, от очень распространенного до, мы надеемся, очень редкого):

  • TRACE Очень подробная информация. Должны быть записаны только в журналы. Используется только для отслеживания потока программы на контрольных точках.

  • DEBUG Подробная информация. Должны быть записаны только в журналы.

  • INFO Известные события во время выполнения. Должно быть сразу видно на консоли, поэтому используйте экономно.

  • WARNING Странности времени выполнения и исправимые ошибки.

  • ERROR Другие ошибки во время выполнения или неожиданные условия.

  • FATAL Серьезные ошибки, вызывающие преждевременное прекращение.

Блоки и Охранники

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

Чтобы избежать такого рода проблем, вы часто хотите написать что-то в форме:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

Если бы вы не использовали эту защиту перед блоком операторов отладки, даже если сообщения могут не выводиться (если, например, ваш регистратор в настоящее время настроен на печать только вещей выше INFOуровня), heavyComputation()метод все равно был бы выполнен ,

конфигурация

Конфигурация в значительной степени зависит от вашей среды ведения журналов, но они предлагают в основном те же методы для этого:

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

Они также предлагают в основном те же возможности:

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

Вот типичный пример декларативной конфигурации с использованием logback.xmlфайла.

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Как уже упоминалось, это зависит от вашей среды и могут быть другие альтернативы (например, LogBack также позволяет использовать скрипт Groovy). Формат конфигурации XML также может варьироваться от одной реализации к другой.

Для большего количества примеров конфигурации, пожалуйста, обратитесь (среди прочих) к:

Немного исторического веселья

Пожалуйста , обратите внимание , что Log4J видит серьезное обновление в данный момент, переход от версии 1.x до 2.x . Возможно, вы захотите взглянуть на оба из них для большего исторического веселья или путаницы, и если вы выбираете Log4J, вероятно, предпочтет перейти с версии 2.x.

Стоит отметить, как отметил Майк Партридж в комментариях, что LogBack был создан бывшим членом команды Log4J. Который был создан для устранения недостатков платформы Java Logging. И что в будущей основной версии Log4J 2.x теперь есть несколько функций, взятых из LogBack.

Рекомендация

Суть в том, чтобы оставаться отстраненным как можно дольше, поиграйтесь с несколькими и посмотрите, что подходит вам лучше всего. В конце концов, это просто каркас регистрации . За исключением случаев, когда у вас есть очень конкретная причина, кроме простоты использования и личных предпочтений, любой из них вполне подойдет, так что нет смысла зависать над ним. Большинство из них также могут быть расширены для ваших нужд.

Тем не менее, если бы мне пришлось сегодня выбирать комбинацию, я бы выбрал LogBack + SLF4J. Но если бы вы спросили меня несколько лет спустя, я бы порекомендовал Log4J с протоколированием Apache Commons, поэтому следите за вашими зависимостями и развивайтесь вместе с ними.

haylem
источник
1
SLF4J и LogBack были написаны парнем, который изначально написал Log4J.
Майк Партридж
4
Для тех, кто может беспокоиться о влиянии
Майк Партридж
2
Стоит отметить, что не совсем ясно, стоит ли вам создавать свои регистраторы static, так как это экономит небольшой объем памяти, но вызывает проблемы в некоторых случаях: slf4j.org/faq.html#declared_static
Восстановите Monica
1
@MikePartridge: мне известно о содержании ссылки, но это, тем не менее, не помешает оценке параметров, например. причина, по которой параметризованное ведение журнала является более производительным, заключается в том, что обработка сообщения журнала не будет выполняться (строка, в частности, конкатенация). Однако любой вызов метода будет выполнен, если он будет передан в качестве параметра. Таким образом, в зависимости от вашего варианта использования блоки могут быть полезны. И, как упоминалось в посте, они также могут быть полезны для вас просто для группировки других действий, связанных с отладкой (не только ведение журнала), которые происходят при включенном уровне DEBUG.
Хайлем
1
@haylem - это правда, моя ошибка.
Майк Партридж
2

использовать каркас регистрации

Большую часть времени есть статический метод фабрики

private static final Logger logger = Logger.create("classname");

тогда вы можете вывести свой лог-код с разными уровнями:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

тогда будет один файл, в котором вы сможете определить, какие сообщения для каждого класса должны быть записаны в вывод журнала (файл или stderr)

чокнутый урод
источник
1

Это как раз то, для чего предназначены такие каркасы журналирования, как log4j или более новый slf4j. Они позволяют очень подробно контролировать ведение журнала и настраивать его даже во время работы приложения.

Майкл Боргвардт
источник
0

Фреймворк журналов - это определенно правильный путь. Однако у вас также должен быть хороший набор тестов. Хорошее покрытие тестами часто может полностью исключить необходимость отладки.

Дима
источник
Если вы используете каркас журналирования и имеете доступ к журналу отладки - придет время, когда это избавит вас от действительно плохого дня.
Fortyrunner
1
Я не сказал, что вы не должны вести журнал. Я сказал, что сначала нужно пройти тесты.
Дима