Spring Boot ile SLF4j ve Log4j Loglama Altyapısı

Sıfırdan bir proje yazmaya başladığımızda ele almamız gereken bir düzine konu mevcut. Güvenliği, loglaması, mesajlaşması, servis altyapısı bir çırpıda aklımıza gelenler. Bugün bu kalemlerden birisi olan hata loglama altyapısını kurgulama hakkında karalayacağım. Konumuz bir loglama frameworkünü tanıtmak olmayacak, bu tarz yazıları çokça yerde bulmak mümkün. Biz daha çok bir Spring Boot projesinde loglama yapısı oturtmaya çalıştığımızda nelere dikkat etmeliyiz sorusunun cevaplarını adım adım birlikte bulacağız.

Spring Boot ile Birlikte SLF4J ve LOG4J2

Örneğin, bir Spring Boot projemiz var ve varsayılan loglama ayarlarıyla kullanmak istemiyoruz. Mevcut yerine farklı bir loglama frameworkü kullanmayı istiyoruz, ancak ileride bundan da başka bir frameworke geçmek istersek kod seviyesinde etkilenmemeyi tercih ediyoruz. Hatta loglama ayarlarımızı, patternlerimizi projenin export edildiği jar dosyasının dışından yönetebilmek istiyoruz. Peki ne yapmalıyız?

Spring Boot varsayılan loglama çözümü olarak Logback ile entegre gelir. spring-boot-starterartifact’ini projemize eklediğimizde compile scope’unda spring-boot-starter-logging isimli bir artifact de eklenmiş olur. Bu artifact’in içerisinde aşağıdaki bağımlılıklar mevcut:

    <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>...</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>...</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>...</version>
<scope>compile</scope>
</dependency>

Buradan 2 yorum çıkıyor. Birincisi Spring Boot projemiz şu anda varsayılan olarak logback ile çalışır durumda hazır geliyor. İkincisi, sfl4j ve log4j kullanmak istiyorsak da dependencyler mevcut. Log4j’nin ne olduğunu aşağı yukarı biliyoruz ancak Slf4j ne acaba?

Slf4j, bizleri loglama frameworklerinden yalıtan, bağımsız olarak çalışmamızı sağlayan bir arayüzdür. Örneğin Logback kullanırken bir paketin altındaki LoggerFactory’e bağımlılığınız olur. Sonraki bir zaman Log4j’ye geçilmek istenirse tüm sınıflarınızdaki factory’lerinizi tek tek Log4j’ye ait factory ile değiştirilmesi gerekir (her sınıfın üzerinde constant olarak tanımlanan Logger instance’ı). Slf4j’nin görevi burada başlar. Spesifik frameworklere ait sınıflara bağımlı olmak yerine kendi sınıflarına bağımlı kılarak bizleri arka tarafta hangi loglama frameworkü olduğundan bihaber tutarak loglama yapılabilmesini sağlar.

Slf4j’ye dair ihtiyaç duyduğumuz dependencylerimiz de elimizde mevcut olduğuna göre şu anda yapmamız gereken logbacki varsayılan olmaktan çıkarıp yerine Log4j2 dependencylerini bağlamak. Önce Logback’i aradan çıkaralım:

Exclude default configuration

Sıradaki adımımız projemize Log4j2'yi bağlamak. Spring boot bunun için de hazır bir dependency sunuyor. spring-boot-starter-log4j2 dependency’sini projemize eklediğimizde artık Log4j2'ye dair özellikler kullanılabilir durumda olacak. Bu dependency’nin içerisinde de aşağıdaki bileşenler bulunuyor:

Spring Boot Log4j2 dependency

Aşağıdaki tarzda bir yapılandırma dosyasını ister yml, ister properties, resource’larımız arasına ekleyebiliriz. Bu adımız Log4j2'yi kullanabilmemiz için son gerekli adımımızdı.

Projemiz şu anda Log4j2 ile çalışmaya hazır durumda. Slf4j’yi kullanarak Factory’sinden aldığımız Logger’lar aracılığıyla artık tanımladığımız appenderlarımıza loglarımızı basabiliriz. Ancak tam da burada bir katman daha kullanacağım. Bu katman işimizi kolaylaştıracak ve static logger tanımlama adımını annotation seviyesine taşıyacak. Bunun için Spring Boot communitysi tarafından yoğunca kullanılan ve start-spring-io da sunduğu Project Lombok ’a ait bir özelliğini kullanacağım. Lombok’u IDE’niz ile nasıl entegre edebileceğinize bu yazıdan bakabilirsiniz.

Eğer aşağıdaki özelliği kullanmasaydım aşağıdaki kodu tüm sınıflarıma yazmam gerekecekti(hayır zorunda değilim dediğinizi duyar gibiyim ancak stacktrace’lerin loglama frameworkünde düzgün oluşabilmesi için önerilen best practice budur).

@Slf4j annotation’ı aracılığıyla yukarıdaki kod yerine sadece annotation ile loglamamı yapabiliyorum.

Adım adım ilerlemeye devam ediyoruz, şu anda boot projemiz Logback yerine Log4j2 ile loglama yapabiliyor, yapılandırması tamamlandı, slf4j arayüz olarak kullanılabiliyor ve bunu lombok aracılığı ile zahmetsizce yapıyoruz. Son bir problemimiz kaldı. Projenin resource’ları içerisinde bulunan yapılandırma dosyamız jar export ettiğimiz anda jar’ın içerisinde kalıyor ve jarımızın dışarıdan yapılandırılabilmesi güçleşiyor. Dilerseniz tüm yapılandırmayı runtime’da yapmanız, appenderlarınızı dinamik bir şekilde root loggera bağlamanız da mümkün. Ya da Spring Boot Admin’i aktifleştirerek loglama frameworkünden bağımsız olarak admin arayüzü üzerinden sadece birkaç tıklamayla paket seviyesindeki loglama levelları ile de oynayabilirsiniz. Ancak bu yazımızda sadece Log4j2'nin yapılandırma dosyasını standalone jar olarak export edilmiş Spring Boot executable’ından çıkarmayı ve dışarıdan yapılandırmasını sağlayacağız.

Öncelikle resources dizini altındaki bu dosyanın jar içine girmesini engellemek için aşağıdaki satırları ekliyoruz.

Sonrasında pom.xml dosyamızın olduğu yerde mvn clean install -DskipTests komutunu çalıştırıyorum. Bu komutun çalışması için geçerli bir maven kurulumunuzun olması gerekir, eğer yoksa da kullandığınız IDE’nin içerisinde entegre bir maven genellikle mevcuttur, IDE’niz aracılığı ile de kodlarınızın maven tarafından build edilmesini de sağlayabilirsiniz. Komutun çıktıları aşağıdaki gibi oldu.

[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.mcy:mcy-sb-slf4j-log4j2 >---------------------
[INFO] Building mcy-sb-slf4j-log4j2 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mcy-sb-slf4j-log4j2 ---
[INFO] Deleting C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mcy-sb-slf4j-log4j2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ mcy-sb-slf4j-log4j2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mcy-sb-slf4j-log4j2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ mcy-sb-slf4j-log4j2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ mcy-sb-slf4j-log4j2 ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:3.1.1:jar (default-jar) @ mcy-sb-slf4j-log4j2 ---
[INFO] Building jar: C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target\mcy-sb-slf4j-log4j2-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.2.RELEASE:repackage (repackage) @ mcy-sb-slf4j-log4j2 ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mcy-sb-slf4j-log4j2 ---
[INFO] Installing C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target\mcy-sb-slf4j-log4j2-0.0.1-SNAPSHOT.jar to C:\Users\PC\.m2\repository\com\mcy\mcy-sb-slf4j-log4j2\0.0.1-SNAPSHOT\mcy-sb-slf4j-log4j2-0.0.1-SNAPSHOT.jar
[INFO] Installing C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\pom.xml to C:\Users\PC\.m2\repository\com\mcy\mcy-sb-slf4j-log4j2\0.0.1-SNAPSHOT\mcy-sb-slf4j-log4j2-0.0.1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.720 s
[INFO] Finished at: 2019-01-20T12:26:14+03:00
[INFO] ------------------------------------------------------------------------
PS C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2>

/target altında oluşan jarımızın içeriğine bakalım. ‘…’ folder’ının altında resources altında bulundurduğumuz dosyaların olduğunu görüyoruz, log4j2-spring.xml dosyası da pom.xml’de belirttiğimiz gibi paketten exclude edilmiş.

Dosyamız pakette olmadığından dolayı jarımız ile aynı pathte veya jarın classpath’inde bulunduğu taktirde boot projemiz ayağa kalkarken bu dosyayı bulup sağlıklı bir şekilde yapılandırmasını tamamlayacaktır. Farklı path’te konumlandıracaksanız jar execute ederken classpath nasıl set edilir şeklinde bir arama ile ilgili yöntemlere ulaşabilirsiniz.

En son olarak mvn clean test komutu ile yazdığımız testin çalışıp çalışmadığnı kontrol edebiliriz. Çıktısı aşağıdaki gibi,

PS C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2> mvn clean test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.mcy:mcy-sb-slf4j-log4j2 >---------------------
[INFO] Building mcy-sb-slf4j-log4j2 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mcy-sb-slf4j-log4j2 ---
[INFO] Deleting C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mcy-sb-slf4j-log4j2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ mcy-sb-slf4j-log4j2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mcy-sb-slf4j-log4j2 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ mcy-sb-slf4j-log4j2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ mcy-sb-slf4j-log4j2 ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mcy.StartupApplicationTests
.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.2.RELEASE)
2019-01-20 12:28:14.765  INFO 12788 --- [           main] c.m.StartupApplicationTests              : Starting StartupApplicationTests on DESKTOP-GDILEJ9 with PID 12788 (started by PC in C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2)
2019-01-20 12:28:14.767 INFO 12788 --- [ main] c.m.StartupApplicationTests : No active profile set, falling back to default profiles: default
2019-01-20 12:28:15.048 INFO 12788 --- [ main] c.m.SpringBean : info
2019-01-20 12:28:15.048 ERROR 12788 --- [ main] c.m.SpringBean : error
2019-01-20 12:28:15.105 INFO 12788 --- [ main] c.m.StartupApplicationTests : Started StartupApplicationTests in 0.575 seconds (JVM running for 1.277)
2019-01-20 12:28:15.230 INFO 12788 --- [ main] c.m.SpringBean : info
2019-01-20 12:28:15.231 ERROR 12788 --- [ main] c.m.SpringBean : error
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.218 s - in com.mcy.StartupApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.166 s
[INFO] Finished at: 2019-01-20T12:28:15+03:00
[INFO] ------------------------------------------------------------------------
PS C:\dvlp\workspaces\basicWorkspace\mcy-sb-slf4j-log4j2>

Yukarıda örnekleri verilmiş koda https://github.com/mehmetcemyucel/springboot-slf4j-log4j2 adresinden ulaşabilirsiniz.

En yalın haliyle…

Mehmet Cem Yücel


Bu yazılar ilgilinizi çekebilir:
Spring Boot Devtools ile Docker Üzerindeki Kodu Debug Etme ve Değiştirme
Twelve Factor Nedir Türkçe ve Java Örnekleri
Blockchain teknolojisi ile ilgileniyor iseniz bunlar da hoşunuza gidebilir:
BlockchainTurk.net yazıları