Uygulama Loglarını Yapılandırma: Python ve PHP

Uygulama logları, bir sistemin sağlığını anlamak için en kritik veri kaynaklarından biri. Ancak çoğu geliştirici ve sysadmin, loglama konusunu “print ekleyip geç” mantığıyla ele alır. Sonuç? Production ortamında bir sorun çıktığında, logların ya çok az bilgi içerdiğini ya da tamamen anlamsız bir gürültü yığınına dönüştüğünü görürsünüz. Bu yazıda Python ve PHP uygulamalarında loglama altyapısını nasıl düzgün kuracağınızı, log rotasyonundan merkezi log yönetimine kadar pratik örneklerle ele alacağız.

Python Loglama Altyapısı

Python’un standart kütüphanesindeki logging modülü oldukça güçlü, ama doğru yapılandırılmadığında ya hiç çalışmaz ya da sizi boğar. Temel konseptleri anlamak önemli: Logger, Handler, Formatter ve Filter. Bu dört bileşen bir araya gelince ciddi bir loglama sistemi oluşturabilirsiniz.

Temel Logger Yapılandırması

En basit haliyle bir logger şöyle kurulur:

# Önce gerekli paketi kuralım (production için)
pip install python-json-logger
# logger_config.py
import logging
import logging.handlers
import os
from pythonjsonlogger import jsonlogger

def setup_logger(app_name, log_dir="/var/log/myapp", level=logging.INFO):
    """
    Uygulama genelinde kullanılacak merkezi logger yapılandırması.
    """
    os.makedirs(log_dir, exist_ok=True)
    
    logger = logging.getLogger(app_name)
    logger.setLevel(level)
    
    # Duplicate handler'ı önle
    if logger.handlers:
        return logger
    
    # JSON formatter - log aggregation araçlarıyla uyumlu
    formatter = jsonlogger.JsonFormatter(
        fmt='%(asctime)s %(name)s %(levelname)s %(message)s',
        datefmt='%Y-%m-%dT%H:%M:%S'
    )
    
    # Rotating file handler - 10MB, 5 yedek dosya
    file_handler = logging.handlers.RotatingFileHandler(
        filename=os.path.join(log_dir, f"{app_name}.log"),
        maxBytes=10 * 1024 * 1024,  # 10MB
        backupCount=5,
        encoding='utf-8'
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.DEBUG)
    
    # Console handler - sadece WARNING ve üstü
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.WARNING)
    console_handler.setFormatter(logging.Formatter(
        '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
    ))
    
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger

Burada dikkat edilmesi gereken nokta: if logger.handlers kontrolü. Django veya Flask uygulamalarında bu kontrolü yapmazsanız, her request’te handler’lar çoğalır ve loglarınız katlanarak büyür.

TimedRotatingFileHandler ile Günlük Log Rotasyonu

RotatingFileHandler boyut bazlı çalışır, ama çoğu zaman tarih bazlı rotasyon daha mantıklıdır. Özellikle monitoring araçlarıyla entegrasyon için:

# timed_logger.py
import logging
import logging.handlers
from datetime import datetime

def setup_timed_logger(app_name, log_dir="/var/log/myapp"):
    logger = logging.getLogger(app_name)
    logger.setLevel(logging.DEBUG)
    
    if logger.handlers:
        return logger
    
    # Her gece yarısı yeni dosya, 30 gün tutulsun
    timed_handler = logging.handlers.TimedRotatingFileHandler(
        filename=f"{log_dir}/{app_name}.log",
        when='midnight',
        interval=1,
        backupCount=30,
        encoding='utf-8',
        utc=True  # Sunucu timezone sorunlarını önler
    )
    
    # Dosya adına tarih ekle
    timed_handler.suffix = "%Y-%m-%d"
    
    formatter = logging.Formatter(
        '%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    timed_handler.setFormatter(formatter)
    
    logger.addHandler(timed_handler)
    return logger


# Kullanım örneği
logger = setup_timed_logger("payment_service")

def process_payment(order_id, amount, user_id):
    logger.info(f"Ödeme işlemi başladı", extra={
        'order_id': order_id,
        'amount': amount,
        'user_id': user_id
    })
    
    try:
        # Ödeme işlemleri...
        result = charge_card(order_id, amount)
        logger.info(f"Ödeme başarılı", extra={
            'order_id': order_id,
            'transaction_id': result.get('transaction_id')
        })
        return result
    except PaymentDeclinedException as e:
        logger.warning(f"Kart reddedildi: {e}", extra={
            'order_id': order_id,
            'error_code': e.code
        })
        raise
    except Exception as e:
        logger.error(f"Beklenmeyen ödeme hatası: {e}", extra={
            'order_id': order_id
        }, exc_info=True)
        raise

exc_info=True parametresi, exception stack trace’ini log satırına ekler. Production’da hata ayıklarken bu bilgi olmadan yapabileceğiniz pek az şey vardır.

Flask Uygulamasında Loglama

# flask_app.py
import logging
import uuid
from flask import Flask, request, g
from logger_config import setup_logger

app = Flask(__name__)

# Uygulama logger'ı
app_logger = setup_logger("flask_app", log_dir="/var/log/flask_app")

@app.before_request
def before_request():
    """Her isteğe benzersiz ID ata - distributed tracing için kritik."""
    g.request_id = request.headers.get('X-Request-ID', str(uuid.uuid4()))
    g.start_time = __import__('time').time()
    
    app_logger.info("İstek alındı", extra={
        'request_id': g.request_id,
        'method': request.method,
        'path': request.path,
        'remote_addr': request.remote_addr,
        'user_agent': request.user_agent.string[:100]
    })

@app.after_request
def after_request(response):
    """İstek tamamlandığında süreyi logla."""
    duration = (__import__('time').time() - g.start_time) * 1000
    
    log_level = logging.WARNING if duration > 2000 else logging.INFO
    
    app_logger.log(log_level, "İstek tamamlandı", extra={
        'request_id': g.request_id,
        'status_code': response.status_code,
        'duration_ms': round(duration, 2),
        'response_size': response.content_length
    })
    
    response.headers['X-Request-ID'] = g.request_id
    return response

@app.errorhandler(500)
def internal_error(error):
    app_logger.error("Sunucu hatası", extra={
        'request_id': g.request_id,
        'error': str(error)
    }, exc_info=True)
    return {'error': 'Internal Server Error'}, 500

Burada özellikle request_id kullanımına dikkat edin. Microservice mimarisinde veya yüksek trafikli uygulamalarda, tek bir kullanıcı işlemini loglar arasında takip edebilmek hayat kurtarır.

PHP Loglama Altyapısı

PHP’de durum biraz daha karmaşık. PHP’nin native loglama sistemi error_log() ile sınırlı kalır ve production ortamları için yetersiz. Monolog kütüphanesi bu sorunu büyük ölçüde çözer. Monolog, hem Laravel hem de Symfony için varsayılan olarak gelir, ama standalone projelerde de rahatlıkla kullanılabilir.

Monolog ile Kurulum ve Temel Yapılandırma

# Composer ile kurulum
composer require monolog/monolog

# Log dizinini oluştur ve izinleri ayarla
mkdir -p /var/log/phpapp
chown www-data:www-data /var/log/phpapp
chmod 755 /var/log/phpapp
<?php
// logger.php - Merkezi logger factory

use MonologLogger;
use MonologHandlerRotatingFileHandler;
use MonologHandlerStreamHandler;
use MonologFormatterJsonFormatter;
use MonologProcessorWebProcessor;
use MonologProcessorIntrospectionProcessor;
use MonologProcessorMemoryUsageProcessor;

require_once 'vendor/autoload.php';

class AppLogger
{
    private static array $instances = [];
    
    public static function getLogger(string $channel = 'app'): Logger
    {
        if (isset(self::$instances[$channel])) {
            return self::$instances[$channel];
        }
        
        $logger = new Logger($channel);
        $logDir = '/var/log/phpapp';
        
        // JSON formatter - ELK stack veya Graylog ile uyumlu
        $jsonFormatter = new JsonFormatter();
        
        // Rotating file handler - günlük rotasyon, 30 gün sakla
        $fileHandler = new RotatingFileHandler(
            filename: "{$logDir}/{$channel}.log",
            maxFiles: 30,
            level: Logger::DEBUG
        );
        $fileHandler->setFormatter($jsonFormatter);
        
        // Kritik hatalar için ayrı dosya
        $errorHandler = new StreamHandler(
            stream: "{$logDir}/{$channel}-errors.log",
            level: Logger::ERROR
        );
        $errorHandler->setFormatter($jsonFormatter);
        
        // Request bilgilerini otomatik ekle
        $logger->pushProcessor(new WebProcessor());
        
        // Hangi dosya/satır/fonksiyondan loglandığını ekle
        $logger->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, ['Monolog\']));
        
        // Bellek kullanımını ekle
        $logger->pushProcessor(new MemoryUsageProcessor());
        
        $logger->pushHandler($fileHandler);
        $logger->pushHandler($errorHandler);
        
        self::$instances[$channel] = $logger;
        return $logger;
    }
}

Laravel’de Özel Log Kanalı Yapılandırması

Laravel kullananlar için config/logging.php dosyasında özel kanal tanımlaması:

<?php
// config/logging.php - İlgili kısım

return [
    'default' => env('LOG_CHANNEL', 'stack'),
    
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['daily', 'slack_critical'],
            'ignore_exceptions' => false,
        ],
        
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'days' => 30,
            'replace_placeholders' => true,
        ],
        
        // Ödeme sistemi için ayrı kanal
        'payment' => [
            'driver' => 'daily',
            'path' => '/var/log/laravel/payment.log',
            'level' => 'debug',
            'days' => 90,  // Finansal loglar daha uzun tutulmalı
            'formatter' => MonologFormatterJsonFormatter::class,
        ],
        
        // Kritik hatalar için Slack bildirimi
        'slack_critical' => [
            'driver' => 'slack',
            'url' => env('LOG_SLACK_WEBHOOK_URL'),
            'username' => 'Laravel Log Bot',
            'emoji' => ':boom:',
            'level' => 'critical',
        ],
        
        // Syslog entegrasyonu - systemd ile uyumlu
        'syslog' => [
            'driver' => 'syslog',
            'level' => env('LOG_LEVEL', 'debug'),
            'facility' => LOG_USER,
        ],
    ],
];

PHP-FPM ve Nginx Log Yapılandırması

Uygulama loglarının yanında, PHP-FPM slow log ayarları da kritik. Yavaş istekleri tespit etmek için:

# /etc/php/8.2/fpm/pool.d/www.conf içinde ilgili satırlar

; Slow log - 5 saniyeden uzun süren istekler
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s
request_slowlog_trace_depth = 20

; Access log formatı
access.log = /var/log/php-fpm/www-access.log
access.format = "%R - %u %t "%m %r%Q%q" %s %f %{mili}d %{kilo}M %C%%"

; Hata loglama
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on

Bu yapılandırmayı yaptıktan sonra systemctl reload php8.2-fpm komutuyla aktif edin. Slow log, özellikle N+1 sorgu problemlerini tespit etmede mucizevi çalışır.

Log Seviyelerini Doğru Kullanmak

Hem Python hem PHP’de log seviyeleri konusunda ciddi bir kargaşa yaşanır. Gerçek dünyadan örnek vermek gerekirse, bir e-ticaret projesinde her veritabanı sorgusunu ERROR olarak loglayan bir ekiple karşılaştım. Sonuç: günde 50GB log dosyası ve hiçbir anlam taşımayan alarmlar.

DEBUG: Geliştirme ortamında detaylı iz bırakmak için. Production’da kapalı tutun.

INFO: Normal uygulama akışı. Kullanıcı girişi, sipariş oluşturma, başarılı API çağrıları.

WARNING: Beklenmedik ama uygulamayı durdurmayan durumlar. Yavaş sorgu, rate limit yaklaşımı, deprecated kullanım.

ERROR: İşlemin başarısız olduğu durumlar. Ödeme hatası, veritabanına yazma başarısızlığı. Müdahale gerektirir.

CRITICAL: Sistem genelinde kritik arıza. Veritabanı bağlantısı kesilmesi, servis çökmesi. Anında aksiyon gerektirir.

Log Rotasyonu: Logrotate Yapılandırması

Uygulama tarafında ne kadar iyi yapılandırırsanız yapılandırın, sistem seviyesinde logrotate konfigürasyonu şarttır. Bu, disk dolmasının önündeki son savunma hattıdır:

# /etc/logrotate.d/myapp

/var/log/myapp/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0644 www-data www-data
    sharedscripts
    
    postrotate
        # Flask/Gunicorn'a sinyal gönder
        if [ -f /var/run/gunicorn/gunicorn.pid ]; then
            kill -USR1 $(cat /var/run/gunicorn/gunicorn.pid)
        fi
        
        # PHP-FPM'i yenile
        /usr/sbin/php-fpm8.2 -t && systemctl reload php8.2-fpm > /dev/null 2>&1 || true
    endscript
}

/var/log/phpapp/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0640 www-data adm
    dateext
    dateformat -%Y%m%d
    
    # 100MB'ı geçerse günde birden fazla rotate et
    size 100M
}

delaycompress önemli bir detay: mevcut günün logu sıkıştırılmaz, bir sonraki rotasyonda sıkıştırılır. Bu, aktif yazma yapılan dosyada sıkıştırma sorunlarını önler.

Merkezi Log Toplama: rsyslog Entegrasyonu

Birden fazla sunucunuz varsa, logları merkezi bir yere toplamak zorunlu hale gelir. rsyslog ile basit bir kurulum:

# /etc/rsyslog.d/50-myapp.conf - Uygulama sunucusunda

# Python/PHP loglarını syslog'a yönlendir
module(load="imfile" PollingInterval="10")

input(type="imfile"
    File="/var/log/myapp/flask_app.log"
    Tag="flask_app"
    Severity="info"
    Facility="local0")

input(type="imfile"
    File="/var/log/phpapp/app.log"
    Tag="php_app"
    Severity="info"
    Facility="local1")

# Merkezi log sunucusuna gönder (TCP ile güvenilir iletim)
local0.* @@log-server.internal:514
local1.* @@log-server.internal:514
# Log sunucusunda - /etc/rsyslog.d/00-receive.conf

# TCP ile alma
module(load="imtcp")
input(type="imtcp" port="514")

# Gelen logları uygulamaya göre ayır
template(name="DynamicFile" type="string"
    string="/var/log/central/%HOSTNAME%/%programname%.log")

local0.* ?DynamicFile
local1.* ?DynamicFile

# rsyslog'u yeniden başlat
systemctl restart rsyslog

Gerçek Dünya Senaryosu: Hata Ayıklama Süreci

Bir müşterinin staging ortamında intermittent 500 hataları alıyorduk. Loglar düzgün yapılandırılmamıştı: sadece error_log() çıktıları, timestamp yok, request ID yok. Problemi bulmak iki gün sürdü.

Monolog’u kurup aşağıdaki yapıyı oluşturduktan sonra aynı problemi 20 dakikada tespit ettik:

<?php
// middleware/RequestLogger.php

class RequestLoggingMiddleware
{
    private Logger $logger;
    
    public function __construct()
    {
        $this->logger = AppLogger::getLogger('request');
    }
    
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = $request->header('X-Request-ID') ?? uniqid('req_', true);
        $startTime = microtime(true);
        
        // Request context'i tüm loglar için ayarla
        $this->logger->pushProcessor(function (array $record) use ($requestId, $request): array {
            $record['extra']['request_id'] = $requestId;
            $record['extra']['user_id'] = auth()->id() ?? 'anonymous';
            $record['extra']['ip'] = $request->ip();
            return $record;
        });
        
        $this->logger->info('Request started', [
            'method' => $request->method(),
            'path' => $request->path(),
            'query' => $request->query() ?: null,
        ]);
        
        $response = $next($request);
        
        $duration = (microtime(true) - $startTime) * 1000;
        
        $logData = [
            'status' => $response->getStatusCode(),
            'duration_ms' => round($duration, 2),
        ];
        
        // 3 saniyeden yavaş istekleri özellikle işaretle
        if ($duration > 3000) {
            $this->logger->warning('Slow request detected', $logData);
        } else {
            $this->logger->info('Request completed', $logData);
        }
        
        $response->headers->set('X-Request-ID', $requestId);
        return $response;
    }
}

Bu middleware sayesinde problemi şu şekilde tespit ettik: belirli bir user_id ile gelen istekler tutarlı olarak yavaştı. Request ID’ler sayesinde o kullanıcıya ait tüm log satırlarını tek bir sorguyla çekip analiz edebildik. Sorun, o kullanıcının hesabına bağlı 50.000’den fazla sipariş kaydı nedeniyle tetiklenen N+1 sorgusuydu.

Log Güvenliği: Hassas Veri Maskeleme

Loglar güçlü bir araç, ama dikkatsiz kullanıldığında güvenlik ihlali de yaratabilir. Kredi kartı numaraları, şifreler ve kişisel veriler loglarda asla görünmemeli:

# sensitive_data_filter.py
import re
import logging

class SensitiveDataFilter(logging.Filter):
    """Loglardan hassas verileri maskele."""
    
    PATTERNS = [
        # Kredi kartı numaraları
        (r'bd{4}[-s]?d{4}[-s]?d{4}[-s]?d{4}b', '****-****-****-****'),
        # TC Kimlik numarası
        (r'bd{11}b', '***********'),
        # E-posta adresleri (kısmen maskele)
        (r'([a-zA-Z0-9._%+-]{2})[a-zA-Z0-9._%+-]+(@[a-zA-Z0-9.-]+.[a-zA-Z]{2,})', r'1***2'),
        # Şifre alanları
        (r'"password"s*:s*"[^"]*"', '"password": "***"'),
        (r"'password's*:s*'[^']*'", "'password': '***'"),
    ]
    
    def filter(self, record: logging.LogRecord) -> bool:
        record.msg = self._mask_sensitive(str(record.msg))
        if record.args:
            if isinstance(record.args, dict):
                record.args = {k: self._mask_sensitive(str(v)) for k, v in record.args.items()}
        return True
    
    def _mask_sensitive(self, text: str) -> str:
        for pattern, replacement in self.PATTERNS:
            text = re.sub(pattern, replacement, text)
        return text


# Logger'a filtre ekle
logger = logging.getLogger('payment_service')
logger.addFilter(SensitiveDataFilter())

Sonuç

Uygulama loglarını düzgün yapılandırmak, bir kez yapıp unutulan bir iş değil. Uygulama büyüdükçe, log stratejisini de gözden geçirmeniz gerekir. Özetlemek gerekirse:

  • Yapılandırılmış loglama kullanın: JSON formatı, log aggregation araçlarıyla entegrasyon için zorunlu hale geliyor.
  • Her log satırına context ekleyin: request_id, user_id, duration gibi alanlar olmadan log analizi karanlıkta el yordamıyla yürümek gibidir.
  • Log seviyelerini ciddiye alın: Her şeyi ERROR olarak loglamak, hiçbir şeyi logalamaktan daha kötü olabilir.
  • Rotasyonu ihmal etmeyin: Disk dolan sunucu, log yapılandırmasından daha büyük bir problem yaratır.
  • Hassas verileri maskeleyin: GDPR ve KVKK kapsamında bu artık yasal zorunluluk.
  • Merkezi log toplama altyapısı kurun: İki sunucudan fazlasına ulaştığınızda rsyslog, Graylog veya ELK stack kaçınılmaz hale gelir.

İyi yapılandırılmış bir loglama sistemi, saatlerce süren hata ayıklama oturumlarını dakikalara indirir. Bu yatırımın geri dönüşünü ilk production krizi anında anlarsınız.

Benzer Konular

Bir yanıt yazın

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