awk ile Syslog Verilerinden Servis Bazlı Hata Frekansı Matrisi Oluşturma
Üretim ortamında gece 2’de çalan telefon, genellikle “bir şeyler bozuldu ama ne olduğunu bilmiyoruz” cümlesiyle başlar. O anlarda syslog dosyalarına bakıp “hangi servis ne kadar hata üretiyor” sorusunu hızlıca cevaplayabilmek, saatlerce log okumakla saatlerce awk scripti çalıştırmak arasındaki fark demektir. Bu yazıda, /var/log/syslog veya /var/log/messages gibi dosyalardan servis bazlı hata frekansı matrisi çıkarmayı, bunu yaparken awk’ın gerçek gücünü kullanmayı anlatacağım.
Syslog Formatını Anlamak
Önce neyle çalıştığımızı netleştirelim. Tipik bir syslog satırı şöyle görünür:
Jan 15 03:22:41 hostname sshd[1234]: error: PAM authentication failed for user root
Jan 15 03:22:42 hostname kernel: EXT4-fs error (device sda1): ...
Jan 15 03:22:45 hostname mysqld[5678]: [ERROR] Table 'db.users' doesn't exist
Yapı şu şekilde: Ay Gün Saat Hostname Servis[PID]: Mesaj. awk’ın alan tabanlı işleme mantığı bu format için biçilmiş kaftan. Varsayılan alan ayırıcı boşluk olduğu için $1 ay, $2 gün, $3 saat, $4 hostname, $5 servis adı ve PID, geri kalanı mesaj oluyor.
Ama hemen bir uyarı: Gerçek dünya syslog dosyaları hiçbir zaman bu kadar temiz olmaz. Multi-line mesajlar, farklı daemon formatları, rsyslog ile journald karışıklıkları… Bunların hepsini göz önünde bulundurarak yazmak gerekiyor.
Temel Servis Çıkarma
İlk adım, servis adını düzgün çekmek. $5 alanı genellikle sshd[1234]: formatında gelir, yani hem servis adını hem PID’i içerir. Bunu temizlemek için:
awk '{
# $5 alanından servis adini cek, PID ve iki noktayi temizle
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
print service
}' /var/log/syslog | sort | uniq -c | sort -rn | head -20
Bu basit script bile bana şunu söyler: hangi servis en çok log üretiyor. Ama biz sadece hataları istiyoruz. Hata kelimelerini filtrelemek için koşul ekleyelim:
awk 'tolower($0) ~ /error|err|fail|critical|crit|emerg|alert/ {
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
print service
}' /var/log/syslog | sort | uniq -c | sort -rn
tolower() kullanmak önemli, çünkü bazı servisler “ERROR” yazar, bazıları “error”, bazıları “Error”. Hepsini yakalamak istiyoruz.
Asıl Matris: Servis x Saat Frekansı
Şimdi gerçek işe gelelim. Sadece “hangi servis kaç hata üretti” değil, “hangi servis hangi saatte ne kadar hata üretti” sorusunu cevaplamak istiyoruz. Bu bir frekans matrisidir ve awk’ın associative array’leri bunun için mükemmeldir.
awk 'tolower($0) ~ /error|err|fail|critical/ {
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
# Saati $3 alanından cek (HH:MM:SS formatinda)
split($3, time_parts, ":")
hour = time_parts[1]
# Matris: matrix[servis][saat] = count
matrix[service][hour]++
# Toplam hata sayisini da tut
total[service]++
# Hangi saatler var?
hours[hour] = 1
}
END {
# Baslik satirini yazdir
printf "%-20s", "SERVIS"
for (h = 0; h < 24; h++) {
printf " %02d", h
}
printf " | TOPLAMn"
# Her servis icin satir yazdir
for (svc in total) {
printf "%-20s", svc
for (h = 0; h < 24; h++) {
printf " %2d", (matrix[svc][sprintf("%02d", h)] + 0)
}
printf " | %dn", total[svc]
}
}' /var/log/syslog | sort -t'|' -k2 -rn
Bu script çalıştığında terminalinizde her servis için 24 saatlik hata dağılımını göreceksiniz. Hangi saatte spike olduğu, hangi servisin gece saatlerinde aktif olduğu, hangi servisin sürekli hata ürettiği hemen anlaşılır hale gelir.
Hata Seviyesi Bazlı Sınıflandırma
Sadece “hata var/yok” demek yetmez. Syslog’da severity seviyeleri var: emerg, alert, crit, err, warning, notice, info, debug. Bunları ayrı ayrı saymak çok daha anlamlı bir tablo verir:
awk '{
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
line = tolower($0)
# Severity tespiti - oncelik sirasina gore kontrol et
if (line ~ /emerg|panic/) {
severity = "EMERG"
} else if (line ~ /bcrit(ical)?b/) {
severity = "CRIT"
} else if (line ~ /balertb/) {
severity = "ALERT"
} else if (line ~ /berr(or)?b/) {
severity = "ERROR"
} else if (line ~ /bwarn(ing)?b/) {
severity = "WARN"
} else {
next # Ilgilenmedigimiz satirlari atla
}
count[service][severity]++
services[service] = 1
}
END {
printf "%-25s %8s %8s %8s %8s %8sn",
"SERVIS", "EMERG", "ALERT", "CRIT", "ERROR", "WARN"
printf "%sn", "----------------------------------------------------------------------"
for (svc in services) {
printf "%-25s %8d %8d %8d %8d %8dn",
svc,
count[svc]["EMERG"] + 0,
count[svc]["ALERT"] + 0,
count[svc]["CRIT"] + 0,
count[svc]["ERROR"] + 0,
count[svc]["WARN"] + 0
}
}' /var/log/syslog
Bu çıktıyı bir ekrana döktüğünüzde hangi servislerin kritik sorunlar yaşadığı ilk bakışta görünür. EMERG veya CRIT kolonu dolu olan bir servis, anlık müdahale gerektiriyor demektir.
Tarih Aralığı Filtreleme
Gerçek senaryolarda genellikle “dün gece 23:00 ile bugün sabah 06:00 arası neler oldu” gibi sorular gelir. Bunu awk içinde ele almak biraz daha dikkat ister çünkü syslog timestamp’i işlemek zahmetlidir:
awk -v start_hour="23" -v end_hour="06" '{
# $3 alani HH:MM:SS formatinda
split($3, t, ":")
h = t[1] + 0
# Gece gecisi araligini kontrol et
if (start_hour > end_hour) {
# Ornegin 23:00 - 06:00 arasi
if (h < start_hour && h >= end_hour) next
} else {
if (h < start_hour || h >= end_hour) next
}
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
if (tolower($0) ~ /error|fail|crit/) {
freq[service]++
}
}
END {
for (svc in freq) {
print freq[svc], svc
}
}' /var/log/syslog | sort -rn
Aynı mantığı birden fazla log dosyasına da uygulayabilirsiniz. awk otomatik olarak birden fazla dosyayı sırayla işler:
awk 'tolower($0) ~ /error|crit|fail/ {
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
freq[service][FILENAME]++
}
END {
for (svc in freq) {
for (f in freq[svc]) {
print svc, f, freq[svc][f]
}
}
}' /var/log/syslog /var/log/syslog.1 /var/log/syslog.2.gz
Dikkat: .gz dosyalarını direkt awk ile açamazsınız. Bunun için:
zcat /var/log/syslog.*.gz | awk '{...}'
Gerçek Dünya Senaryosu: MySQL ve Apache Hata Korelasyonu
Bir e-ticaret sisteminde şöyle bir şikayet geldi: “Öğle saatlerinde site yavaşlıyor.” Syslog’a baktım, hem Apache hem MySQL hataları görüyorum ama hangisi tetikleyici? Korelasyon matrisi bu soruyu çözdü:
awk 'tolower($0) ~ /error|fail/ {
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
# Sadece ilgilendigimiz servislere bak
if (service !~ /apache2|httpd|mysqld|mysql/) next
split($3, t, ":")
hour = t[1]
minute = int(t[2] / 15) * 15 # 15 dakikalik dilimler
slot = hour ":" sprintf("%02d", minute)
matrix[service][slot]++
slots[slot] = 1
}
END {
# Slot listesini sirala
n = asorti(slots, sorted_slots)
printf "%-10s", "ZAMAN"
for (svc in matrix) printf " %-10s", svc
printf "n"
for (i = 1; i <= n; i++) {
slot = sorted_slots[i]
printf "%-10s", slot
for (svc in matrix) {
printf " %-10d", matrix[svc][slot] + 0
}
printf "n"
}
}' /var/log/syslog
Bu script 15 dakikalık dilimlerde Apache ve MySQL hatalarını yan yana koydu. Sonuç açıktı: MySQL hataları Apache hatalarından tam 2-3 dakika önce başlıyordu. Yani sorun MySQL’deydi, Apache sadece buna tepki veriyordu. Connection pool’u artırınca sorun çözüldü.
Script Haline Getirme ve Parametreleştirme
Bu komutları her seferinde yeniden yazmak yerine kullanışlı bir script haline getirelim:
#!/bin/bash
# servis_hata_matrisi.sh
# Kullanim: ./servis_hata_matrisi.sh [log_dosyasi] [min_hata_sayisi]
LOG_FILE="${1:-/var/log/syslog}"
MIN_COUNT="${2:-5}"
if [ ! -f "$LOG_FILE" ] && [ "$LOG_FILE" != "-" ]; then
echo "Hata: $LOG_FILE bulunamadi" >&2
exit 1
fi
echo "=== Servis Bazli Hata Frekans Matrisi ==="
echo "Log: $LOG_FILE | Minimum hata esigi: $MIN_COUNT"
echo ""
awk -v min_count="$MIN_COUNT" '
BEGIN {
# Severity agirliklari - puanlama icin
weight["EMERG"] = 100
weight["ALERT"] = 50
weight["CRIT"] = 20
weight["ERROR"] = 5
weight["WARN"] = 1
}
{
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
if (service == "") next
line = tolower($0)
if (line ~ /emerg|panic/) sev = "EMERG"
else if (line ~ /balertb/) sev = "ALERT"
else if (line ~ /bcrit(ical)?b/) sev = "CRIT"
else if (line ~ /berr(or)?b/) sev = "ERROR"
else if (line ~ /bwarn(ing)?b/) sev = "WARN"
else next
count[service][sev]++
total[service]++
score[service] += weight[sev]
services[service] = 1
}
END {
printf "%-25s %6s %6s %6s %6s %6s %8s %8sn",
"SERVIS", "EMERG", "ALERT", "CRIT", "ERROR", "WARN", "TOPLAM", "SKOR"
# Skora gore sirali cikti icin once diziye al
n = 0
for (svc in services) {
if (total[svc] >= min_count) {
svc_list[++n] = svc
}
}
# Bubble sort - kucuk veri seti icin yeterli
for (i = 1; i <= n; i++) {
for (j = i+1; j <= n; j++) {
if (score[svc_list[i]] < score[svc_list[j]]) {
tmp = svc_list[i]
svc_list[i] = svc_list[j]
svc_list[j] = tmp
}
}
}
for (i = 1; i <= n; i++) {
svc = svc_list[i]
printf "%-25s %6d %6d %6d %6d %6d %8d %8dn",
svc,
count[svc]["EMERG"] + 0,
count[svc]["ALERT"] + 0,
count[svc]["CRIT"] + 0,
count[svc]["ERROR"] + 0,
count[svc]["WARN"] + 0,
total[svc],
score[svc]
}
}' "$LOG_FILE"
Buradaki “skor” konsepti özellikle işe yarar. Her severity seviyesi farklı ağırlıkta puan taşır; böylece 1 EMERG alan servis, 100 WARNING üreten servisten önce gelir. Çünkü operasyonel öncelik açısından bu doğru yaklaşımdır.
journald ile Çalışmak
Modern sistemlerde /var/log/syslog yerine journalctl kullanıyorsunuzdur. journald çıktısını awk’a pipe etmek çok kolay:
journalctl --since "24 hours ago" --no-pager -o short-iso |
awk 'tolower($0) ~ /error|fail|crit/ {
# journald formati: 2024-01-15T03:22:41+0300 hostname sshd[1234]: mesaj
service = $3
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
# ISO timestamp den saat cekme
split($1, dt, "T")
split(dt[2], time_parts, ":")
hour = substr(time_parts[1], 1, 2)
freq[service][hour]++
total[service]++
}
END {
for (svc in total) {
if (total[svc] < 3) continue
printf "%-20s toplam:%d dagilim:", svc, total[svc]
for (h = 0; h < 24; h++) {
cnt = freq[svc][sprintf("%02d", h)] + 0
if (cnt > 0) printf " %02d:%d", h, cnt
}
print ""
}
}' | sort -t: -k2 -rn
Priority filtresi eklemek için journalctl’in kendi parametrelerini kullanmak daha doğru:
# Sadece error ve ustunu getir (priority 0-3)
journalctl -p err --since "yesterday" --no-pager |
awk '{
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
count[service]++
}
END {
for (s in count) print count[s], s
}' | sort -rn | head -15
Çıktıyı Daha Okunabilir Hale Getirme
Terminal genişliği sınırlı olduğunda 24 saatlik matris sığmayabilir. Bunun yerine yoğunluk karakterleri kullanmak hoş bir çözüm:
awk 'tolower($0) ~ /error|fail|crit/ {
service = $5
gsub(/[.*]/, "", service)
gsub(/:$/, "", service)
split($3, t, ":")
hour = t[1] + 0
matrix[service][hour]++
if (matrix[service][hour] > max_val[service])
max_val[service] = matrix[service][hour]
services[service] = 1
}
END {
# Yogunluk karakterleri
density[0] = "."
density[1] = ":"
density[2] = "+"
density[3] = "#"
printf "%-20s 00 03 06 09 12 15 18 21n", "SERVIS"
for (svc in services) {
printf "%-20s ", svc
for (h = 0; h < 24; h++) {
cnt = matrix[svc][h] + 0
mx = max_val[svc]
if (cnt == 0) {
printf " "
} else if (mx > 0) {
level = int((cnt / mx) * 3)
printf "%s", density[level]
}
}
printf " max:%dn", max_val[svc]
}
}' /var/log/syslog
Bu yaklaşım 24 karakterlik bir “histogram” çizer. Nokta az hata, diyez yoğun hata demek. Hangi saatte patlama olduğu bir bakışta görülür.
Sonuç
awk’ı syslog analizi için kullanmanın gerçek değeri, kurulum gerektirmemesinde yatar. Nagios, Grafana, ELK Stack güzel araçlar ama gece 2’de bir prodüksiyon sunucusunda bunlara erişiminiz olmayabilir ya da henüz kurulmamış olabilir. Ama awk her Linux sistemde hazır bekliyor.
Bu yazıda anlattığım yaklaşımların birkaç pratik özeti:
- Associative array’ler awk’ın en güçlü özelliği, matris yapıları için birebir
- Servis adı çıkarma için
gsub(/[.*]/, "", service)deseni evrensel çalışır - Severity ağırlıklandırma operasyonel önceliklendirme için kritik
- 15 dakikalık dilimler saatlik dilimlerden çok daha anlamlı korelasyon verir
- journalctl pipe modern sistemlerde syslog okumaktan daha güvenilir
Bir de şunu söyleyeyim: Bu scriptleri bir kere yazıp /usr/local/bin/ altına koyun, çalıştırılabilir yapın. Bir sonraki alarm geldiğinde tek komutla 5 dakikalık analiz yapmak, 45 dakika log okumaktan hem daha hızlı hem de daha doğru sonuç verir. Deneyim bu işin temelini oluşturuyor, ama doğru araçlara sahip olmak o deneyimi eyleme dönüştürür.
