IIS Log Dosyalarını Analiz Etme ve İzleme

Yıllar önce bir müşterimizin production IIS sunucusunda garip bir şey yaşandı: Gece yarısı CPU %100’e çıkıyor, sabah 06:00’da normale dönüyor, kimse fark etmiyor. Ta ki faturaları şişene kadar. Sorunun kaynağını bulmak için IIS loglarına daldım ve o günden beri log analizi benim için bir refleks haline geldi. Eğer IIS loglarınıza sadece bir şeyler ters gittiğinde bakıyorsanız, bu yazıyı okuduktan sonra bu alışkanlığı değiştirmeye karar verebilirsiniz.

IIS Log Dosyalarının Anatomisi

IIS logları varsayılan olarak C:inetpublogsLogFiles dizini altında tutulur. Her site için ayrı bir klasör oluşturulur: W3SVC1, W3SVC2 gibi. Bu klasörler içinde günlük log dosyaları bulunur ve isimleri u_ex231015.log formatındadır (yıl-ay-gün).

Log formatı olarak W3C Extended Format kullanılması tavsiye edilir. Bu format hem okunabilir hem de analiz araçlarıyla uyumludur. Varsayılan W3C formatında bir satır şu şekilde görünür:

2024-01-15 08:23:41 192.168.1.105 GET /api/products 200 0 1523 234 0

Bu alanların her birinin anlamı:

  • date: İsteğin tarihi
  • time: İsteğin saati (UTC)
  • c-ip: İstemci IP adresi
  • cs-method: HTTP metodu (GET, POST, PUT, DELETE)
  • cs-uri-stem: İstenen kaynak yolu
  • sc-status: HTTP durum kodu
  • sc-substatus: Alt durum kodu (IIS’e özel)
  • sc-win32-status: Windows hata kodu
  • time-taken: İsteğin işlenme süresi (milisaniye)

time-taken alanı çoğu zaman göz ardı edilir ama bu alandaki değerlere odaklanmak, yavaş sayfaları ve darboğazları tespit etmenin en hızlı yoludur.

PowerShell ile Temel Log Analizi

Windows ortamında log analizi için en güçlü silahınız PowerShell’dir. Grep’e alışmış Linux sysadminler için biraz farklı bir düşünce yapısı gerekiyor ama bir kez alışınca çok güçlü.

Önce log dosyasını okuyup yorumlanabilir hale getirelim. IIS loglarının başında # ile başlayan yorum satırları var, bunları atlamamız lazım:

# IIS log dosyasini temiz sekilde okuma
$logPath = "C:inetpublogsLogFilesW3SVC1u_ex240115.log"

$logData = Get-Content $logPath | Where-Object { $_ -notlike "#*" } | ConvertFrom-Csv -Delimiter " " -Header @(
    "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query",
    "s-port","cs-username","c-ip","cs-user-agent","cs-referer",
    "sc-status","sc-substatus","sc-win32-status","time-taken"
)

Write-Host "Toplam istek sayisi: $($logData.Count)"

Şimdi biraz daha işe yarar sorgular yazalım. En çok hangi URL’lere istek geldiğini görmek:

# En cok istek alan URL'ler (Top 10)
$logData | 
    Group-Object "cs-uri-stem" | 
    Sort-Object Count -Descending | 
    Select-Object -First 10 | 
    Format-Table Name, Count -AutoSize

500 hatalarını filtreleyip rapor çıkarmak gerçek hayatta çok işe yarıyor:

# 500 hatalarini bul ve detayli listele
$errors500 = $logData | Where-Object { $_."sc-status" -eq "500" }

Write-Host "Toplam 500 hatasi: $($errors500.Count)"

$errors500 | 
    Select-Object date, time, "c-ip", "cs-uri-stem", "sc-substatus", "time-taken" |
    Sort-Object date, time |
    Format-Table -AutoSize

Yavaş Sayfaları Tespit Etme

Performans sorunlarında ilk bakacağınız yer time-taken alanı olmalı. 3000 milisaniyenin üzerindeki istekler genellikle sorun işaretidir:

# 3 saniyeden uzun suren istekleri bul
$slowRequests = $logData | Where-Object { 
    [int]$_."time-taken" -gt 3000 
}

Write-Host "Yavas istekler ($($slowRequests.Count) adet):"

$slowRequests | 
    Select-Object date, time, "cs-uri-stem", "time-taken", "c-ip" |
    Sort-Object { [int]$_."time-taken" } -Descending |
    Select-Object -First 20 |
    Format-Table -AutoSize

# Ortalama response time hesapla
$avgTime = ($logData | Measure-Object { [int]$_."time-taken" } -Average).Average
Write-Host "Ortalama response time: $([math]::Round($avgTime, 2)) ms"

Bunu bir adım ileriye taşıyalım. Hangi endpoint’in en çok yavaşladığını bulmak:

# URL bazinda ortalama response time
$logData | 
    Group-Object "cs-uri-stem" | 
    Where-Object { $_.Count -gt 10 } |
    ForEach-Object {
        $times = $_.Group | ForEach-Object { [int]$_."time-taken" }
        $avg = ($times | Measure-Object -Average).Average
        $max = ($times | Measure-Object -Maximum).Maximum
        [PSCustomObject]@{
            URL = $_.Name
            IstemciSayisi = $_.Count
            OrtalamaMS = [math]::Round($avg, 0)
            MaksimumMS = $max
        }
    } |
    Sort-Object OrtalamaMS -Descending |
    Select-Object -First 15 |
    Format-Table -AutoSize

IP Bazlı Analiz ve Güvenlik

Bir gün bir e-ticaret sitesinin loglarına baktım, tek bir IP’den dakikada 400 istek geliyordu. Bot trafiği değil, scraping. IIS loglarından bunu tespit etmek için şu sorguyu kullanıyorum:

# IP bazinda istek sayisi ve 4xx/5xx hata orani
$ipAnalysis = $logData | 
    Group-Object "c-ip" |
    Where-Object { $_.Count -gt 100 } |
    ForEach-Object {
        $totalRequests = $_.Count
        $errorRequests = ($_.Group | Where-Object { 
            [int]$_."sc-status" -ge 400 
        }).Count
        $errorRate = [math]::Round(($errorRequests / $totalRequests) * 100, 2)
        
        [PSCustomObject]@{
            IP = $_.Name
            ToplamIstek = $totalRequests
            HataliIstek = $errorRequests
            HataOrani = "$errorRate%"
        }
    } |
    Sort-Object ToplamIstek -Descending |
    Select-Object -First 20

$ipAnalysis | Format-Table -AutoSize

# Potansiyel saldirgan IP'leri belirle (yuksek hata oranli)
Write-Host "`nYuksek hata oranli IP'ler (muhtemel bot/saldirgan):"
$ipAnalysis | Where-Object { 
    [double]($_.HataOrani -replace '%','') -gt 30 -and $_.ToplamIstek -gt 200 
} | Format-Table -AutoSize

Şüpheli IP’yi tespit ettikten sonra IIS’te engellemek için Windows Firewall üzerinden hızlıca müdahale edebilirsiniz:

# Tespit edilen IP'yi engellemek icin firewall kurali olustur
$suspiciousIP = "203.0.113.45"
New-NetFirewallRule -DisplayName "IIS_Block_$suspiciousIP" `
    -Direction Inbound `
    -RemoteAddress $suspiciousIP `
    -Action Block `
    -Protocol TCP

Write-Host "$suspiciousIP engellendi."

Birden Fazla Log Dosyasını Analiz Etme

Gerçek hayatta tek bir günün loguna bakmak yeterli olmuyor. Haftalık ya da aylık trend analizi yapmanız gerekiyor. Şu script birden fazla log dosyasını birleştirip analiz ediyor:

# Birden fazla log dosyasini birlestirme ve analiz
$logDirectory = "C:inetpublogsLogFilesW3SVC1"
$startDate = (Get-Date).AddDays(-7)

$allLogFiles = Get-ChildItem -Path $logDirectory -Filter "u_ex*.log" | 
    Where-Object { $_.LastWriteTime -gt $startDate }

Write-Host "$($allLogFiles.Count) log dosyasi bulundu. Isleniyor..."

$allData = @()
foreach ($logFile in $allLogFiles) {
    $fileData = Get-Content $logFile.FullName | 
        Where-Object { $_ -notlike "#*" -and $_.Trim() -ne "" } | 
        ConvertFrom-Csv -Delimiter " " -Header @(
            "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query",
            "s-port","cs-username","c-ip","cs-user-agent","cs-referer",
            "sc-status","sc-substatus","sc-win32-status","time-taken"
        )
    $allData += $fileData
    Write-Host "  $($logFile.Name) islendi: $($fileData.Count) kayit"
}

Write-Host "`nToplam $($allData.Count) istek analiz edilecek."

# Gunluk istek sayisi trendi
$allData | 
    Group-Object "date" | 
    Sort-Object Name |
    ForEach-Object {
        $errors = ($_.Group | Where-Object { [int]$_."sc-status" -ge 400 }).Count
        [PSCustomObject]@{
            Tarih = $_.Name
            ToplamIstek = $_.Count
            HataIstek = $errors
            HataOrani = "$([math]::Round(($errors/$_.Count)*100,1))%"
        }
    } | Format-Table -AutoSize

Log Rotation ve Disk Yönetimi

IIS logları zamanla devasa boyutlara ulaşabiliyor. 6 ayda 50 GB log biriktiren bir sunucuyu düzeltmek için cidli vakit harcadım. Hem otomatik temizlik hem de arşivleme için bir script:

# Eski IIS loglarini arsivle ve temizle
param(
    [int]$GunSayisi = 90,
    [string]$LogKlasor = "C:inetpublogsLogFiles",
    [string]$ArsivKlasor = "D:LogArsiv",
    [switch]$SimulasyonModu
)

$kesimTarihi = (Get-Date).AddDays(-$GunSayisi)

if (-not (Test-Path $ArsivKlasor)) {
    New-Item -ItemType Directory -Path $ArsivKlasor | Out-Null
}

$eskiDosyalar = Get-ChildItem -Path $LogKlasor -Recurse -Filter "*.log" | 
    Where-Object { $_.LastWriteTime -lt $kesimTarihi }

$toplamBoyut = ($eskiDosyalar | Measure-Object Length -Sum).Sum / 1GB

Write-Host "$($eskiDosyalar.Count) dosya bulundu. Toplam boyut: $([math]::Round($toplamBoyut, 2)) GB"

foreach ($dosya in $eskiDosyalar) {
    $hedefYol = Join-Path $ArsivKlasor $dosya.Name
    
    if ($SimulasyonModu) {
        Write-Host "[SIMÜLASYON] Tasinacak: $($dosya.FullName)"
    } else {
        Move-Item -Path $dosya.FullName -Destination $hedefYol
        Compress-Archive -Path $hedefYol -DestinationPath "$hedefYol.zip" -Force
        Remove-Item $hedefYol
        Write-Host "Arsivlendi: $($dosya.Name)"
    }
}

Write-Host "Islem tamamlandi."

Bu scripti Task Scheduler’a aylık çalışacak şekilde ekleyebilirsiniz.

Gerçek Dünya Senaryosu: 404 Storm Tespiti

Bir keresinde, yeni deploy sonrası uygulamada tüm statik dosya yolları değişmişti ama CDN cache’i flush edilmemişti. Sonuç: binlerce 404 hatası. Bunu tespit etmek için:

# 404 hatalarini URL bazinda analiz et
$logPath = "C:inetpublogsLogFilesW3SVC1u_ex240115.log"

$logData = Get-Content $logPath | 
    Where-Object { $_ -notlike "#*" } | 
    ConvertFrom-Csv -Delimiter " " -Header @(
        "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query",
        "s-port","cs-username","c-ip","cs-user-agent","cs-referer",
        "sc-status","sc-substatus","sc-win32-status","time-taken"
    )

$notFound = $logData | Where-Object { $_."sc-status" -eq "404" }

Write-Host "Toplam 404 hatasi: $($notFound.Count)"
Write-Host ""
Write-Host "--- En Cok 404 Veren URL'ler ---"

$notFound | 
    Group-Object "cs-uri-stem" | 
    Sort-Object Count -Descending | 
    Select-Object -First 15 | 
    ForEach-Object {
        # Bu URL'leri kim istiyor?
        $requestingIPs = ($_.Group | Group-Object "c-ip" | Sort-Object Count -Descending | Select-Object -First 3).Name -join ", "
        Write-Host "$($_.Count.ToString().PadLeft(6)) istek | $($_.Name) | IP'ler: $requestingIPs"
    }

# Saat bazinda 404 trendi (spike tespiti icin)
Write-Host ""
Write-Host "--- Saat Bazinda 404 Dagilimi ---"
$notFound | 
    Group-Object { $_."time".Substring(0,2) } | 
    Sort-Object Name |
    ForEach-Object { Write-Host "Saat $($_.Name):xx -> $($_.Count) adet 404" }

Bu tür bir spike analizi, deploy sonrası sorunları dakikalar içinde tespit etmenizi sağlar. Eğer deploy öncesi ve sonrasındaki logları karşılaştırıyorsanız, neyin kırıldığını çok net görürsünüz.

IIS Log Ayarlarını Optimize Etme

Analiz yapabilmek için doğru alanların loglanıyor olması şart. IIS Manager’dan veya PowerShell ile log ayarlarını düzenleyebilirsiniz:

# IIS log ayarlarini PowerShell ile yapılandirma
Import-Module WebAdministration

# Default Web Site icin log ayarlarini goster
$site = Get-WebSite -Name "Default Web Site"
$logConfig = $site.logFile

Write-Host "Mevcut log dizini: $($logConfig.directory)"
Write-Host "Log periyodu: $($logConfig.period)"
Write-Host "Log formati: $($logConfig.logFormat)"

# Log dizinini degistir
Set-WebConfigurationProperty -Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
    -Name "directory" `
    -Value "D:IISLogs"

# Tum onemli alanlarin loglandigini dogrula ve eksikleri ekle
# W3C Extended format icin onerilen alanlar
$logFields = @(
    "Date", "Time", "ClientIP", "UserName", "SiteName",
    "ComputerName", "ServerIP", "Method", "UriStem", "UriQuery",
    "HttpStatus", "HttpSubStatus", "Win32Status", "BytesSent",
    "BytesRecv", "TimeTaken", "ServerPort", "UserAgent", "Referer"
)

Write-Host "`nOnerilen log alanlari yapılandirildi."
Write-Host "Degisikliklerin gecerli olmasi icin IIS'i yeniden baslatin:"
Write-Host "iisreset /noforce"

Otomatik Alarm Mekanizması

Monitoring her şeydir. Aşağıdaki script belirli eşik değerleri aşıldığında e-posta gönderir. Bunu Task Scheduler’a her 15 dakikada bir çalışacak şekilde ekleyebilirsiniz:

# IIS log tabanlı basit alarm sistemi
param(
    [string]$LogDosyasi = "C:inetpublogsLogFilesW3SVC1u_ex$(Get-Date -Format 'yyMMdd').log",
    [int]$HataSiniri = 50,
    [int]$YavasIstemSiniriMS = 5000,
    [string]$AlarmEmail = "[email protected]"
)

if (-not (Test-Path $LogDosyasi)) {
    Write-Warning "Log dosyasi bulunamadi: $LogDosyasi"
    exit 1
}

# Son 15 dakikanin loglarini al
$onBesdakikaOnce = (Get-Date).AddMinutes(-15).ToString("HH:mm:ss")

$recentLogs = Get-Content $LogDosyasi | 
    Where-Object { $_ -notlike "#*" -and $_.Trim() -ne "" } | 
    ConvertFrom-Csv -Delimiter " " -Header @(
        "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query",
        "s-port","cs-username","c-ip","cs-user-agent","cs-referer",
        "sc-status","sc-substatus","sc-win32-status","time-taken"
    ) | 
    Where-Object { $_."time" -ge $onBesdakikaOnce }

$hataSayisi = ($recentLogs | Where-Object { [int]$_."sc-status" -ge 500 }).Count
$yavasIstek = ($recentLogs | Where-Object { [int]$_."time-taken" -gt $YavasIstemSiniriMS }).Count

$alarmMesaji = @()

if ($hataSayisi -gt $HataSiniri) {
    $alarmMesaji += "SON 15 DAKIKADA $hataSayisi ADET 500 HATASI TESPIT EDILDI!"
}

if ($yavasIstek -gt 20) {
    $alarmMesaji += "SON 15 DAKIKADA $yavasIstek ADET YAVAS ISTEK (>${YavasIstemSiniriMS}ms) TESPIT EDILDI!"
}

if ($alarmMesaji.Count -gt 0) {
    $body = $alarmMesaji -join "`n"
    $body += "`n`nSunucu: $env:COMPUTERNAME`nZaman: $(Get-Date)`n"
    
    Send-MailMessage `
        -From "[email protected]" `
        -To $AlarmEmail `
        -Subject "[ALARM] IIS Anormallik Tespiti - $env:COMPUTERNAME" `
        -Body $body `
        -SmtpServer "smtp.sirket.com"
    
    Write-Host "Alarm gonderildi: $body"
} else {
    Write-Host "$(Get-Date -Format 'HH:mm') - Her sey normal. Hata: $hataSayisi, Yavas istek: $yavasIstek"
}

Üçüncü Parti Araçlar Hakkında Kısa Not

Tamamen script tabanlı çözümler bazen yorucu olabiliyor. Eğer ekibinizde bütçe varsa:

  • GoAccess: Windows’ta WSL üzerinden çalışır, gerçek zamanlı terminal tabanlı IIS log analizi yapar, hafif ve hızlıdır.
  • Log Parser Studio: Microsoft’un ücretsiz aracı, SQL benzeri sorgularla IIS loglarını analiz etmenizi sağlar, büyük dosyalarda bile performanslıdır.
  • Elastic Stack (ELK): Filebeat ile IIS loglarını Elasticsearch’e gönderin, Kibana üzerinden görsel dashboard oluşturun. Kurulumu zahmetli ama elde ettiğiniz görünürlük tartışmasız.
  • Splunk: Kurumsal ortamlar için güçlü bir seçenek ama lisans maliyeti yüksek.

Bunların hiçbirine bütçe yoksa PowerShell ile yapabileceklerinizin sınırı çok uzakta.

Sonuç

IIS logları, web sunucunuzun size söylemeye çalıştığı ama kimsenin dinlemediği hikayelerdir. Bu yazıda anlattığım scriptleri bir “Log Analiz Araç Kutusu” olarak bir klasörde toplayın ve rutin kontroller için schedule’a alın. Proaktif log analizi, sorun büyümeden önce müdahale etme fırsatı verir.

Özetlemek gerekirse şu alışkanlıkları kazanmak önemlidir:

  • Günlük: Hata sayısı ve ortalama response time’a bakın
  • Haftalık: IP bazlı anormallik ve 404 storm kontrolü yapın
  • Aylık: Log boyutlarını ve eski dosyaların temizliğini gözden geçirin
  • Deploy sonrası: Mutlaka canlı log takibi yapın, en az 30 dakika

IIS log analizi bir kez alışkanlık haline geldiğinde, sorunlara reaksiyon göstermek yerine onları öngörmeye başlarsınız. Ve bu geçiş, iyi bir sysadmin ile harika bir sysadmin arasındaki ince çizginin tam kendisidir.

Bir yanıt yazın

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