Что значит логировать исключение java

Вопросы по Java на собеседовании (3)

1. Понятие «Исключение»
2. Операторы исключений
3. Оператор throws
4. Блоки кода try/catch и try/finally
5. Может ли блок finally не выполняться?
6. Проверяемые и непроверяемые исключения
7. Возбуждение исключения
8. Определение исключения в сигнатуре метода
9. Особенность RuntimeException
10. Возбуждение исключения в методе main
11. Множественные исключения
12. Последовательность нескольких блоков catch
13. Поглащение исключений в блоке try. finally
14. Исключение SQLException
15. Ошибка Error
16. Обобщение исключений
17. Логирование исключений

1. Понятие «Исключение»

Исключение — это ошибка, возникающая во время выполнения программы. Причины возникновения исключения могут разные, например :

  • некорректно определены (не определены) данные;
  • невозможно прочитать или создать файл;
  • обрыв сетевого соединения или соединения с сервером базы данных.

Исключение в Java является объектом. Поэтому они могут не только создаваться автоматически виртуальной машиной JVM при возникновении исключительной ситуации, но и порождаться самим разработчиком.

2. Операторы исключений

Java имеет пять ключевых операторов для определения блока исключений, перехвата и возбуждения исключений :

  1. try — начала блока кода, в котором может возникнуть исключение, и которое следует перехватить;
  2. catch — начала блока кода, предназначенного для перехвата и обработки исключений (параметром catch является тип ожидаемого исключения);
  3. throw — оператор для генерации исключений;
  4. throws — ключевое слово, используемое в сигнатуре метода, и обозначающее, что метод потенциально может вызвать исключение с определенным типом;
  5. finally — начала дополнительного блока кода, размещаемый после последнего блока catch. Блок finally не является обязательным, но всегда получает управление.
Читайте также:  Что значит развивающие технологии

Общая структура «перехвата» исключительной ситуации выглядит следующим образом :

3. Оператор throws

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

4. Блоки кода try/catch и try/finally

Каждый оператор try требует наличия либо catch, либо finally, либо сочетания catch и finally. Блок кода finally не является обязательным, и может отсутствовать при использовании try/catch. Оператором finally создаётся блок кода, который должен быть выполнен после завершения блока try/catch.

Если необходимо гарантировано выполнить определенный участок кода, то используется finally. Связка try/finally позволяет обеспечить выполнение блока кода независимо от того, какие исключения были возбуждены и перехвачены, даже в тех случаях, когда в методе нет соответствующего возбужденному исключению раздела catch.

Пример использования try/finally представлен здесь.

5. Может ли блок finally не выполняться?

Код блока finally не будет исполнен, если в код программы включен предшествующий блоку finally системный выход. Следующий пример демонстрирует данную ситуацию.

6. Проверяемые и непроверяемые исключения

Все исключения делятся на «проверяемые» (checked) и «непроверяемые» (unchecked). Данное свойство присуще базовому классу исключения Throwable и передается по наследству (Error, Exception, RuntimeException). В исходном коде класса исключения данное свойство недоступно. Ниже представлена иерархия классов исключений.

Исключения Throwable и Exception, а также все их наследники, за исключением Error и RuntimeException, являются «проверяемыми» checked исключениями. Error и RuntimeException, а также все их наследники, относятся к «непроверяемым» unchecked исключениям.

Проверка исключения на checked выполняется компилятором (compile-time checking). Непроверяемые исключения можно перехватить (catch) в момент исполнения программы (runtime checking).

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

Сигнатура метода divide (операция деления) включает определение возможного исключения типа Exception, наследуемого от Throwable. Если делитель (i2) равен 0, то создается объект исключения типа Throwable и возбуждается исключение (throw t), которое не пропускает компилятор, т.к. тип возбуждаемого исключения не соответствует типу исключения в сигнатуре метода. Если в сигнатуре метода определить исключение типа Throwable, то ошибки не будет.

В методе main определен объект obj с инициализацией определенным значением. В следующей строке компилятор находит ошибку, связанную с отсутствием метода charAt(int) в объекте типа Object. Если выполнить приведение типа obj к String, то компилятор пропустит код : char c = ((String)ref).charAt(0).

Пример unchecked исключения представлен здесь.

7. Возбуждение исключения

Как было отмечено выше, исключение является объектом, который можно создать программно. Чтобы возбудить (выбросить) исключение используется оператор throw.

8. Определение исключения в сигнатуре метода

В сигнатуре метода можно определить возможное проверяемое (checked) исключение. Следующий пример демонстрирует определение исключения в сигнатуре метода f1() и его перехват в конструкторе класса.

9. Особенность RuntimeException

Исключение RuntimeException расширяет свойства Exception и является базовым классом для ошибок во время выполнения приложения. Данное исключение относится к необрабатываемым исключениям (unchecked). Согласно описанию класса это исключение может возникнуть во время нормальной работы JVM.

Следующий код демонстрирует пример использования непроверяемого исключения NumberFormatException (наследующего свойства RuntimeException). В функции parseInt при преобразовании строкового значения в число в режиме run-time может возникнуть исключение. Можно метод функции Integer.parseInt() «обернуть» в try/catch, а можно передать обработку исключения функции в вызывающий метод, для чего в сигнатуре определяется соответствующее исключение (throws).

10. Возбуждение исключения в методе main

Если в методе main возбудить исключение, то оно будет передано в виртуальную машину Java (JVM).

11. Множественные исключения

В сигнатуре метода можно определить несколько возможных исключений. Для этого используется оператор throws и исключения, разделенные запятыми. Следующий пример демонстрирует метод callMethods с множественными возможными исключениями :

Чтобы перехватить несколько возможных исключений можно искользовать конструкцию try с несколькими catch.

12. Последовательность нескольких блоков catch

При определение нескольких блоков catch следует руководствоваться правилом обработки исключений от «младшего» к старшему. Т.е. нельзя размещать первым блоком catch (Exception e) <. >, поскольку все остальные блоки catch() уже не смогут перехватить исключение. Помните, что Exception является базовым классом, поэтому его стоит размещать последним.

Рассмотрим следующую иерархию наследования исключений :

Cамым младшим исключением является EOFException, поэтому он должен располагаться перед IOException и Exception, если используется несколько блоков catch с данными типами исключений. Следующий код является демонстрацией данного принципа.

13. Поглащение исключений в блоке try. finally

Если было вызвано два исключения — одно в блоке try, а второе в finally — то, при отсутствии catch, исключение в finally «проглотит» предыдущее исключение. Следует блоки с возможными исключениями всегда обрамлять операторами try/catch, чтобы не потерять важную информацию. Следующий пример демонстрирует «поглащение» исключения в блоке try новым исключением в блоке finally.

В результате в консоль будет выведено следующее сообщение :

Чтобы не «потерять» исключение, необходимо его корректно перехватить и обработать. В примере следует убрать комментарий с блока catch.

14. Исключение SQLException

Исключение SQLException связано с ошибками при работе с базой данных. Данное исключением относится к checked исключениям, и, следовательно, проверяется на этапе компиляции.

Споры вокруг SQLException связаны с тем, что исключение возникает во время исполнения, а обрабатывать его приходится в коде, чтобы не ругался компилятор; может быть следовало бы отнести его к unchecked run-time исключениям? Убедительный довод разработчиков данного исключения связан с тем, что необходимо программисту обработать свои возможные ошибки при работе с базой данных.

15. Ошибка Error

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

Согласно спецификации Java, не следует пытаться обрабатывать Error в собственной программе, поскольку они связаны с проблемами уровня JVM. Исключения такого рода возникают, если, например, закончилась память, доступная виртуальной машине.

16. Обобщение исключений

При определении в сигнатуре метода возможных исключений можно вместо нескольких проверяемых исключений указать общее (базовое) java.lang.Throwable. В этом случае, компилятор «пропустит код» и программа возможно отработает без сбоев. Например :

Использование Exception или Throwable в сигнатуре метода делает почти невозможным правильное обращение с исключениями при вызове метода. Вызывающий метод получает только информацию о том, что что-то может отработать некорректно.

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

17. Логирование исключений

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

Иногда исключение может быть частью ожидаемого поведения. В этом случае нет необходимости его регистрировать.

Источник

Логирование в Java / quick start

В ходе моей работы в компании DataArt я, в числе прочего, занимаюсь менторской деятельностью. В частности это включает в себя проверку учебных заданий сделанных практикантами. В последнее время в заданиях наметилась тенденция «странного» использования логеров. Мы с коллегами решили включить в текст задания ссылку на статью с описанием java logging best practices, но оказалось, что такой статьи в которой бы просто и без лишних деталей на практике объяснялось бы как надо писать в лог на Java, вот так вот с ходу не находится.

Данная статья не содержит каких-то откровений, в ней не рассматриваются тонкости какого либо из многочисленных java logging frameworks. Здесь рассказываю как записать в лог так, чтобы это не вызвало удивления у Ваших коллег, основная цель написания включить ее в список обязательного чтения для практикантов. Если все еще интересно, читайте дальше

Несколько разъяснений.

  • Весь код примеров использует java.util.logging framework. Вопрос «Какой из фреймворков логирования ниболее кошерен» я оставлю за кадром. Скажу только что до java.util.logging проще всего дотянуться ибо он уже идет вместе с JRE и на самом деле рассказанное в данной статье с минимальными косметическими правками верно для подавляющего большинства систем логирования.
  • В целом рецепты приведенные в данной статье не являются единственно верными, есть моменты о которых можно поспорить, но в целом эти рецепты используются многие годы, многими разработчиками, во многих проектах и они достаточно хороши чтобы им следовать если у Вас нет каких-то совсем уже серьезных возражений.
  • В статье не рассматриваются такие «продвинутые» топики как:
    • Конфигурирование уровней для отдельных логеров
    • Форматирования логов
    • Асинхронное логирование
    • Создание собственных уровней логирования в Log4J
    • Контекстное логирование
    • И многое другое
  • Слово logging я пишу по русски как логирование с одной буквой «г» в основном потом, что такой вариант перевода чаще встречается
  • Советы, что, с каким уровнем логировать я пожалуй тоже оставлю за кадром т.к. тут все сильно зависит от приложения, условий эксплуатации, отношений с заказчиком и т.п. тонких вещей.
Пример №1
Хорошо
  1. Логер это статическое поле класса инициализируемое при загрузке класса, имеет простое, короткое имя, важно чтобы во всех Ваших классах переменная логера называлась одинаково (это диктуется общим правилом, одинаковые вещи в программе должны делаться одинаковым образом).
  2. В качестве имени логера я использую имя класса, на самом деле это не единственный способ, можно пытаться организовать какую-то свою иерархию логирования (например transport layer/app layer для подсистем имеющих дело с обменом данными), но как показывает практика выдумывать и главное потом неукоснительно следовать такой иерархии крайне сложно, а вариант с именами логеров совпадающими с именами классов весьма хорош и используется в 99% проектов
  3. Здесь для записи в лог я использую короткий метод .info, а не более общий метод .log, так много лаконичнее
  4. Имя логера берется как SomeClass.class.getName(), а не как «com.dataart.demo.java.logging.SomeClass», оба способа по идее одинаковы, но первый защищает Вас от сюрпризов при рефакторинге имени/пакета класса
Плохо

По сути тоже самое но букв больше и читается не так легко.

Замечание между примерами

Вы наверное обратили внимание, что все сообщения в примерах на английском языке. Это не случайно. Дело в том, что даже если все-все кто работает и будет работать с Вашим кодом говорят по русски, есть вероятность, что Вам придется просматривать лог сообщения на удаленном компьютере например через ssh при этом в большом количестве случаев Вы увидите примерно такое сообщение «. . . » (я безусловно знаю что через ssh можно протащить русские буквы, но вот почему-то далеко не всегда все оказывается настроенным должным образом).
Или даже на локальной машине в cmd вы можете увидеть что вот такое:
INFO: ╨Ъ╨░╨║╨╛╨╡-╤В╨╛ ╤Б╨╛╨╛╨▒╤Й╨╡╨╜╨╕╨╡ ╨▓ ╨╗╨╛╨│

С этим безусловно тоже можно бороться. Но не всегда легко объяснить заказчику на том конце телефонной трубки, как сделать так чтобы вместо крякозябр были видны русские буквы.
Совет: Пишите лог сообщения на английском языке, ну или в крайнем случае латинскими буквами.

Пример №2
Хорошо
  1. Если Вам необходимо залогировать исключение, для этого служит метод .log(level,message,exception)
  2. Если вы специально не настроили конфигурацию лог системы, сообщения с уровнем ниже info, например fine выводиться не будут. Но писать их по крайней мере для важных частей системы стоит. Когда что-то пойдет не так, Вы настроите более подробный уровень логирования и увидите много интересного.
  3. Слишком много лог сообщений, даже если они физически не пишутся в лог файл из-за своего слишком маленького уровня, могут существенно замедлить выполнение программы. Особенно если для подготовки самого сообщения надо потратить много ресурсов. Для этого есть метод .isLoggable(level) — он позволяет узнать пропустит ли текущая конфигурация логера данное сообщение
Плохо

Если логировать только ex.toString(), то потом Вы не сможете понять в какой строке изначально сработало исключение.

Пример №3

Логер надо конфигурировать. Есть конфигурация по умолчанию она выводит в консоль все сообщения с уровнем INFO и выше. Она достаточно хороша, для разработки из IDE, но для реального приложения ее обычно неплохо бы подправить.

Какие тут есть варианты

По умолчанию: Файл logging.properties для уровня INFO, вывод в консоль

#Console handler
handlers= java.util.logging.ConsoleHandler
.level=INFO

Делаем логирование более подробным выводим еще и сообщения уровня FINE

#Console handler
handlers= java.util.logging.ConsoleHandler
.level=FINE
java.util.logging.ConsoleHandler.level = FINE

Что мы тут сделали

  • Установили уровень FINE для корневого логера, просто чтобы сообщения пролезали внутрь лог системы.
  • И сказали что все что пролезет через лог систему надо выводить на консоль от уровня FINE и выше.
Выводим лог сообщения куда-то еще

Чем плох вывод на консоль? Консоль это по сути дела старый добрый stderr. Что это значит:

  • Если приложение запускается с помощью javaw Вы вообще ничего не увидите.
  • Если вывод идет в консоль и нужное вам сообщение промелькнуло 4 часа назад буфер консоли его уже съел, информация пропала.
  • Если вывод консоли направлен в файл java com.yourcompanyname.EntryClass 2>>application_log.txt и приложение работает не останавливаясь несколько недель — файл будет весьма и весьма большим, рискуя занять весь диск.

Чтобы решить эти проблемы был придуман java.util.logging.FileHandler — хэндлер который выводит лог сообщения в файл. При этом он умеет ротировать файлы, т.е. после достижения максимально допустимого размера, он дописывает в файл текщуее лог сообщение и открывает новый файл с инкрементальным префиксом. И так по кругу. Например

создаст вот такие файлы (последняя колонка — размер в байтах)

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

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

copy & paste конфиг для реальной жизни, его вполне хватает для большинства service, console и desktop приложений.

Последняя часть магии

Ну и последнее о чем осталось рассказать — как собственно сконфигурировать логер из файла свойств. Есть два способа:

  1. Из командной строки запуска приложения
  2. В первых строчках кода Вашего приложения

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

Вот так

java Djava.util.logging.config.file=logging.properties com.dataart.application.ClassName

Но к сожалению менять строку запуска не всегда можно или не всегда удобно. Второй способ тоже неплохо работает.

  • Здесь MainApplicationEntryClass — это класс — точка входа в Ваше приложение, видимо имя класса у Вас будет другое
  • Сам файл logging.properties как правило в таких случаях кладется в корень иерархии классов и выглядит это например вот так

Что осталось за кадром

В реальной жизни как минимум половина всех Java приложений это web приложения. Сама техничка логирования в них совершенно не отличается от изложенного выше. Ну может быть за тем исключением что разные сервера приложений могут использовать разные библиотеки логирования такие например как:

  • Log4J
  • JULI logger (строго говоря это не вполне самостоятельный фреймворк, а своего рода надстройка над java.util.logging)
  • SLF4J
  • Commons Logging

Соответственно несколько отличается настройка и имена методов. Но сам принцип меняется мало. Конкретные особенности как правило хорошо описаны в документации на сам сервер приложений, например

  • Tomcat
  • JBoss
  • Resin

Источник

Оцените статью