📜 ⬆️ ⬇️

Java logging. Hello world

Introduction


I think it is 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:


In this article, each of the above frameworks at the “hello world” level will be considered. There will be given simple examples of using the basic functionality and configuration. The article does not pursue the goal of comparing loggers among themselves and identifying the best of them; the author reserves this opportunity for you, dear readers. At the end of the article will be given the sources where you can get more detailed information on each framework. Also, before reading this article, I recommend reading the publication “Java Logging: Nightmare Story” , which describes the history of the development of logging systems in Java.

System.err.println


The first and most primitive method of logging was the System.err.println method. I think comments are superfluous, 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 don’t need to download and connect anything extra. JUL has the following levels of ascending logging: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, as well as ALL and OFF, enabling and disabling all levels, respectively.
A 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 patterns, 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<String> 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 with the Level type. 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 turn to the configuration of the framework. By default, JUL will display messages to the console, but you can set the configuration in the properties file. To specify the way messages are displayed, it is necessary for your logger to specify which handlers it will use. The following handler classes exist: FileHandler, ConsoleHandler, StreamHandler, SocketHandler, MemoryHandler. A special feature of JUL is that the handler settings are set for the whole class as a whole, and not for a specific instance, which can cause quite a few problems, for example, if you need to display messages from different loggers to different files or with different formatting. Consider a simple example of a 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 execute the following code when starting the application:

 LogManager.getLogManager().readConfiguration(< >.class.getResourceAsStream("logging.properties")); 

Log4j


This framework currently has a second version, which unfortunately is not compatible with the first. Since the first version of log4j exists quite a long time ago and, due to its great popularity, there are quite a few 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 logging level than JUL: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, as well as ALL and OFF, which enable and disable all levels, respectively.
The logger is created by calling the static method of the org.apache.logging.log4j.Logger class:

 Logger log = LogManager.getLogger(LoggingLog4j.class); //  Logger log = LogManager.getLogger(“name”); 

Logger can take in addition to the usual String, Object and Throwable two 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: coinciding with the name of the logging level and log methods, which take the logging level as a parameter. The first are:

 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 do not define the configuration, then when you start log4j2, it will display an angry message that the configuration is not set and will print your messages to the console with a level no lower than ERROR. The log4j2 configuration is defined by several options: xml, json, yaml. It is worth noting that from the second version there is no configuration support from the property file. The configuration file is automatically searched for the classpath, should be named log4j2 and located in the default package.
The log4j2 configuration consists of a description of the loggers, appenders, and filters. For a more detailed study, refer to the documentation, now just note a couple of key points. First, there are various goodies in the form of filters, including by markers:

Secondly, there is a wide range of appender classes, including asynchronous appenders and appenders wrapping a group of other appenders:

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 a configuration example 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:

 <?xml version="1.0" encoding="UTF-8"?> <Configuration> <!--   --> <Appenders> <!--   --> <File name="file" fileName="log.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern> </PatternLayout> </File> <!--   --> <File name="file2" fileName="log2.log"> <!--    --> <MarkerFilter marker="fileonly" onMatch="DENY" onMismatch="ACCEPT"/> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern> </PatternLayout> </File> </Appenders> <!--   --> <Loggers> <!--   --> <Root level="trace"> <AppenderRef ref="file" level="DEBUG"/> </Root> <!--    --> <Logger name="logging.log4j.LoggingLog4j" level="info" additivity="false"> <AppenderRef ref="file2" level="INFO"/> </Logger> </Loggers> </Configuration> 

Commons logging


A rather old project, which is a wrapper over JUL and log4j, without adding any additional functionality. The logging levels of JCL coincide with log4j, and in the case of interaction with JUL, the following mapping occurs:

 fatal = Level.SEVERE error = Level.SEVERE warn = Level.WARNING info = Level.INFO debug = Level.FINE trace = Level.FINEST 

For use of JCL we 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"); 

The 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 the configuration, you use your own implementation, called SimpleLog, which displays messages on 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 the 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 wrapper SLF4J, which we will consider 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 looks like this:

 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 templates, 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 levels of logging 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 consider the immediate logback functionality. The configuration is searched in the classpath in the following order:
  1. Tries to find logback.groovy
  2. Otherwise, trying to find logback-test.xml
  3. Otherwise trying to find logback.xml
  4. Otherwise, it uses the basic configuration - we print messages to the console.

The main configuration items are loggers, appenders, layouts, and filters.
The following filters are available:

The following appenders are available:

I suggest reading about the details of Layouts and Encoders in logback in detail in the documentation, and now I’ll just give you a simple example of the logback.xml file:

 <?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- --> <!--  --> <appender name="file" class="ch.qos.logback.core.FileAppender"> <file>log.log</file> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</Pattern> </layout> </appender> <!--  --> <appender name="sout" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> </layout> </appender> <!--  --> <!--    --> <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter"> <Marker>marker</Marker> <OnMatch>DENY</OnMatch> </turboFilter> <!--  --> <!--   --> <root level="info"> <appender-ref ref="file" /> </root> <!--    --> <logger name="logging.logback.LoggingLogback" level="info" > <appender-ref ref="sout" /> </logger> </configuration> 


SLF4J


As mentioned earlier, SLF4J is a wrapper around logback, as well as over JUL, log4j, or JCL, as well as over any logger that implements its interface. To work with SLF4J, you need the slf4j-api-1.xxjar library and the 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 like slf4j-jcl-1.x.jar, slf4j-log4j12-1.x.jar, slf4j-nop-1.x.jar, etc. P. If in the classpath the implementation of the logger is not found (or the nop stub), the SLF4J will angrily swear and refuse to work. The configuration will be searched accordingly, depending on the implementation in the classpath.
We considered the SLF4J API in the previous paragraph, so let's consider 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 don’t know about SLF4J . In order not to adapt to each logger, but to send all messages through one implementation of the SLF4J interface, bridging can be used. The wrapper package contains the jcl-over-slf4j.jar, log4j-over-slf4j.jar and jul-to-slf4j.jar libraries, which override the behavior of the corresponding loggers and redirect the messages to the wrapper.
To make this clearer, 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. We will use logback as the wrapper implementation, the configuration of this disgrace will look like this:

 <?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- --> <!--   JUL --> <appender name="jul" class="ch.qos.logback.core.FileAppender"> <file>log_jul.log</file> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</Pattern> </layout> </appender> <!--   log4j --> <appender name="log4j" class="ch.qos.logback.core.FileAppender"> <file>log_log4j.log</file> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</Pattern> </layout> </appender> <!--  --> <appender name="sout" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> </layout> </appender> <!--  --> <!--   --> <root level="info" > <appender-ref ref="sout" /> </root> <!--   jul --> <logger name="julLog" additivity="false" > <level value="trace" /> <appender-ref ref="jul" /> </logger> <!--   log4j --> <logger name="log4jLog" additivity="false" > <level value="trace" /> <appender-ref ref="log4j" /> </logger> </configuration> 

In order for the bridge to work it is necessary to execute the code:

 SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); 

I want to know more



Conclusion


In conclusion, I would like to tell you that the final choice of the logging framework is always yours, but this must be approached sensibly. The choice should be conditioned by satisfying many criteria, such as high performance, convenient API, 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, you should not decide which user should use which logger and in place of this give preference to the wrapper.

Source: https://habr.com/ru/post/247647/


All Articles