JavaRush /Java блог /Random UA /Java Логування. Розмотати клубок стектрейсу

Java Логування. Розмотати клубок стектрейсу

Стаття з групи Random UA
"Доброго дня, сьогодні був зафіксований інцидент на промі, прошу розробника приєднатися до групи розбору." Приблизно так може початися один із ваших днів на роботі, а може, ранок — не важливо. Але розпочнемо все по порядку. Вирішуючи завдання тут на JavaRush, ти вчишся писати код, який працює і виконує те, що від нього чекають. Якщо поглянути на розділ допомоги , то ясно, що це не завжди виходить з першого разу. На роботі буде так само. Ти не завжди вирішуватимеш з першого разу завдання: баги — вічні наші супутники. Важливо, щоби можна було відновити події появи бага. Логування.  Розмотати клубок стектрейсу - 1Давай одразу на прикладі. Уявімо, що ти поліцейський. Тебе викликали на місце події (у магазині розбабо скло), ти приїхав, і від тебе чекають відповіді, що сталося. З чого почати? Я не знаю, як поліція працює. Дуже умовно — вони починають шукати свідків, докази і таке інше. А що якби саме місце могло розповісти подробиці, що сталося? Наприклад, так:
  • 21:59 власник увімкнув сигналізацію (5 хвабон до повного включення)
  • 22:00 власник зачинив двері
  • 22:05 повне включення сигналізації
  • 22:06 власник смикав ручку
  • 23:15 увімкнувся датчик шуму
  • 23:15 повз, голосно гавкаючи, пробігла зграя собак
  • 23:15 датчик шуму вимкнувся
  • 01:17 увімкнувся датчик удару на зовнішньому склі вітрини
  • 01:17 голуб влетів у скло
  • 01:17 скло розбилося
  • 01:17 включена сирена
  • 01:17 голуб обтрусився і полетів
Ну ось, з такими подробицями довго копатися не доведеться, одразу ясно, що сталося. У розробці так само. Дуже круто, коли за записами ти можеш розповісти, що діялося. Зараз ти, можливо, згадуєш про дебаг, адже можна все віддебажити. А ось і ні. Ти пішов додому, а вночі все зламалося, дебагати нічого: треба зрозуміти, чому зламалося і полагодити. Ось тут на сцену і виходять логи, історія всього, що сталося за ніч. Пропоную тобі по ходу статті подумати, який один із найвідоміших логерів (не зовсім логер, швидше за моніторинг), про який, напевно, чули всі, хто слухає (дивиться) новини? Завдяки йому відновлюють деякі події. А тепер серйозно. Логування Java це процес запису будь-яких подій, які відбуваються в коді. Це твій обов'язок як програміста - записати, що зробив твій код, бо потім тобі ж ці логи і дадуть для розбору. Якщо все зробити добре, тоді будь-яка бага буде дуже швидко розібрана та усунена. Тут, напевно, не заглиблюватимуся в те, які логери є. У цій статті обмежимося простимjava.util.Logger: його більш ніж достатньо для знайомства Кожен запис лога містить дату-час, рівень події, повідомлення. Дата-час проставляється автоматично. Рівень події вибирає автор повідомлення. Рівні є кілька. Основні – це info, debug, error.
  • INFO — це інформаційні повідомлення, про те, що відбувається, щось на кшталт історії за датами: 1915 — сталося те, 1916 — ще щось.
  • DEBUG - докладніше описує події конкретного моменту. Наприклад, подробиці якоїсь битви в історії - це рівень debug. " Полководець Такойтович висунувся зі своєю армією у бік села Сьоловича ".
  • ERROR - сюди зазвичай пишуть помилки, які відбуваються. Ти, напевно, помічав, коли обертаєш щось у try-catch, у блоці catchпідставляється e.printStacktrace(). Він виводить запис лише у консоль. За допомогою логер можна відправити цей запис в логер (ха-ха), ну ти зрозумів.
  • WARN – сюди пишуть попередження. Наприклад, лампочка перегріву у машині. Це просто попередження і краще щось змінити, але це ще не поломка. Ось коли машина зламається, тоді будемо логувати з рівнем ERROR.
Із рівнями розібралися. Але не хвилюйся: межа між ними дуже тонка - не кожен може пояснити її. Плюс від проекту до проекту вона може відрізнятись. Старший розробник тобі пояснить, з яким рівнем та що логувати. Головне, щоб цих записів тобі було достатньо для майбутнього аналізу. А це розуміється на ходу. Далі – налаштування. Логерам можна вказати, куди писати (в консоль, файл, jms або ще кудись), вказати рівень (info, error, debug ...). Приклад налаштувань для нашого простого логера виглядає так:
handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler

java.util.logging.FileHandler.level     = INFO
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.FileHandler.append    = true
java.util.logging.FileHandler.pattern   = log.%u.%g.txt

java.util.logging.ConsoleHandler.level     = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
У цьому випадку все налаштовано так, щоб логер писав у файл і консоль одночасно. На той випадок? якщо в консолі щось зітреться плюс шукати по файлу простіше. Рівень INFO для обох. Також для файлу задано патерн імені. Це мінімальний конфіг, який дозволяє писати відразу і консоль, і файл. java.util.logging.FileHandler.appendвстановлений у true , щоб старі записи не стиралися у файлі. Приклад використання такої (без коментарів, логер сам себе коментує):
public class Main {
    static Logger LOGGER;
    static {
        try(FileInputStream ins = new FileInputStream("C:\\log.config")){ \\полный путь до файлу с конфигами
            LogManager.getLogManager().readConfiguration(ins);
            LOGGER = Logger.getLogger(Main.class.getName());
        }catch (Exception ignore){
            ignore.printStackTrace();
        }
    }
    public static void main(String[] args) {
        try {
            LOGGER.log(Level.INFO,"Начало main, создаем лист с типизацией Integers");
            List<Integer> ints = new ArrayList<Integer>();
            LOGGER.log(Level.INFO,"присваиваем лист Integers листу без типипзации");
            List empty = ints;
            LOGGER.log(Level.INFO,"присваиваем лист без типипзации листу строк");
            List<String> string = empty;
            LOGGER.log(Level.WARNING,"добавляем строку \"бла бла\" в наш переприсвоенный лист, возможна ошибка");
            string.add("бла бла");
            LOGGER.log(Level.WARNING,"добавляем строку \"бла 23\" в наш переприсвоенный лист, возможна ошибка");
            string.add("бла 23");
            LOGGER.log(Level.WARNING,"добавляем строку \"бла 34\" в наш переприсвоенный лист, возможна ошибка");
            string.add("бла 34");


            LOGGER.log(Level.INFO,"выводим все элементы листа с типизацией Integers в консоль");
            for (Object anInt : ints) {
                System.out.println(anInt);
            }

            LOGGER.log(Level.INFO,"Размер дорівнює " + ints.size());
            LOGGER.log(Level.INFO,"Получим первый элемент");
            Integer integer = ints.get(0);
            LOGGER.log(Level.INFO,"выведем его в консоль");
            System.out.println(integer);

        }catch (Exception e){
            LOGGER.log(Level.WARNING,"что-то пошло не так" , e);
        }
    }
}
Це не найкращий приклад, взяв той, що був під рукою. Приклад висновку:
апр 19, 2019 1:10:14 AM generics.Main main
INFO: Начало main, создаем лист с типизацией Integers
апр 19, 2019 1:10:14 AM generics.Main main
INFO: присваиваем лист Integers листу без типипзации
апр 19, 2019 1:10:14 AM generics.Main main
INFO: присваиваем лист без типипзации листу строк
апр 19, 2019 1:10:14 AM generics.Main main
WARNING: добавляем строку "бла бла" в наш переприсвоенный лист, возможна ошибка
апр 19, 2019 1:10:14 AM generics.Main main
WARNING: добавляем строку "бла 23" в наш переприсвоенный лист, возможна ошибка
апр 19, 2019 1:10:14 AM generics.Main main
WARNING: добавляем строку "бла 34" в наш переприсвоенный лист, возможна ошибка
апр 19, 2019 1:10:14 AM generics.Main main
INFO: выводим все элементы листа с типизацией Integers в консоль
апр 19, 2019 1:10:14 AM generics.Main main
INFO: Размер дорівнює 3
апр 19, 2019 1:10:14 AM generics.Main main
INFO: Получим первый элемент
апр 19, 2019 1:10:14 AM generics.Main main
WARNING: что-то пошло не так
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at generics.Main.main(Main.java:45)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Тут хочу наголосити на записах:
апр 19, 2019 1:10:14 AM generics.Main main
INFO: Размер дорівнює 3
апр 19, 2019 1:10:14 AM generics.Main main
INFO: Получим первый элемент
Такий запис досить марний, не інформативний. Як і запис про помилку:
WARNING: что-то пошло не так
Не варто писати таке: це ліг заради ліг, він тільки заважатиме. Намагайтеся завжди писати осмислені речі. Думаю цього достатньо, щоб перестати користуватися System.out.printlnта перейти до дорослих іграшок. Має java.util.loggingнедоліки. Наприклад, рівні тих, що я описав вище, тут немає, але вони є в більшості використовуваних логерів. Для статті я вибрав java.util.logging, тому що він не вимагає додаткових маніпуляцій із підключенням. Зауважу також, що можна використовувати LOGGER.infoзамість LOGGER.log(Level.INFO... Один із недоліків спливає вже тут: LOGGER.log(Level.WARNING,"что-то пошло не так" , e);— дозволяє передати повідомлення та об'єкт Exception, логер сам його красиво запише. Водночас якLOGGER.warning("");приймає лише повідомлення, тобто. виняток передати не можна, треба самому переводити його в рядок. Сподіваюся такого прикладу достатньо, щоб познайомитися з Java логуванням. Далі можна підключити інші логери (log4j, slf4j, Logback ...) - їх багато, але суть одна записувати історію дій. Офіційний туторіал
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ