Java 9 Platform Logging API

API ведения журнала платформы Java 9

1. Вступление

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

Этот API был представлен в Java дляprovide a common mechanism to handle all the platform logs and to expose a service interface that can be customized by libraries and applications.. Таким образом, журналы платформы JDK могут использовать ту же структуру ведения журналов, что и приложение, и зависимости проекта могут быть уменьшены.

2. Создание индивидуальной реализации

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

2.1. СозданиеLogger

Основной класс, который нам нужно создать, - этоLogger. Этот класс должен реализовать интерфейс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() возвращаетString,, а методisLoggable() возвращаетtrue во всех случаях. Наконец, у нас есть метод 2log(), выводящий на консоль.

2.2. СозданиеLoggerFinder

После создания нашего регистратораwe need to implement a LoggerFinder that creates instances of our ConsoleLogger.

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

public class CustomLoggerFinder extends System.LoggerFinder {

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

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

Finally, we need to register our LoggerFinder as a Service so it can be discovered by the JDK. Если мы не предоставим реализацию, по умолчанию будет использоватьсяSimpleConsoleLogger.

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

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

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

Для получения дополнительной информации о модулях Java посетитеthis other tutorial.

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.example.logging.app {
}

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

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

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

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

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

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

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

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

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

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

В нашем предыдущем примере мы записывали все наши сообщения в консоль, что аналогично тому, что делает регистратор по умолчанию. One of the most useful uses of the Logging API in Java 9 is to let applications route the JDK logs to the same logging framework the application is using, и этим мы займемся в этом разделе.

Мы создадим новый модуль, который будет использовать 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.

Для остальных методовwe’ll rely on the implementation on the SLF4J logger instance. Следовательно, наш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.example.logging.slf4j {
    requires org.slf4j;
    provides java.lang.System.LoggerFinder
      with com.example.logging.slf4j.Slf4jLoggerFinder;
    exports com.example.logging.slf4j;
}

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

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

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

Notice that we have to place the slf4j-api jar in the mods directory to compile this module. Also, keep in mind to use a modularized version of the library. Последнюю версию можно найти вMaven Central.

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

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

As before, we have to make sure we’re using a modularized version of the library. Опять же, последнюю версию можно найти вMaven Central.

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


    
        
            
                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -- %msg%n
            
        
    

    
        
    

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

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

В этом случаеwe also need to specify our Logback configuration file:

java --module-path mods \
  -Dlogback.configurationFile=mods/logback.xml \
  -m com.example.logging.app/com.example.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.

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