"Доброго дня, сьогодні був зафіксований інцидент на промі, прошу розробника приєднатися до групи розбору." Приблизно так може початися один із ваших днів на роботі, а може, ранок — не важливо. Але розпочнемо все по порядку. Вирішуючи завдання тут на JavaRush, ти вчишся писати код, який працює і виконує те, що від нього чекають. Якщо поглянути на розділ допомоги , то ясно, що це не завжди виходить з першого разу. На роботі буде так само. Ти не завжди вирішуватимеш з першого разу завдання: баги — вічні наші супутники. Важливо, щоби можна було відновити події появи бага. Давай одразу на прикладі. Уявімо, що ти поліцейський. Тебе викликали на місце події (у магазині розбабо скло), ти приїхав, і від тебе чекають відповіді, що сталося. З чого почати? Я не знаю, як поліція працює. Дуже умовно — вони починають шукати свідків, докази і таке інше. А що якби саме місце могло розповісти подробиці, що сталося? Наприклад, так:
Із рівнями розібралися. Але не хвилюйся: межа між ними дуже тонка - не кожен може пояснити її. Плюс від проекту до проекту вона може відрізнятись. Старший розробник тобі пояснить, з яким рівнем та що логувати. Головне, щоб цих записів тобі було достатньо для майбутнього аналізу. А це розуміється на ходу. Далі – налаштування. Логерам можна вказати, куди писати (в консоль, файл, jms або ще кудись), вказати рівень (info, error, debug ...). Приклад налаштувань для нашого простого логера виглядає так:
- 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.util.Logger
: його більш ніж достатньо для знайомства Кожен запис лога містить дату-час, рівень події, повідомлення. Дата-час проставляється автоматично. Рівень події вибирає автор повідомлення. Рівні є кілька. Основні – це info, debug, error.
- INFO — це інформаційні повідомлення, про те, що відбувається, щось на кшталт історії за датами: 1915 — сталося те, 1916 — ще щось.
- DEBUG - докладніше описує події конкретного моменту. Наприклад, подробиці якоїсь битви в історії - це рівень debug. " Полководець Такойтович висунувся зі своєю армією у бік села Сьоловича ".
- ERROR - сюди зазвичай пишуть помилки, які відбуваються. Ти, напевно, помічав, коли обертаєш щось у
try-catch
, у блоціcatch
підставляєтьсяe.printStacktrace()
. Він виводить запис лише у консоль. За допомогою логер можна відправити цей запис в логер (ха-ха), ну ти зрозумів. - WARN – сюди пишуть попередження. Наприклад, лампочка перегріву у машині. Це просто попередження і краще щось змінити, але це ще не поломка. Ось коли машина зламається, тоді будемо логувати з рівнем ERROR.
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 ...) - їх багато, але суть одна записувати історію дій. Офіційний туторіал
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ