Summary
When <condition> (the Janino-free alternative introduced in logback 1.5.x) is placed as a child of <if> instead of as a preceding sibling, both <then> and <else> branches are silently skipped. Instead of a helpful error about incorrect <condition> placement, logback falls through to the Janino code path and emits a misleading error:
ERROR in IfModelHandler - Could not find Janino library on the class path. Skipping conditional processing.
This is confusing because the user explicitly opted out of Janino by using the <condition> element.
Logback version
1.5.32 (also confirmed on 1.5.22)
Reproduction
Broken config (<condition> inside <if> — produces misleading Janino error, both branches skipped):
<configuration debug="true">
<property name="testKey" value="testValue"/>
<if>
<condition class="ch.qos.logback.core.boolex.ExpressionPropertyCondition">
<expression>propertyEquals("testKey", "testValue")</expression>
</condition>
<then>
<appender name="THEN_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>THEN: %msg%n</pattern></encoder>
</appender>
<root level="DEBUG"><appender-ref ref="THEN_APPENDER"/></root>
</then>
<else>
<appender name="ELSE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>ELSE: %msg%n</pattern></encoder>
</appender>
<root level="DEBUG"><appender-ref ref="ELSE_APPENDER"/></root>
</else>
</if>
</configuration>
Working config (<condition> as preceding sibling of <if>):
<configuration debug="true">
<property name="testKey" value="testValue"/>
<condition class="ch.qos.logback.core.boolex.ExpressionPropertyCondition">
<expression>propertyEquals("testKey", "testValue")</expression>
</condition>
<if>
<then>
<appender name="THEN_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>THEN: %msg%n</pattern></encoder>
</appender>
<root level="DEBUG"><appender-ref ref="THEN_APPENDER"/></root>
</then>
<else>
<appender name="ELSE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder><pattern>ELSE: %msg%n</pattern></encoder>
</appender>
<root level="DEBUG"><appender-ref ref="ELSE_APPENDER"/></root>
</else>
</if>
</configuration>
Unit test (both tests pass, demonstrating the bug):
package logback.bug;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.status.Status;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.net.URL;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class ConditionInsideIfTest {
private LoggerContext context;
@BeforeEach
void setUp() {
context = new LoggerContext();
}
@Test
void conditionBeforeIf_thenBranchExecuted() throws JoranException {
configure("condition-before-if.xml");
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
// Works: <condition> as sibling before <if>
assertThat(root.getAppender("THEN_APPENDER")).isNotNull();
assertThat(root.getAppender("ELSE_APPENDER")).isNull();
}
@Test
void conditionInsideIf_bothBranchesSkipped() throws JoranException {
configure("condition-inside-if.xml");
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
// BUG: Both branches skipped
assertThat(root.getAppender("THEN_APPENDER")).isNull();
assertThat(root.getAppender("ELSE_APPENDER")).isNull();
// BUG: Misleading "Janino not found" error instead of helpful message
List<Status> errors = context.getStatusManager().getCopyOfStatusList().stream()
.filter(s -> s.getLevel() >= Status.ERROR)
.toList();
assertThat(errors).isNotEmpty()
.anyMatch(s -> s.getMessage().contains("Janino"));
}
private void configure(String resourceName) throws JoranException {
URL url = getClass().getClassLoader().getResource(resourceName);
assertThat(url).isNotNull();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
configurator.doConfigure(url);
}
}
pom.xml dependencies: logback-classic:1.5.32, junit-jupiter, assertj-core — no Janino (testing the <condition> element path).
Expected behavior
When <condition> is placed inside <if>, logback should emit a clear warning or error, e.g.:
<condition> element found inside <if> — it must be placed as a preceding sibling of <if>, not as a child
Actual behavior
- The
<condition> element inside <if> is processed (the ExpressionPropertyCondition is instantiated and evaluates successfully) but its result is not linked to the <if>.
IfModelHandler doesn't find a condition result, falls through to janinoFallback().
- Janino is not on the classpath, so it emits:
"Could not find Janino library on the class path. Skipping conditional processing."
- Both
<then> and <else> branches are silently skipped.
Root cause analysis
In IfModelHandler.handle(), the condition result is looked up from BranchState which is set by ConditionModelHandler only when <condition> is a preceding sibling of <if>. When <condition> is a child of <if>, the ConditionModelHandler runs inside the <if> scope, so the BranchState is not found by IfModelHandler. The handler then falls through to janinoFallback() which:
- Looks for a
condition attribute on <if> (old Janino syntax) — not found
- Checks for Janino on the classpath — not found
- Emits the misleading Janino error
- Both branches are skipped
Suggested fix
In IfModelHandler, when no BranchState is found and no condition attribute exists, check if <condition> appears as a child element of <if> and emit a helpful warning:
WARN: <condition> element found as child of <if> but it must be a preceding sibling. See https://logback.qos.ch/...
This would save users significant debugging time, as the current "Janino not found" message sends them on a wild goose chase.
Summary
When
<condition>(the Janino-free alternative introduced in logback 1.5.x) is placed as a child of<if>instead of as a preceding sibling, both<then>and<else>branches are silently skipped. Instead of a helpful error about incorrect<condition>placement, logback falls through to the Janino code path and emits a misleading error:This is confusing because the user explicitly opted out of Janino by using the
<condition>element.Logback version
1.5.32 (also confirmed on 1.5.22)
Reproduction
Broken config (
<condition>inside<if>— produces misleading Janino error, both branches skipped):Working config (
<condition>as preceding sibling of<if>):Unit test (both tests pass, demonstrating the bug):
pom.xml dependencies:
logback-classic:1.5.32,junit-jupiter,assertj-core— no Janino (testing the<condition>element path).Expected behavior
When
<condition>is placed inside<if>, logback should emit a clear warning or error, e.g.:Actual behavior
<condition>element inside<if>is processed (theExpressionPropertyConditionis instantiated and evaluates successfully) but its result is not linked to the<if>.IfModelHandlerdoesn't find a condition result, falls through tojaninoFallback()."Could not find Janino library on the class path. Skipping conditional processing."<then>and<else>branches are silently skipped.Root cause analysis
In
IfModelHandler.handle(), the condition result is looked up fromBranchStatewhich is set byConditionModelHandleronly when<condition>is a preceding sibling of<if>. When<condition>is a child of<if>, theConditionModelHandlerruns inside the<if>scope, so theBranchStateis not found byIfModelHandler. The handler then falls through tojaninoFallback()which:conditionattribute on<if>(old Janino syntax) — not foundSuggested fix
In
IfModelHandler, when noBranchStateis found and noconditionattribute exists, check if<condition>appears as a child element of<if>and emit a helpful warning:This would save users significant debugging time, as the current "Janino not found" message sends them on a wild goose chase.