Logback - can you define appender name and class from env variables?

Антон Антонов :

I want to have dynamic logback appender properties which can easily be added to the logback config file, however trying to set the class and name of the appender(which are in a xml attributes instead of elements under the appender element).

So here is my application.yml(hardcoded the values for the example, but in the real world use case those will be passed as env variables from Helm, during the deploy to K8s cluster):

log:
  config:
    appender:
      name: CONSOLE
      class: ch.qos.logback.core.ConsoleAppender

And here is how I try to access those in logback-spring.xml(spring boot version - 2.2.4.RELEASE

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- use Spring default values like patterns -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!-- declaration of ENV properties:   -->
    <springProperty name="LOG_CONFIG_APPENDER_NAME" source="log.config.appender.name"/>
    <springProperty name="LOG_CONFIG_APPENDER_CLASS" source="log.config.appender.class"/>

    <appender name="${LOG_CONFIG_APPENDER_NAME}" class="${LOG_CONFIG_APPENDER_CLASS}">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="${LOG_CONFIG_APPENDER_NAME}"/>
    </root>
</configuration>

This leads to the following exception:

ERROR in ch.qos.logback.core.joran.action.AppenderAction - Could not create an Appender of type [${LOG_CONFIG_APPENDER_CLASS}]. ch.qos.logback.core.util.DynamicClassLoadingException: Failed to instantiate type ${LOG_CONFIG_APPENDER_CLASS}

So my question is: is it possible to define the appender name and class dynamically ?

Антон Антонов :

I have ended up doing it in Groovy, as mapping the config to the Spring profiles does not work for me and my team, as we are using the dev profile for both our local and our shared dev env(hosted on K8s cluster).

I am not proud of it, but ended doing it with a simple if:

def loggingType = System.getenv('LOGGING_TYPE')
def loggingLevelEnvVar = System.getenv('CUSTOM_LOGGING_LEVEL')
def loggingLevel = loggingLevelEnvVar == null ? INFO : Level.valueOf(loggingLevelEnvVar)
// please do not use a coloured pattern for consoles that will be scrapped
def loggingPattern = System.getenv('LOGGING_LEVEL_PATTERN')
...
if ('JSON'.equalsIgnoreCase(loggingType)) {
    appender('CONSOLE', ConsoleAppender) {
        encoder(LayoutWrappingEncoder) {
            layout(JsonLayout) {
                timestampFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
                timestampFormatTimezoneId = 'Etc/UTC'
                appendLineSeparator = true
                jsonFormatter(JacksonJsonFormatter) {
                    prettyPrint = false
                }
            }
        }
    }
} else {
    appender('CONSOLE', ConsoleAppender) {
        encoder(PatternLayoutEncoder) {
            pattern = loggingPattern
        }
    }
}
...

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=421675&siteId=1