Перейти к основному содержимому

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

· 8 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Введение

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

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

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

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

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

Основной класс, который нам нужно создать, это 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() возвращает строку, а метод 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.foreach.logging {
provides java.lang.System.LoggerFinder
with com.foreach.logging.CustomLoggerFinder;
exports com.foreach.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.foreach.logging.app {
}

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

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

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

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

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

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

java --module-path mods \
-m com.foreach.logging.app/com.foreach.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.foreach.logging.slf4j {
requires org.slf4j;
provides java.lang.System.LoggerFinder
with com.foreach.logging.slf4j.Slf4jLoggerFinder;
exports com.foreach.logging.slf4j;
}

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

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

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

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

3.3. Добавление журнала

Мы почти закончили, но нам все еще нужно добавить зависимости и конфигурацию 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.foreach.logging.app/com.foreach.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 .