PowerShell ile Log Dosyası Analizi ve Filtreleme

Yıllarca Linux’ta grep, awk, sed dörtlüsüyle log analizi yaptıktan sonra Windows Server tarafına geçtiğimde ilk tepkim şaşkınlık olmuştu. “Peki ben burada logları nasıl analiz edeceğim?” diye düşünmüştüm. Ama PowerShell’i tanıdıkça fark ettim ki aslında elimde çok daha güçlü bir araç var. Nesne tabanlı pipeline yapısı, metin tabanlı araçların çoğunun önüne geçiyor. Bu yazıda gerçek dünyada işinize yarayacak log analizi tekniklerini, sıfırdan ileri seviye senaryolara kadar ele alacağım.

Log Analizi Neden Bu Kadar Önemli?

Bir prodüksiyon sunucusunda bir şeyler ters gittiğinde ilk başvurduğunuz yer log dosyalarıdır. IIS logları, Windows Event logları, uygulama logları, servis logları… Bunların hepsi size hikayeyi anlatır. Ama bu dosyalar bazen yüzlerce megabayt, hatta gigabayt büyüklüğünde olabilir. 500 MB’lık bir IIS log dosyasını Notepad ile açmaya çalışmak hem pratik değildir hem de sisteminizi yorar.

PowerShell’in buradaki avantajı şu: Dosyayı tamamen belleğe almadan satır satır işleyebilir, pipe’lar aracılığıyla filtreleyebilir ve sonuçları istediğiniz formata dönüştürebilirsiniz. Üstelik öğrendikten sonra her seferinde aynı işlemi tekrar tekrar yapmak yerine script haline getirip otomatikleştirebilirsiniz.

Temel Log Okuma Komutları

Başlamadan önce en temel komutları hatırlayalım. PowerShell’de log dosyası okumak için iki ana cmdlet kullanırız.

Get-Content klasik okuma cmdlet’idir. Dosyayı satır satır okur ve her satırı bir string nesnesi olarak pipeline’a gönderir.

# Basit dosya okuma
Get-Content -Path "C:inetpublogsLogFilesW3SVC1u_ex231015.log"

# Son 100 satırı oku
Get-Content -Path "C:Logsapp.log" -Tail 100

# Dosyayı canlı takip et (Linux'taki tail -f gibi)
Get-Content -Path "C:Logsapp.log" -Wait -Tail 50

-Wait parametresi özellikle prodüksiyon sorunlarını canlı takip ederken hayat kurtarır. Bir servis çöküyorsa ve logları anlık izlemeniz gerekiyorsa bu parametreyi kullanın.

Büyük dosyalar için Get-Content yerine .NET StreamReader kullanmak çok daha performanslı olacaktır:

# Büyük log dosyaları için StreamReader kullanımı
$reader = [System.IO.StreamReader]::new("C:Logsbüyük_log.log")
$satirSayisi = 0
while ($null -ne ($satir = $reader.ReadLine())) {
    $satirSayisi++
    if ($satir -match "ERROR") {
        Write-Output "Satır $satirSayisi`: $satir"
    }
}
$reader.Close()
Write-Output "Toplam $satirSayisi satır işlendi."

Bu yöntemle 2 GB’lık bir log dosyasını bile belleği patlatmadan işleyebilirsiniz. Bir müşteri sunucusunda 1.8 GB’lık IIS logunu bu yöntemle taramak 4 dakika sürmüştü, Get-Content ile denemek istedim ve 3. dakikada memory uyarısı aldım.

Select-String ile Metin Filtreleme

grep‘in PowerShell karşılığı olan Select-String cmdlet’i, log analizinin temel taşıdır. Regex desteğiyle birlikte son derece güçlü bir araçtır.

# Basit kelime arama
Select-String -Path "C:Logsapp.log" -Pattern "ERROR"

# Büyük/küçük harf duyarsız arama
Select-String -Path "C:Logsapp.log" -Pattern "error" -CaseSensitive:$false

# Birden fazla pattern arama (OR mantığı)
Select-String -Path "C:Logsapp.log" -Pattern "ERROR", "CRITICAL", "FATAL"

# Eşleşmeyen satırları bul (NOT mantığı)
Select-String -Path "C:Logsapp.log" -Pattern "SUCCESS" -NotMatch

# Birden fazla dosyada arama
Select-String -Path "C:Logs*.log" -Pattern "OutOfMemoryException"

Select-String’in döndürdüğü nesneler sadece metin değildir. Her eşleşme bir MatchInfo nesnesidir ve içinde dosya adı, satır numarası ve eşleşen içerik bulunur. Bu bize çok daha fazla kontrol imkanı tanır:

# Eşleşme detaylarını görüntüle
$eslesmeler = Select-String -Path "C:Logs*.log" -Pattern "ERROR"
$eslesmeler | ForEach-Object {
    [PSCustomObject]@{
        Dosya     = $_.Filename
        SatirNo   = $_.LineNumber
        Icerik    = $_.Line.Trim()
        Tarih     = (Get-Item $_.Path).LastWriteTime
    }
}

IIS Log Analizi – Gerçek Dünya Senaryosu

IIS logları web sunucusu yöneticileri için altın değerindedir. Hangi IP en çok istek yapıyor, hangi sayfalar 500 hatası veriyor, response time’lar ne durumda… Hepsini PowerShell ile analiz edebilirsiniz.

IIS log formatı şöyle bir yapıya sahiptir (W3C Extended format): 2023-10-15 08:23:41 192.168.1.100 GET /index.html 80 - 10.0.0.1 Mozilla/5.0 200 0 0 125

Şimdi gerçekçi bir senaryo düşünelim: Bir müşteriniz “sitemiz yavaş” diye arıyor. İlk işimiz IIS loglarından yavaş istekleri çıkarmak olacak.

# IIS Log Analizi - Yavaş İstekleri Bulma
$logDosyasi = "C:inetpublogsLogFilesW3SVC1u_ex231015.log"

# Yorum satırlarını atla, parse et
$istekler = Get-Content $logDosyasi | Where-Object { $_ -notmatch "^#" } | 
    ForEach-Object {
        $parcalar = $_ -split " "
        if ($parcalar.Count -ge 13) {
            [PSCustomObject]@{
                Tarih        = $parcalar[0]
                Saat         = $parcalar[1]
                SunucuIP     = $parcalar[2]
                Metod        = $parcalar[3]
                URI          = $parcalar[4]
                IstemciIP    = $parcalar[8]
                StatusKod    = [int]$parcalar[11]
                ResponseTime = [int]$parcalar[14]
            }
        }
    }

# 3 saniyeden uzun süren istekler (time-taken ms cinsinden)
$yavasIstekler = $istekler | Where-Object { $_.ResponseTime -gt 3000 }
Write-Output "Yavaş istek sayısı: $($yavasIstekler.Count)"

# En yavaş 10 isteği göster
$yavasIstekler | Sort-Object ResponseTime -Descending | 
    Select-Object -First 10 | 
    Format-Table Tarih, Saat, URI, StatusKod, ResponseTime -AutoSize

Bu script sayesinde “site yavaş” şikayetini somut verilere dönüştürebilirsiniz. Artık müşteriye “3 saniyeden uzun süren 847 istek var, bunların %80’i /api/rapor endpoint’ine gidiyor” diyebilirsiniz.

Windows Event Log Analizi

Windows Event Log’ları, PowerShell’de Get-WinEvent cmdlet’i ile işlenir. Get-EventLog eski ve daha yavaş bir alternatiftir, mümkün olduğunda Get-WinEvent kullanın.

# Son 24 saatte oluşan kritik sistem olayları
$baslangic = (Get-Date).AddHours(-24)
Get-WinEvent -FilterHashtable @{
    LogName   = 'System'
    Level     = 1, 2  # 1=Critical, 2=Error
    StartTime = $baslangic
} | Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List

# Belirli bir Event ID'yi ara (4625 = Başarısız oturum açma)
Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    Id      = 4625
    StartTime = (Get-Date).AddHours(-1)
} | ForEach-Object {
    $xml = [xml]$_.ToXml()
    [PSCustomObject]@{
        Zaman        = $_.TimeCreated
        KullaniciAdi = $xml.Event.EventData.Data | 
                       Where-Object {$_.Name -eq 'TargetUserName'} | 
                       Select-Object -ExpandProperty '#text'
        KaynakIP     = $xml.Event.EventData.Data | 
                       Where-Object {$_.Name -eq 'IpAddress'} | 
                       Select-Object -ExpandProperty '#text'
    }
}

Bu son örnek gerçekten çok işlevseldir. Bir güvenlik olayı yaşandığında, son 1 saatte hangi IP adreslerinden kaç başarısız giriş denemesi yapıldığını anında görebilirsiniz.

Brute Force Tespiti Scripti

Gelin bunu daha da ilerletelim. Pratik bir brute force tespit scripti yazalım:

# Brute Force Tespit Scripti
param(
    [int]$SonDakika = 60,
    [int]$EsikDeger = 10  # Kaç başarısız deneme şüpheli sayılsın
)

$baslangic = (Get-Date).AddMinutes(-$SonDakika)

Write-Output "Son $SonDakika dakikada başarısız giriş denemeleri analiz ediliyor..."

$basarisizGirisler = Get-WinEvent -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4625
    StartTime = $baslangic
} -ErrorAction SilentlyContinue

if ($null -eq $basarisizGirisler -or $basarisizGirisler.Count -eq 0) {
    Write-Output "Belirtilen sürede başarısız giriş denemesi bulunamadı."
    exit
}

Write-Output "Toplam başarısız giriş: $($basarisizGirisler.Count)"

# IP bazlı gruplama
$ipGruplari = $basarisizGirisler | ForEach-Object {
    $xml = [xml]$_.ToXml()
    $ip = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
    if ($ip -and $ip -ne '-' -and $ip -ne '::1') {
        $ip
    }
} | Group-Object | Sort-Object Count -Descending

Write-Output "`nŞüpheli IP Adresleri (eşik: $EsikDeger):"
$ipGruplari | Where-Object { $_.Count -ge $EsikDeger } | ForEach-Object {
    Write-Warning "IP: $($_.Name) - Deneme Sayısı: $($_.Count)"
}

Bu scripti görev zamanlayıcıya ekleyip her 15 dakikada çalıştırabilir, sonuçları e-posta ile kendinize gönderebilirsiniz.

Çoklu Log Dosyasını Birleştirme ve Analiz

Gerçek ortamlarda çoğu zaman tek bir log dosyasıyla değil, günlere göre bölünmüş onlarca dosyayla çalışırsınız. IIS her gün yeni bir log dosyası oluşturur. Geçen haftanın tamamını analiz etmek istiyorsanız:

# Son 7 günün IIS loglarından 500 hatalarını çıkar
$logKlasoru = "C:inetpublogsLogFilesW3SVC1"
$baslangicTarihi = (Get-Date).AddDays(-7)

$hatalar500 = Get-ChildItem -Path $logKlasoru -Filter "*.log" | 
    Where-Object { $_.LastWriteTime -gt $baslangicTarihi } |
    ForEach-Object {
        $dosyaAdi = $_.Name
        Get-Content $_.FullName | 
            Where-Object { $_ -notmatch "^#" -and $_ -match " 500 " } |
            ForEach-Object {
                $parcalar = $_ -split " "
                [PSCustomObject]@{
                    LogDosyasi = $dosyaAdi
                    Tarih      = $parcalar[0]
                    Saat       = $parcalar[1]
                    Metod      = $parcalar[3]
                    URI        = $parcalar[4]
                    IstemciIP  = $parcalar[8]
                }
            }
    }

Write-Output "Toplam 500 hatası: $($hatalar500.Count)"

# URI bazlı gruplama - hangi sayfa en çok hata veriyor?
Write-Output "`nEn çok 500 hatası veren sayfalar:"
$hatalar500 | Group-Object URI | 
    Sort-Object Count -Descending | 
    Select-Object -First 10 |
    ForEach-Object {
        Write-Output "  $($_.Count) kez: $($_.Name)"
    }

# Sonuçları CSV'ye kaydet
$hatalar500 | Export-Csv -Path "C:Raporlar500_Hatalar_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8
Write-Output "`nSonuçlar CSV'ye kaydedildi."

Log Rotasyon ve Temizlik Scripti

Analiz yaptıktan sonra disk yönetimi de önemli bir konu. 6 aydan eski logları arşivleyip sıkıştırmak için:

# Log Arşivleme ve Temizlik Scripti
param(
    [string]$LogKlasoru = "C:Logs",
    [int]$ArşivGunu = 30,       # 30 günden eski dosyaları arşivle
    [int]$SilGunu = 180,         # 180 günden eski arşivleri sil
    [string]$ArsivKlasoru = "C:LogArsiv"
)

# Arşiv klasörünü oluştur
if (-not (Test-Path $ArsivKlasoru)) {
    New-Item -ItemType Directory -Path $ArsivKlasoru | Out-Null
    Write-Output "Arşiv klasörü oluşturuldu: $ArsivKlasoru"
}

$simdikiZaman = Get-Date
$arsivEsigi = $simdikiZaman.AddDays(-$ArşivGunu)
$silEsigi = $simdikiZaman.AddDays(-$SilGunu)

# Eski log dosyalarını bul ve sıkıştır
$arsivlenecekler = Get-ChildItem -Path $LogKlasoru -Filter "*.log" -Recurse |
    Where-Object { $_.LastWriteTime -lt $arsivEsigi }

Write-Output "Arşivlenecek dosya sayısı: $($arsivlenecekler.Count)"

foreach ($dosya in $arsivlenecekler) {
    $hedefDosya = Join-Path $ArsivKlasoru "$($dosya.BaseName)_$(Get-Date -Format 'yyyyMMdd').zip"
    
    try {
        Compress-Archive -Path $dosya.FullName -DestinationPath $hedefDosya -Force
        Remove-Item -Path $dosya.FullName -Force
        Write-Output "Arşivlendi: $($dosya.Name)"
    }
    catch {
        Write-Warning "Hata: $($dosya.Name) arşivlenemedi. $_"
    }
}

# Çok eski arşivleri sil
$silinecekler = Get-ChildItem -Path $ArsivKlasoru -Filter "*.zip" |
    Where-Object { $_.LastWriteTime -lt $silEsigi }

Write-Output "`nSilinecek eski arşiv sayısı: $($silinecekler.Count)"
$silinecekler | Remove-Item -Force
Write-Output "Temizlik tamamlandı."

Regex ile Gelişmiş Pattern Eşleştirme

Bazen basit kelime araması yetmez. Örneğin log dosyasından sadece IP adreslerini çıkarmak isteyebilirsiniz:

# Log dosyasından benzersiz IP adreslerini çıkar
$ipRegex = 'b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)b'

$benzersizIPler = Get-Content "C:Logsaccess.log" |
    Select-String -Pattern $ipRegex -AllMatches |
    ForEach-Object { $_.Matches.Value } |
    Sort-Object -Unique

Write-Output "Bulunan benzersiz IP adresi sayısı: $($benzersizIPler.Count)"
$benzersizIPler | Out-File -FilePath "C:Raporlarip_listesi.txt"

# Belirli bir tarih aralığında hata mesajlarını çıkar
$tarihRegex = '(d{4}-d{2}-d{2} d{2}:d{2}:d{2})'
$hataRegex = 'ERROR|CRITICAL|FATAL'

Get-Content "C:Logsuygulama.log" | 
    Where-Object { $_ -match $hataRegex } |
    ForEach-Object {
        if ($_ -match $tarihRegex) {
            $tarih = [datetime]::ParseExact($Matches[1], "yyyy-MM-dd HH:mm:ss", $null)
            if ($tarih -gt (Get-Date).AddDays(-1)) {
                Write-Output "$tarih - $_"
            }
        }
    }

Performans İpuçları

Büyük dosyalarla çalışırken bilmemeniz halinde saatler kaybettirebilecek bazı pratik noktalar var:

  • Get-Content -ReadCount: Satırları batch halinde okur, büyük dosyalarda ciddi hız farkı yaratır. -ReadCount 1000 ile 1000 satırı bir seferde okuyup işleyebilirsiniz.
  • Where-Object yerine -Filter: Cmdlet’lerin kendi filter parametreleri her zaman Where-Object’ten hızlıdır. Get-WinEvent için FilterHashtable kullanmak, tüm eventları çekip Where-Object ile filtrelemekten 10 kat daha hızlı olabilir.
  • [System.IO.StreamReader]: 100 MB üzeri dosyalarda Get-Content yerine bu sınıfı kullanın.
  • -Tail parametresi: Dosyanın sonunu okumak için tamamını belleğe almaz, doğrudan sona atlar.
  • ForEach-Object yerine foreach döngüsü: Büyük koleksiyonlarda foreach ($item in $koleksiyon) sözdizimi, $koleksiyon | ForEach-Object {} pipeline versiyonundan genellikle daha hızlıdır.

Otomatik Raporlama

Tüm bu analizleri manuel yapmak yerine otomatikleştirip günlük rapor oluşturabilirsiniz. Aşağıdaki script her sabah çalışacak şekilde Task Scheduler’a eklenebilir:

# Günlük Log Özet Raporu
$raporTarihi = Get-Date -Format "dd.MM.yyyy"
$dunBaslangic = (Get-Date).Date.AddDays(-1)
$dunBitis = (Get-Date).Date

$rapor = @"
=== GÜNLÜK LOG RAPORU - $raporTarihi ===

"@

# Windows Event Log özeti
$kritikOlaylar = Get-WinEvent -FilterHashtable @{
    LogName   = 'System', 'Application'
    Level     = 1, 2
    StartTime = $dunBaslangic
    EndTime   = $dunBitis
} -ErrorAction SilentlyContinue

$rapor += "WINDOWS EVENT LOG:`n"
$rapor += "  Kritik/Hata olay sayısı: $($kritikOlaylar.Count)`n"

if ($kritikOlaylar.Count -gt 0) {
    $rapor += "  En sık karşılaşılan hatalar:`n"
    $kritikOlaylar | Group-Object Id | 
        Sort-Object Count -Descending | 
        Select-Object -First 5 |
        ForEach-Object {
            $rapor += "    Event ID $($_.Name): $($_.Count) kez`n"
        }
}

# IIS Log özeti (eğer IIS kullanılıyorsa)
$iisLogKlasoru = "C:inetpublogsLogFilesW3SVC1"
if (Test-Path $iisLogKlasoru) {
    $dunLoglar = Get-ChildItem -Path $iisLogKlasoru -Filter "*.log" |
        Where-Object { $_.LastWriteTime.Date -eq $dunBaslangic.Date }
    
    $rapor += "`nIIS LOGLARI:`n"
    
    if ($dunLoglar) {
        $toplamIstek = 0
        $hata500 = 0
        
        foreach ($log in $dunLoglar) {
            $satirlar = Get-Content $log.FullName | Where-Object { $_ -notmatch "^#" }
            $toplamIstek += $satirlar.Count
            $hata500 += ($satirlar | Where-Object { $_ -match " 500 " }).Count
        }
        
        $rapor += "  Toplam istek: $toplamIstek`n"
        $rapor += "  500 hatası: $hata500`n"
        
        if ($toplamIstek -gt 0) {
            $hataOrani = [math]::Round(($hata500 / $toplamIstek) * 100, 2)
            $rapor += "  Hata oranı: %$hataOrani`n"
        }
    }
    else {
        $rapor += "  Dün için IIS log dosyası bulunamadı.`n"
    }
}

$rapor += "`n=== RAPOR SONU ==="

# Raporu dosyaya kaydet
$raporDosyasi = "C:RaporlarGunlukRapor_$(Get-Date -Format 'yyyyMMdd').txt"
$rapor | Out-File -FilePath $raporDosyasi -Encoding UTF8
Write-Output $rapor
Write-Output "`nRapor kaydedildi: $raporDosyasi"

Sonuç

PowerShell ile log analizi, başta karmaşık görünebilir ama temel cmdlet’leri ve pipeline mantığını kavradıktan sonra neredeyse her soruyu sormak mümkün hale gelir. Get-Content ve Select-String ile başlayın, Get-WinEvent ile Windows event loglarına hakim olun, büyük dosyalar için StreamReader kullanmayı alışkanlık edinin.

En büyük tavsiyem şu: Her sorunla karşılaştığınızda çözdüğünüz scripti bir kütüphanede biriktirin. 6 ay sonra benzer bir sorunla karşılaştığınızda sıfırdan başlamak yerine eski scriptinizi uyarlarsınız. Ben kendi PSLogTools klasörümü yıllardır taşıyorum, her yeni işte en çok işe yarayan şeylerden biri oluyor.

Log analizi reaktif değil proaktif yapılmalıdır. Günlük otomatik raporlar oluşturun, eşik değerlerini aşan durumlar için uyarı mekanizmaları kurun. Bir şeyler patlamadan önce trendleri görmeniz, gece 3’te çağrı almaktan çok daha iyidir.

Yorum yapın