Hannes
Hannes

Reputation: 2073

Display Spring-Boot Banner with Root-Logger WARN

Under development and test environment the ROOT logger level is DEBUG or INFO. The spring-boot banner is displayed at application startup:

2017-03-23 14:31:00,322 [INFO ]                 - 
 :: Spring Boot ::         (v1.5.2.RELEASE)
 :: Application ::         AcMe (v1.0-SNAPSHOT)
 :: Build ::               2017-03-23 09:53

But when running in a production environment the my ROOT logger level is normally WARN. This causes the banner not to be printed out.

How to configure logback so that the banner will be displayed also in production?

My guess was to add another logger, but the following (and alike configuration) did not work:

<logger name="org.springframework.web" level="INFO" additivity="false">
    <appender-ref ref="FILE"/>
</logger>

Here my configuration

application.properties:

  spring.main.banner-mode=log

application-devel.properties:

  logging.config=classpath:logging-spring-devel.xml

application-production.properties:

  logging.config=classpath:logging-spring-production.xml

logging-devel.xml (banner displayed)

        LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}application.log}"/>
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}</file>
            ...
        </appender>
        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </configuration>

logging-production.xml (banner not displayed)

        LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}application.log}"/>
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_FILE}</file>
            ...
        </appender>
        <root level="WARN">
            <appender-ref ref="FILE"/>
        </root>
    </configuration>

Upvotes: 13

Views: 8060

Answers (4)

ocarlsen
ocarlsen

Reputation: 1440

I had same problem and just set this property in application.properties:

spring.main.banner-mode=LOG

Now it prints to both console and file, with log level INFO. As long as you have your root log level and appenders set to accept INFO, you will see it.

<root level="info">
    <appender-ref ref="RollingFile" />
    <appender-ref ref="Console" />
</root>

Upvotes: 13

Sasha Shpota
Sasha Shpota

Reputation: 10310

During printing a banner Spring Boot uses logger of class org.springframework.boot.SpringApplication with INFO level.

The simples solution would be to enable INFO level for this particular class:

<logger name="org.springframework.boot.SpringApplication"
        level="INFO" additivity="false">
    <appender-ref ref="FILE"/>
</logger>

Upvotes: 13

chimmi
chimmi

Reputation: 2085

First, I must admit that I have not tested this, but at least it may give you some ideas.

You can remove spring.main.banner-mode=log and provide your own wrapper implementation that will use logger instead of provided output stream. Code should look something like this:

public class BannerLoggerWrapper implements Banner {

    private static final Log logger = LogFactory.getLog(BannerLoggerWrapper.class);
    private Banner actual;

    public BannerLoggerWrapper(Banner actual) {
        this.actual = actual;
    }

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        try {
            logger.info(createStringFromBanner(environment, sourceClass));
        } catch (UnsupportedEncodingException ex) {
            logger.warn("Failed to create String for banner", ex);
        }
    }

    private String createStringFromBanner(Environment environment, Class<?> sourceClass) throws UnsupportedEncodingException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        actual.printBanner(environment, sourceClass, new PrintStream(baos));
        String charset = environment.getProperty("banner.charset", "UTF-8");
        return baos.toString(charset);
    }

}

You can replace logger.info with logger.warn in this class or you can create additional config specificaly for this logger:

<logger name="your.package.name.BannerLoggerWrapper" level="INFO" additivity="false">
    <appender-ref ref="FILE"/>
</logger>

According to documentation you can configure Spring Boot to use your Banner implementation using SpringApplication.setBanner(…​).

Upvotes: 0

Hannes
Hannes

Reputation: 2073

This is, what I came up with. It wraps around the idea of just replacing the logger in regular implementation.

The problem with using the default log implementation is the way commons-logging is adapted via slf4j bridge.

This is probably one of the ugliest code alive, so hopefully we will see a fix in upcoming spring-boot releases...

Step 1: Register a new application listener

/META-INF/spring.factory

 org.springframework.context.ApplicationListener=ac.me.appevents.BannerDisplay

Step 2: Implement the Application Listener

package ac.me.appevents;

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.boot.ResourceBanner;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

public class BannerDisplay implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    /**
     * Banner location property key.
     */
    private static final String BANNER_LOCATION_PROPERTY = "banner.location";

    /**
     * Default banner location.
     */
    private static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt";

    private static final Logger LOG = LoggerFactory.getLogger(BannerDisplay.class);

    private static final Marker MRK = MarkerFactory.getMarker("Banner");

    private ResourceLoader resourceLoader;

    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        Environment environment = event.getEnvironment();

        String location = environment.getProperty(BANNER_LOCATION_PROPERTY, BANNER_LOCATION_PROPERTY_VALUE);
        ResourceLoader resLoader = getResourceLoader();
        Resource resource = resLoader.getResource(location);
        if (resource.exists()) {
            ResourceBanner banner = new ResourceBanner(resource);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            banner.printBanner(environment, deduceMainApplicationClass(), new PrintStream(baos));
            String charset = environment.getProperty("banner.charset", "UTF-8");
            try {

                LOG.info(MRK, baos.toString(charset));
            }
            catch (UnsupportedEncodingException e) {
                LOG.warn(MRK, "Unsupported banner charset encoding.", e);
            }

        }
    }

    @NotNull
    private ResourceLoader getResourceLoader() {
        if (resourceLoader == null) {
            this.resourceLoader = new DefaultResourceLoader(ClassUtils.getDefaultClassLoader());
        }
        return resourceLoader;
    }

    public void setResourceLoader(final ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

Upvotes: 2

Related Questions