The simplest way to configure logging in your Spring Boot application is to modify or add some entries in application.properties file.

So for our case of setting the location where the log files will be written and the log level, this can be achieved by the following two configuration properties

logging.file=/home/bogdan/cool_app/logs/output.log
logging.level.root=info

This is all fine, but this means that you pack this file into the resulting jar from your Spring Boot application. The result is that you can configure the logging only in development.

Sometimes you may want to give your users the possibility of configuring this from within the UI of your application.

There are two problems to solve:

  1. Externalize the configuration. We need a way to configure the desired properties in an external file (not bundled within the jar) which should be read by the application at startup
  2. Dynamically change the configuration at runtime. You want to changes to be felt by the user instantly. That is, without having to restart the application to pick up the changes in the external file.

Let’s approach the two problems

Externalize the configuration

Spring Boot provides support for a variety of logging frameworks. One of the most popular and coming out-of-the-box with Spring Boot is logback; if you use one of the starter dependencies as they include spring-boot-starter-logging.

To get more flexibility and power from logback, we need to create a new XML file into the resource folder (same place where application.properties exists).

We name it logback-spring.xml. At startup, when Spring scans the classpath it will find this file and use the configuration from inside it.
Full files content is appended at the end. I will shortly explain the relevant parts of it here.
We have two variables of interest. One for the log level and one for the log file.
We define defaults with the help of property tags:

<property scope=”context” name=”LOG_LEVEL” value=”INFO”/>
<property scope=”context” name=”LOG_FILE” value=”/home/bogdan/cool_app/logs/cool_app.log”/>

Then we include our externally configured file. The properties defined in this external file will override the ones defined in logback_spring.xml.

This file is also included at the end. It contains comments to explain the various structures in the file.

<include file=”/home/bogdan/external_logging.config.xml”/>

This is all the configuration needed for the external configuration to work. You only need to write this file whenever the logging configuration needs to change (for example, through a rest service).

As mentioned, these changes will be picked up only when the app is restarted.
Let’s see how we can pick up these changes at runtime without an application restart.

Dynamically change the configuration at runtime

I mentioned that you can change the content of the included file. At the same spot, you can also add a few lines of code to dynamically tell the logger to change the new configuration

private ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
//newLevel – The new desired log level as string. E.g.: DEBUG
rootLogger.setLevel(Level.toLevel(newLevel));
//newFilepath – New file path where the logs will be written. For example: /home/bogdan/cool_app/logs/new_output.log
((FileAppender) rootLogger.getAppender(“FILE”)).setFile(newFilepath);
//Need to restart the appender to pick up the new file location
((FileAppender)rootLogger.getAppender(“FILE”)).start();

— Included files —

logback_spring.xml

<?xml version=”1.0″ encoding=”UTF-8″?>
<configuration scan=”true”>

<!– Define default values for our variables –>
<property scope=”context” name=”LOG_LEVEL” value=”INFO”/>
<property scope=”context” name=”LOG_FILE” value=”/home/bogdan/cool_app/logs/cool_app.log”/>

<!– Include the file where the configurable values are there. They will override the defaults defined above –>
<include file=”/home/bogdan/external_logging.config.xml”/>

<!– Define appender for console. We want output also there –>
<appender name=”CONSOLE” class=”ch.qos.logback.core.ConsoleAppender”>
<encoder>
<charset>UTF-8</charset>
<Pattern>%d %-4relative [%thread] %-5level %logger{35} # %msg%n</Pattern>
</encoder>
</appender>

<!– Define a file appender. We want logs written into file –>
<appender name=”FILE” class=”ch.qos.logback.core.rolling.RollingFileAppender”>
<encoder>
<pattern>%d %-4relative [%thread] %-5level %logger{35} # %msg%n</pattern>
</encoder>

<!– Use variable to devine the location where logs will be written –>
<file>${LOG_FILE}</file>

<!– Define rolling policy. Max 10 files are held on disk at the same time.–>
<rollingPolicy class=”ch.qos.logback.core.rolling.FixedWindowRollingPolicy”>
<fileNamePattern>${LOG_FILE}.%i</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>10</maxIndex>
</rollingPolicy>

<!– File is rotated when reaching 10 MB –>
<triggeringPolicy
class=”ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy”>
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>

<!– Define the log level for the root logger –>
<root level=”${LOG_LEVEL}”>
<appender-ref ref=”CONSOLE” />
<appender-ref ref=”FILE”/>
</root>
</configuration>

external_logging.config.xml

<included>

<!– The new location does not have to exist on disk. It will be created by the framework. –>
<property scope=”context” name=”LOG_FILE” value=”/home/bogdan/new_logs_location/new_output.log”/>
<property scope=”context” name=”LOG_LEVEL” value=”INFO”/>
</included>