Jak zmienić poziom logowania w aplikacji Spring Boot dla uruchomionej aplikacji?
Kiedy mamy problem z odtworzeniem błędu w aplikacji na lokalnym środowisku, przydatne może być przejrzenie logów ze środowiska, na którym ten błąd występuje. Zazwyczaj wymaga to zmiany poziomu logowania np. na DEBUG
lub TRACE
. Standardowo możemy ustawić poziom logowania w preferencjach aplikacji i ją zrestartować.
1# ustawienie poziomu DEBUG dla specyficznej klasy
2logging.level.pl.speedlog.example.logging_level.SchedulerOne=DEBUG
Zdarza się, że na danym środowisku trwają intensywne testy i nie możemy zrestartować aplikacji po zmianie preferencji.
Jak zmienić poziom logowania w aplikacji Spring Boot bez restartu aplikacji?
Rozwiązań jest co najmniej kilka:
- ustawienie śledzenia zmian w pliku konfiguracyjnym logback, umieszczonym w katalogu aplikacji
- użycie
springboot-config-server
i wywołanie endpoint'a do odświeżenia konfiguracji - użycie endpointów
actuatora
W tym artykule opiszę ostatni sposób czyli użycie biblioteki actuator
1. Za jej pomocą możemy sprawdzić i zmienić poziom logowania bez konieczności restartu aplikacji.
- Dodajemy zależność
Plik pom.xml
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-actuator</artifactId>
4</dependency>
lub build.gradle
1implementation 'org.springframework.boot:spring-boot-starter-actuator'
- Ustawiamy endpoint w konfiguracji aplikacji. Plik
application.properties
1management.endpoints.web.exposure.include=loggers
- Przykładowa aplikacja
Przygotujemy aplikację, która co dwie sekundy dodaje w logach komunikat na poziomach INFO
, DEBUG
, TRACE
z dwóch klas SchedulerOne
oraz AnotherScheduler
.
Dodajemy adnotację @EnableScheduling
do głównej klasy.
1@SpringBootApplication
2@EnableScheduling
3public class LoggingLevelApplication {
4
5 public static void main(String[] args) {
6 SpringApplication.run(LoggingLevelApplication.class, args);
7 }
8
9}
Pierwsza klasa dodająca wpisy do logów 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}
Druga klasa dodająca wpisy do logów 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}
- Uruchamiamy aplikację
1./gradlew :bootRun
Co 2 sekundy pokazują się logi z 2 klas ma poziomie INFO
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
- Sprawdzenie aktualnego poziomu logowania ustawionego w aplikacji dla klasy
SchedulerOne
.
Wywołujemy metodę GET
dodając do endpint'a nazwę oraz pakiet klasy pl.speedlog.example.logging_level.SchedulerOne
.
1curl -s http://localhost:8080/actuator/loggers/pl.speedlog.example.logging_level.SchedulerOne | jq '.'
Wynik
1{
2 "configuredLevel": null,
3 "effectiveLevel": "INFO"
4}
configuredLevel
- pokazuje poziom logowania ustawiony w konfiguracji dla tej konkretnej klasy. Wartośćnull
oznacza, że poziom ten nie jest ustawiony, więc aplikacja będzie korzystać domyślnego poziomu logowania ustawionego dla całej aplikcji - poziomROOT
.effectiveLevel
- pokazuje realny poziom logowania. W tym przypadku poziom logowaniaINFO
wynika z poziomu logowania ustawionego dla całej aplikacji - poziomROOT
.
- Ustawienie logowania klasy
SchedulerOne
na naziomDEBUG
Wywołujemy metodę POST
, gdzie w endpoint podajemy nazwę klasy wraz z pakietem pl.speedlog.example.logging_level.SchedulerOne
.
W body wywołania przekazujemy json'a z nowym poziomem logowania:
1{
2 "configuredLevel": "DEBUG"
3}
Przykład wywołania
1curl -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' \
2 http://localhost:8080/actuator/loggers/pl.speedlog.example.logging_level.SchedulerOne
W logach uruchomionej aplikacji widać, że co 2 sekundy z klasy SchedulerOne
pojawiły się logi na poziomie DEBUG
.
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
- Ustawienie domyślnego poziomu (
ROOT
) logowania naDEBUG
Przykład wywołania
1curl -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}' \
2 http://localhost:8080/actuator/loggers/ROOT
Po wywołaniu tego polecenia w logach widzimy, że teraz obie klasy AnotherScheduler
oraz SchedulerOne
logują na poziomie DEBUG
.
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
- Usunięcie skonfigurowanego poziomu logowania
W celu usunięcia ustawionego poziomu logowania dla konkretnej klasy wystarczy przekazać wartość null
. Poniżej polecenie cofające konfigurację kategorii ROOT
. Po tej zmianie klasa AnotherScheduler
ponownie pokazuje logi tylko na poziomie INFO
.
1curl -i -X POST -H 'Content-Type: application/json' -d '{"configuredLevel": null}' \
2 http://localhost:8080/actuator/loggers/ROOT
Zmiana poziomów logowania przez wywołanie endpointu actuatora nie jest stała - po restarcie aplikacji ustawione poziomy logowania wracają do tych, które są ustawione w konfiguracji aplikacji.
Co w przypadku kiedy mamy uruchomionych wiele instancji aplikacji?
Opcji jest kilka. Pierwszą z nich może być ręczne uruchomienie poleceń dla każdej z instancji.
Jeżeli korzystacie z ansible poniżej wrzucam playbook'a, który uruchamia konfigurację poziomu logowania dla grupy serwerów.
Zapiszmy playbooka pod nazwą change-logging-level.yaml
.
Adres GIST'a: 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"
Możemy teraz uruchomić playbooka podając zmienne:
nodes
- lista adresów IP lub nazwa grupy serwerów, na której działa aplikacjacategory
- ROOT lub nazwa pakietu lub nazwa pakietu i klasy, dla których będzie ustawiany poziom logowanialevel
- poziom logowania np.DEBUG
lub wartośćnull
Przykład wywołania ustawiającego poziom TRACE
dla klasy 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"
Dobre praktyki
Zaprezentowana aplikacja i jej konfiguracja jest uproszczona na potrzeby artykułu. Konfigurując bibliotekę actuator
na produkcji dobrą praktyką jest:
- Wystawienie endpointów actuatora na osobnym porcie i interfejsie sieciowym.
- Autoryzowanie dostępu do actuatora np. za pomocą basic auth.
- Dostęp do endpointów actuatora szyfrowanym kanałem (https).
Repozytorium GIT
Na github możecie znaleźć przykładową aplikację gotową do przećwiczenia powyższych poleceń.