How to change logging level when Spring Boot application is running?

When we have a problem with reproducing the bug in local environment, it may be useful to view logs from environment where a bug could be reproduced. Usually it requires to change logging level to DEBUG or TRACE. We can do this by changing logging level in application preferences and then restart application.

1# setting DEBUG level for specific class
2logging.level.pl.speedlog.example.logging_level.SchedulerOne=DEBUG

There are times when intense testing is underway in a given environment and we cannot restart the application after changing preferences.

How to change logging level in Spring Boot application in runtime?

There is a few solutions:

  • set tracking changes in logback configuration file
  • using springboot-config-server and invoke endpoint for refresh configuration
  • using actuator endpoints

In this article I will describe the last solution - using actuator1 endpoints. Using actuator we can check and change logging level without restarting application.

  1. Add dependency

File pom.xml

1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-starter-actuator</artifactId>
4</dependency>

or build.gradle

1implementation 'org.springframework.boot:spring-boot-starter-actuator'
  1. Set endpoint in application configuration. File application.properties
1management.endpoints.web.exposure.include=loggers
  1. Example application

We prepare application, which add in logs every two seconds, information in INFO, DEBUG, TRACE level from two classes SchedulerOne and AnotherScheduler.

We add annotation @EnableScheduling in main class.

1@SpringBootApplication
2@EnableScheduling
3public class LoggingLevelApplication {
4
5	public static void main(String[] args) {
6		SpringApplication.run(LoggingLevelApplication.class, args);
7	}
8
9}

First class, that adds information in logs SchedulerOne.java

 1@Component
 2class SchedulerOne {
 3
 4    private static final Logger log = LoggerFactory.getLogger(SchedulerOne.class);
 5
 6    @Scheduled(fixedRate = 2000)
 7    public void logExample() {
 8        log.trace("This is TRACE level message from class: {}", this.getClass().getSimpleName());
 9        log.debug("This is DEBUG level message from class: {}", this.getClass().getSimpleName());
10        log.info("This is INFO level message from class: {}", this.getClass().getSimpleName());
11    }
12}

Second class, that adds information in logs AnotherScheduler.java

 1@Component
 2class AnotherScheduler {
 3
 4    private static final Logger log = LoggerFactory.getLogger(AnotherScheduler.class);
 5
 6    @Scheduled(fixedRate = 2000)
 7    public void logExample() {
 8        log.trace("This is TRACE level message from class: {}", this.getClass().getSimpleName());
 9        log.debug("This is DEBUG level message from class: {}", this.getClass().getSimpleName());
10        log.info("This is INFO level message from class: {}", this.getClass().getSimpleName());
11    }
12}
  1. We run application
1./gradlew :bootRun

Every 2 seconds application add logs from two classes in INFO level

12022-02-13 23:49:11.086  INFO 65959 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
22022-02-13 23:49:11.087  INFO 65959 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
3
42022-02-13 23:49:13.086  INFO 65959 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
52022-02-13 23:49:13.086  INFO 65959 ---[   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
6
72022-02-13 23:49:15.086  INFO 65959 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
82022-02-13 23:49:15.086  INFO 65959 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
  1. Checking actual logging level for class SchedulerOne.

We invoke method GET and add to the end of the endpoint class name with package pl.speedlog.example.logging_level.SchedulerOne.

1curl -s http://localhost:8080/actuator/loggers/pl.speedlog.example.logging_level.SchedulerOne | jq '.'

Result

1{
2  "configuredLevel": null,
3  "effectiveLevel": "INFO"
4}
  • configuredLevel - shows configured logging level for class. Value null means, that logging level is not configured, so application will use default logging level configured for application - level ROOT.
  • effectiveLevel - shows effective logging level for class. In this case level INFO results from default logging level configured for application - level ROOT.
  1. Setting DEBUG logging level for class SchedulerOne

We invoke method POST and add to the end of the endpoint class name with package pl.speedlog.example.logging_level.SchedulerOne.
In body we pass json with new logging level:

1{
2  "configuredLevel": "DEBUG"
3}

Example invoke

1curl -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' \
2      http://localhost:8080/actuator/loggers/pl.speedlog.example.logging_level.SchedulerOne

Every 2 seconds application add logs from class SchedulerOne in DEBUG level

12022-02-13 00:14:31.726  INFO 67510 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
22022-02-13 00:14:31.726 DEBUG 67510 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is DEBUG level message from class: SchedulerOne
32022-02-13 00:14:31.726  INFO 67510 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
4
52022-02-13 00:14:33.726  INFO 67510 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
62022-02-13 00:14:33.726 DEBUG 67510 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is DEBUG level message from class: SchedulerOne
72022-02-13 00:14:33.726  INFO 67510 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
  1. Setting DEBUG as default (category ROOT) logging level

Example invoke

1curl -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' \
2      http://localhost:8080/actuator/loggers/ROOT

After invoking above command, we can see in logs, that both classes AnotherScheduler and SchedulerOne log in DEBUG level.

12022-02-13 08:54:33.843 DEBUG 13113 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is DEBUG level message from class: AnotherScheduler
22022-02-13 08:54:33.843  INFO 13113 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
32022-02-13 08:54:33.843 DEBUG 13113 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is DEBUG level message from class: SchedulerOne
42022-02-13 08:54:33.843  INFO 13113 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
5
62022-02-13 08:54:35.843 DEBUG 13113 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is DEBUG level message from class: AnotherScheduler
72022-02-13 08:54:35.843  INFO 13113 --- [   scheduling-1] p.s.e.logging_level.AnotherScheduler     : This is INFO level message from class: AnotherScheduler
82022-02-13 08:54:35.843 DEBUG 13113 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is DEBUG level message from class: SchedulerOne
92022-02-13 08:54:35.843  INFO 13113 --- [   scheduling-1] p.s.example.logging_level.SchedulerOne   : This is INFO level message from class: SchedulerOne
  1. Remove configured logging level

To remove configured logging level, we just pass null value in json. Below command revert logging configuration for ROOT category. After invoking that command, class AnotherScheduler again will show logs only in INFO level.

1curl -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": null}' \
2      http://localhost:8080/actuator/loggers/ROOT
Warning

Changing logging level by invoking actuator endpoints is not permanent - after application restart logging levels will be reverted.

How to change logging level in many instances?

There are several options. First option is manually invoke endpoint for each instance.

If you are using ansible, below I show example playbook, which run changing logging level for group of servers.

Let's save playbook in file change-logging-level.yaml. GIST: gist.github.com/speedlog/1efb9996d3b9813407b3814b3a462744

 1---
 2- hosts: "{{ nodes }}"   
 3  gather_facts: no 
 4  vars:
 5    - category: ROOT # pass here ROOT or package or classname with package
 6    - level: INFO # pass here logging level or null
 7  tasks:
 8    - name: "Change logging level to '{{ level }}' for '{{ category }}'"
 9      when: level != "null"
10      uri:
11        url: "http://localhost:8080/actuator/loggers/{{ category }}"
12        method: POST
13        status_code: 204
14        body: "{\"configuredLevel\": \"{{ level }}\" }"
15        body_format: json
16        headers:
17          Content-Type: "application/json"
18    - name: "Remove configured logging for '{{ category }}'"
19      when: level == "null"
20      uri:
21        url: "http://localhost:8080/actuator/loggers/{{ category }}"
22        method: POST
23        status_code: 204
24        body: "{\"configuredLevel\": null }"
25        body_format: json
26        headers:
27          Content-Type: "application/json"

We can run playbook with variables:

  • nodes - list of IP addresses or group name
  • category - ROOT or package oraz class name with package
  • level - logging level, for example DEBUG or null value

Example that set TRACE logging level for class pl.speedlog.example.logging_level.AnotherScheduler.

1ansible-playbook change-logging-level.yaml \
2--extra-vars "nodes=localhost category=pl.speedlog.example.logging_level.AnotherScheduler level=TRACE"

Best practies

Example application and it's configuration is simplified for the purposes of the article. Configuring actuator for production it is good practice:

  1. Set actuator endpoints on a separate port and network interface.
  2. Authorizing actuator endpoints using for example basic auth.
  3. Access to actuator endpoints via encrypted channel (https).

GIT repository

In github you can find example application for practice above scenarios.

github.com/speedlog/springboot-change-logging-level

Translations: