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

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

Статья из группы Random
участников
"Добрый день, сегодня был зафиксирован инцидент на проме, прошу разработчика присоединиться к группе разбора." Примерно так может начаться один из ваших дней на работе, а может утро — не важно. Но начнем все по порядку. Решая задачи тут на 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(""); принимает только сообщение, т.е. исключение передать нельзя, надо самому переводить его в строку. Надеюсь такого примера достаточно, чтобы познакомитmся с Java логированием. Дальше можно подключить другие логеры (log4j, slf4j, Logback...) — их много, но суть одна^ записывать историю действий. Официальный туториал
Комментарии (21)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Vladimir
Уровень 19
20 июля 2023, 19:06
а где мы путь к файлу, куда логи будут сохраняться, прописываем?
Volodymyr Horbachev
Уровень 63
Expert
23 октября 2023, 12:30
В папке resources в файле log4j.xml
Abubakr05
Уровень 33
8 сентября 2022, 20:30
ВОт эту байду куда писать, то???
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
Galina Kravchenko
Уровень 39
24 декабря 2022, 01:41
в отдельный файл с расширением .properties 🤔
Abubakr05
Уровень 33
14 февраля 2023, 17:48
А это будут объяснять дальше ?
Daniel CEO в BicycleInventionAcad
27 апреля 2023, 15:51
А эта строчка не навела на мысли?
try(FileInputStream ins = new FileInputStream("C:\\log.config"))
Poll Star
Уровень 0
8 мая 2022, 06:10
Куда и как прописывать настройки логгера?
Yarick8bit PPC & Web Analyst
13 июля 2022, 07:03
Плюсую! Уже не первый раз натыкаюсь, это похоже на конфиг файл, который лежит в файлах с жавой, но у меня почему-то его нет. Java 8
Сергеев Виктор
Уровень 40
Master
14 июля 2022, 19:52
Первое, логгер можно запускать без конфига - подтянется дефолтная Второе, если хочется конфиг все же указать, то можно через JVM атрибут -Djava.util.logging.config.file=/path/to/app.properties https://stackoverflow.com/questions/960099/how-to-set-up-java-logging-using-a-properties-file-java-util-logging
Abubakr05
Уровень 33
8 сентября 2022, 20:37
Здравствуйте, Виктор. НЕ подскажите, куда писать это? В лк вообще ничего не говорится про это.
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
был бы благодарен)
Сергеев Виктор
Уровень 40
Master
8 сентября 2022, 23:47
комментарием выше ответил, что можно написать в любой файл, просто при запуске кода указать путь в нему через аргумент -Djava.util.logging.config.file
Viktor Vdovichenko
Уровень 0
25 февраля 2023, 08:30
дальше по коду видно, что конфигурацию мы читаем в поток из файла: log.config. Вот кусок кода: try(FileInputStream ins = new FileInputStream("C:\\log.config") Соответственно, нужно создать такой файл и положить на диск С:\
Alex Serbin
Уровень 4
27 октября 2021, 09:44
Здравствуйте, объясните, автор пишет что будет описывать только стандартный логгер, но уровни описывает из логгера log4j Стандартныйе уровни логгирования: SEVERE - WARNING - INFO CONFIG - FINE - FINER -FINEST Чего я не понимаю?
Сергеев Виктор
Уровень 40
Master
27 октября 2021, 13:43
да косяк
Viktor Korotkov
Уровень 23
29 января 2021, 16:10
Большое спасибо за статью! Особенно порадовало слово типипзация:)))😆
7 сентября 2020, 10:19
Доброй ночи. Вы вампир?
vinsler
Уровень 35
Expert
21 апреля 2019, 18:24
Я не юзал логгер еще ни разу. )) Скорее всего могу предположить, что это нужно для средних проектов, на пол года + времени разработки в 5+ разработчиков, хотя могу ошибаться. Пару вопросов, которые были бы интересны, ну мне: 1) Как определять места для логирования? Может есть туториалы или правила или еще что? 2) Нужна ли информация из стектрейса в логгер или есть какие-то общие правила как заполнять систему вывода в сообщении, помимо времени и даты, например такой-то класс, такая-то функция, ошибка между строками при действии таком-то итд итп? Или логгер просто выводит точку в которой произошла ошибка, а потом для дебага все-равно ничего лучше sout не придумали? ))) 3) Если проект маленький, и заказчику на этот логер пофигу, а так же он не просит поддерживать проект после сдачи и на сопровождение он положил, стоит игра свеч?
Сергеев Виктор
Уровень 40
Master
21 апреля 2019, 19:42
Мое мнение - логгер нужен всегда. 1. Никак =) сначала логируешь какие-то важные точки (вход в метод, ветвление логики if-else), по сути просто описываешь что делает программа, тут нет четкого лимита, либо же он может быть задан правилами проекта. Дальше уже по ходу работы тебе прилетают баги (с тестов, прома), причем прилетают логи. Я потратитв пару дней на поиск ошибки предпочитаю её выделить, чтобы в следующий раз понять быстрее. Выделить например доп логами или новым Exception 2. К данному моменту все мои проекты выводят весь стектрейс в лог. Это очень важно когда идет полиморфные вызовы, которые зависят от кучи факторов. И вместо проверки 10-50 вариантов, ты точно знаешь в каком классе что-то случилось Помимо времени и даты выводят класс, метод, сообщение, иногда имя потока и id операции (об этом возможно напишу отдельно). Логгер выводит всю инфу, которую ты ему пропишешь. Как пример: мне надо было обращаться к каталогу, но метод, который возвращал ссылку на него мог вернуть null если каталог не создан. Это был редкий баг, т.к. каталоги были уже созданы. Но поймав его один раз в виде NPE и потратив несколько часов на разбор, я решил делать более понятную запись в лог "каталого такой-то возможно ещё не создан" Логи нужны не кому-то другому, они нужны поддержке и разработчику, чтобы понимать что пошло не так и почему все сломалось. К сожалению от клиентов чаще всего инфа "я ничего не трогала, оно само". Если нет логов, тогда и разбирать нечего. 3. Да. Первое, заказчик может забить, но если через неделю он придет к тебе и скажет, что половина функций не работает исправляй, а у тебя не будет логов, то тебе будет очень сложно, просто не возможно ) Не все поймут, не многие вспомнят. Когда-то давно валидатор не давал инфы по валидации. Он писал "Ваш код не прошел тестирование" и все, ничего больше. Вот представьте, что сейчас валидатор не дает требований и не говорит, что не так. Просто неправильно и все. Вот тут и важность логера.
Roman Beekeeper тг-канал по java разработ в t.me/romankh3
5 ноября 2019, 19:11
Вот реальный кейс - это логгировать данные, которые отпраляются из сервера по JMS, например используя RabbitMQ. Как минимум пока не наладится нормальное взаимодействие между consumer и producer :D
Стас Пасинков Software Developer в Zipy Master
20 апреля 2019, 03:15
я думаю самый популярный вопрос по логгированию у новичков - это чем отличается log4j от slf4j :) и какой из них лучше)))
Сергеев Виктор
Уровень 40
Master
20 апреля 2019, 13:57
хм.. ну это уже после знакомства. Отвечу как знаю. В мире java из-за отсутствия какого-то общего логгера, который нравился бы всем наполодилось много разных (несколько компаний сделали свои логеры). log4j и log4j2, logback, стандартный java логгер и может ещё какие. Когда подключаешь себе зависимости они могут использовать внутри себя разные логеры. slf4j старается решить эту проблему, создав обертки для каждого логера. Т.е. дает возможность использовать их все как один. Например в проекте 3 зависимости: 1. использует log4j 2. использует logback 3. использует java.util без slf4j пришлось бы конфигурировать каждый. А с ним можно задать один конфиг и спокойно с ним работать, как будто во всем проекте один логгер. Но могу быть не прав )