Im using Spring Boot 3.3.3. When I try to use Spring properties that contain curly braces in logback-spring.xml they are not escaped.
I have the following application.yml:
smtp:
host: myhost
username: myusername
password: "A{B" # note the curly brace
logback-spring.xml for the Logback extension:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<springProperty name="smtpHost" source="smtp.host" defaultValue="not-set"/>
<springProperty name="smtpUsername" source="smtp.username" defaultValue="not-set"/>
<springProperty name="smtpPassword" source="smtp.password" defaultValue="not-set"/>
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<smtpHost>${smtpHost}</smtpHost>
<username>${smtpUsername}</username>
<password>${smtpPassword}</password>
...
</appender>
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="EMAIL"/>
</root>
</springProfile>
</configuration>
When I start my application I get an exception as the smtpPassword is not properly substituted. The problem disappears if I remove the curly brace from the password. Unfortunately, in production that is not an option for me. I'm also not able to escape the password e.g. with A'{'B or A\\{B. Here is the exception I'm getting:
Logging system failed to initialize using configuration from 'null'
java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.core.model.util.VariableSubstitutionsHelper@3e37aa90 - Problem while parsing [${smtpPassword}] java.lang.IllegalArgumentException: All tokens consumed but was expecting "}"
at org.springframework.boot.logging.logback.LogbackLoggingSystem.reportConfigurationErrorsIfNecessary(LogbackLoggingSystem.java:282)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:260)
at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:81)
at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:61)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:193)
at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:332)
at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:298)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:370)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
at org.MyApp.main(MyApp.java:28)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50)
Suppressed: java.lang.IllegalArgumentException: All tokens consumed but was expecting "}"
at ch.qos.logback.core.subst.Parser.expectNotNull(Parser.java:160)
at ch.qos.logback.core.subst.Parser.expectCurlyRight(Parser.java:165)
at ch.qos.logback.core.subst.Parser.T(Parser.java:93)
at ch.qos.logback.core.subst.Parser.E(Parser.java:58)
at ch.qos.logback.core.subst.Parser.Eopt(Parser.java:75)
at ch.qos.logback.core.subst.Parser.E(Parser.java:62)
at ch.qos.logback.core.subst.Parser.Eopt(Parser.java:75)
at ch.qos.logback.core.subst.Parser.E(Parser.java:62)
at ch.qos.logback.core.subst.Parser.Eopt(Parser.java:75)
at ch.qos.logback.core.subst.Parser.E(Parser.java:62)
at ch.qos.logback.core.subst.Parser.Eopt(Parser.java:75)
at ch.qos.logback.core.subst.Parser.E(Parser.java:62)
at ch.qos.logback.core.subst.Parser.Eopt(Parser.java:75)
at ch.qos.logback.core.subst.Parser.E(Parser.java:62)
at ch.qos.logback.core.subst.Parser.parse(Parser.java:54)
at ch.qos.logback.core.subst.NodeToStringTransformer.tokenizeAndParseString(NodeToStringTransformer.java:59)
at ch.qos.logback.core.subst.NodeToStringTransformer.handleVariable(NodeToStringTransformer.java:102)
at ch.qos.logback.core.subst.NodeToStringTransformer.compileNode(NodeToStringTransformer.java:77)
at ch.qos.logback.core.subst.NodeToStringTransformer.transform(NodeToStringTransformer.java:64)
at ch.qos.logback.core.subst.NodeToStringTransformer.substituteVariable(NodeToStringTransformer.java:52)
at ch.qos.logback.core.util.OptionHelper.substVars(OptionHelper.java:112)
at ch.qos.logback.core.model.util.VariableSubstitutionsHelper.subst(VariableSubstitutionsHelper.java:52)
at ch.qos.logback.core.model.processor.ModelInterpretationContext.subst(ModelInterpretationContext.java:166)
at ch.qos.logback.core.model.processor.ImplicitModelHandler.doBasicProperty(ImplicitModelHandler.java:105)
at ch.qos.logback.core.model.processor.ImplicitModelHandler.handle(ImplicitModelHandler.java:86)
at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:241)
at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:216)
at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:133)
at ch.qos.logback.core.joran.GenericXMLConfigurator.doConfigure(GenericXMLConfigurator.java:178)
at ch.qos.logback.core.joran.GenericXMLConfigurator.doConfigure(GenericXMLConfigurator.java:123)
at ch.qos.logback.core.joran.GenericXMLConfigurator.doConfigure(GenericXMLConfigurator.java:66)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.configureByResourceUrl(LogbackLoggingSystem.java:292)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.lambda$loadConfiguration$1(LogbackLoggingSystem.java:254)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.withLoggingSuppressed(LogbackLoggingSystem.java:472)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:248)
... 25 more
Comment From: philwebb
Unfortunately this looks a Logback bug and we don't have the option of escaping values. See https://jira.qos.ch/browse/LOGBACK-1461 and https://github.com/qos-ch/logback/issues/836. If either of those issues are fixed we can reopen this one and automatically apply escaping.