As we all know, java logging is a mess and I believe most developers are wondering how the output to terminal or file can be rocket science?
Those who want to save some sanity and keep things simple, I'm sure are already are using Timbre.
But, my use case was different - the application had to log as much as possible, but that had to be fast as possible and provide plenty of configurable options for the end-user, without touching source code.
From these tests 3 years ago, log4j2 seems like ultimate solution, especially if properly configured (check Recommendation section in given link).
Setup
Without further ado, let's see how to get this running. I'll assume Leiningen is used.
In project.clj
add this:
:dependencies [...
[clojure.tools.logging "0.6.0"]
[org.apache.logging.log4j/log4j-api "2.13.0"]
[org.apache.logging.log4j/log4j-core "2.13.0"]
[org.apache.logging.log4j/log4j-jcl "2.13.0"]
...]
log4j-api
and log4j-core
are obligatory. log4j-jcl
is a router
for
Apache Commons Logging
and is a good thing to have, especially because clojure.tools.logging
order of detection
will pick up Apache Commons library before Log4j, if found in
classpath. Many libraries depend on Apache Commons and you can end up
scratching your head, wondering why logger isn't working properly.
If you don't want log4j-jcl
as a dependency, this behavior can be
mitigated by setting clojure.tools.logging.factory
option at JVM
startup. In Leiningen project.clj
this line will do the work:
:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/log4j2-factory"]
log4j2.xml configuration
Log4j2 has tons of configuration options, but I wanted this behavior:
- All logs should go to folder
logs
by default and the folder must be created if it is not present. - User can override logs folder with
logPath
property. - Application logs (debug, info, error) goes to
logs/application.log
. - Errors and exceptions go to
logs/error.log
. - Both files must be rotated after size reaches 100 MB. After the rotation, older files must be gzipped.
- After five rotations, delete old files. This will assure the disk is not filled up with old log archives.
- Application logs everything to console as well.
Here is a configuration that will do the above. Save it in
resources/log4j2.xml
file so Log4j can find it, but also so it can
be shipped with jar or uberjar.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<!-- console output -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %p %m%n" />
</Console>
<!-- default output -->
<RollingFile
name="RollingFile"
bufferedIO="true"
fileName="${sys:logPath:-logs}/application.log"
filePattern="${sys:logPath:-logs}/application.%i.log.gz">
<PatternLayout pattern="%d %p %m%n" />
<Policies>
<SizeBasedTriggeringPolicy size="100MB" />
</Policies>
<DefaultRolloverStrategy max="5" />
</RollingFile>
<!-- errors output -->
<RollingFile
name="RollingFileErrors"
bufferedIO="true"
fileName="${sys:logPath:-logs}/error.log"
filePattern="${sys:logPath:-logs}/error.%i.log.gz">
<PatternLayout pattern="%d %p %m%n" />
<Policies>
<SizeBasedTriggeringPolicy size="100MB" />
</Policies>
<DefaultRolloverStrategy max="5" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="all" includeLocation="false">
<AppenderRef ref="RollingFile" level="DEBUG"/>
<AppenderRef ref="RollingFileErrors" level="ERROR"/>
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
Notice that I'm using RollingFile
appender for taking care of
rotating logs and keeping their size to max 100 MB. Be aware that
it will add overhead; for ultimate performance, File
appender can
be used with asynchronous logger and leave to external services like
logrotate to perform file
rotations. Or, use Console
appender and let init service like
systemd route all logs to
syslog or whatever system logging facility is installed.
In my case, I could not rely that logrotate will be present on the system where an application is run (macOS, Windows, Linux not properly configured, and so on).
The nice thing about Log4j is the ability to override provided
log4j2.xml
with custom configuration, using
log4j.configurationFile option, like:
java -Dlog4j.configurationFile=custom.xml -jar app.jar
Log2j has excellent performance page explaining what to use when and their appenders page will give enough details for every appender.
Enjoy!