Spring Boot&ELK 2 — Logstash
Merhabalar.
Bir önceki yazımız olan Spring Boot&ELK 1 — ElasticSearch yazımızda Spring Boot ve ElasticSearch’ün birlikte nasıl kullanılabileceğine değinmiştik. Bu yazımızda ise konumuzu bir adım öteye götürüp Logstash ile nasıl kullanabileceğimize, iletişime geçirebileceğimize değinelim.
Önceki yazıda da belirttiğim gibi örnek kodlara https://github.com/ilkgunel/SpringBootELK adresinden erişebilirsiniz.
Öncelikle Logstah nedir, onu bir inceleyerek başlayalım.
Logstash Nedir?
ELK Stack’in bir diğer üyesi olan Logstash isminden de anlaşılabileceği gibi log toplama, işleme ve transfer etme görevileri olan bir köprüdür diyebiliriz. Logstash bir dosyadan veri alıp başka bir dosyaya yazabilir, bir dosyadan logları alıp Elasticsearch’e bildirebilir ya da bir API vasıtası ile kendisine gelen logları Elasticsearch’e bildirebilir gibi gibi.
Şimdi örneklerimizi inceleyerek Logstash’in Spring Boot ve Elasticsearch ile birlikte kullanımına değinelim.
Spring Boot Tarafı
Az önce bahsettiğimiz gibi Logstash’in görevi log toplamak. Bunun için Spring Boot uygulamamızın log atması gerekiyor.
Öncelikle application.properties dosyamıza şu şekilde bir satır ekliyoruz:
application.properties
logging.file.name=/Users/ilkaygunel/Desktop/SpringBootELK.log
Akabinde DriverService sınıfımız içerisinde getDriverByName(…) ve getDriverById(…) metotlarına birer log satırı ekliyorum. logger objemizi LoggerFacotory üzerinden oluşturtuyoruz.
DriverService -> getDriverByName
logger.info("Searching Driver With Name:{}", driverName);
DriverService -> getDriverById
logger.info("Searching Driver With Id:{}", id);
Spring Boot uygulamamızda yapacaklarımız bu kadar. Uygulamamız bizim için loglarımızı SpringBootELK.log dosyamıza yazacak.
NOT: Bu kısımdaki Logger objesi edinme ve loglama işlemleri Spring Boot içerisinde default gelen Logback ile yapıldı, yazının ilerleyen kısımlarında Logback exclude edildiği ve Log4J2’ya geçiş yaptığımızdan bu kısımlar kaynak kod içerisinde olmayabilir ya da comment’li olabilir fakat kaynak kodlardaki hali de dosyaya log yazdırıyor, sadece kullanılan kütüphane değişti.
Logstash Tarafı
Şimdi Logstash tarafında ne yapacağımızı örneklerimizle inceleyelim.
Örnek 1 — File Input –> Logstash –> File Output
Öncelikle bizim bir Logstah.conf dosyasına ihtiyacımız var. Bu conf dosyası Logstash ayaklanırken Logstash’in nasıl log toplayacağı, nereye göndereceği gibi ayarlamaları içerir. Bu yazı için örnek kullanacağımız conf dosyalarından birisi şöyle:
logstash.conf
input {
file {
path => '/var/log/logstash/SpringBootELK.log'
sincedb_path => "/dev/null"
#ignore_older => 0
discover_interval => 0
}
}
output {
file {
path => '/var/log/logstash/output.txt'
#codec => rubydebug
codec => line { format => "custom format: %{message}"}
}
}
Şimdi bu conf dosyasını biraz inceleyelim:
- input kısmı Logstash’in nereden log dinleyeceğini bildiriyor.
- input içerisinde file dememiz logları bir dosyadan dinlemesini söylediğimiz anlamına geliyor.
- file içerisindeki path de logları hangi dosyadan dinleyeceğini söylediğimiz anlamına geliyor.
- output kısmı logları nereye göndereceğini bildiriyor.
- output içerisinde file dememiz gelen/işlenmiş logları bir dosyaya gönder demek oluyor.
- file içerisindeki path kısmı gelen/işlenmiş logların hangi dosyaya gönderileceğini bildiriyor. Biz output.txt dosyanıza gelen logları göndereceğiz.
- codec kısmı da aslında bir nevi loglar nasıl bir fomatta gönderilecek onu belirtmemizi sağlıyor. Biz sadece gelen kısımdan message bilgisini alıp kullanacağız.
Ben Logstash’i Docker üzerinden çalıştıracağım. Bu çalıştırma için de şöyle bir Docker komutunu kullanacağım:
docker run --rm -it -v /Users/ilkaygunel/Desktop/SpringBootELK.log:/var/log/logstash/SpringBootELK.log -v /Users/ilkaygunel/Desktop/output.txt:/var/log/logstash/output.txt -v /Users/ilkaygunel/Desktop/logstash.conf:/usr/share/logstash/pipeline/logstash.conf --name myLogstash --network=elastic-network -e "ssl_certificate_verification=false" logstash:8.8.1
Bu komut ile Spring Boot uygulamasının loglarını yazdıracağı SpringBootELK.log dosyasını docker instance’ına mount etmiş oluyoruz, böylece log dosyasında oluşacak değişecekler otomatik docker instace’ının içindeki dosyaya da yansıyacak. Aynı şekilde output.txt’yi de mount ediyoruz, böylece Logstash’in docker instance’ının içindeki output.txt’de yapacağı değişiklikler otomatik lokaldeki dosyaya yansıyacak.
Şimdi ben http://localhost:8181/driver/Schumacher adresine bir HTTP GET isteği gönderiyorum. Spring Boot uygulaması şu şekilde bir log atıyor:
2023-07-03 13:47:53.148 INFO 24270 --- [nio-8181-exec-1] com.ilkaygunel.service.DriverService : Searching Driver With Name:Schumacher
SpringBootELK.log dosyasını kontrol ettiğimde uygulamada atılan log’un buraya da yansıdığını görüyorum.
Lokalimdeki output.txt dosyasını kontrol ettiğimde de aynı şekilde atılan logun buraya geldiğini görüyorum.
Örnek 2 — File Input –> Logstash –> Elasticsearch
Şimdi ikinci örneğimizde de dosyadan gelen logu Logstash’e ileteceğiz, Logstash’in de Elastichsearch’e iletmesini sağlayacağız.
Öncelikle logstash.conf dosyamızı şu şekilde güncelleyelim:
logstash.conf for Elasticsearch
input {
file {
path => '/var/log/logstash/SpringBootELK.log'
sincedb_path => "/dev/null"
#ignore_older => 0
discover_interval => 0
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
#data_stream => "true"
index => "test"
codec => line { format => "custom format: %{message}"}
}
}
logstash.conf’da şu şekilde değişikliğe gittik:
- output kısmında file yerine elasticsearch’e geçtik.
- elasticsearch tanımı içerisinde hosts bilgisi ile logların hangi elasticsearch servislerine gönderileceğini belirliyoruz.
- index bilgisi ile logstash’den gönderilen logların Elasticsearch’de hangi index’e kaydolacağını belirliyoruz.
- codec de önceki örnekte bahsettiğimiz gibi gelen logların nasıl formatlanacağı bilgisini içerir. Gelen log datası içerisinden sadece message bilgisini elasticsearch’e göndereceğiz.
Şimdi logstash’i koşturan Docker instance’ını durduruyoruz ve önceki örnekte bahsettiğimiz Docker komutu ile logstash’i yeniden başlatıyoruz. Akabinde http://localhost:8181/driver/Barichello adresine HTTP GET isteği gönderiyorum.
Şimdi de Elasticvue üzerinden logstash.conf’da belirttiğimiz test index’ini kontrol ettiğimde Barichello için uygulamamızın attığı log Elasticsearch’e de gelmiş durumda.
{
"_index": "test",
"_id": "a4yXG4kBY8pbrw3BOeUS",
"_version": 1,
"_seq_no": 3,
"_primary_term": 1,
"found": true,
"_source": {
"@version": "1",
"@timestamp": "2023-07-03T11:50:42.560087051Z",
"log": {
"file": {
"path": "/var/log/logstash/SpringBootELK.log"
}
},
"message": "2023-07-03 14:50:36.285 INFO 24270 --- [http-nio-8181-exec-4] com.ilkaygunel.service.DriverService : Searching Driver With Name:Barichello",
"host": {
"name": "da23e0d1fd7c"
},
"event": {
"original": "2023-07-03 14:50:36.285 INFO 24270 --- [http-nio-8181-exec-4] com.ilkaygunel.service.DriverService : Searching Driver With Name:Barichello"
}
}
}
Örnek 3 -> API Input –> Logstash –> Elasticsearch
Son örneğimizde de Logstash’e doğrudan API üzerinden log gönderimi yapıp ElasticSearch’e ilettireceğiz. Bu işlem için Log4J2 kullanacağız.
Öncelikle pom.xml dosyamıza şu bağımlılığı ekliyoruz:
pom.xml Log4J2 Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Ardından Spring Boot projemizde resources klasörü altına log4j2.xml dosyamızı şu içerikle ekliyoruz:
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="logPath">/Users/turgi/Desktop</Property>
<Property name="rollingFileName">SpringBootELK</Property>
<Property name="defaultPattern">[%highlight{%-5level}] %d{DEFAULT} %c{1}.%M() - %msg%n%throwable{short.lineNumber}</Property>
<Property name="logStashPattern">[%d{ISO8601}][%-5p][%-25c]%notEmpty{[%X{pipeline.id}]}%notEmpty{[%X{plugin.id}]} %m%n]</Property>
</Properties>
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="${defaultPattern}" />
</Console>
<RollingFile name="rollingFile" fileName="${logPath}/${rollingFileName}.log" filePattern="${logPath}/${rollingFileName}_%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${defaultPattern}" />
<Policies>
<OnStartupTriggeringPolicy />
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
</Policies>
</RollingFile>
<Socket name="socket" host="${sys:logstash.host.name:-localhost}" port="${sys:logstash.port.number:-5200}" reconnectionDelayMillis="5000">
<PatternLayout pattern="${logStashPattern}" />
</Socket>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="console" />
<AppenderRef ref="rollingFile"/>
<AppenderRef ref="socket"/>
</Root>
</Loggers>
</Configuration>
log4j2.xml dosyamızda bizim için en önemli nokta Socket kısmı. Bu kısımda host bilgisine loglarımızı toplayacak Logstash API adresini yazıyoruz, port bilgisine de ilgili Logstash’in bizim loglarımızı dinlemesi için açtığımız portu yazıyoruz. Bu portun nasıl açıldığına az sonra değineceğiz. Son kısımdaki Loggers içerisindeki Root’a da Socket appender’ı ekliyoruz ve bu kısmı bitiriyoruz. Log4J2 bizim için üretilen logları localhost:5200 adresine gönderecek.
Logstash.conf dosyamızda ise şu şekilde değişikliğe gidiyoruz:
logstash.conf
input {
tcp {
port => 5200
codec => plain {
format => "custom format: %{message}"
#charset => "ISO-8859-1"
}
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "test"
codec => line { format => "custom format: %{message}"}
}
}
- input kısmında artık bir API vasıtası ile logları dinleyeceğimizden input tipini tcp olarak değiştirdik.
- tcp içerisinde port bilgisi ile logları hangi port üzerinden dinleyeceğimizi bildiriyoruz.
- Codec ile de yine log formatı ile ilgili ayarlamamızı yapıyoruz.
- output formatı için bir değişikliğimiz yok, bir önceki ile aynı.
Son olarak da Docker komutunda yaptığımız ufak değişiklikleri inceleyelim.
Logstash Run Command
docker run --rm -it -v /Users/turgi/Desktop/logstash.conf:/usr/share/logstash/pipeline/logstash.conf --name myLogstash --network=elastic-network -e "ssl_certificate_verification=false" -p 5200:5200 logstash:8.8.1
- Öncekinden farklı olarak buradaki file mounting’leri kaldırdık çünkü artık herhangi bir dosyadaki değişimini takip etmeyeceğiz.
- Port bilgisi 5200 portunu dışarıdan erişimlere açtık, bu port Spring Boot uygulamamızın Log4J2’daki Socket vasıtası logları ileteceği port.
Şimdi Spring Boot uygulamamızı da Logstash’imizi de yeniden başlatıyoruz ve ardından http://localhost:8181/driver/Sergeant adresine HTTP GET isteği gönderiyoruz.
ElasticVue üzerinden test index’ini kontrol ettiğimde arattığım isimle ilgili logun Elasticsearch’e gittiğini görüyorum.
Bu yazıda anlatacaklarım da bu kadar arkadaşlar.
Bir sonraki yazıda ELK Stack’in son üyesi olan Kibana üzerinde duracağız.
Görüşene kadar hoşçakalın.