
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
Last updated: May 14, 2025
The Logback logging framework is a successor to the Log4j project and provides some unique, flexible, and advanced features that are missing in other logging systems. One such feature is conditional logging, which enables us to control the log output. This is useful when we have different use cases at runtime to redirect, control, or suppress logs.
In this tutorial, we’ll look into some interesting features the Logback framework provides for conditional logging.
Logback requires the Simple Logging Facade for Java (SLF4J) library as a dependency. However, as SLF4J is the reference implementation of Logback, we don’t need to import it explicitly. Importing the logback-classic library automatically pulls slf4j-api.jar and logback-core.jar into the project through Maven’s transitive dependency rules.
So, let’s add the Logback library to our POM:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
Moreover, conditional processing in Logback configuration files requires the Janino library. Janino is an embedded Java compiler that can compile Java code or expressions dynamically at run-time. Let’s add it to our POM:
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.12</version>
</dependency>
We can get more recent versions of the logback-classic and janino dependencies from Maven Central.
Conditional logging allows us to control log output based on specific conditions. This can be useful to filter the logs dynamically based on certain runtime scenarios, such as:
Logback supports conditional logging through conditional processing of configuration files. Often, applications running in different environments have numerous configuration files per environment, which creates additional complexity for the developers who update them. There’s a high chance of error that a developer may miss updating the configuration file for a different environment.
In addition, most of these configuration files share common parts and differ only in a few places. Instead, Logback provides certain tags (<if>, <then>, and <else> elements) which we can use to have a single configuration file that targets all environments.
Let’s see the general format for using conditional statements:
<!-- if-then form -->
<if condition="conditional expression">
<then>
...
</then>
</if>
In addition, Logback supports if-then-else blocks too:
<!-- if-then-else form -->
<if condition="conditional expression">
<then>
...
</then>
<else>
...
</else>
</if>
The “condition” in the above snippet is a Java expression in which only the system or context properties are accessible. The conditional expressions are enclosed within if-else tags. Moreover, we can use conditional processing anywhere within the <configuration> element. Nested if-then-else statements are also supported.
To access a specific system or context property, we use the property() or the shorter equivalent of p() methods, which return the String value.
For instance, to access a property with the key “ENVIRONMENT“, we can use property(“ENVIRONMENT”) or p(“ENVIRONMENT”). This method returns an empty string when the key is undefined.
Here’s an example of how to access a system property:
<if condition='property("ENVIRONMENT").contains("PROD")'>
<then>
<appender name="FILE_APPENDER" class="ch.qos.logback.core.FileAppender">
<file>${outputDir}/conditional.log</file>
<encoder>
<pattern>%d %-5level %logger{35} -%kvp- %msg %n</pattern>
</encoder>
</appender>
</then>
</if>
Likewise, if we want to check null values, we can use the isNull() method:
<if condition='isNull("ENVIRONMENT")'>
<then>
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %-5level %logger{35} -%kvp- %msg %n</pattern>
</encoder>
</appender>
</then>
</if>
In addition, we can use the isDefined() method to verify the existence of a property. Both the isNull() and isDefined() methods can be helpful when we need to transmit our logs to an external server (such as Logstash) for centralized logging:
<if condition='isDefined("LOG_STASH_URL")'>
<then>
<appender name="LOG_STASH_APPENDER" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<destination>${LOG_STASH_URL}</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app_name": "TestApp"}</customFields>
</encoder>
</appender>
</then>
</if>
Logback supports conditional processing anywhere within the <configuration> root element.
Logback provides multiple built-in filters that can be chained together to compose an arbitrarily complex filtering policy. These filters are based on ternary logic and return one of the DENY, NEUTRAL, or ACCEPT values.
Returning DENY would mean that the log event is dropped immediately. In the case of NEUTRAL, the next filter will be consulted. Finally, ACCEPT implies that the processing of the logging event is successful and skips the remaining filters.
Conditional logging can also be achieved using the EvaluatorFilter, which evaluates a Java expression at runtime. The OnMatch and OnMismatch tags provide options to return one of the three ternary values (DENY, NEUTRAL, or ACCEPT). Accordingly, this Logback filter can evaluate conditions and control the logs’ output at runtime.
Now, we can see a quick example that demonstrates the usage of EvaluationFilter:
<configuration>
<appender name="FILTER_APPENDER" class="ch.qos.logback.core.FileAppender">
<file>filtered.log</file>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator>
<expression>return message.contains("billing");</expression>
</evaluator>
<OnMatch>DENY</OnMatch>
<OnMismatch>NEUTRAL</OnMismatch>
</filter>
<encoder>
<pattern>%d %-4relative [%thread] %-5level %logger -%kvp -%msg%n </pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILTER_APPENDER" />
</root>
</configuration>
As seen above, the EvaluatorFilter looks for an expression to evaluate, matches the conditions, and segregates the log output to the right appender. This is particularly useful when we need to restrict certain logs to avoid transmitting user-sensitive data (such as phone numbers) to the log file.
As seen above, Logback supports the conditional processing of configuration files and the EvaluatorFilter class to restrict log output. However, both these approaches require additional lines to the XML syntax. Subsequently, having too many conditionals may quickly render the configuration files unreadable and unmanageable in the long run.
However, there are alternative approaches to managing the logs natively. For Spring-based applications, we could use Spring Profiles, which is much cleaner than managing the logs using expressions or filters. For example, we can have logback-dev.xml for development, logback-staging.xml for staging, and logback-prod.xml for production environments.
Alternatively, we can use environment-specific variables to conditionally point to specific loggers at runtime. For instance, we can define the variable “ROOT_APPENDER” via the system property, so that the actual root appender will be chosen at runtime through variable substitution. This way, we can dynamically redirect logs to a designated output, which can be particularly useful for maintaining environment-specific appenders such as LogStash.
These approaches eliminate the need for multiple if-then-else blocks required for conditional processing or additional lines for evaluating expressions using the EvaluationFilter method.
In this article, we discussed the conditional logging features provided by the Logback framework. First, we looked at the conditional processing feature along with a few helpful utility methods to check the availability of a specific property. Next, we discussed the EvaluatorFilter, which evaluates the Java expression at runtime. Finally, we saw some alternatives to these features, where we can try Spring profiles or environment-specific variables to achieve the same result.
As always, the code presented in this article is available over on GitHub.