Her Laravel uygulaması aynı dosyayı yazar: storage/logs/laravel.log. Ve çoğu ekipte bu dosya aynı şekilde okunur—sunucuya ssh, tail -f, ERROR için grep, doğru sunucuda olduğunu um. İki app server'a çıkana ya da gece 02:00'deki kötü deploy'a kadar bu idare eder.
Bu rehber, bir geliştirici ekibinin Laravel loglarını ELK stack'e taşımak için kurduğu küçük bir pipeline'a dayanıyor: Elasticsearch, Logstash ve Kibana çalıştıran bir Vagrant box, artı Laravel'in log formatını—naif parser'ları kıran çok satırlı stack trace'ler dahil—gerçekten anlayan bir Logstash config'i. Ben temizledim, eskiyen kısımları işaretledim, hâlâ doğru dersi veren kısımları korudum.
Kısa cevap: Laravel (Monolog üzerinden) [2016-04-16 17:26:30] local.ERROR: <mesaj> biçiminde satırlar yazar; stack trace'ler sonraki satırlara taşar. Logstash'te devam satırlarını multiline codec ile üst event'e yapıştırın (pattern => "^\[", negate => true, what => "previous"), başlığı \[%{TIMESTAMP_ISO8601:timestamp}\] %{DATA:env}\.%{LOGLEVEL:severity}: %{GREEDYDATA:log_message} grok pattern'i ile parse edin, date filter ile @timestamp'i düzeltin ve elasticsearch output'unu cluster'ınıza yönlendirin. Artık severity:ERROR bir grep değil, bir sorgu.
Laravel logları neden Elasticsearch'te durmalı
Laravel'in varsayılan loglaması dürüst ama düz. Tek dosya, event başına tek satır—bir exception yirmi satırlık stack trace'iyle gelene kadar. Format stabil, iyi haber bu:
[2016-04-16 17:25:45] local.INFO: Hello logstash
[2016-04-16 17:26:30] local.ERROR: Symfony\Component\Debug\Exception\FatalThrowableError: Parse error: ...
Stack trace:
#0 [internal function]: App\Providers\RouteServiceProvider->...
O başlıkta üç alan saklanıyor: timestamp, environment (local, production) ve severity (INFO, ERROR). Bunlar Elasticsearch'te gerçek field olduğunda Kibana'nın kurulma amacı olan sorular açılır: saat başına hata, hangi ortam gürültülü, deploy'dan sonra ne değişti. Düz dosyada kaldığında her soru birinin ezberlemesi gereken bir shell komutu.
Laravel log formatındaki iki problem
Problem 1: stack trace'ler çok satırlı
Satır bazlı bir shipper her satırı ayrı event sayar. Yukarıdaki örneği beslediğinizde işe yaramaz bir index alırsınız: bir temiz ERROR dokümanı ve arkasından #3 /home/... ile başlayan yirmi öksüz doküman—timestamp yok, severity yok, geri gruplamanın yolu yok.
Bu pipeline'daki çözüm input'taki multiline codec. Mantık log formatının kendisi gibi okunuyor: gerçek bir Laravel event'i [ ile başlar. Başlamayan her şey bir önceki event'e aittir.
codec => multiline {
pattern => "^\["
negate => true
what => "previous"
}
Bütün config'in en kritik bloğu bu. Bunu yanlış kurarsanız hiçbir grok pattern sizi kurtaramaz; çünkü event'lerin kendisi zaten kırık.
Problem 2: başlık grok istiyor
Orijinal config'teki grok pattern şuydu:
\[%{TIMESTAMP_ISO8601:timestamp}\] %{DATA:env}\.%{DATA:severity}: %{DATA:message}
Çalışıyor ve doğru fikri gösteriyor—env ve severity noktadan ayrılıyor, timestamp bütün yakalanıyor. Ama production öncesi iki detay düzeltilmeli. Birincisi, pattern sonundaki %{DATA} tembeldir ve hiçbir şey eşleşmeyebilir; kuyruktaki mesaj için %{GREEDYDATA} kullanın. İkincisi, overwrite olmadan message field'ına grok yapmak size ham satır + parse edilmiş değer içeren bir array verir—Kibana'da kafa karıştırır. Ya overwrite edin ya yeni bir field adı kullanın. İki davranış da Elastic'in grok filter dokümantasyonunda var.
Sanitize edilmiş, production biçimli config
Aynı pipeline'ın modernize edilmiş, stdout yerine Elasticsearch'e yazan hali:
input {
file {
path => "/var/www/app/storage/logs/laravel.log"
start_position => "beginning"
codec => multiline {
pattern => "^\["
negate => true
what => "previous"
}
}
}
filter {
grok {
match => {
"message" => "\[%{TIMESTAMP_ISO8601:timestamp}\] %{DATA:env}\.%{LOGLEVEL:severity}: %{GREEDYDATA:log_message}"
}
}
date {
match => ["timestamp", "yyyy-MM-dd HH:mm:ss"]
target => "@timestamp"
}
}
output {
elasticsearch {
hosts => ["https://<elasticsearch-adresiniz>:9200"]
index => "laravel-logs-%{+YYYY.MM.dd}"
}
}
date filter göründüğünden önemli: onsuz @timestamp, hatanın olduğu an değil Logstash'in satırı okuduğu andır. Eski bir log dosyasını replay edin, her şey ingest zamanına yığılır. Filter varken replay edilen dosya doğru zaman çizgisine oturur.
Dışarıdan doğrulama, orijinal README'deki yaklaşımla:
curl "http://localhost:9200/laravel-logs-*/_search?pretty&q=severity:ERROR"
severity içinde ERROR olan bir field olarak dönüyorsa pipeline işini yapıyor.
Vagrant ile lokal ELK lab
Kaynak materyalin ikinci yarısı bir lab: Java, Elasticsearch, Kibana ve Logstash'i provision eden, 5601 ve 9200 portlarını host'a forward eden, çalışma dizinini VM'e mount eden bir Vagrant box (Ubuntu, 2 CPU, 2 GB RAM). Akış gayet derli toplu:
vagrant up
vagrant ssh
cd /vagrant/logstash-laravel-logs
/opt/logstash/bin/logstash -f logstash.conf < logs/laravel.log
Örnek log dosyasını stdin'den Logstash'e boru ile vermek, bildiğim en hızlı grok geri bildirim döngüsü: pattern'i düzelt, tekrar çalıştır, parse çıktısını oku. Agent yok, restart yok, iterasyon başına saniyeler.
Lab'ın sürümleri müzelik, bunu bilin—Elasticsearch 2.x, Logstash 2.0, Kibana 4.3.1 ve sonraki sürümlerde kaldırılan logstash agent alt komutu. Pattern test akışı modern stack'e aynen taşınır; bugün olsa güncel Elasticsearch ve Kibana'yı Docker Compose'da çalıştırır, aynı stdin numarasını korurdum. Production yolunda multiline birleştirmeyi kaynakta Filebeat'e, grok'u Logstash'e (veya ingest pipeline'a) vermek de masada olmalı. Loglar akmaya başladıktan sonra da yazdığınız cluster'ı izleyin—ekiplerin atladığı kısım bu ve searchali.com/tr/monitoring tam bunun için var.
Öğrendiklerimiz
- Önce multiline, sonra grok. Stack trace'leri
pattern => "^\[",negate => true,what => "previous"ile tek event'e topla; parse ondan sonra. env.severity'yi grok'ta ayır.%{DATA:env}\.%{LOGLEVEL:severity}Laravel başlığını iki filtrelenebilir field'a çevirir.- Severity ve kuyruk mesaj için
%{DATA}yerine%{LOGLEVEL}ve%{GREEDYDATA}. Daha sıkı eşleşme, daha az sürpriz. datefilter kullan ki@timestampingest anını değil event'i göstersin—eski dosya replay'inde kritik.- Grok'u stdin'de test et. Production shipper'a dokunmadan önce örnek dosyayla dene; en ucuz iterasyon döngüsü bu.
overwriteolmadanmessage'a grok yapma; yoksa ham + parse edilmiş değerlerden oluşan bir array indexlersin.
Sık Sorulan Sorular
Laravel logları için grok pattern nedir?
\[%{TIMESTAMP_ISO8601:timestamp}\] %{DATA:env}\.%{LOGLEVEL:severity}: %{GREEDYDATA:log_message} — Laravel'in yazdığı standart Monolog satır formatını eşler; timestamp, environment, severity ve mesaj gövdesini ayrı field'lar olarak yakalar.
Laravel stack trace'lerini Logstash'te nasıl yönetirim?
multiline codec (veya Filebeat'in multiline ayarları) ile: pattern => "^\[", negate => true, what => "previous". [ ile başlamayan her satır önceki event'e eklenir; exception ve tüm stack trace'i tek Elasticsearch dokümanı olur.
Bunun için hâlâ Logstash mı, yoksa Filebeat mi?
Lab veya tek sunucu için dosyayı doğrudan Logstash'in okuması yeterli. Gerçek filolarda Filebeat app server'larda çalışır (multiline birleştirme orada), grok adımı Logstash'te veya Elasticsearch ingest pipeline'ında yapılır. Pattern iki yerde de aynıdır.
Parse etmeyi bırakıp Laravel'den JSON loglayamaz mıyım?
Loglayabilirsiniz—Monolog JSON üretebilir ve üreticiyi kontrol ediyorsanız structured logging regex parse'ı her zaman yener. Grok, elinizde zaten olan loglarda değerlidir: yıllarca birikmiş düz metin laravel.log dosyaları, vendor kutuları veya bugün yeniden deploy edemeyeceğiniz uygulamalar.
Pipeline'ın cluster tarafının da uygulama tarafı kadar dikkatle izlenmesini ister misiniz? → searchali.com
