awk ile Nginx Access Log Verilerinden Saatlik İstek Yoğunluğu ve Hata Oranı Raporu Oluşturma

Prodüksiyonda bir Nginx sunucusu yönetiyorsanız, access log dosyası sizin için altın madeni gibidir. Ama ham hâliyle o log dosyasına bakıp bir şey anlamak neredeyse imkânsız. Günde milyonlarca satır olabiliyor, tail -f ile izlemek de belirli bir noktadan sonra işe yaramıyor. İşte tam bu noktada awk devreye giriyor ve log dosyasından saatlik istek yoğunluğu, hata oranları, en çok istek alan endpoint’ler gibi kritik bilgileri dakikalar içinde çıkarmanıza olanak tanıyor.

Bu yazıda gerçek bir senaryo üzerinden gideceğim: Bir e-ticaret sitesinin gece 02:00-04:00 arası neden yavaşladığını, hangi saatlerde 5xx hatalarının patladığını ve hangi endpoint’lerin sistemi çökerteceğini awk ile nasıl analiz ettiğimi adım adım göstereceğim.

Nginx Access Log Formatını Tanımak

Önce neyle çalıştığımızı netleştirelim. Varsayılan Nginx combined log formatı şöyle görünür:

192.168.1.100 - - [15/Jan/2025:14:23:45 +0300] "GET /api/products HTTP/1.1" 200 1234 "https://example.com" "Mozilla/5.0..."

Bu satırda awk ile erişebileceğimiz alanlar şunlar:

  • $1: IP adresi (192.168.1.100)
  • $4: Tarih ve saat bilgisi ([15/Jan/2025:14:23:45)
  • $7: İstenen URL (/api/products)
  • $9: HTTP durum kodu (200)
  • $10: Gönderilen byte miktarı (1234)

$4 alanında köşeli parantez var, bunu temizlemek için substr($4, 2) kullanacağız. Saati çıkarmak içinse substr ile karakteri kesip alacağız.

Saatlik İstek Yoğunluğu Raporu

İlk hedefimiz saate göre kaç istek geldiğini bulmak. Bunu bilen bir sysadmin, yük dengeleme kararlarını, cron job zamanlamalarını ve bakım pencerelerini çok daha sağlıklı belirleyebilir.

awk '{
    # Saat bilgisini çıkar: [15/Jan/2025:14:23:45 -> 14
    split($4, tarih, ":")
    saat = substr(tarih[2], 1, 2)
    istek_sayisi[saat]++
}
END {
    print "SaattIstek Sayisi"
    print "----t------------"
    for (s in istek_sayisi) {
        printf "%s:00t%dn", s, istek_sayisi[s]
    }
}' /var/log/nginx/access.log | sort -t: -k1 -n

Bu komut çalıştığında her saat için kaç istek geldiğini göreceksiniz. Ama çıktı sıralanmamış gelecek, sort ile düzeltiyoruz. Şimdi bunu biraz daha işlevsel hâle getirelim.

Saatlik Hata Oranı Hesaplama

Sadece istek sayısı yetmez. O isteklerin kaçının hatayla sonuçlandığını da bilmek gerekiyor. 4xx client hatası, 5xx ise server hatası demek. Özellikle 5xx’ler gece uyandıran cinsten şeyler.

awk '{
    split($4, tarih, ":")
    saat = substr(tarih[2], 1, 2)
    durum_kodu = $9
    
    toplam[saat]++
    
    if (durum_kodu ~ /^4/) dort_xx[saat]++
    if (durum_kodu ~ /^5/) bes_xx[saat]++
}
END {
    printf "%-8s %-12s %-10s %-10s %-12sn", "Saat", "Toplam", "4xx", "5xx", "Hata Orani"
    printf "%-8s %-12s %-10s %-10s %-12sn", "----", "------", "---", "---", "----------"
    
    for (s in toplam) {
        hata = dort_xx[s] + 0 + bes_xx[s] + 0
        oran = (hata / toplam[s]) * 100
        printf "%-8s %-12d %-10d %-10d %-11.2f%%n", 
            s":00", toplam[s], dort_xx[s]+0, bes_xx[s]+0, oran
    }
}' /var/log/nginx/access.log | sort -k1

Burada dikkat etmek gereken bir nokta var: dort_xx[saat]+0 kullanımı. Eğer o saatte hiç 4xx yoksa dizi elemanı tanımlı değil ve awk bunu boş string olarak ele alıyor. +0 ekleyerek sayısal sıfıra dönüştürüyoruz, yoksa aritmetik işlemler bozulabiliyor.

Gerçek Dünya Senaryosu: Gece Yavaşlamasının Analizi

Şimdi asıl hikâyeye gelelim. Bir müşterimizin sitesi her gece 02:00-04:00 arası yavaşlıyordu ve kimse nedenini bilmiyordu. Log dosyası 2 GB’tı ve grep ile boğuluyorduk. Şu komutu çalıştırdığımızda tablo netleşti:

awk '{
    split($4, tarih, ":")
    saat = substr(tarih[2], 1, 2)
    durum_kodu = $9
    byte = $10 + 0
    
    toplam[saat]++
    toplam_byte[saat] += byte
    
    if (durum_kodu == 499) timeout[saat]++
    if (durum_kodu ~ /^5/) bes_xx[saat]++
}
END {
    for (s in toplam) {
        ort_byte = toplam_byte[s] / toplam[s]
        printf "%s:00 | Istek: %d | 5xx: %d | 499: %d | Ort.Boyut: %.0f bytesn", 
            s, toplam[s], bes_xx[s]+0, timeout[s]+0, ort_byte
    }
}' /var/log/nginx/access.log | sort

Sonuç şüphe uyandırıcıydı: Gece 02:00-03:00 arasında istek sayısı normalin çok altına düşmesine rağmen 5xx sayısı tavan yapıyordu ve 499 (client’ın bağlantıyı kapattığı durum) sayısı da anormal yüksekti. Demek ki az istek geliyordu ama bu istekler çok uzun sürüyordu, istemciler bağlantıyı kesiyor, sunucu da 5xx üretiyordu. Nedeni? Geceleri çalışan yedekleme job’ı I/O’yu killiyordu.

En Çok Hata Üreten URL’leri Bulmak

Saatlik analiz bize zamanı verdi, şimdi de hangi endpoint’lerin sorun çıkardığını bulalım:

awk '$9 >= 500 {
    gsub(/?.*/, "", $7)  # Query string temizle
    hata_url[$7]++
    toplam_hata++
}
END {
    print "Top 10 Hata Ureten Endpoint:"
    print "----------------------------"
    
    # Sayıya göre sıralamak için geçici dosya taktiği
    for (url in hata_url) {
        printf "%dt%sn", hata_url[url], url
    }
}' /var/log/nginx/access.log | sort -rn | head -10

gsub(/?.*/, "", $7) satırı kritik. /api/products?id=123&page=2 gibi URL’lerde query string kısmını temizliyoruz, yoksa her farklı parametre kombinasyonu ayrı URL gibi görünüyor ve analiz anlamsızlaşıyor.

Dakikalık Spike Tespiti

Saatlik analiz genel eğilimi gösterir ama ani spike’ları kaçırabilir. Özellikle DDoS benzeri durumlar veya ani trafik artışları için dakikalık analiz daha değerli:

awk '{
    # Tarih ve dakika: 15/Jan/2025:14:23
    split($4, tarih, ":")
    gun = substr(tarih[1], 2)   # [ karakterini sil
    saat = tarih[2]
    dakika = tarih[3]
    
    zaman_dilimi = saat":"dakika
    istek[zaman_dilimi]++
    
    if ($9 >= 500) hata[zaman_dilimi]++
}
END {
    for (z in istek) {
        if (istek[z] > 100) {  # Sadece yüksek trafikli dakikaları göster
            printf "%s | %d istek | %d hatan", z, istek[z], hata[z]+0
        }
    }
}' /var/log/nginx/access.log | sort

Bu komut bir dakikada 100’den fazla istek gelen dönemleri filtreler. Eşiği kendi ortamınıza göre ayarlayın. Normal trafiğiniz düşükse 20-30 bile yeterli olabilir.

IP Bazlı Analiz ile Anormal Davranış Tespiti

Bazı IP adresleri gereğinden fazla istek gönderiyor olabilir. Rate limiting kurmadan önce kimlerin sınıra takılacağını anlamak için:

awk '{
    ip = $1
    durum = $9
    
    ip_istek[ip]++
    
    if (durum ~ /^4/ && durum != 404) {
        ip_hata[ip]++
    }
    
    if (durum == 404) {
        ip_not_found[ip]++
    }
}
END {
    print "Yuksek Istek Hacmli IP Adresleri (min 1000 istek):"
    for (ip in ip_istek) {
        if (ip_istek[ip] >= 1000) {
            printf "IP: %-16s | Istek: %-8d | 4xx: %-6d | 404: %dn", 
                ip, ip_istek[ip], ip_hata[ip]+0, ip_not_found[ip]+0
        }
    }
}' /var/log/nginx/access.log | sort -t'|' -k2 -rn

Çok sayıda 404 üreten IP’ler genellikle web scraper veya güvenlik tarayıcısı. Bunları fail2ban ile engellemek ya da Nginx’te rate limit uygulamak için bu çıktıyı doğrudan kullanabilirsiniz.

Kapsamlı Günlük Rapor Script’i

Şimdiye kadar öğrendiklerimizi tek bir script altında toplayalım. Bunu crontab’a ekleyip her sabah e-posta ile alabilirsiniz:

#!/bin/bash

LOG_FILE="/var/log/nginx/access.log"
RAPOR_DOSYASI="/tmp/nginx_rapor_$(date +%Y%m%d).txt"
TARIH=$(date +"%d/%b/%Y")

echo "=== NGINX ERISIM LOG RAPORU ===" > $RAPOR_DOSYASI
echo "Tarih: $(date)" >> $RAPOR_DOSYASI
echo "Log Dosyasi: $LOG_FILE" >> $RAPOR_DOSYASI
echo "" >> $RAPOR_DOSYASI

awk -v hedef_tarih="$TARIH" '
BEGIN {
    print "=== SAATLIK ISTEK VE HATA OZETI ==="
}
{
    # Sadece bugünün loglarını işle
    if (index($4, hedef_tarih) == 0) next
    
    split($4, t, ":")
    saat = t[2]
    durum = $9
    byte = ($10 == "-") ? 0 : $10
    
    toplam[saat]++
    toplam_byte[saat] += byte
    
    if (durum ~ /^2/) iki_xx[saat]++
    else if (durum ~ /^3/) uc_xx[saat]++
    else if (durum ~ /^4/) dort_xx[saat]++
    else if (durum ~ /^5/) bes_xx[saat]++
}
END {
    genel_toplam = 0
    genel_5xx = 0
    
    for (s in toplam) {
        genel_toplam += toplam[s]
        genel_5xx += bes_xx[s]+0
        
        hata_orani = ((dort_xx[s]+0 + bes_xx[s]+0) / toplam[s]) * 100
        ort_boyut = toplam_byte[s] / toplam[s] / 1024
        
        printf "  %s:00 -> Toplam:%-7d 2xx:%-7d 3xx:%-5d 4xx:%-5d 5xx:%-5d Hata:%.1f%% OrtBoyut:%.1fKBn",
            s, toplam[s], iki_xx[s]+0, uc_xx[s]+0, dort_xx[s]+0, bes_xx[s]+0, hata_orani, ort_boyut
    }
    
    print ""
    print "=== GENEL OZET ==="
    printf "  Toplam Istek : %dn", genel_toplam
    printf "  Toplam 5xx   : %dn", genel_5xx
    if (genel_toplam > 0)
        printf "  5xx Orani    : %.2f%%n", (genel_5xx/genel_toplam)*100
}
' $LOG_FILE | sort >> $RAPOR_DOSYASI

echo "" >> $RAPOR_DOSYASI
echo "=== TOP 5 HATA VEREN ENDPOINT ===" >> $RAPOR_DOSYASI

awk -v hedef_tarih="$TARIH" '
{
    if (index($4, hedef_tarih) == 0) next
    if ($9 < 500) next
    gsub(/?.*/, "", $7)
    hata[$7]++
}
END {
    for (url in hata)
        printf "%dt%sn", hata[url], url
}
' $LOG_FILE | sort -rn | head -5 | awk '{printf "  %s hata -> %sn", $1, $2}' >> $RAPOR_DOSYASI

cat $RAPOR_DOSYASI

# Mail atmak için: mail -s "Nginx Gunluk Rapor" [email protected] < $RAPOR_DOSYASI

Script’i çalıştırılabilir yapıp test edin:

chmod +x nginx_rapor.sh
./nginx_rapor.sh

Crontab’a eklemek için:

# Her gün sabah 07:00'de çalıştır
0 7 * * * /opt/scripts/nginx_rapor.sh | mail -s "Nginx Gunluk Rapor - $(date +%d/%m/%Y)" [email protected]

Compressed Log Dosyalarını Analiz Etmek

Nginx log rotate yapıyorsa eski loglar .gz formatında olacak. zcat ile awk‘ı pipe edebilirsiniz:

# Tek sıkıştırılmış dosya
zcat /var/log/nginx/access.log.1.gz | awk '{print $9}' | sort | uniq -c | sort -rn

# Birden fazla gz dosyasını birleştir
zcat /var/log/nginx/access.log.*.gz | awk '{
    split($4, t, ":")
    gun = substr(t[1], 2, 11)  # tarihi al
    durum = $9
    
    if (durum ~ /^5/) gun_hata[gun]++
    gun_toplam[gun]++
}
END {
    for (g in gun_toplam) {
        printf "%s | Toplam: %d | 5xx: %d | Oran: %.2f%%n",
            g, gun_toplam[g], gun_hata[g]+0, 
            (gun_hata[g]+0/gun_toplam[g])*100
    }
}' | sort

Bu sayede haftalık veya aylık trend analizini de awk ile yapabilirsiniz. Harici bir araç gerekmez.

Performans İpuçları

Büyük log dosyalarında awk beklenenden yavaş çalışabilir. Bunu hızlandırmanın birkaç yolu var:

  • LC_ALL=C kullanın: LC_ALL=C awk '{...}' log.txt şeklinde çalıştırarak locale işlemlerini devre dışı bırakın. Özellikle Türkçe locale ayarlı sistemlerde ciddi hız farkı yaratıyor.
  • Büyük dosyaları önceden filtreleyin: Sadece belirli bir tarihin loglarını analiz ediyorsanız önce grep "15/Jan/2025" ile filtreleyin, sonra awk‘a verin.
  • gawk yerine mawk deneyin: Debian/Ubuntu sistemlerde mawk çok daha hızlı çalışır, syntax büyük ölçüde uyumludur.
  • Paralel işleme için split: Log dosyasını split -l 500000 access.log parca_ ile parçalara bölüp paralel çalıştırabilirsiniz, sonuçları birleştirirsiniz.

Sonuç

awk ile Nginx log analizi yapmak, dışarıdan bakıldığında karmaşık gibi görünse de temel mantığı kavradıktan sonra son derece pratik bir beceriye dönüşüyor. Burada anlattıklarım bir “framework” gibi düşünülebilir: saatlik yoğunluk, hata oranı, endpoint analizi ve IP bazlı inceleme. Bu dört yapıyı birleştirerek çoğu prodüksiyon sorununu log dosyasından çözebilirsiniz.

En önemli nokta şu: Bu analizleri sorun çıktıktan sonra değil, rutin olarak yapmak gerekiyor. Sabah gelen otomatik rapor, henüz kullanıcılar fark etmeden bir sorunun işaretini verebilir. Yukarıdaki script’i kurun, crontab’a ekleyin ve log dosyasının size söylemeye çalıştığı şeyleri dinleyin.

Elasticsearch, Grafana, Datadog gibi araçlar elbette çok daha zengin görselleştirme sunuyor. Ama bir SSH bağlantısı ve awk ile dakikalar içinde kritik bir soruyu yanıtlayabilmek, her koşulda işe yarayan bir beceri. Özellikle gece 03:00’te bir incident sırasında, ekstra araç kurmak yerine tek bir komutla durumu anlamak paha biçilmez.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir