Reputation: 2073
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
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
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
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
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