
Java logging. Hello world
Introduction
I think it’s no secret to anyone what loggers are and why they are needed. During the existence of java, many logging frameworks have been created. Among the most famous are:
- JUL - java.util.logging
- log4j
- JCL - jakarta commons logging
- Logback
- SLF4J - simple logging facade for java
In this article, each of the above frameworks at the hello world level will be considered. Simple examples of using the basic functionality and configuration will be given. The article does not pursue the goal of comparing loggers with each other and identifying the best of them, the author leaves this opportunity for you, dear readers. At the end of the article will be given sources where you can get more detailed information on each framework. Also, before reading this article, I recommend that you familiarize yourself with the publication “Java Logging: A Nightmare History,” which describes the history of the development of logging systems in Java.
System.err.println
The first and most primitive way of logging was the System.err.println method. I think the comments are unnecessary, just look at the code below:
// Определяем файл в который будем писать лог
System.setErr(new PrintStream(new File("log.txt")));
// Выводим сообщения
System.err.println("Сообщение 1");
System.err.println("Сообщение 2");
// Выводим сообщение об ошибке
try {
throw new Exception("Сообщение об ошибке");
} catch (Exception e) {
e.printStackTrace();
}
Java.util.logging
This framework is included in the standard and comes with the JDK, so you do not need to download or connect anything else. JUL has the following logging levels in ascending order: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, as well as ALL and OFF, turning on and off all levels, respectively.
The logger is created by calling one of the static methods of the java.util.logging.Logger class:
Logger log = Logger.getLogger(LoggingJul.class.getName());
Logger methods can take string messages, message templates, exceptions, localized message text resources, and, starting with Java 8, string message providers as arguments:
// Строковое сообщение
String stringMessage = "Сообщение";
// Строковое сообщение с параметрами
String stringMessageFormat ="Сообщение {0}";
// Исключение
Throwable throwable = new Throwable();
// ResourceBundle хранящий сообщения
ResourceBundle resourceBundle = ResourceBundle.getBundle("logging.jul.bundle");
// Поставщик сообщений
Supplier stringMessageSupplier = ()->"Сообщение";
Two groups of methods are distinguished: the name of which corresponds to the logging level and the log, loggp, logrb methods that take the logging level as a parameter of type Level. The first group contains two types of methods: receiving a string message or a string message provider:
log.info(stringMessage);
log.info(stringMessageSupplier);
The second group of methods has the following variations:
// Вывести сообщение с указанием уровня логгирования
log.log(new LogRecord(Level.INFO, stringMessage));
log.log(Level.INFO, stringMessage);
log.log(Level.INFO, stringMessageSupplier);
log.log(Level.INFO, stringMessageFormat, args);
log.log(Level.INFO, stringMessage, throwable );
log.log(Level.INFO, throwable, stringMessageSupplier);
// Вывести сообщение с указанием уровня логгирования, класса и метода
log.logp(Level.INFO, "ClassName", "MethodName", stringMessage);
log.logp(Level.INFO, "ClassName", "MethodName", stringMessageSupplier);
log.logp(Level.INFO, "ClassName", "MethodName", stringMessageFormat, args);
log.logp(Level.INFO, "ClassName", "MethodName", stringMessage, throwable);
log.logp(Level.INFO, "ClassName", "MethodName", throwable, stringMessageSupplier);
// Вывести сообщение с указанием уровня логгирования, класса,
// метода и resourceBundle, хранящего сообщения
log.logrb(Level.INFO, "ClassName", "MethodName", resourceBundle, "messageId");
log.logrb(Level.INFO, "ClassName", "MethodName", resourceBundle, "messageId", throwable);
// Вывести сообщение об ошибке
log.throwing("ClassName","MethodName", throwable);
Now let's look at the configuration of the framework. By default, JUL will print messages to the console, but you can configure it in the properties file. To set the method for displaying messages, it is necessary for your logger to specify which handlers it will use. The following handler classes exist: FileHandler, ConsoleHandler, StreamHandler, SocketHandler, MemoryHandler. A feature of JUL is that the handler settings are set as a whole for the entire class, and not for a specific instance, which can cause quite a few problems, for example, if you need messages from different loggers to be output to different files or with different formatting. Consider a simple example configuration file:
# Настройки глобального логгера
handlers =java.util.logging. FileHandler
.level=ALL
# Конфигурация файлового хендлера
java.util.logging.FileHandler.level =ALL
java.util.logging.FileHandler.formatter =java.util.logging.SimpleFormatter
java.util.logging.FileHandler.limit = 1000000
java.util.logging.FileHandler.pattern = log.txt
# Конфигурация консольного хендлера
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.pattern = log.log
java.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormatter
In order for JUL to apply this configuration, you need to pass the -Djava.util.logging.config.file = <path to file> parameter, or run the code when the application starts:
LogManager.getLogManager().readConfiguration(<ваш класс>.class.getResourceAsStream("logging.properties"));
Log4j
This framework currently has a second version, which, alas, is not compatible with the first. Since the first version of log4j has existed for a long time and, in view of its great popularity, there are many articles on the Internet, today we will consider the second. To use log4j2 you need to connect the log4j-api-2.x and log4j-core-2.x libraries . Log4j has a slightly different naming of logging levels from JUL: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, as well as ALL and OFF turning on and off all levels, respectively.
The logger is created by invoking the static method of the org.apache.logging.log4j.Logger class:
Logger log = LogManager.getLogger(LoggingLog4j.class);
// или
Logger log = LogManager.getLogger(“name”);
In addition to the usual String, Object and Throwable, the logger can accept two more new types - MapMessage and Marker:
// Карта сообщений (напечатается как msg1="Сообщение 1” msg2="Сообщение 2”)
MapMessage mapMessage = new MapMessage();
mapMessage.put("msg1", "Сообщение 1");
mapMessage.put("msg2", "Сообщение 2");
// Маркер, объект по которому можно фильтровать сообщения
Marker marker = MarkerManager.getMarker("fileonly");
// Строковое сообщение
String stringMessage = "Сообщение";
// Строковое сообщение с параметрами
String stringMessageFormat = "Сообщение {}, от {}";
// Исключение
Throwable throwable = new Throwable();
// Объект
Object object = new Object();
In the classic style for loggers, the methods are divided into two types: the logging level that matches the name and the log methods that take the logging level as a parameter. The first ones are of the form:
log.info(mapMessage);
log.info(object);
log.info(stringMessage);
log.info(marker, mapMessage);
log.info(marker, object);
log.info(marker, stringMessage);
log.info(object, throwable);
log.info(stringMessage, throwable);
log.info(stringMessageFormat, args);
log.info(marker, mapMessage, throwable);
log.info(marker, object, throwable);
log.info(marker, stringMessageFormat, args);
log.info(marker, stringMessage, throwable);
log.throwing(throwable);
The log methods in log4j2 look like this:
log.log(Level.INFO, mapMessage);
log.log(Level.INFO, object);
log.log(Level.INFO, stringMessage);
log.log(Level.INFO, marker, mapMessage);
log.log(Level.INFO, marker, object);
log.log(Level.INFO, marker, stringMessage);
log.log(Level.INFO, object, throwable);
log.log(Level.INFO, stringMessageFormat, args);
log.log(Level.INFO, stringMessage, throwable);
log.log(Level.INFO, marker, mapMessage, throwable);
log.log(Level.INFO, marker, object, throwable);
log.log(Level.INFO, marker, stringMessageFormat, args);
log.log(Level.INFO, marker, stringMessage, throwable);
log.throwing(Level.INFO, throwable);
If you don’t determine the configuration, then when you start log4j2 it will give an angry message stating that the configuration is not set and will print your messages to the console at a level not lower than ERROR. The configuration of log4j2 is set by several options: xml, json, yaml. It is worth noting that since the second version there is no configuration support from the property file. The configuration file is automatically searched for by classpath, should have the name log4j2 and be located in the package by default.
The configuration of log4j2 consists of a description of loggers, appenders and filters. For a more detailed study, refer to the documentation, now just a couple of key points. Firstly, there are various goodies in the form of filters, including markers:
- Burstfilter
- Composite composite
- DynamicThresholdFilter
- Mapfilter
- Marker filter
- Regexfilter
- StructuredDataFilter
- ThreadContextMapFilter
- ThresholdFilter
- Timefilter
Secondly, there is a wide range of appender classes, including asynchronous appenders and appenders wrapping a group of other appenders:
- Asyncappppender
- ConsoleAppender
- Failoverapppp
- Fileappender
- Flumeappender
- JDBCAppender
- JMSAppender
- JPAAppender
- MemoryMappedFileAppender
- NoSQLAppender
- OutputStreamAppender
- RandomAccessFileAppender
- Rewriteappender
- RollingFileAppender
- RollingRandomAccessFileAppender
- RoutingAppender
- SMTPAppender
- Socketappender
- Syslogappppender
It is also worth noting that log4j can create many different appenders of the same class, for example, several file appenders that write to different files.
Consider an example configuration in which two loggers are declared (root and for our class), the first of which writes to the log.log file, and the second writes to log2.log using filtering by marker:
%d %p %c{1.} [%t] %m %ex%n %d %p %c{1.} [%t] %m %ex%n
Commons-logging
A rather old project, which is a wrapper over JUL and log4j, which does not bring any additional functionality. The JCL logging levels are the same as log4j, and when interacting with JUL, the following mapping occurs:
fatal = Level.SEVERE
error = Level.SEVERE
warn = Level.WARNING
info = Level.INFO
debug = Level.FINE
trace = Level.FINEST
To use JCL, connect commons-logging-1.x.jar . Create a logger by calling the factory method:
Log log = LogFactory.getLog(LoggingCl.class);
// или
Log log = LogFactory.getLog("name");
JCL methods are very simple, coincide with the name of the logging levels, accept only objects and exceptions, and have two variations:
Object object = "Сообщение";
Throwable throwable = new Throwable();
log.info(object);
log.info(object, throwable);
The JCL configuration contains separate blocks for log4j, JUL, and its own implementation. If you do not specify a configuration, then you use your own implementation, called SimpleLog, which displays messages to the console. Consider an example configuration file:
#Log4j
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
log4j.configuration=log4j.properties
#JUL
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=INFO
java.util.logging.FileHandler.pattern=jul.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.limit=50000
java.util.logging.FileHandler.count=1
#SimpleLog
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
org.apache.commons.logging.simplelog.defaultlog=fatal
org.apache.commons.logging.simplelog.showlogname=true
org.apache.commons.logging.simplelog.showShortLogname=true
org.apache.commons.logging.simplelog.showdatetime=true
You can specify a JCL configuration file as follows:
java -Djava.util.logging.config.file=/absolute/path/to/your/config/file/commons-logging.properties -jar /absolute/path/to/your/jar/file/MyClass.jar
Logback
This framework is used only in conjunction with the SLF4J wrapper, which we will discuss later. To get started, you need logback-core-1.x.jar and logback-classic-1.xxjar , as well as slf4j-api-1.xxjar .
We will interact with the logger through the API provided by the SLF4J wrapper. Logging levels are the same as log4j. Creating a logger in this case is as follows:
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoggingLogback.class);
// или
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger("name");
The API allows you to display string messages, string message patterns, exceptions, and use markers:
// Строковое сообщение
String stringMessage = "Сообщение";
// Шаблон сообщения
String stringMessageFormat = "Сообщение {} {}";
// Ошибка
Throwable throwable = new Throwable();
// Маркер
Marker marker = MarkerFactory.getMarker("marker");
The names of the methods coincide with the logging levels and have the form:
log.info(stringMessage);
log.info(stringMessageFormat, args);
log.info(stringMessage, throwable);
log.info(marker, stringMessage);
log.info(marker, stringMessage, throwable);
log.info(marker,stringMessageFormat, args);
Now let's look at the logback functionality directly. The configuration is looked up in the classpath in the following order:
- Trying to find logback.groovy
- Otherwise, it tries to find logback-test.xml
- Otherwise, it tries to find logback.xml
- Otherwise, it uses the basic configuration - we display messages on the console
The main configuration items are loggers, appenders, lightouts, and filters.
The following filters are available:
- Regular filters
- Levelfilter
- ThresholdFilter
- EvaluatorFilter
- Matchers
- Turbofilters
- Countingfilter
The following appenders are available:
- OutputStreamAppender
- ConsoleAppender
- Fileappender
- RollingFileAppender
- SocketAppender and SSLSocketAppender
- ServerSocketAppender and SSLServerSocketAppender
- SMTPAppender
- Syslogappppender
- Siftingappppender
- Asyncappppender
I propose to read about what Layouts and Encoders in logback are in the documentation, and now I will just give a simple example of the logback.xml file:
log.log %date %level [%thread] %logger{10} [%file:%line] %msg%n %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n marker DENY
SLF4J
As mentioned earlier, SLF4J is a wrapper over logback, as well as over JUL, log4j, or JCL, as well as any logger that implements its interface. To work with SLF4J, you need the slf4j-api-1.xxjar library and an implementation of one of the loggers or a stub. As a rule, implementations of all loggers (except logback) are supplied with SLF4J and have names similar to slf4j-jcl-1.x.jar, slf4j-log4j12-1.x.jar, slf4j-nop-1.x.jar, etc. P. If the logger implementation (or the nop stub) is not found in the classpath, SLF4J will angrily curse and refuse to work. The configuration will accordingly be searched depending on the implementation laid down in the classpath.
We discussed the SLF4J API in the previous paragraph, so let's look at another wrapper option. In an ideal world, we have to output messages through the wrapper interface, and then everything will be fine, but the real cruel world suggests that we all have to interact with third-party libraries or code that use other loggers and who do not know about SLF4J . In order not to adapt to each logger, but to let all messages through one implementation of the SLF4J interface, you can use bridging. The wrapper supply contains the jcl-over-slf4j.jar, log4j-over-slf4j.jar and jul-to-slf4j.jar libraries, which override the behavior of the respective loggers and redirect messages to the wrapper.
To make it clearer what was said above, consider an example. Suppose we have the following loggers:
java.util.logging.Logger julLog = java.util.logging.Logger.getLogger("julLog");
java.util.logging.Logger log4jLog = java.util.logging.Logger.getLogger("log4jLog");
org.slf4j.Logger slf4jLog = org.slf4j.LoggerFactory.getLogger(LoggingSlf4j.class);
julLog.info("Сообщение от jul");
log4jLog.info("Сообщение от log4j");
slf4jLog.info("Сообщение от slf4j");
We want the message from JUL to be written to one file, from log4j to another, and from slf4j to the console. As the implementation of the wrapper, we will use logback, the configuration of this disgrace will look like this:
log_jul.log %date %level [%thread] %logger{10} [%file:%line] %msg%n log_log4j.log %date %level [%thread] %logger{10} [%file:%line] %msg%n %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
In order for the bridge to work, you must execute the code:
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
I want to know more
- JUL - read Java Logging: Logger and Java Logging: Configuration
- Log4j2 - read Welcome to Log4j 2
- JCL- read How to use Commons Logging
- Logback- read The logback manual
- SLF4J - read SLF4J documentation and Bridging legacy APIs
Conclusion
In conclusion, I would like to tell you that the final choice of the logging framework is always yours, but you should approach this sensibly. The choice should be based on the satisfaction of many criteria, such as high performance, a convenient API, the availability of the necessary ways to store logged data, and the specifics of your projects, for example, if your product will be used in other projects, then you should not decide for the user which logger he will have to use, and in place of this give preference to the wrapper.