Logback Conditional Logging Example
Logging is a critical part of any production-grade application. It helps in debugging, monitoring, and analyzing runtime behavior. However, not all logs are required at all times. Sometimes, you may want to log only under specific conditions — for example, only when an exception occurs, or only when a certain MDC key is present. This is where conditional logging comes into play, and Logback provides powerful ways to support it. Let us delve into understanding how java logback conditional logging works and how it can be leveraged to control log output based on dynamic conditions.
1. What is Logback?
Logback is a powerful and flexible logging framework developed by Ceki Gülcü, the original author of Log4j. It is intended to be the successor of Log4j and is now considered one of the most widely adopted logging frameworks in the Java ecosystem. Logback was designed to improve upon the shortcomings of its predecessor by offering better performance, a more expressive configuration syntax, and native support for filtering and conditional logging.
It serves as the default logging implementation for many modern Java applications, particularly those built with the Spring Boot framework. Logback is natively supported by SLF4J (Simple Logging Facade for Java), which acts as an abstraction layer, allowing developers to plug in different logging frameworks without changing their application code.
Logback is divided into three primary modules:
- logback-core: This is the foundational module that provides core functionalities shared across other Logback modules. It includes essential classes and components such as appenders, encoders, and filters. Any other module that is built on top of Logback will depend on this core module
- logback-classic: This module extends logback-core and provides a full implementation of the SLF4J API. It is the most commonly used module and is typically included when using Logback in a standard Java or Spring Boot application. It supports powerful features like configuration via XML or Groovy, automatic reloading of configuration files, and MDC (Mapped Diagnostic Context) for contextual logging.
- logback-access: This module is specifically designed for web applications and can be integrated with servlet containers such as Tomcat or Jetty. It enables HTTP-access logging, similar to what Apache HTTP Server or Nginx does. Developers can use this module to log incoming requests, headers, session attributes, and other HTTP-related information.
Overall, Logback is built to be faster and more memory-efficient than Log4j, while providing a rich set of features such as conditional logging, asynchronous appenders, and filtering mechanisms. These capabilities make it a preferred choice for building high-performance and maintainable Java applications.
2. Code Example
Let’s say we want to log all messages, but log DEBUG
messages only when the application is running in dev
profile.
2.1 logback.xml Configuration
Let us delve into understanding how logback conditional logging
works and how it can be configured using filters and expressions to control log output dynamically. The following is a sample configuration using logback.xml
that demonstrates how to conditionally enable DEBUG
level logging only when the application is running in the dev
environment.
<configuration> <!-- Define application environment --> <property name="APP_ENV" value="${APP_ENV:-prod}" /> <!-- Console Appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> <!-- Only allow DEBUG logs if APP_ENV is 'dev' --> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> <evaluator> <expression> level == DEBUG && property("APP_ENV").equals("dev") </expression> </evaluator> <OnMatch>ACCEPT</OnMatch> <OnMismatch>DENY</OnMismatch> </filter> <!-- Fallback filter for INFO+ if not DEBUG --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> </appender> <root level="DEBUG"> <appender-ref ref="CONSOLE" /> </root> </configuration>
2.1.1 Code Explanation
<property name="APP_ENV" value="${APP_ENV:-prod}" />
: This line defines a property namedAPP_ENV
and attempts to fetch its value from the system environment. If it’s not provided, it defaults toprod
. This is used to determine the running environment of the application.<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
: This sets up a console appender that logs messages to the standard output (typically the terminal or IDE console).<encoder>
: The encoder defines the format of each log message. In this case, it includes the timestamp, thread name, log level, logger name (up to 36 characters), and the message itself.<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
: This is the heart of conditional logging. It uses anEvaluatorFilter
to conditionally allow or deny logging ofDEBUG
messages.<expression>level == DEBUG && property("APP_ENV").equals("dev")</expression>
— This expression checks if the log level isDEBUG
and the environment isdev
.<OnMatch>ACCEPT</OnMatch>
— If the condition evaluates to true, the log message is accepted.<OnMismatch>DENY</OnMismatch>
— Otherwise, the message is ignored.
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
: This fallback filter ensures that all messages with levelINFO
or higher (likeWARN
,ERROR
) are still logged regardless of environment. It prevents the appender from being completely silent ifDEBUG
logs are denied.<root level="DEBUG">
: The root logger is configured withDEBUG
level to allow all messages of levelDEBUG
or higher to be considered. However, actual output still depends on the filters configured within the appender.
This configuration allows developers to maintain verbose logging in development environments while keeping production logs clean and focused. Such conditional logic ensures minimal performance impact and greater control over log verbosity in different stages of the application lifecycle.
2.2 Java Code
The following Java class is a simple demonstration that generates log messages at different levels: DEBUG
, INFO
, and WARN
. These log messages will be filtered and printed based on the Logback configuration discussed in the previous section.
// ConditionalLoggingDemo.java import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ConditionalLoggingDemo { private static final Logger logger = LoggerFactory.getLogger(ConditionalLoggingDemo.class); public static void main(String[] args) { logger.debug("This is a DEBUG log."); logger.info("This is an INFO log."); logger.warn("This is a WARN log."); } }
2.2.1 Code Explanation
The ConditionalLoggingDemo
class demonstrates basic usage of SLF4J with Logback for logging messages at different levels. It defines a static logger instance using LoggerFactory.getLogger()
, which binds this logger to the current class. Inside the main
method, it logs three messages: one at DEBUG
level, one at INFO
level, and one at WARN
level. The visibility of these log messages at runtime depends on the Logback configuration (such as filters defined in logback.xml
), making this a practical example for understanding how conditional logging works in a Java application using Logback.
2.3 Running With Different Profiles
This section demonstrates how the behavior of logging changes based on the runtime environment profile using Java Logback conditional logging. By setting the APP_ENV
system property, we can control whether DEBUG
messages should be displayed or not. This is especially useful for differentiating between development and production environments, ensuring that verbose debug information is only available when needed.
2.3.1 Case 1: Run with dev profile
In this scenario, we explicitly set the environment variable APP_ENV
to dev
when starting the application. According to the Logback configuration discussed earlier, the EvaluatorFilter
allows DEBUG
messages to pass through only when APP_ENV
is dev
. As a result, all log levels—DEBUG
, INFO
, and WARN
—are logged to the console.
$ java -DAPP_ENV=dev -cp target/your-app.jar ConditionalLoggingDemo
12:00:01.234 [main] DEBUG ConditionalLoggingDemo - This is a DEBUG log. 12:00:01.236 [main] INFO ConditionalLoggingDemo - This is an INFO log. 12:00:01.237 [main] WARN ConditionalLoggingDemo - This is a WARN log.
Each line in the output includes a timestamp, the thread name (main
), the log level (e.g., DEBUG
), the class that generated the log (ConditionalLoggingDemo
), and the message. Since the app is running in dev
mode, all messages are accepted and printed to the console.
2.3.2 Case 2: Run with prod profile (default)
In this case, we run the application without explicitly setting the APP_ENV
variable. As per the configuration, it defaults to prod
. The EvaluatorFilter
now evaluates the condition level == DEBUG && property("APP_ENV").equals("dev")
as false
for DEBUG
logs, causing them to be denied. However, the INFO
and WARN
messages still appear due to the ThresholdFilter
allowing log levels above INFO
.
$ java -cp target/your-app.jar ConditionalLoggingDemo
12:00:01.236 [main] INFO ConditionalLoggingDemo - This is an INFO log. 12:00:01.237 [main] WARN ConditionalLoggingDemo - This is a WARN log.
This output confirms that the DEBUG
message is conditionally suppressed in a production-like environment, while higher-severity logs are still recorded. This behavior is highly desirable in real-world scenarios where you want to reduce logging overhead and clutter in production environments.
3. Spring Profiles and Variable Substitutions
Modern Java applications, especially those built using Spring Boot, often run in multiple environments like dev
, test
, stage
, and prod
. Spring Boot simplifies configuration management in such cases by introducing the concept of profiles. These profiles allow you to load different configurations or activate specific behaviors depending on the active environment.
Logback integrates nicely with Spring profiles through variable substitution using environment variables or system properties. Within the logback.xml
file, you can use the ${...}
syntax to dynamically inject environment values. For example:
<property name="APP_ENV" value="${APP_ENV:-prod}" />
In this line:
APP_ENV
is the name of the property being defined for use inside the Logback configuration.${APP_ENV:-prod}
uses default value substitution: ifAPP_ENV
is not set as a system property or environment variable, it falls back toprod
.
You can set the active Spring profile (and hence the APP_ENV
) using one of the following methods:
- As a JVM system property:
-DAPP_ENV=dev
- As an environment variable:
export APP_ENV=dev
(Unix/macOS) orset APP_ENV=dev
(Windows) - Inside
application.properties
orapplication.yml
(for Spring):spring.profiles.active=dev
Once the variable is defined, Logback filters like EvaluatorFilter
can reference it using the property()
function to conditionally control logging. For example:
<expression>level == DEBUG && property("APP_ENV").equals("dev")</expression>
This integration makes your logging behavior highly flexible and context-aware, reducing the need to maintain separate configuration files for different environments. Instead, a single Logback configuration can adapt dynamically based on runtime settings.
By aligning Spring profiles with Logback’s variable substitution mechanism, you achieve clean separation of concerns, enhanced maintainability, and environment-specific log visibility without touching application logic.
4. Conclusion
Conditional logging is a powerful tool to keep logs clean, reduce noise in production, and enable fine-grained control during development. With Logback’s EvaluatorFilter
, you can leverage expressions to conditionally allow or deny logs based on level, context, properties, and more. Using properties like APP_ENV
allows you to control behavior without changing application code.