JUL (Java util Logging) - фреймворк для логирования входящий в состав Java SE. Находится в пакете java.util.logging. Если говорить о Java 9+, то он представляет собой модуль java.logging.

JUL, несмотря на его присутствие в стандартной поставке Java, как и System.out.println() или System.err.println(), не стоит его использовать в реальных проектах, т.к. он появился в Java значительно позже, по сравнению со специализированными решениями (библиотеками) для логирования и значительно уступает им по функциональности и производительности. В современных проектах чаще всего используют связки:

  • Slf4j + Log4j2

  • Slf4j + Logback

JUL появился в Java 1.4. Работа с JUL происходит через класс java.util.logging.Logger. Получить экземпляр этого класса можно через фабричный метод getLogger():

import java.util.logging.Logger;

public class JavaUtilLoggingExample {
    public static final Logger logger = Logger.getLogger(JavaUtilLoggingExample.class.getName());
}

Для записи сообщений в лог используются методы полученного экземпляра класса Logger. Самый главный метод у которого public void log(LogRecord record). Все остальные методы логирования вызывают этот метод. Напрямую его использовать не стоит, так как вызывать другие методы гораздо проще, например public void log(Level level, String msg). Этому методу достаточно указать уровень логирования и сообщение.

Уровней логирования всего семь:

  • SEVERE (ошибка)

  • WARNING (предупреждение)

  • INFO (информационное сообщение)

  • CONFIG

  • FINE (сообщение об успешной операции)

  • FINER

  • FINEST

Сообщение же может быть любым текстом.

Пример записи сообщения в лог с уровнем INFO:
import java.util.logging.Level;
import java.util.logging.Logger;

public class JavaUtilLoggingExample {
    public static final Logger logger = Logger.getLogger(JavaUtilLoggingExample.class.getName());

    public void printLogWithInfo() {
        logger.log(Level.INFO, "Print log with info level");
    }
}

Также для каждого из уровней логирования есть огромное множество перегруженных методов с различным числом параметров. Например, тот же самый результат можно получить вызвав метод info()

import java.util.logging.Logger;

public class JavaUtilLoggingExample {
    public static final Logger logger = Logger.getLogger(JavaUtilLoggingExample.class.getName());

    public void printInfoLog() {
        logger.info("Print info log");
    }
}

Также можно писать сообщения об ошибках с другими уровнями логирования.

import java.util.logging.Logger;

public class JavaUtilLoggingExample {
    public static final Logger logger = Logger.getLogger(JavaUtilLoggingExample.class.getName());

    public void printLogWithAnotherLevel() {
        logger.severe("Ошибка");
        logger.warning("Предупреждение");
        logger.info("Информация");
        logger.fine("Отладка");
        logger.finer("Отладка с деталями");
        logger.finest("Отладка с огромным количеством деталей");
    }
}

В результате вызова этого метода в консоль будет выведено:

Output
Oct 10, 2022 6:59:22 AM com.rakovets.course.java.core.example.jul.JavaUtilLoggingExample printLogWithAnotherLevel
SEVERE: Ошибка
Oct 10, 2022 6:59:22 AM com.rakovets.course.java.core.example.jul.JavaUtilLoggingExample printLogWithAnotherLevel
WARNING: Предупреждение
Oct 10, 2022 6:59:22 AM com.rakovets.course.java.core.example.jul.JavaUtilLoggingExample printLogWithAnotherLevel
INFO: Информация

Если необходимо передать исключение в лог, то нужно использовать специальный метод, принимающий Throwable:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JavaUtilLoggingExample {
    public static final Logger logger = Logger.getLogger(JavaUtilLoggingExample.class.getName());

    public void catchExceptionAndPrintLog() {
        try {
            Files.readAllBytes(Paths.get("/file/does/not/exist"));
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error message", ex);
        }
    }
}
Output
Oct 10, 2022 7:02:21 AM com.rakovets.course.java.core.example.jul.JavaUtilLoggingExample catchExceptionAndPrintLog
SEVERE: Error message
java.nio.file.NoSuchFileException: /file/does/not/exist
	at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
	at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
	at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
	at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:219)
	at java.base/java.nio.file.Files.newByteChannel(Files.java:371)
	at java.base/java.nio.file.Files.newByteChannel(Files.java:422)
	at java.base/java.nio.file.Files.readAllBytes(Files.java:3206)
	at com.rakovets.course.java.core.example.jul.JavaUtilLoggingExample.catchExceptionAndPrintLog(JavaUtilLoggingExample.java:39)
	at com.rakovets.course.java.core.example.jul.JavaUtilLoggingExample.main(JavaUtilLoggingExample.java:17)

1. Handler

Logging workflow

Logging workflow

Каждое сообщение, отправленное в логгер проходит фильтры, которые определяют, должно ли оно попасть в результирующий лог, например, по LogLevel, затем через обработчики. Обработчики (Handlers) направляют результат лога в файл, консоль или куда-нибудь по сети.

В самой Java уже есть пять таких обработчиков, но можно добавлять свои. Стандартные обработчики:

  • StreamHandler пишет в OutputStream.

  • ConsoleHandler пишет в System.err.

  • FileHandler пишет в файл.

  • SocketHandler отправляет по сети на указанный порт.

  • MemoryHandler просто сохраняет в ОЗУ.

JUL hierarchy

JUL hierarchy

2. Formatter

Formatter используются для записи логов в определённом формате, например в XML или HTML.

3. Configuration

Настраивать JUL можно двумя способами:

  • Конфигурационный класс
    Нужно указать аргумент -Djava.util.logging.config.class=<имя конфигурационного класса> при запуске Java программы.

  • Конфигурационный файл
    Нужно указать аргумент -Djava.util.logging.config.file=<путь к конфигурационному файлу> при запуске Java программы.

Второй способ более предпочтительный, так как не требует повторной сборки приложения. Если ничего из этого в аргументах JVM не указано, то используется конфигурация по умолчанию.

Файл с конфигурацией, указываемый в -Djava.util.logging.config.file — это обычный properties-файл. Например:

logging.properties
handlers=java.util.logging.FileHandler,java.util.logging.ConsoleHandler
java.util.logging.FileHandler.level=FINEST
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.encoding=UTF-8
java.util.logging.FileHandler.limit=10000000
java.util.logging.FileHandler.pattern=/var/log/julexample/julexample%u.log
java.util.logging.ConsoleHandler.level=FINEST
  • В свойстве handlers указывается список обработчиков (handler-ов), которые нужно добавить к корневому logger.

  • Можно более детально описать работу handlers, в частности FileHandler и ConsoleHandler.

  • В свойстве level указывается уровень логирования. В данном случае сообщения с уровнем логирования от SEVERE до FINEST будут попадать в лог.

  • В свойстве formatter для FileHandler указано SimpleFormatter. Можно указать XMLFormatter или реализовать свой formatter.

  • Свойство encoding позволяет задать кодировку для логов.

  • В limit для FileHandler указывается ограничение на размер файла логов для ротации в байтах.

  • Свойство pattern позволяет указать путь к файлу лога для FileHandler, где %u будет заменяться на числа (0, 1, …​), для разрешения конфликтов имени файла.