Naplózás
Szintén egy régi tartozásomat szeretném letudni, méghozzá a naplózó keretrendszerek ismertetését.
Az egyik legrégebbi keretrendszer a Log4J, melyet Ceki Gülcü kezdett el fejleszteni. Bizonyos Apache projekteken belül az Apache Commons Logging volt elterjedt. Valamint bekerült naplózás a JDK-ba is, bár kicsit későn reagált az igényekre, a java.util.logging
csomagba került,
ezért gyakran hívjuk JUL-nak is. Mivel az eltérő library-k eltérő naplózó keretrendszereket definiáltak függőségként, ezért egy alkalmazás fejlesztésekor sajnos több naplózó rendszer is bekerült az alkalmazásba.
Erre a problémára megoldásként hozta létre szintén Ceki Gülcü az SLF4J
keretrendszert, mely egy egyszerű API a különböző naplózó keretrendszerek elé. Van egy egyszerű implementációja is (SimpleLogger
), de az összes többi keretrendszerhez van illesztése. Mindenképpen érdemes ezt használnunk, hiszen ekkor bármikor cserélhetjük alatta az implementációt.
A Log4J fejlesztése közben tanultak alapján alkotta meg Ceki Gülcü a következő keretrendszert, Logback néven.
Hogy még bonyolultabb legyen a helyzet, kijött a Log4J 2 is, melybe a Logbackből átemeltek pár dolgot, de úgy, hogy közben javítottak is rajta, olyan dolgokat, melyeket a Logbackben nem lehetett annak architektúrális megkötései miatt.
Ebben a posztban nézzük meg pár trükköt a naplózó keretrendszerek használatával kapcsolatban.
(Ennek kapcsán frissítettem egy régi Log4J cikkem, mely elérhető a GitHub-on, valamint van Log4J példa projekt is. A poszt további része természetesen a Logbacket is tárgyalja.)
SLF4J használata
Ahogy említettem, mindenképp érdemes az alkalmazásunkban az SLF4J-t használni, és a többi naplózó keretrendszeren nem függni. Ami mindenképpen szükséges, az a org.slf4j:slf4j-api
jar a classpath-on, hiszen ebben találhatóak a Logger
interfész és LoggerFactory
osztály, melyet a kódunkban használunk. Az alkalmazást elindítva a következő hibát kaphatjuk:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Ez azt jelenti, hogy nincs implementáció, mely az API-t megvalósítaná. A legegyszerűbb, hogy a classpath-ba felvesszük még a org.slf4j:slf4j-simple
jar-t is, azonban ez csak egy nagyon egyszerű naplózást tesz lehetővé, mely környezeti változókkal paraméterezhető.
A Logback natívan implementálja az SLF4J API-t, ezért ott külön trükközés nem szükséges, elegendő a ch.qos.logback:logback-classic
(, és ennek függőségét, a ch.qos.logback:logback-core
) jar-t elhelyezni a classpath-on.
A JUL esetén még egyszerűbb a helyzet, hiszen az a standard osztálykönyvtár része, ezért csak
a org.slf4j: slf4j-jdk14
) jar-t elhelyezni a classpath-on, ami nem más, mint egy adapter az SLF4J-hez, ami az SLF4J naplózás hívásokat a JUL-hoz továbbítja.
Ha azt akarjuk, hogy a Log4J legyen az SLF4J alatt, akkor egyrészt a Log4J jar-okat kell elhelyezni a classpath-on (log4j:log4j
), valamint az SLF4J adaptert (org.slf4j:slf4j-log4j12
).
A koncepcióról a következő ábra ad iránymutatást.
Függés több keretrendszeren
Sajnos előfordulhat olyan, hogy mégis több naplózó keretrendszer kerül be az alkalmazásunkba. Ez azért lehet, mert mi lehet, hogy csak SLF4J-t használunk, alatta Logback implementációval, az általunk használt library-k használhatnak más keretrendszereket is (pl. a Spring Apache Commons Logging keretrendszert használ).
Ilyenkor ún. bridge-eket kell használni. Tegyük fel, hogy az egyik keretrendszer hivatkozik a commons-logging:commons-logging
jar-ra is, és az emiatt Apache Commons Logging keretrendszerrel próbál naplózni. Ilyenkor mind a két keretrendszert külön kéne konfigurálni. Emiatt azonban tehetjük azt, hogy az eredeti jar helyett, egy SLF4J bridge-et használunk (Apache Commons Logging esetén org.slf4j:jcl-over-slf4j
). Ezen bridge azokat az osztályokat implementálja, melyek az eredeti jar-ban is szerepelnek (tehát azonos csomag és osztálynévvel), de a hívásokat az SLF4-hez továbbítja, ami meg az alatta lévő implementációnak. Azaz ebben az esetben annyi a feladatunk, hogy a naplózó keretrendszer jar-jait kivesszük az alkalmazásunkból, és betesszük a bridge-eket. Természetesen egy alkalmazásban több bridge-et is használhatunk. Amennyiben Mavent használunk, és az tranzitív függőségként húzza be a számunkra nem szükséges keretrendszert, akkor exclude-áljuk a pom.xml
fájlban.
Elvárások
A naplózással kapcsolatban a következő elvárásaim vannak:
- Az alkalmazás forráskódját a fejlesztőeszközben megnyitva, és az alkalmazást futtatva azonnal legyen egy viszonylag részletes naplózás, mindenféle konfiguráció nélkül. Ahhoz, hogy a fejlesztőkörnyezet konzolján jelenjen meg, ennek a konzolra kell mennie.
- Ugyanez igaz a tesztesetek futtatására is.
- A Continuous Integration szerveren a tesztesetek naplózását felül lehessen bírálni.
- Amennyiben az alkalmazást release-eljük, és telepítjük, legyen egy default naplózás, mindenféle konfiguráció nélkül.
- A naplózást az alkalmazáson kívül is lehessen konfigurálni, így minden környezetre feltelepíthető legyen ugyanaz a jar/war/ear, és ne az alkalmazást kelljen a naplózáshoz módosítani.
Log4J konfiguráció felolvasása
A Log4J az előbb definiált igényeket alapból teljesíti. Az alkalmazás futtatáshoz tegyünk
a classpath-ra egy log4j.properties
állományt (pl. Maven esetén az src/main/resources
könyvtárba)
. A tesztesetek naplózásához tegyünk egy log4j.properties
állományt a
classpath-ra, és arra kell ügyelni, hogy előbb olvassa fel ezt. A Maven esetén szerencsére
ez automatikusan megtörténik, azaz ha az src/test/resources
könyvtárba teszünk egy
log4j.properties
állományt, azt fogja a Log4J betölteni.
Mind a CI szerver, mind a különböző környezetekben futó alkalmazások naplózását egyszerűen
felül tudjuk bírálni rendszerváltozó megadásával, azaz a JVM-nek -D
kapcsolóval meg kell
adni a log4j.configuration
változó értékeként egy konfigurációs állomány elérési útvonalát.
Ezt a file:
előtaggal kell megtennünk, mivel ellenkező esetben a classpath-on fogja más
elérési útvonalon keresni.
Azaz pl. a következő paranccsal indítsuk az alkalmazásunk:
java -Dlog4j.configuration=file:/home/jtechlog/Documents/jtechlog-log4j-sample/target/log4j.properties \
-jar jtechlog-log4j-sample-1.0.0-SNAPSHOT-jar-with-dependencies.jar
Windows esetén szükség van a meghajtóra is, azaz pl. file:/c:/log4j.properties
.
Amennyiben a naplózás nem úgy történik, ahogy beállítottuk, annak az lehet az okat, hogy több jarban is szerepel log4j.properties
állomány,
és nem azt olvasta be, amelyiket szerettük volna. Ekkor a -Dlog4j.debug
kapcsoló megadásával nyomon követhetjük, hogyan történik a beolvasás.
Ekkor valami ilyen üzenetet kaphatunk:
log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@55f96302.
log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@55f96302 class loader.
log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().
log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@55f96302.
log4j: Using URL [jar:file:/home/jtechlog/Documents/jtechlog-log4j-sample/target/jtechlog-log4j-sample-1.0.0-SNAPSHOT-jar-with-dependencies.jar!/log4j.properties] for automatic log4j configuration.
log4j: Reading configuration from URL jar:file:/home/jtechlog/Documents/jtechlog-log4j-sample/target/jtechlog-log4j-sample-1.0.0-SNAPSHOT-jar-with-dependencies.jar!/log4j.properties
Logback konfiguráció felolvasása
A Logback hasonlóképp működik, mint a Log4J. Annyi a különbség, hogy előbb a logback-test.xml
állományt, majd a logback.xml
állományt próbálja beolvasni. Ezért a fő ágon, és a teszt ágon lévő konfigurációs fájlok neve is eltérhet. Valamint a
konfigurációs fájl helyét a -Dlogback.configurationFile
kapcsolóval lehet megadni, valamint a Logback 1.0.4 verziójától
kezdve használható a -Dlogback.debug
kapcsoló is.
Spring naplózás
A Spring fejlesztésének elején úgy döntöttek, hogy az Apache Commons Logging keretrendszert választják naplózáshoz. Azóta ez rossz döntésnek bizonyult, és a helyes megoldás, ha saját alkalmazásunkban SLF4J-t használunk, és a Spring naplózását is ebbe vezetjük. A fentebb leírt módszer alapján, ha Mavent használunk ehhez először ki kell exclude-álnunk a commons-logging:commons-logging
függőséget a org.springframework:spring-core
függőségnél, és bevezetni a org.slf4j:slf4j-api
függőséget, valamint a org.slf4j:jcl-over-slf4j
függőséget a Spring napló üzeneteinek SLF4J keretrendszerbe irányításához. Aztán tegyünk alá egy implementációt, pl. Logback esetén a ch.qos.logback:logback-classic
(, és ennek függőségét, a ch.qos.logback:logback-core
) függőséget. Erről bővebben a referencia Logging fejezete ír.
Használjuk a Maven mvn dependency:tree
parancsát, hogy meggyőződjünk arról, hogy nem kerül be más naplózó keretrendszer a classpath-ra. Ha igen, akkor exclude-áljuk.
Spring Boot naplózás
A Spring Boot naplózás konfigurációjáról a referencia Logging fejezete ír.
Használható a JUL, Log4J 2 és Logback is. Amennyiben startert használunk, a Logback lesz használva, és a megfelelő bridge-ek is a classpath-on vannak. Alapból info szinten naplóz a konzolra, de ezt a --debug
kapcsolóval, vagy a debug=true
property beállításával (pl. application.properties
állományban) debug szintre tudjuk állítani.
A Twelve-Factor App ajánlás szerint csak konzolra naplózzunk, de amennyiben fájlba is szeretnénk, használjuk a logging.file
vagy logging.path
property-t (ide is info szinten naplóz, és 10 MB után rotálja a napló állományt). Ha még jobban szeretnénk konfigurálni, akkor a classpath-on javasolt elhelyezni egy logback-spring.xml
állományt, vagy a logging.config
property segítségével beállíthatjuk, hogy hol van a konfigurációs állomány.
A Logback tökéletes választás, de amennyiben más naplózó keretrendszert szeretnénk használni, deklaráljuk függőségként, valamint használjuk a org.springframework.boot.logging.LoggingSystem
környezeti változót. Ennek paraméterül egy LoggingSystem
interfészt implementáló osztály minősített nevét kell megadni.