Effective Way of Using Conditional Expressions in Logback
Today I’m going to show how to effectively use conditional expressions in logback.
This pattern has many use cases, for example:
- Use different log output on server vs when application is running locally (JSON on server, regular logs on localhost)
- Send ERRORs to your monitoring service - but only when application is deployed to production
- Use different prefixes depending when application is running
Setup
In order to use conditional expressions in logback you need 2 things. Logback itself and Janino library.
Because I’m running on Java 11, I’m using following versions of the libraries:
logback-classic
version1.3.0-alpha4
janino
version3.0.12
This is the build.sbt
snippet:
libraryDependencies ++ = Seq(
"ch.qos.logback" % "logback-classic" % logbackVersion,
"org.codehaus.janino" % "janino" % janinoVersion
)
I recommand to adding a comment to explain the purpose of janino
library because if you remove it (for example by mistake), your project will still compile but logging will be broken.
Example logback.xml
My comments are below
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<if condition='isDefined("KUBERNETES_POD_NAME")'>
<then>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<timeZone>UTC</timeZone>
</encoder>
</then>
<else>
<encoder>
<pattern>%d{"yyyy-MM-dd'T'HH:mm:ss.SSS"} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</else>
</if>
</appender>
<logger name="com.orbitz.consul" level="INFO"/>
<if condition='isDefined("ERROR_COLLECTION_SERVER")'>
<then>
<appender name="ERROR_COLLECTION" class="com.errors.logback.ErrorAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<endpoint>http://$ERROR_COLLECTION_SERVER/error</endpoint>
</appender>
</then>
</if>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
In the example above I’ll be using 2 environment variables:
KUBERNETES_POD_NAME
- is a made up name that will be set by Kubernetes when my application is running on the k8s clusterERROR_COLLECTION_SERVER
- points to the server that collects errors from applications running on production
When an application is running on production k8s cluster, k8s will set values for those 2 environment variables. I use them to detect this scenario and configure my logging accordingly.
When KUBERNETES_POD_NAME
is set, logs will be generated as JSON using LogstashEncoder
, otherwise logs will be generated as regular text. This allows for easy readability of logs when testing locally, but on production logs can follow agreed on schema.
When ERROR_COLLECTION_SERVER
is set, a ERROR
level filter is applied and all errors are sent to apropirate server for analysis or monitoring. When running locally, this variable isn’t set, effectively disabling this feature.
Summary
The pattern described above is a simple and straightforward way to configure logging in your JVM application automatically based on different conditions.
I find it very easy and try to follow where possible to achieve best flexibility when running different applications in multiple environments like Kubernetes, Nomad, localhost, etc.
I didn’t find any drawbacks of this method, the only thing to keep in mind is the janino
dependency that has to be added to your build setup.