Spring Boot integracija Logback-a za prilagođavanje okvira za logovanje
O Logback-u
Sistem za logovanje jedna je od obaveznih osobina produkcionog projekta. Predstavnički okviri za logovanje — Log4j, SLF4J i Logback — zapravo su rođena braća; imaju istog oca, a to je džin Ceki Gulcu.
Pošto je podrazumevani okvir za logovanje u Spring Boot-u Logback, a s obzirom na to da je Log4j2 ranije imao ozbiljnu ranjivost, ovog puta ćemo se fokusirati samo na Logback.
- Logback veoma prirodno implementira SLF4J, pa ne zahteva dodavanje adapterskog sloja kao Log4j i JUL.

- Podrazumevani okvir za logovanje u Spring Boot-u jeste Logback, pa prilikom pokretanja projekta Codingmore možete videti logove koje zapisuje Logback.

Otkud znam da je logback?
Iskreno, ne vidi se, hahaha, ali dokaze možemo naći na zvaničnom sajtu Spring Boot-a.

Takođe, može se naslutiti i iz izvornog koda.

- logback je podeljen na tri modula:
- logback-core, modul jezgra, pruža ključne opšte mehanizme;
- logback-classic, može se posmatrati kao poboljšana verzija log4j-a, implementira jednostavno lice za logovanje SLF4J;
- logback-access, uglavnom se koristi za interakciju sa Servlet kontejnerima, na primer Tomcat, i pruža određene funkcije HTTP pristupa.
Ako želite da proučavate izvorni kod logback-a, možete ga istraživati po ta tri modula.
Praktičan početak
Bez dugih uvoda, napravimo novi prazan Spring Boot projekat i u testnu klasu dodajmo dve linije koda.
@SpringBootTest
class CodingmoreLogbackApplicationTests {
static Logger logger = LoggerFactory.getLogger(CodingmoreLogbackApplicationTests.class);
@Test
void contextLoads() {
logger.info("logback testing");
}
}Logger i LoggerFactory dolaze iz SLF4J-a, tako da ako projekat prelazi sa Log4j + SLF4J na Logback, kod u tom trenutku ne zahteva nikakvu izmenu.
Ne treba raditi ništa drugo; nakon pokretanja videćete da logback već normalno radi.

Bez konfiguracionog fajla sve je podrazumevano, a Logback-ove poruke logova ispisivaće se na konzolu. StatusPrinter-om možete ispisati interne informacije Logback-a:
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);Ponovo pokrenite testnu klasu i na konzoli možete videti sledeće:

Drugim rečima, Logback prvo traži fajl logback-test.xml na classpath-u; ako ga ne pronađe, traži logback.xml; ako ne pronađe ni njega, ispisuje na konzolu.
Takođe, podrazumevani nivo logovanja za Logger jeste INFO, što znači da ako probate sledeći kod:
logger.debug("Chenmo Wang Er je glup X");Nećete videti taj log na konzoli. Nivoi logovanja u Logback-u su sledeći:
TRACE < DEBUG < INFO < WARN < ERROR
Drugim rečima, logovi ispod nivoa INFO se ne zapisuju; samo logovi na nivou INFO ili višem se zapisuju.
Analiza slučaja logovanja u projektu Codingmore
Iako je podrazumevana konfiguracija korisna, verovatno neće zadovoljiti naše stvarne razvojne potrebe, pa moramo pronaći elegantnije rešenje.
Kada u direktorijumu src/main/resources postoji konfiguracioni fajl pod jednim od sledećih imena, Spring Boot će ga automatski učitati kao konfiguracione stavke za Logback:
- logback-spring.xml
- logback.xml
- logback-spring.groovy
- logback.groovy
Spring Boot preporučuje korišćenje konfiguracionih fajlova sa sufiksom -spring, čime se omogućava korišćenje Profile funkcije Spring Boot-a (koja za različita okruženja — razvojno, testno, produkciono — pruža različite konfiguracione stavke).
Codingmore koristi logback-spring.xml (može se naći u projektu codingmore-admin):
<!--
configuration ima tri svojstva:
scan: kada je ovo svojstvo postavljeno na true, konfiguracioni fajl će se ponovo učitati ako se promeni; podrazumevana vrednost je true.
scanPeriod: postavlja vremenski interval provere da li je konfiguracioni fajl izmenjen; ako nije data vremenska jedinica, podrazumevana jedinica je milisekunda. Ovo svojstvo važi kada je scan true. Podrazumevani interval je 1 minut.
debug: kada je ovo svojstvo postavljeno na true, ispisivaće se interni logovi logback-a, radi praćenja stanja rada logback-a u realnom vremenu. Podrazumevana vrednost je false.
-->
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- Definiše ime fajla logova -->
<property name="APP_NAME" value="codingmore-admin" />
<!-- Definiše putanju fajla logova -->
<property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs" />
<!-- Definiše ime fajla logova -->
<property name="LOG_FILE" value="${LOG_PATH}/codingmore-admin.log" />
<!-- Klizno zapisivanje logova: prvo se logovi zapisuju u zadati fajl, a kada se ispuni određeni uslov, prebacuju se u drugi fajl -->
<appender name="APPLICATION"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- Navodi ime fajla logova -->
<file>${LOG_FILE}</file>
<!--
Kada dođe do klizanja, određuje ponašanje RollingFileAppender-a, uz pomeranje i preimenovanje fajlova.
TimeBasedRollingPolicy: najčešće korišćena strategija klizanja; strategija klizanja se određuje na osnovu vremena; odgovorna je i za samo klizanje i za okidanje klizanja.
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
Mesto čuvanja i ime fajla nastalog klizanjem
%d{yyyy-MM-dd}: dnevno klizanje logova
%i: kada veličina fajla pređe maxFileSize, klizanje fajlova se vrši prema i
-->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--
Opcioni čvor, kontroliše najveći broj arhiviranih fajlova koji se zadržavaju; kad se pređe taj broj, brišu se stari fajlovi. Pretpostavimo da je klizanje podešeno dnevno,
a maxHistory je 7; tada se čuvaju samo fajlovi iz poslednjih 7 dana, a stari se brišu.
Napomena: prilikom brisanja starih fajlova, brišu se i direktorijumi kreirani radi arhiviranja.
-->
<maxHistory>7</maxHistory>
<!--
Kada fajl logova pređe veličinu zadatu sa maxFileSize, klizanje fajla logova se vrši prema gorepomenutom %i.
Napomena: konfiguracija SizeBasedTriggeringPolicy ovde ne može da ostvari klizanje po veličini fajla;
mora se konfigurisati timeBasedFileNamingAndTriggeringPolicy.
-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- Format ispisa logova: -->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
</layout>
</appender>
<!-- ch.qos.logback.core.ConsoleAppender označava ispis na konzolu -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--
Format ispisa logova:
%d označava datum i vreme, %green zelenu boju
%thread označava ime thread-a, %magenta magenta boju
%-5level: nivo se prikazuje u 5 znakova sleva, %highlight boju isticanja
%logger{36} označava da ime logger-a može biti dugo najviše 36 znakova, inače se deli po tačkama, %yellow žutu boju
%msg: poruka loga
%n je znak za novi red
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%magenta(%thread)] %highlight(%-5level) %yellow(%logger{36}): %msg%n</pattern>
</layout>
</appender>
<!--
root i logger su u odnosu roditelj-dete; ako nije posebno definisano, podrazumevano je root. Svaka klasa odgovara samo jednom logger-u,
bilo definisanom logger-u, bilo root-u; ključno je pronaći taj logger, a zatim pročitati njegov appender i level.
-->
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="APPLICATION" />
</root>
</configuration>Specifična značenja su sva napisana u komentarima, možete ih pažljivo pročitati — dovoljno je samo da ih razumete. U suštini, Logback konfiguracija u produkcionom okruženju izgleda ovako, uz mogućnost sitnih podešavanja.
Možete koristiti i sledeći logback-spring.xml.
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="APP_NAME" value="codingmore-admin" />
<property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs" />
<property name="LOG_FILE" value="${LOG_PATH}/codingmore-admin.log" />
<appender name="APPLICATION"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>7</maxHistory>
<maxFileSize>50MB</maxFileSize>
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="APPLICATION" />
</root>
</configuration>Napomenimo samo nekoliko razlika.
FILE_LOG_PATTERN i CONSOLE_LOG_PATTERN podrazumevano su definisani u Spring Boot-u.

SizeAndTimeBasedRollingPolicy ima jedno svojstvo više za podešavanje veličine fajla logova u odnosu na TimeBasedRollingPolicy: maxFileSize; u ostalom su potpuno isti.
totalSizeCap, ukupna veličina svih fajlova logova (opciono). Kada se pređe ta veličina, stari fajlovi logova biće asinhrono obrisani. Mora se koristiti zajedno sa svojstvom maxHistory i predstavlja drugi uslov.
Pokrenimo projekat u Intellij IDEA-i i pogledajmo efekat konfigurisanih logova (u konzoli).

Pošto smo dodali podešavanje boja, konzolni logovi su sada nešto prijatniji za oči.
Gde se nalaze konfigurisani fajlovi logova? Pod user.home; ako niste sigurni koja je tačno vrednost, možete je dobiti preko System.getProperty("user.home").

Prateći tu putanju možete pronaći generisane fajlove logova, a strategija generisanja logova u skladu je sa našim očekivanjima.

Logovanje pomoću lombok-a
U gornjem primeru moramo u klasi deklarisati statički Logger objekat da bismo ga koristili tamo gde je potrebno zapisivati logove.
static Logger logger = LoggerFactory.getLogger(CodingmoreLogbackApplicationTests.class);Takav boilerplate kod je mrzak!
Ovaj problem možemo rešiti pomoću lombok-a, tako što ćemo u fajl pom.xml dodati zavisnost.
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>Zatim na klasu dodajte anotaciju @Slf4j, nakon čega možete direktno koristiti log (dodavanjem anotacije @Slf4j automatski se dodaje polje log) za zapisivanje logova.
@SpringBootTest
@Slf4j
class CodingmoreLogbackApplicationTests {
@Test
void testSlf4j() {
log.info("Chenmo Wang Er je velika budala");
}
}
Putanja izvornog koda
- Codingmore: https://github.com/itwanger/coding-more
- Za detaljnu Logback konfiguraciju: https://github.com/itwanger/coding-more
