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 actuator
1 endpoints. Using actuator
we can check and change logging level without restarting application.
- 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'
- Set endpoint in application configuration. File
application.properties
1management.endpoints.web.exposure.include=loggers
- 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}
- 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
- 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. Valuenull
means, that logging level is not configured, so application will use default logging level configured for application - levelROOT
.effectiveLevel
- shows effective logging level for class. In this case levelINFO
results from default logging level configured for application - levelROOT
.
- Setting
DEBUG
logging level for classSchedulerOne
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
- Setting
DEBUG
as default (categoryROOT
) 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
- 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
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 namecategory
- ROOT or package oraz class name with packagelevel
- logging level, for exampleDEBUG
ornull
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:
- Set actuator endpoints on a separate port and network interface.
- Authorizing actuator endpoints using for example basic auth.
- Access to actuator endpoints via encrypted channel (https).
GIT repository
In github you can find example application for practice above scenarios.