Який правильний спосіб обробити вихід налагодження на 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 на істину, якщо ви не дивитесь лише на пару з них.

І навпаки, я (як - думаю - багато інших) не хотів би переводити всю програму в режим налагодження, оскільки кількість виведеного тексту може бути величезною.

Отже, чи є правильний спосіб архітектурно вирішити таку ситуацію чи найбільш правильним способом є використання члена класу DEBUG?


14
у Java правильний спосіб НЕ використовувати домашній код для ведення журналу. Виберіть встановлену структуру, що не винаходьте колесо
комар

Я використовую булеву DEBUG в деяких моїх складніших заняттях з тієї ж причини, як ви заявили. Зазвичай я не хочу налагоджувати всю програму, просто клас створює мені проблеми. Звичка виникла з моїх днів COBOL, де єдиними формами налагодження є заяви DISPLAY.
Гілберт Ле Бланк

1
Я також рекомендую покладатися більше на налагоджувач, коли це можливо, і не засмічувати свій код операторами налагодження.
Ендрю Т Фіннелл

1
Чи практикуєте ви тест-керовану розробку (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 або файл властивостей - це, можливо, спочатку потрібно ).

Вони також пропонують в основному ті ж можливості:

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

Ось загальний приклад декларативної конфігурації з використанням 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, тому слідкуйте за своїми залежностями та розвивайтесь з ними.


1
SLF4J та LogBack були написані хлопцем, який спочатку написав Log4J.
Майк Партрідж

4
Для тих, хто може турбуватися про ефективність ведення журналу: slf4j.org/faq.html#logging_performance
Майк Партрідж

2
Варто зазначити, що не так чітко слід робити ваші реєстратори static, оскільки це економить невеликий обсяг пам’яті, але викликає проблеми в певних випадках: slf4j.org/faq.html#declared_static
Моніку

1
@MikePartridge: Я знаю вміст посилання, але це все ще не перешкоджає оцінці параметрів, наприклад. причина, параметризована в байтовому журналі, є більш ефективною, оскільки обробка повідомлення журналу не відбудеться (рядок, зокрема, конкатенація). Однак будь-який виклик методу буде виконуватися, якщо він переданий як параметр. Отже, залежно від випадку використання, блоки можуть бути корисними. І як було зазначено у публікації, вони також можуть бути корисними для вас лише для того, щоб згрупувати інші заходи, пов’язані з налагодженням (не лише ведення журналу), які відбуватимуться, коли рівень DEBUG увімкнено.
haylem

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
Я не казав, що ви не повинні мати лісозаготівлю. Я сказав, що для початку потрібно пройти тести.
Діма
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.