«1. Введение

В этом руководстве мы рассмотрим недавно представленный API ведения журналов в Java 9 и реализуем несколько примеров для наиболее распространенных случаев.

Этот API был представлен в Java, чтобы предоставить общий механизм для обработки всех журналов платформы и предоставить интерфейс службы, который можно настраивать библиотеками и приложениями. Таким образом, журналы платформы JDK могут использовать ту же структуру ведения журналов, что и приложение, а зависимости проекта могут быть уменьшены.

2. Создание пользовательской реализации

В этом разделе мы собираемся показать основные классы Logging API, которые мы должны реализовать для создания нового регистратора. Мы сделаем это, реализуя простой регистратор, который выводит все журналы на консоль.

2.1. Создание регистратора

Основной класс, который нам нужно создать, — это регистратор. Этот класс должен реализовать интерфейс System.Logger и как минимум эти четыре метода:

    getName(): возвращает имя регистратора. Он будет использоваться JDK для создания логгеров по имени isLoggable(): указывает, на каких уровнях логгер включен для log(): это метод, который печатает лог в любую базовую систему, которую использует приложение — консоль в нашем случае . Необходимо реализовать 2 метода log(), каждый из которых получает разные параметры

Давайте посмотрим, как будет выглядеть наша реализация:

public class ConsoleLogger implements System.Logger {

    @Override
    public String getName() {
        return "ConsoleLogger";
    }

    @Override
    public boolean isLoggable(Level level) {
        return true;
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
        System.out.printf("ConsoleLogger [%s]: %s - %s%n", level, msg, thrown);
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
        System.out.printf("ConsoleLogger [%s]: %s%n", level, 
          MessageFormat.format(format, params));
    }
}

Наш класс ConsoleLogger переопределяет четыре упомянутых метода. Метод getName() возвращает строку, а метод isLoggable() возвращает true во всех случаях. Наконец, у нас есть метод 2 log(), который выводит данные на консоль.

2.2. Создание LoggerFinder

После создания нашего регистратора нам нужно реализовать LoggerFinder, который создает экземпляры нашего ConsoleLogger.

Для этого нам нужно расширить абстрактный класс System.LoggerFinder и реализовать метод getLogger():

public class CustomLoggerFinder extends System.LoggerFinder {

    @Override
    public System.Logger getLogger(String name, Module module) {
        return new ConsoleLogger();
    }
}

В этом случае мы всегда возвращаем наш ConsoleLogger.

Наконец, нам нужно зарегистрировать наш LoggerFinder как сервис, чтобы JDK мог его обнаружить. Если мы не предоставим реализацию, SimpleConsoleLogger будет использоваться по умолчанию.

Механизм, используемый JDK для загрузки реализаций, называется ServiceLoader. Вы можете найти больше информации об этом в этом уроке.

Поскольку мы используем Java 9, мы упакуем наш класс в модуль и зарегистрируем наш сервис в файле module-info.java:

module com.baeldung.logging {
    provides java.lang.System.LoggerFinder
      with com.baeldung.logging.CustomLoggerFinder;
    exports com.baeldung.logging;
}

Для получения дополнительной информации о модулях Java ознакомьтесь с этим другим руководство.

2.3. Тестирование нашего примера

Чтобы протестировать наш пример, давайте создадим еще один модуль, который будет действовать как приложение. Он будет содержать только класс Main, который использует нашу реализацию службы.

Этот класс получит экземпляр нашего ConsoleLogger, вызвав метод System.getLogger():

public class MainApp {

    private static System.Logger LOGGER = System.getLogger("MainApp");

    public static void main(String[] args) {
        LOGGER.log(Level.ERROR, "error test");
        LOGGER.log(Level.INFO, "info test");
    }
}

Внутри JDK выберет нашу реализацию CustomLoggerFinder и создаст экземпляр нашего ConsoleLogger.

После этого давайте создадим файл module-info для этого модуля:

module com.baeldung.logging.app {
}

На этом этапе структура нашего проекта будет выглядеть так:

├── src
│   ├── modules
│   │   ├── com.baeldung.logging
│   │   │   ├── com
│   │   │   │   └── baeldung
│   │   │   │       └── logging
│   │   │   │           ├── ConsoleLogger.java
│   │   │   │           └── CustomLoggerFinder.java
│   │   │   └── module-info.java
│   │   ├── com.baeldung.logging.app
│   │   │   ├── com
│   │   │   │   └── baeldung
│   │   │   │       └── logging
│   │   │   │           └── app
│   │   │   │               └── MainApp.java
│   │   │   └── module-info.java
└──

Наконец, мы собираемся скомпилировать наш два модуля, и мы поместим их в каталог модов:

javac --module-path mods -d mods/com.baeldung.logging \
  src/modules/com.baeldung.logging/module-info.java \
  src/modules/com.baeldung.logging/com/baeldung/logging/*.java

javac --module-path mods -d mods/com.baeldung.logging.app \
  src/modules/com.baeldung.logging.app/module-info.java \
  src/modules/com.baeldung.logging.app/com/baeldung/logging/app/*.java

Наконец, давайте запустим класс Main модуля приложения:

java --module-path mods \
  -m com.baeldung.logging.app/com.baeldung.logging.app.MainApp

Если мы посмотрим на вывод консоли, мы увидим что наши журналы печатаются с помощью нашего ConsoleLogger:

ConsoleLogger [ERROR]: error test
ConsoleLogger [INFO]: info test

3. Добавление внешней среды ведения журналов

В нашем предыдущем примере мы записывали все наши сообщения в консоль, что аналогично тому, что делает регистратор по умолчанию. . Одно из наиболее полезных применений Logging API в Java 9 — позволить приложениям направлять журналы JDK в ту же среду ведения журналов, которую использует приложение, и это то, что мы собираемся сделать в этом разделе.

Мы создадим новый модуль, который использует SLF4J в качестве фасада ведения журнала и Logback в качестве среды ведения журнала.

Поскольку мы уже объяснили основы в предыдущем разделе, теперь мы можем сосредоточиться на том, как добавить внешнюю структуру ведения журналов.

3.1. Пользовательские реализации с использованием SLF4J

«Во-первых, мы реализуем другой Logger, который будет создавать новый регистратор SLF4J для каждого экземпляра:

public class Slf4jLogger implements System.Logger {

    private final String name;
    private final Logger logger;

    public Slf4jLogger(String name) {
        this.name = name;
        logger = LoggerFactory.getLogger(name);
    }

    @Override
    public String getName() {
        return name;
    }
    
    //...
}

Обратите внимание, что этот Logger является org.slf4j.Logger.

Для остальных методов мы будем полагаться на реализацию экземпляра регистратора SLF4J. Следовательно, наш Logger будет включен, если включен регистратор SLF4J:

@Override
public boolean isLoggable(Level level) {
    switch (level) {
        case OFF:
            return false;
        case TRACE:
            return logger.isTraceEnabled();
        case DEBUG:
            return logger.isDebugEnabled();
        case INFO:
            return logger.isInfoEnabled();
        case WARNING:
            return logger.isWarnEnabled();
        case ERROR:
            return logger.isErrorEnabled();
        case ALL:
        default:
            return true;
    }
}

И методы журнала будут вызывать соответствующий метод регистратора SLF4J в зависимости от используемого уровня журнала:

@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
    if (!isLoggable(level)) {
        return;
    }

    switch (level) {
        case TRACE:
            logger.trace(msg, thrown);
            break;
        case DEBUG:
            logger.debug(msg, thrown);
            break;
        case INFO:
            logger.info(msg, thrown);
            break;
        case WARNING:
            logger.warn(msg, thrown);
            break;
        case ERROR:
            logger.error(msg, thrown);
            break;
        case ALL:
        default:
            logger.info(msg, thrown);
    }
}

@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
    if (!isLoggable(level)) {
        return;
    }
    String message = MessageFormat.format(format, params);

    switch (level) {
        case TRACE:
            logger.trace(message);
            break;
        // ...
        // same as the previous switch
    }
}

Наконец, давайте создадим новый LoggerFinder, использующий наш Slf4jLogger:

public class Slf4jLoggerFinder extends System.LoggerFinder {
    @Override
    public System.Logger getLogger(String name, Module module) {
        return new Slf4jLogger(name);
    }
}

3.2. Конфигурация модуля

После того, как мы реализовали все наши классы, давайте зарегистрируем наш сервис в нашем модуле и добавим зависимость от модуля SLF4J:

module com.baeldung.logging.slf4j {
    requires org.slf4j;
    provides java.lang.System.LoggerFinder
      with com.baeldung.logging.slf4j.Slf4jLoggerFinder;
    exports com.baeldung.logging.slf4j;
}

Этот модуль будет иметь следующую структуру:

├── src
│   ├── modules
│   │   ├── com.baeldung.logging.slf4j
│   │   │   ├── com
│   │   │   │   └── baeldung
│   │   │   │       └── logging
│   │   │   │           └── slf4j
│   │   │   │               ├── Slf4jLoggerFinder.java
│   │   │   │               └── Slf4jLogger.java
│   │   │   └── module-info.java
└──

Теперь мы можем скомпилировать этот модуль в каталог модов, как мы делали в предыдущем разделе.

Обратите внимание, что мы должны поместить jar slf4j-api в каталог модов, чтобы скомпилировать этот модуль. Кроме того, не забывайте использовать модульную версию библиотеки. Последнюю версию можно найти в Maven Central.

3.3. Добавление Logback

Мы почти закончили, но нам все еще нужно добавить зависимости и конфигурацию Logback. Для этого поместите файлы logback-classic и logback-core в каталог модов.

Как и раньше, мы должны убедиться, что используем модульную версию библиотеки. Опять же, последнюю версию можно найти в Maven Central.

Наконец, давайте создадим файл конфигурации Logback и поместим его в папку с модами:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -- %msg%n
            </pattern>
        </encoder>
    </appender>

    <root>
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

3.4. Запуск нашего приложения

Теперь мы можем запустить наше приложение, используя наш модуль SLF4J.

В этом случае нам также нужно указать наш файл конфигурации Logback:

java --module-path mods \
  -Dlogback.configurationFile=mods/logback.xml \
  -m com.baeldung.logging.app/com.baeldung.logging.app.MainApp

Наконец, если мы проверим вывод, мы увидим, что наши журналы печатаются с использованием нашей конфигурации Logback:

2018-08-25 14:02:40 [main] ERROR MainApp -- error test
2018-08-25 14:02:40 [main] INFO  MainApp -- info test

4 Заключение

В этой статье мы показали, как создать пользовательское средство ведения журнала в Java 9 с помощью нового API ведения журнала платформы. Кроме того, мы реализовали пример с использованием внешней среды ведения журналов, что является одним из наиболее полезных вариантов использования этого нового API.

Как всегда, полный исходный код примеров доступен на GitHub.