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-serveri 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 actuator1. 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śćnulloznacza, ż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 logowaniaINFOwynika z poziomu logowania ustawionego dla całej aplikacji - poziomROOT.
- Ustawienie logowania klasy
SchedulerOnena 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.DEBUGlub 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ń.