1. Список рівнів подій

Логування – це запис будь-яких подій, які відбуваються під час роботи програми. Ваш обов'язок як програміста — запротоколювати все важливе, тому що потім, коли на production будуть дивні та/або серйозні помилки, окрім цих логів у вас більше нічого не буде.

Будь-яку помилку буде усунено в рази швидше, якщо у вас буде вся інформація про неї та всі передісторії викликів. Але звідси випливає простий висновок – логувати взагалі все: виклики всіх методів, значення всіх параметрів.

Це теж не вихід: забагато інформації – так само погано, як і замало. Нам потрібне розумне логування. Зроблене людиною для людини. І тут ми підходимо до першого факту про логування – всі записи в лог ще під час їх створення діляться на категорії.

Програміст, коли пише якусь подію в лог, повинен сам вирішити, наскільки важлива ця інформація. Рівень важливості події обирає автор повідомлення. Існує log4j 5 рівнів важливості логованої інформації:

  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL

Нижче розповімо про них докладніше.

2. DEBUG

Рівень DEBUG вважається найменш важливим. Інформація, яка пишеться в лог з таким рівнем важливості, потрібна лише під час дебагу програми. Щоб записати в лог інформацію, потрібну під час дебагу, використовується метод debug().

Приклад:


class Manager {
    private static final Logger logger = LoggerFactory.getLogger(Manager.class);

    public boolean processTask(Task task) {
        logger.debug("processTask id = " + task.getId());
        try {
            task.start();
            task.progress();
            task.complete();
            return true;
        } catch (Exception e) {
            logger.error("Unknown error", e);
            return false;
        }
    }
}

Зверніть увагу: метод debug знаходиться на самому початку методу (метод ще не встиг нічого зробити) і пише в лог значення змінної, яка передається до методу. Це найчастіший сценарій використання методу debug().

3. INFO та WARN

Наступні два рівні – це INFO і WARN. Для них існує два методи – info() і warn().

Рівень INFO використовується просто для інформаційних повідомлень: відбувається і те, й інше. Коли починаєш розбір помилки в лозі, буває дуже корисно почитати її передісторію. Для цього відмінно підходить метод info().

Рівень WARN використовується для запису попереджень (від слова warning). Зазвичай з таким рівнем важливості пишеться інформація про те, що щось пішло не так, але програма знає, як діяти в цій ситуації.

Наприклад, у процесі запису файлу на диск з'ясувалося, що такий файл вже існує. Тут програма може записати в лог попередження (warning), але показати користувачеві діалогове вікно та запропонувати обрати інше ім'я файлу.

Приклад:


class FileManager {
    private static final Logger logger = LoggerFactory.getLogger(FileManager.class);

    public boolean saveFile(FileData file) {
        logger.info(“зберігаємо файл ” + file.getName());
        boolean resultOK = SaveUtils.save(file);
        if (resultOK) return true;

        logger.warn(“проблема із записом файла ” + file.getName());
        String filename = Dialog.selectFile();
        boolean result = SaveUtils.save(file, filename);
        return result;
    }

4. ERROR та FATAL

І нарешті, два найважливіших рівня логування – ERROR і FATAL. Для них теж є спеціальні методи з однойменними назвами: error() і fatal().

Помилки теж вирішили розділити на дві категорії – звичайні помилки та фатальні помилки. Фатальна помилка найчастіше призводить до аварійного закриття програми (для десктопних програм) або падіння вебсервісу (для вебзастосунків).

Ще один хороший приклад – операційна система Windows. Якщо в тебе просто впала програма, з точки зору операційної системи це Error. А якщо впала сама операційна система і ти бачиш синій екран смерті від Windows, це вже Fatal error.

У Java-застосунках найчастіше події Error і Fatal пов'язані з винятками, що виникають. Приклад:


class Manager {
    private static final Logger logger = LoggerFactory.getLogger(Manager.class);

    public boolean processTask(Task task) {
        logger.debug("processTask id = " + task.getId());
        try {
            task.start();
            task.progress();
            task.complete();
            return true;
        } catch (Exception e) {
            logger.error("Unknown error", e);
            return false;
        }
    }
}

5. Що потрібно логувати

Ясна річ, логувати все одразу не варто. Це може різко погіршити читабельність лога, а він пишеться насамперед для того, щоб його читали.

До того ж, не можна писати в лог різну особисту та фінансову інформацію. Зараз із цим все строго, і можна нарватися на штрафи чи судові процеси. Рано чи пізно такий лог може потрапити в сторонні руки, а це велика проблема.

То що ж треба логувати?

По-перше, потрібно логувати початок роботи програми. Після того, як програма запустилася, рекомендується вивести в лог його режим роботи і різні важливі налаштування – так буде простіше читати лог в майбутньому.

По-друге, потрібно логувати стан усіх третьосторонніх сервісів, з якими твоя програма працює: системи розсилок, будь-які зовнішні сервіси. Як мінімум, потрібно залогувати момент підключення до них, щоб переконатися, що вони штатно працюють.

По-третє, логувати потрібно всі винятки. Якщо вони очікувані, інформацію про них можна записати компактно. Повна інформація про винятки дає 50-80% важливої інформації під час пошуку помилки.

Також потрібно логувати завершення роботи програми. Додаток повинен завершуватися штатно і при цьому не сипати в лог десятки помилок. Часто в цьому місці можна знайти завдання, які підвисли, проблеми з пулом потоків або проблеми з видаленням тимчасових файлів.

Обов'язково логуй те, що пов'язане з безпекою та авторизацією користувача. Якщо користувач 10 разів поспіль намагається залогінитись або скинути пароль, цю інформацію потрібно відобразити в логах.

Логуй максимум інформації про асинхронні завдання – часто винятки в таких потоках губляться. За асинхронним завданням обов'язково логуй її старт та завершення. Успішне завершення потрібно логувати так само, як і проблемне.

Що ще? Запуск завдань, що виконуються за таймером, запуск збережених даних SQL-процедур, синхронізацію даних та все, що стосується розподілених транзакцій. Думаю, для початку цього вистачить. У майбутньому ти самотужки доповниш цей перелік.