I never imagined that there are software projects these days that write their own logger. Recently, I learned that this assumption was wrong… I see some problems when you write your own logger, which I would briefly describe here. Then I will say which logger I recommend today and how to configure it.
I see two reasons why people write their own logger: First, they do not know an official logger. Second, they believe they have requirements that can not be covered with an off-the-shelf logger. To point one, yes this is quite unfortunate. To point two, I believe that one should strongly question its requirements. Normally one should adjust its requirements so that a normal logger is sufficient and in most cases it is more than that.
Writing a separate logger is to reinvent the wheel. There are numerous loggers that have been developed over the years and are widely used and popular. In addition, time is invested in technical details rather than focusing on business logic. Furthermore, most developers know official loggers and find themselves right in a new project immediately. On the other hand, an own written logger has to be understood first. You think these are enough reasons why not writing an own logger? The best reason only come now: Own loggers are probably buggy and do not work as expected! Recently, I had an issue with an application wherefore an own logger was implemented. The application crashed regularly after a couple of hours. As the logger was implemented with System.out.println, I had absolutely no clue what was going on. There was no output at all that could indicate me what’s happening. This was quite a big blind flight. Only when I migrated the logger to slf4j, I was able to see what was going on. An OutOfMemory happened..And guess why? It was the own written logger! Oh my god…Something like the following was implemented:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static StringBuilder dbgSb = null; | |
public static void DoLog(String s) { | |
if (dbgSb == null) { | |
dbgSb = new StringBuilder(); | |
dbgSb.append("\n###********************\n"); | |
} | |
dbgSb.append(s + "\n"); | |
} | |
public static String getLogString() { | |
String s = dbgSb.toString(); | |
dbgSb = null; | |
return s; | |
} |
It took me not a long time to see that this is generating an OutOfMemory if the getLogString() is never called. Ok, enough of this stuff… Let’s concentrate of how to do it properly.
Today, I recommend to use logback. Logback is developed by the same developer as Log4j was and has a couple of adavantages over Log4j. Primarly it is faster. To put logback in place in your project, use these two dependencies:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>ch.qos.logback</groupId> | |
<artifactId>logback-classic</artifactId> | |
<version>1.0.13</version> | |
</dependency> | |
<dependency> | |
<groupId>org.codehaus.janino</groupId> | |
<artifactId>janino</artifactId> | |
<version>2.7.8</version> | |
</dependency> |
Actually, only the former dependency is used, but the janino dependency is included here to enable conditional features in the logback configuration file as we will see below. Next, you need to create a logback.xml and place it under src/main/resources
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<configuration> | |
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} — [%t] %-40.40logger{39} : %m%n}"/> | |
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} — [%t] %-40.40logger{39} : %m%n"/> | |
<property name="LOG_FILE" value="logs/mylog.log"/> | |
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | |
<encoder> | |
<pattern>${CONSOLE_LOG_PATTERN}</pattern> | |
<charset>utf8</charset> | |
</encoder> | |
</appender> | |
<appender name="FILE" | |
class="ch.qos.logback.core.rolling.RollingFileAppender"> | |
<encoder> | |
<pattern>${FILE_LOG_PATTERN}</pattern> | |
</encoder> | |
<file>${LOG_FILE}</file> | |
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> | |
<fileNamePattern>${LOG_FILE}.%i</fileNamePattern> | |
</rollingPolicy> | |
<triggeringPolicy | |
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> | |
<MaxFileSize>10MB</MaxFileSize> | |
</triggeringPolicy> | |
</appender> | |
<root level="INFO"> | |
<appender-ref ref="CONSOLE" /> | |
<appender-ref ref="FILE" /> | |
</root> | |
<if condition='property("profiles.active").contains("debug")'> | |
<then> | |
<logger name="com.company.tools" level="DEBUG" additivity="false"> | |
<appender-ref ref="CONSOLE" /> | |
<appender-ref ref="FILE" /> | |
</logger> | |
</then> | |
</if> | |
</configuration> |
I do not go in further details here. It is how to configure a logger and can be read in numerous other documentations. The only thing I want to mention is the conditional setting of the logging level at the end of the file. Whenever the application is started with this application property , the logging level is set to debug.
java -Dprofiles.active=debug -jar app.jar
At last, loggers can be included is the source code as follows:
private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);