1. Перший логер – log4j

Як ти вже знаєш, історія логів почалася з System.err.println() – виведення запису в консолі. Його і зараз активно використовують під час дебага. Наприклад, Intellij IDEA за допомогою нього виводить повідомлення про помилки в консолі. Але жодних налаштувань цей варіант не має, тож підемо далі.

Перший і найбільш популярний логер називався Log4j. Це було вдале рішення із гнучкими налаштуваннями. Через різні обставини це рішення так і не потрапило до JDK, чим дуже засмутило все ком'юніті.

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

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

У log4j для цього були три речі:

  • логування підпакетів;
  • безліч appender'ів (результатів);
  • гаряче перезавантаження налаштувань.

По-перше, налаштування log4j можна було прописати таким чином, щоб увімкнути логування в одному пакеті та вимкнути в іншому. Наприклад, можна було ввімкнути логування в пакеті com.javarush.server, але водночас вимкнути його в com.javarush.server.payment. Це дозволяло швидко прибрати з лога непотрібну інформацію.

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

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

І нарешті, по-третє, log4j дозволяв змінити налаштування лога безпосередньо під час роботи програми, без її перезапуску. Це було дуже зручно, коли потрібно було підкоригувати роботу логів, щоб знайти додаткову інформацію щодо певної помилки.

Важливо! Є дві версії лога log4j: 1.2.x і 2.xx, які несумісні одна з одною.

Підключити логер до проєкту можна за допомогою коду:

<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.2</version>
  </dependency>
 
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.2</version>
  </dependency>
</dependencies>

2. Перший офіційний логер – JUL: java.util.logging

Після того, як у Java-спільноті з'явився зоопарк логерів, розробники JDK вирішили зробити один стандартний логер, яким би користувалися всі. Так з'явився логер JUL: пакет java.util.logging.

Однак, при його створенні автори логера взяли за основу не log4j, а варіант логера від IBM, що вплинуло на його розвиток. Хороша новина – логер JUL входить до складу JDK, погана – ним мало хто користується.

JUL

Мало того, що розробники JUL зробили «ще один універсальний стандарт», вони ще зробили для нього свої рівні логування, які відрізнялися від того, що було в популярних логерів того часу.

І це була велика проблема. Адже продукти Java часто зібрані з великої кількості бібліотек, і в кожній такій бібліотеці був свій логер. Отже, потрібно було конфігурувати всі логери, які є в програмі.

Хоча сам по собі логер доволі непоганий. Створення логера більш-менш схоже. Для цього потрібно зробити імпорт:


java.util.logging.Logger log = java.util.logging.Logger.getLogger(LoggingJul.class.getName());

Ім'я класу спеціально передається для того, щоб знати, звідки йде логування.

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

Також цей логер підтримує лямбда-вирази та ліниві обчислення. Починаючи з Java 8 до нього можна передавати Supplier<String>. Це допомагає рахувати і створювати рядок лише в той момент, коли це дійсно потрібно, а не щоразу, як це було раніше.

Методи з аргументом Supplier<String> msgSupplier виглядають таким чином:


public void info(Supplier<String> msgSupplier) {
   log(Level.INFO, msgSupplier);
}

3. Перший логер-обгортка – JCL: jakarta commons logging

Довгий час не було єдиного стандарту серед логерів: JUL мав стати таким, але він був гіршим за log4j, тому єдиного стандарту так і не з'явилося. Натомість з'явився цілий зоопарк логерів, і кожен з них хотів стати тим самим.

Однак звичайним Java-розробникам не подобалося, що майже кожна бібліотека має власний логер, і його потрібно якось по-особливому конфігурувати. Тому спільнота вирішила створити спеціальну обгортку над іншими логерами. Так з'явився JCL: jakarta commons logging

І знову ж таки, проєкт, який створювався, щоб бути лідером, не став ним. Не можна створити переможця – переможцем можна лише стати. Функціональність JCL була дуже бідною, і ніхто не хотів користуватися ним. Логер, створений, щоб стати заміною для всіх логерів, спіткала та ж сама доля, що й JUL – ним не користувалися.

Хоча його додали до багатьох бібліотек, що випускаються спільнотою Apache, зоопарк логерів лише розростався.

4. Перший останній логер – Logback

Але це ще не все. Розробник log4j вирішив, що він найрозумніший (адже його логером користувалося найбільше людей) і вирішив написати новий покращений логер, який поєднуватиме в собі плюси log4j та інших логерів.

Новий логер носив назву Logback. Саме цей логер мав стати майбутнім єдиним логером, яким би користувалися всі. В основі була та сама ідея, що і в log4j.

Підключити до проєкту цей логер можна за допомогою коду:


<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.6</version>
</dependency>

Відмінності були в тому, що в Logback:

  • покращено продуктивність;
  • додано нативну підтримку slf4j;
  • розширено опцію фільтрації.

Ще однією перевагою цього логера було те, що він мав дуже хороші налаштування за замовчуванням. І конфігурувати логер потрібно було лише якщо ви хотіли щось в них змінити. Також файл налаштувань був краще адаптований під корпоративний софт – всі його конфігурації вказувалися як xml/.

За замовчуванням Logback не вимагає жодних налаштувань і записує всі логи від рівня DEBUG і вище. Якщо вам потрібна інша поведінка, її можна налаштувати через xml конфігурацію:

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>app.log</file>
        <encoder>
            <pattern>%d{HH:mm:ss,SSS} %-5p [%c] - %m%n</pattern>
        </encoder>
    </appender>
    <logger name="org.hibernate.SQL" level="DEBUG" />
    <logger name="org.hibernate.type.descriptor.sql" level="TRACE" />
    <root level="info">
        <appender-ref ref="FILE" />
    </root>
</configuration>

5. Останній універсальний логер – SLF4J: Simple Logging Facade for Java

Яким довгим буває шлях до пошуку золотої середини.

У 2006 році один із творців log4j вийшов із проєкту і вирішив ще раз спробувати створити універсальний логер. Але цього разу це був не новий логер, а новий універсальний стандарт (обгортка), який дозволяв взаємодіяти різним логерам між собою.

Цей логер назвали slf4j — Simple Logging Facade for Java. Він був обгорткою навколо log4j, JUL, common-loggins та logback. Цей логер вирішував реальну проблему – управління зоопарком логерів, тому всі одразу почали ним користуватися.

Ми героїчно вирішуємо проблеми, які самі собі створюємо. Як бачимо, прогрес дійшов до того, що створили обгортку над обгорткою.

Сама обгортка складається із двох частин:

  • API, який використовується у застосунках;
  • Реалізацій, які додаються як окремі залежності кожного логера.

Підключити логер до проєкту можна за допомогою коду:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.17.2</version>
</dependency>

Достатньо підключити правильну реалізацію – і все: весь проєкт працюватиме з нею.

6. Оптимізація у slf4j

Slf4j підтримує нові функції, такі як форматування рядків для логування. До цього була ось така проблема. Скажімо, ти хочеш вивести в лог повідомлення:


log.debug("User " + user + " connected from " + request.getRemoteAddr());

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

  • user.toString();
  • request.getRemoteAddr();

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

З погляду логіки цю проблему потрібно було вирішувати у самій бібліотеці логування. І в першій версії log4j з'явилося таке рішення:


if (log.isDebugEnabled()) {
    log.debug("User " + user + " connected from " + request.getRemoteAddr());
}

Замість одного рядка для лога тепер потрібно було писати три. Це різко погіршило читабельність коду і знизило популярність log4j.

Логер slf4j зміг трохи покращити ситуацію за допомогою розумного логування. Виглядало воно так:


log.debug("User {} connected from {}", user, request.getRemoteAddr());

де {} позначають вставки аргументів, які передаються методом. Тобто перша {} відповідна до user, друга {} – до request.getRemoteAddr().

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

Після цього SLF4J став швидко зростати в популярності: наразі це найкраще рішення.

Тому розглядатимемо логування на прикладі зв'язки slf4j-log4j12.