Skip to content
Siber Vatan

Büyük Boyutlu Logların Anlık JSON Parse ve CLI Renklendirme İşlemleri ​

Devran Baştemur · @DevranBastemur

Can Polat Zorkun · @Polat174

Bedirhan İhtiyar · @acegladd

Umut Açıkgöz · @umutackgoz

Emirhan ÖZGEN · @MESLEKDAA

Modern sistem mimarilerinde, mikroservis kümelerinde ve Güvenlik Operasyon Merkezlerinde (SOC) günlük olarak yüzlerce gigabaytlık log verisi (Zeek ağ kayıtları, Nginx erişim olayları, sistem logları) üretilmektedir. Bu verilerin canlı uçbirimde (CLI - Command Line Interface) incelenmesi ve analiz edilmesi sırasında yapılan en kritik hata, dosyanın tamamını tek seferde belleğe (RAM) yüklemeye çalışmaktır.

Bu çalışmada; yüksek hacimli log dosyalarının bellek darboğazları oluşturmadan akış (streaming) mimarisiyle nasıl işlendiğini, Python, C# ve Go ortamlarında optimize veri okuma modellerini, geliştirilen analiz araçlarının modüler bir Plugin (MainChain) sistemine entegrasyonunu ve uçbirimde (CLI) anlık JSON ayrıştırma ile renklendirme tekniklerini adım adım ele alıyoruz.


1. Zeek (Conn.log) Analiz Sistemlerinde Bellek Yerelliği ve Performans Darboğazları ​

Siber Güvenlik Operasyon Merkezlerinde (SOC) ve Ağ Tehdit Avcılığı (Threat Hunting) süreçlerinde, Zeek (Bro) gibi Ağ Algılama ve Yanıt (NDR) sistemleri günlük olarak gigabaytlarca conn.log verisi üretir. Bu tür devasa (GB/TB ölçeğinde) metin tabanlı log dosyalarının analizinde seçilen veri işleme mimarisi, sistem kaynaklarının verimliliğini doğrudan etkiler.

İlk aşamada geliştirilen temel log analiz prototipi, dosya işleme mantığını doğrulamakla birlikte büyük dosyalarda şiddetli bellek darboğazına (Memory Bottleneck) ve işlemci yüküne yol açmaktadır.

1.1. readlines() ve Bellek Şişmesi (Memory Bloat) ​

Klasik dosya okuma yaklaşımında dosyadaki tüm satırlar tek bir hamlede belleğe (RAM) aktarılır:

python
def read_huge_log(dosya_yolu):
    if not os.path.exists(dosya_yolu):
        raise FileNotFoundError(f"[!] HATA: {dosya_yolu} bulunamadı!")

    with open(dosya_yolu, 'r', encoding='utf-8', errors='ignore') as f:
        tum_satirlar = f.readlines()  # Tüm satırlar aynı anda RAM'e yüklenir
        return tum_satirlar

10 GB boyutundaki bir log dosyası, Python'ın dinamik obje işaretçileri (pointers) ve dize (string) yapıları sebebiyle RAM üzerinde yaklaşık 15–20 GB alan işgal eder. Bu durum, RAM yetersizliğinde sistemin SWAP alanına düşmesine ve işletim sistemi seviyesinde Out-Of-Memory (OOM) Killer mekanizmasının tetiklenerek sürecin sonlandırılmasına yol açar.

1.2. String İşlemleri ve Garbage Collection Yükü ​

Döngü içerisinde uygulanan satir.strip() ve satir.split('\t') işlemleri, her satır için bellek üzerinde yeni metin objeleri ve alt listeler türetir:

python
for satir in log_listesi:
    satir = satir.strip()
    if satir.startswith('#'):
        continue

    sutunlar = satir.split('\t')
    if len(sutunlar) > 7:
        prot = sutunlar[6]
        serv = sutunlar[7]
        protokol_sayaci[prot] += 1
        if serv != '-':
            servis_sayaci[serv] += 1

Milyonlarca satırlık bir log kümesinde, her satır için bellek tahsisi (heap allocation) yapılması, CPU döngülerinin önemli bir kısmının bellek temizliğine (Garbage Collection) harcanmasına neden olur.

Figür 1: 10 GB Log Dosyası İçin Yaklaşık RAM Tüketim Karşılaştırması

1.3. Performans Benchmark Projeksiyonu ​

Aşağıdaki tablo, 10 GB boyutundaki (~30 Milyon satır) sentetik bir conn.log dosyası üzerinde yapılan donanımsal ve teorik projeksiyon karşılaştırmasını göstermektedir:

MetrikMevcut Yöntem (readlines)Akış Yöntemi (Generator)Polars / Arrow Mimarisi
RAM Kullanımı~18 GB (Ölçeklenemez)~50 MB (Sabit)~1.5 GB
İşlem Süresi~120 - 180 Saniye~35 - 45 Saniye~3 - 6 Saniye
I/O KarakteristiğiArdışık Okuma, Yüksek HeapArdışık Akış OkumaDoğrudan Bellek Eşleme
Zaman/Alan KarmaşıklığıO(N) Alan / O(N) ZamanO(1) Alan / O(N) ZamanO(K) Alan / Paralel Zaman

Bu analiz göstermektedir ki; algoritma doğru çıktılar üretse bile, tüm veriyi aynı anda belleğe yığan mimariler canlı üretim ortamları için sürdürülebilir değildir. Sistemin bellekten bağımsız çalışabilmesi için akış (streaming) modeline geçilmesi zorunludur.


2. Büyük Log Dosyalarını Satır Satır İşlemek: Python, C# ve Go ​

Bir ağ logu birkaç megabaytken dosyanın tamamını belleğe almak pratik görünebilir. Dosya gigabayt seviyesine ulaştığında ise aynı yöntem gereksiz bellek tüketimine dönüşür. Büyük dosyalarla çalışırken temel mimari ilke basittir: Her kaydı oku, işle ve sıradakine geç.

2.1. Dosyayı Biriktirmeden Okumak (Streaming / Generator) ​

Python uygulamasında okuma işlemi bir generator üzerinden yürütülür. yield, her çağrıda yalnızca bir satır üretip fonksiyonun kaldığı yerden devam etmesini sağlar. Böylece bütün satırları içeren devasa bir liste oluşturmaya gerek kalmaz:

python
# -*- coding: utf-8 -*-
import os
import time
from collections import Counter

def read_huge_log(dosya_yolu):
    if not os.path.exists(dosya_yolu):
        raise FileNotFoundError(f"[!] HATA: {dosya_yolu} bulunamadı!")

    # RAM'i şişirmemek için yield generator kullanıyoruz
    with open(dosya_yolu, 'r', encoding='utf-8', errors='ignore') as f:
        for satir in f:
            yield satir.strip()

def ana_fonksiyon():
    LOG_FILE = "connf.log"
    print("[*] Altay_DEV Log Analiz Sistemi Başlatılıyor...\n")

    protokol_sayaci = Counter()
    servis_sayaci = Counter()
    baslangic = time.perf_counter()

    try:
        log_stream = read_huge_log(LOG_FILE)
        satir_sayaci = 0

        for satir in log_stream:
            satir_sayaci += 1
            if satir.startswith('#'):
                continue

            sutunlar = satir.split('\t')
            if len(sutunlar) > 7:
                prot = sutunlar[6]
                serv = sutunlar[7]
                protokol_sayaci[prot] += 1
                if serv != '-':
                    servis_sayaci[serv] += 1

        bitis = time.perf_counter()
        gecen_sure = bitis - baslangic

        print("=" * 40)
        print("              SONUÇLAR")
        print("=" * 40)
        print(f"[*] Toplam taranan satır : {satir_sayaci:,}")
        print(f"[*] İşlem süresi         : {gecen_sure:.2f} saniye")

        print("\n[+] PROTOKOL DAĞILIMI")
        for p, count in protokol_sayaci.most_common():
            print(f"  -> {p.upper():<5} : {count:,}")

        print("\n[+] TOP 5 SERVİS")
        for s, count in servis_sayaci.most_common(5):
            print(f"  -> {s.upper():<5} : {count:,}")

    except FileNotFoundError as err:
        print(err)
    except Exception as e:
        print(f"[!] Beklenmedik bir hata oluştu: {e}")

if __name__ == "__main__":
    ana_fonksiyon()

Buradaki temel kazanç, dosyanın tamamını bellekte tutmamaktır. Yine de "RAM kullanımı hiç artmaz" demek doğru olmaz: Okuma tamponları, işlenen satır ve sayaçlar bellek kullanır. Özellikle farklı kategori sayısı arttıkça sayaçların kapladığı alan da büyür; ancak log dosyası boyutu 1 GB'tan 50 GB'a çıksa dahi anlık bellek kullanımı sabit kalır.

2.2. Aynı Yaklaşım, Üç Farklı Dil ​

Aynı akış stratejisi farklı dillerde benzer tasarım kalıplarıyla hayata geçirilebilir. Her dil dosyayı sırayla tarar; okuma ve sayma için kendi standart araçlarını kullanır:

DilSatır Okuma YöntemiKategorileri Sayma
PythonDosya üzerinde döngü ve yieldcollections.Counter
C#StreamReader.ReadLine() ve yield returnDictionary<string, int>
Gobufio.Scanner ve Scan()map[string]int

Go Ortamında Token Sınırı

Go sürümünde uzun satırlara dikkat etmek gerekir. bufio.Scanner varsayılan olarak yaklaşık 64 KiB token sınırı kullanır. Ağ paketlerinin ham yükünü taşıyan daha uzun kayıtlar için scanner.Buffer() ile üst sınırın manuel ayarlanması zorunludur.

2.3. Logdan Hangi Bilgiler Çıkarılıyor? ​

Kodlar, # ile başlayan başlık satırlarını analiz dışında bırakır ve kayıtları sekme (\t) karakterine göre ayırır. Zeek conn.log düzeninde yedinci sütun protokolü, sekizinci sütun ise servisi temsil eder. Servis alanındaki - değeri doğrudan trafik eşleşmesi bulunmadığını belirttiği için servis istatistiklerine dahil edilmez.

İşlem sonunda taranan toplam satır sayısı, geçen süre, protokol dağılımı ve en sık görülen ilk 5 servis uçbirime (CLI) yazdırılır. Toplam satır sayacı başlıkları ve servisi tanımlanmamış kayıtları da kapsadığı için, TOP 5 servis toplamının genel satır toplamından düşük olması beklenen ve mimariyle uyumlu doğal bir sonuçtur.


3. Geliştirilen Araçların Modüler Plugin (MainChain) Sistemine Entegrasyonu ​

Geliştirilen bu analiz yeteneklerinin bağımsız birer kütüphane haline getirilerek çalışma zamanında (runtime) ana sisteme dahil edilmesi, mimarinin genişletilebilirliği açısından kritik bir adımdır. Bu aşamada C# (.NET 8) ile geliştirdiğimiz MainChain modüler eklenti altyapısı devreye girmektedir.

3.1. MainChain Plugin Motorunun Mimarisi ​

MainChain, Açık/Kapalı Prensibi (Open-Closed Principle) temelinde çalışan dinamik bir orkestratördür:

  • Kendi dizinindeki plugins/ klasörünü otomatik olarak izler.
  • Bu klasörün içine atılan her .dll dosyasını çalışma zamanında .NET Reflection API (Assembly.LoadFrom) ile dinamik olarak okur.
  • Yüklenen kütüphanedeki tipleri tarayarak Plugin arayüzünü (interface) uygulayan sınıfları bulur.
  • Nesne örneğini oluşturur (Activator.CreateInstance) ve ortak sözleşme olan Execute() metodunu tetikler.
text
MainChain/
├── MainChain.exe          # Ana çalıştırıcı CLI motoru
├── Dockerfile             # Konteyner yapılandırması
└── plugins/               # Dinamik modüller klasörü
    ├── LogAnalyzer2.dll   # Akış (streaming) tabanlı optimize eklenti
    ├── Scapy_Sniffer.dll  # Ağ paketlerini dinleyen eklenti
    └── LogAnalyzerOld.dll # Karşılaştırma amaçlı geleneksel eklenti

Kullanıcı sistemi uçbirimden (CLI) basit komutlarla yönetebilir:

  • .\MainChain.exe -l : plugins/ klasöründeki mevcut modülleri numaralarıyla listeler.
  • .\MainChain.exe -r 1 : İstenen eklentiyi sıra numarasıyla veya dosya adıyla çalıştırır.
  • .\MainChain.exe -a <yol> : Dışarıdaki bir DLL dosyasını plugins klasörüne kopyalar.
  • .\MainChain.exe -d <ad/no> : Belirtilen eklentiyi siler.

3.2. Visual Studio ile Class Library Projesinin Oluşturulması ​

Entegrasyonun ilk adımı, eklenti olarak çalışacak kod için doğru proje şablonunu seçmektir. Visual Studio üzerinde konsol uygulaması yerine Class Library (Sınıf Kütüphanesi) şablonu seçilir.

Figür 2: Visual Studio Proje Oluşturma - Class Library Seçimi

Neden Console App Değil de Class Library?

Bir Console Application, doğrudan işletim sistemi tarafından başlatılabilen bağımsız bir Main metodu içerir ve derlendiğinde .exe üretir.

Class Library ise tek başına çalışamaz; içerisinde Main metodu barındırmaz ve sadece başka programların referans alıp dinamik yükleyebileceği bir .dll dosyası üretir. Ana sistemimiz (MainChain.exe) zaten bağımsız bir konsol programı olduğundan, ona eklenecek parçaların dinamik kütüphane (.dll) formatında olması gerekir.

Projenin devamında, ana sistemle tam uyum sağlaması adına hedef çatı (Target Framework) olarak .NET 8.0 (Long Term Support) seçilerek proje yapılandırılır.

3.3. Plugin İçin Gerekli Kod Yapısı ve Mimari Kurallar ​

Kodun MainChain tarafından tanınabilmesi için belirli bir mimari standarda uyulması zorunludur:

csharp
namespace LogAnalyzer2
{
    // 1. Ortak sözleşme arayüzü
    public interface Plugin
    {
        void Execute();
    }

    // 2. Arayüzü uygulayan eklenti sınıfı
    public class DevLogAnalyzer : Plugin
    {
        // 3. Ana sistemin tetikleyeceği giriş metodu
        public void Execute()
        {
            string logFile = "conn.log";
            Console.WriteLine("[*] Altay_DEV Log Analiz Sistemi Başlatılıyor...\n");

            if (!File.Exists(logFile))
            {
                Console.WriteLine($"[!] HATA: {logFile} bulunamadı!");
                return;
            }

            var protokolSayaci = new Dictionary<string, int>();
            var servisSayaci = new Dictionary<string, int>();
            Stopwatch sw = Stopwatch.StartNew();
            int satirSayaci = 0;

            try
            {
                // File.ReadLines -> IEnumerable<string> döndürür, bellek tüketimi O(1) kalır
                foreach (string satir in File.ReadLines(logFile))
                {
                    satirSayaci++;
                    if (satir.StartsWith("#")) continue;

                    string[] sutunlar = satir.Split('\t');
                    if (sutunlar.Length > 7)
                    {
                        string prot = sutunlar[6];
                        string serv = sutunlar[7];

                        if (protokolSayaci.ContainsKey(prot)) protokolSayaci[prot]++;
                        else protokolSayaci[prot] = 1;

                        if (serv != "-")
                        {
                            if (servisSayaci.ContainsKey(serv)) servisSayaci[serv]++;
                            else servisSayaci[serv] = 1;
                        }
                    }
                }

                sw.Stop();
                Console.WriteLine(new string('=', 40));
                Console.WriteLine("              SONUÇLAR");
                Console.WriteLine(new string('=', 40));
                Console.WriteLine($"[*] Toplam taranan satır : {satirSayaci:N0}");
                Console.WriteLine($"[*] İşlem süresi         : {sw.Elapsed.TotalSeconds:F2} saniye");

                Console.WriteLine("\n[+] PROTOKOL DAĞILIMI");
                foreach (var item in protokolSayaci.OrderByDescending(x => x.Value))
                {
                    Console.WriteLine($"  -> {item.Key.ToUpper().PadRight(5)} : {item.Value:N0}");
                }

                Console.WriteLine("\n[+] TOP 5 SERVİS");
                foreach (var item in servisSayaci.OrderByDescending(x => x.Value).Take(5))
                {
                    Console.WriteLine($"  -> {item.Key.ToUpper().PadRight(5)} : {item.Value:N0}");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"[!] Beklenmedik bir hata oluştu: {e.Message}");
            }
        }
    }
}

Bu yapının arkasındaki 4 temel tasarım kararı şunlardır:

  1. public interface Plugin Neden Var?
    Ana sistem ile eklentiler arasında bir sözleşme (contract) kurar. MainChain, harici DLL içinde hangi sınıfların olduğunu önceden bilemez; ancak arayüz sayesinde o sınıfın parametresiz bir Execute() metodu içerdiğini kesin olarak garanti altına alır.
  2. Neden Main Yerine Execute Yazıyoruz?
    Bir işletim sistemi sürecinde yalnızca tek bir Main giriş noktası bulunabilir; bu nokta ana orkestratör olan MainChain.exe'ye aittir. Eklentinin kendi başına değil, ana sistem tarafından dışarıdan tetiklenebilmesi için Execute metodu kullanılır.
  3. Sınıf Adından Sonra Neden : Plugin Ekliyoruz?
    Ana sistem Reflection ile DLL'i tararken type.GetInterfaces() kontrolü yapar. Eğer sınıf bu arayüzü miras almışsa geçerli bir modül olarak sisteme kaydeder.
  4. Neden .dll Kullanıyoruz?
    Çıktı .exe olsaydı işletim sisteminde ayrı bir process olarak ayağa kalkar, izole bir bellek havuzu oluşturur ve eklenti olmaktan çıkardı. .dll formatı sayesinde kod ana sistemin bellek bağlamında dinamik olarak çalıştırılır.

3.4. Eklentinin Dağıtımı ve Test Edilmesi ​

Proje geliştirme sürecinde bin/Debug/net8.0/ altında derlenen LogAnalyzer2.dll dosyası MainChain altındaki plugins/ klasörüne kopyalanır.

Üretim ve Performans İçin Release Derlemesi

Geliştirme aşamasında hata ayıklama sembolleri içeren Debug çıktıları kullanılabilir. Ancak kesin performans ölçümlerinde ve canlı üretim ortamlarında, derleyici optimizasyonlarının devrede olduğu Release derlemesi (dotnet build -c Release veya dotnet publish -c Release) tercih edilmelidir.

Uçbirim (CLI) üzerinden eklentiler listelenir:

powershell
.\MainChain.exe -l

Figür 3: Mevcut Eklentilerin Listelenmesi (-l)

Eklenti sıra numarasıyla çalıştırılır:

powershell
.\MainChain.exe -r 1
text
[+] LogAnalyzer2.dll Başlatılıyor...

[*] Altay_DEV Log Analiz Sistemi Başlatılıyor...

========================================
              SONUÇLAR
========================================
[*] Toplam taranan satır : 1,452,890
[*] İşlem süresi         : 2.14 saniye

[+] PROTOKOL DAĞILIMI
  -> TCP   : 1,120,450
  -> UDP   : 331,890
  -> ICMP  : 550

[+] TOP 5 SERVİS
  -> HTTP  : 840,120
  -> DNS   : 330,500
  -> SSL   : 198,200
  -> SSH   : 12,400
  -> FTP   : 4,200

Ölçüm Notu: Yukarıdaki çıktı, yaklaşık 1.45 milyon satırlık gerçek bir Zeek conn.log dosyası üzerinde alınan canlı CLI çalışma sonucudur. Bölüm 1.3'teki 10 GB (~30 milyon satır) sentetik donanım projeksiyonu ile kıyaslandığında; akış yönteminin 1.45M satırda 2.14 saniye sürmesi, mimarinin pratikteki doğruluğunu kanıtlamaktadır.

3.5. Docker ile Konteynerizasyon ​

Sistemin taşınabilirliğini sağlamak için hazırlanan Docker altyapısı:

dockerfile
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app

# Ağ paketlerini dinleyen 'Scapy_Sniffer.dll' eklentisinin gereksinimleri (libpcap, scapy)
RUN apt-get update && \
    apt-get install -y python3 python3-pip tcpdump libpcap-dev && \
    rm -rf /var/lib/apt/lists/*

RUN pip3 install --no-cache-dir scapy --break-system-packages

# Canlı üretim ve optimize performans için Release derlemesi aktarılır
COPY bin/Release/net8.0/ ./

ENTRYPOINT ["dotnet", "MainChain.dll"]

Dockerfile Bağımlılık Gerekçesi

LogAnalyzer2.dll saf C# .NET kodundan oluşur ve harici bir kütüphaneye ihtiyaç duymaz. Dockerfile içindeki python3, libpcap-dev ve scapy kurulumları; görselde listelenen ve ham ağ paketlerini koklayıp analiz eden Scapy_Sniffer.dll eklentisinin gereksinimleridir. Salt log analizi yapılacak ortamlarda bu paketler elenerek imaj boyutu önemli ölçüde küçültülebilir.

İmaj derleme ve çalıştırma:

bash
docker build -t altay-main .
docker run -it --rm altay-main -l
docker run -it --rm altay-main -r 1

4. Anlık JSON Ayrıştırma (Parse) ve Uçbirim (CLI) Renklendirme Mimarisi ​

Büyük boyutlu logların akış mimarisiyle okunup işlenmesinin ardından, analistin bu verileri insan algısına uygun biçimde hızlıca yorumlayabilmesi için anlık JSON dönüşümü ve uçbirim renklendirme teknikleri uygulanmalıdır.

4.1. Bellek Dostu Log İşleme: Buffer vs. Streaming ​

Karşılaştırma KriteriTamponlama / Tam Yükleme (Buffer)Akış / Satır Satır Okuma (Streaming)
Bellek (RAM) TüketimiDosya boyutuyla doğru orantılı (Çok Yüksek)Sabit ve öngörülebilir (~birkaç megabayt)
İlk Çıktı Süresi (Latency)Dosyanın tamamı okunana kadar gecikirİlk satır geldiği anda çıktı üretilir
Canlı Akış Uyumutail -f benzeri canlı boru hatlarında çalışmazBoru hatlarına (pipe) doğrudan entegre olur
Sistem KararlılığıBellek kilitlenmesi ve çökme riski yüksekYüksek hacimli veri akışlarında kesintisiz çalışır

Akış yönteminde dosya; bloklar veya yeni satır (\n) sınırları referans alınarak parça parça işlenir. Bu sayede bellek tüketimi dosya hacminden bağımsız hale getirilerek sabit seviyede tutulur.

4.2. Ham Logların Yapılandırılmış JSON Formatına Dönüştürülmesi ​

Üretim sistemlerindeki kayıtlar çoğunlukla düz metin (örneğin Nginx Combined formatı) veya satır bazlı JSON (NDJSON / JSONLines) biçimindedir.

Örnek bir Nginx erişim kaydı:

text
192.168.1.105 - - [21/Apr/2026:14:32:10 +0300] "GET /api/v1/auth HTTP/1.1" 401 128 "-" "Mozilla/5.0"

Düz metin logları ayrıştırarak yapılandırılmış JSON nesnesine dönüştürmek, alan bazlı aramayı, SIEM iletimini ve uçbirim üzerinde renk kodlamasını doğrudan mümkün kılar:

json
{
  "timestamp": "2026-04-21T14:32:10+03:00",
  "client_ip": "192.168.1.105",
  "method": "GET",
  "endpoint": "/api/v1/auth",
  "status_code": 401,
  "response_size": 128
}

Devasa log verilerini ayrıştırırken en verimli yaklaşım her satırın bağımsız bir JSON nesnesi olduğu NDJSON (Newline Delimited JSON) biçimidir. Dizi formatındaki klasik JSON belgeleri ([{...}, {...}]) bütün verinin aynı anda belleğe alınmasını şart koştuğundan akış mimarisine uygun değildir.

4.3. Python ile Log Akışını Yakalama ve Renklendirme ​

Python ortamında akış halindeki veriyi tüketmek, yapılandırılmış sözlüğe (JSON nesnesine) dönüştürmek ve renklendirerek uçbirime basmak için rich kütüphanesinden yararlanılır:

python
#!/usr/bin/env python3
"""
Büyük Boyutlu Loglar İçin Streaming Parser ve Terminal Renklendirici
"""

import sys
import re
import json
from rich.console import Console

console = Console()

# Nginx Combined Log Deseni
LOG_REGEX = re.compile(
    r'^(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) \S+" (?P<status>\d{3}) (?P<size>\S+)'
)

def stream_lines(source):
    """Girdiyi bellek şişmesine yol açmadan satır satır okur."""
    for line in source:
        clean_line = line.strip()
        if clean_line:
            yield clean_line

def parse_line(line):
    """Düz metin log satırını ayrıştırarak yapılandırılmış JSON nesnesine dönüştürür."""
    match = LOG_REGEX.match(line)
    if not match:
        return None
    data = match.groupdict()

    # 4.2'deki standart JSON şemasına eşleme
    return {
        "timestamp": data["time"],
        "client_ip": data["ip"],
        "method": data["method"],
        "endpoint": data["path"],
        "status_code": int(data["status"]),
        "response_size": int(data["size"]) if data["size"].isdigit() else 0
    }

def get_status_style(status_code):
    """HTTP yanıt koduna göre terminal stili belirler."""
    if status_code < 300:
        return "bold green"
    elif status_code < 400:
        return "bold cyan"
    elif status_code < 500:
        return "bold yellow"
    else:
        return "bold red"

def main():
    try:
        # sys.stdin üzerinden kesintisiz veri akışı
        for raw_line in stream_lines(sys.stdin):
            entry = parse_line(raw_line)
            if not entry:
                continue

            status_style = get_status_style(entry["status_code"])

            # Uçbirimde (CLI) formatlı ve renkli satır basımı
            # Rich kütüphanesi sys.stdout.isatty() kontrolünü arka planda otomatik yönetir.
            console.print(
                f"[dim]{entry['timestamp']}[/dim] │ "
                f"[cyan]{entry['client_ip']:<15}[/cyan] │ "
                f"[magenta]{entry['method']:<6}[/magenta] │ "
                f"[{status_style}]{entry['status_code']}[/{status_style}] │ "
                f"[yellow]{entry['response_size']:>6}B[/yellow] │ "
                f"[white]{entry['endpoint']}[/white]"
            )
    except KeyboardInterrupt:
        console.print("\n[bold red]İşlem kullanıcı tarafından durduruldu.[/bold red]")

if __name__ == "__main__":
    main()

4.4. Yüksek Hızlı Sistem Yaklaşımı: Go ile Bufio Scanner ​

Saniyede on binlerce satırın aktığı ve Python'ın CPU darboğazına girebileceği ortamlarda, derlenen ve bellek tahsisini (memory allocation) sıkı kontrol edebilen Go dili tercih edilir.

Aşağıdaki Go uygulaması, NDJSON formatında gelen akışı bufio.Scanner ile tamponlayarak okur; HTTP durum kodlarına göre renk ataması yapar ve standart kütüphane üzerinden terminal kontrolü gerçekleştirerek boru hatlarında temiz çıktı üretir:

go
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
)

type LogPayload struct {
	Timestamp    string `json:"timestamp"`
	ClientIP     string `json:"client_ip"`
	Method       string `json:"method"`
	Status       int    `json:"status_code"`
	Endpoint     string `json:"endpoint"`
	ResponseSize int    `json:"response_size"`
}

// ANSI Kaçış Dizileri
const (
	ColorReset  = "\033[0m"
	ColorRed    = "\033[31m"
	ColorGreen  = "\033[32m"
	ColorYellow = "\033[33m"
	ColorCyan   = "\033[36m"
)

func formatStatus(code int, useColor bool) string {
	if !useColor {
		return fmt.Sprintf("%d", code)
	}
	switch {
	case code >= 200 && code < 300:
		return fmt.Sprintf("%s%d%s", ColorGreen, code, ColorReset)
	case code >= 300 && code < 400:
		return fmt.Sprintf("%s%d%s", ColorCyan, code, ColorReset)
	case code >= 400 && code < 500:
		return fmt.Sprintf("%s%d%s", ColorYellow, code, ColorReset)
	case code >= 500:
		return fmt.Sprintf("%s%d%s", ColorRed, code, ColorReset)
	default:
		return fmt.Sprintf("%d", code)
	}
}

func main() {
	// Uçbirim (TTY) Tespiti: Çıktı bir dosyaya yönlendirildiğinde ANSI kodlarını devre dışı bırak
	fileInfo, _ := os.Stdout.Stat()
	isTerminal := (fileInfo.Mode() & os.ModeCharDevice) != 0

	scanner := bufio.NewScanner(os.Stdin)

	// Çok uzun satırlar için tampon belleğin genişletilmesi
	buf := make([]byte, 64*1024)
	scanner.Buffer(buf, 10*1024*1024) // Maksimum 10MB satır desteği

	for scanner.Scan() {
		var log LogPayload
		rawBytes := scanner.Bytes()

		if err := json.Unmarshal(rawBytes, &log); err != nil {
			continue // Bozuk JSON satırlarını göz ardı et
		}

		ipFormatted := log.ClientIP
		if isTerminal {
			ipFormatted = fmt.Sprintf("%s%s%s", ColorCyan, log.ClientIP, ColorReset)
		}

		fmt.Printf("[%s] %s -> %s %s [%s] (%dB)\n",
			log.Timestamp,
			ipFormatted,
			log.Method,
			log.Endpoint,
			formatStatus(log.Status, isTerminal),
			log.ResponseSize,
		)
	}

	if err := scanner.Err(); err != nil {
		fmt.Fprintf(os.Stderr, "Girdi okuma hatası: %v\n", err)
	}
}

4.5. Terminal ve CLI Görselleştirme Teknikleri ​

Büyük veri akışlarında analistin dikkatini kritik olaylara odaklamak için şu görselleştirme kuralları uygulanır:

  1. ANSI Kaçış Kodları (Escape Codes): Harici bağımlılık olmadan renk kodları (\033[31m, \033[32m, \033[36m vb.) kullanılarak HTTP durum kodları (2xx başarılı, 3xx yönlendirme, 4xx istemci hatası, 5xx sunucu hatası), hata seviyeleri ve IP adresleri renklendirilir.
  2. TTY ve Boru Hattı Tespiti (isatty): Komut satırı aracı standart bir uçbirimde çalışırken renk kodları basmalı; çıktısı başka bir dosyaya veya araca yönlendirildiğinde (tool > output.log veya tool | grep) ANSI renk kodlarını otomatik devre dışı bırakmalıdır. Python tarafında Rich kütüphanesi sys.stdout.isatty() kontrolünü dahili olarak yürütürken; Go örneğinde standart kütüphanenin os.ModeCharDevice biti üzerinden uçbirim kontrolü sağlanarak yönlendirmelerde log dosyasının kirlenmesi engellenmiştir.
  3. Filtreleme ve Alarm: Yüksek veri hızında yalnızca şüpheli durumlar (örneğin 401 Unauthorized veya 500 Internal Server Error) kırmızı vurgulanarak analistin reaksiyon süresi hızlandırılır.

Olay Müdahalesi (Incident Response) Açısından Önemi

Brute-force veya Web Shell yükleme gibi saldırı senaryolarında anlık HTTP durum kodlarının (401, 403, 500) renk ayrımlarıyla uçbirime akması, saldırının başladığı anı saniyeler içinde tespit etmeyi mümkün kılar.


Kaynakça ​

Büyük Boyutlu Logların İşlenmesi ve CLI Renklendirme İçin Kaynakça