Core Java

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 named APP_ENV and attempts to fetch its value from the system environment. If it’s not provided, it defaults to prod. 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 an EvaluatorFilter to conditionally allow or deny logging of DEBUG messages.
    • <expression>level == DEBUG && property("APP_ENV").equals("dev")</expression> — This expression checks if the log level is DEBUG and the environment is dev.
    • <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 level INFO or higher (like WARN, ERROR) are still logged regardless of environment. It prevents the appender from being completely silent if DEBUG logs are denied.
  • <root level="DEBUG">: The root logger is configured with DEBUG level to allow all messages of level DEBUG 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: if APP_ENV is not set as a system property or environment variable, it falls back to prod.

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) or set APP_ENV=dev (Windows)
  • Inside application.properties or application.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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button