# Hata Ayıklama ve Loglar

https://dev.wisecp.com/tr/hata-ayiklama-ve-loglar

Gerçekte ne olduğunu öğrenin: hatalar nereye kaydediliyor ve kabuktan nasıl okunur. Sessiz bir sayfa çoğu zaman eksik değil, yakalanmış bir hatadır.

## Genel Bakış

Hatalar kendiliğinden görünmez. Hepsi, çağıranın ne göreceğine karar veren tek bir işleyiciden geçer. Bu bir HTML hata sayfası, bir JSON hata gövdesi ya da teşhisler kapalıysa hiçbir şey olabilir. Her durumda hata, hata günlüğüne yazılır ve okuduğunuz şey o günlüktür.

Ekranda mesaj olmaması size neredeyse hiçbir şey söylemez; günlük her şeyi söyler.

## Ön Koşullar

- Üzerinde çalıştığınız kurulumda teşhislerin açık olması.
- Sitenin çalıştığı PHP'nin aynısına kabuk erişimi. Araç HTTP üzerinden 404 döner; yalnızca komut satırı aracıdır.

## Adım Adım

### Genel Görünümle Başlayın

1. `php coremio/errlog.php stats` çalıştırın.
2. Ölümcül ve hata sayılarını, bir de son yirmi dört saati okuyun. Sıçrayan sayı, bakılacak yerdir.
3. Komut işe yarar bir şey göstermiyorsa loglama kapalıdır ya da depolama dizini yazılabilir değildir. Önce onu düzeltin.

### Hatayı Bulun

1. Son kayıtları `php coremio/errlog.php list --limit=10` ile listeleyin ya da `--level=FATAL` ile daraltın.
2. Her kayıt bir imza, bir sayaç, dosya ve satır ile en son görülme zamanını taşır.
3. Birini `php coremio/errlog.php show <imza>` ile açın: tam kayıtta istek ve yığın vardır.

### Bildirilen Kimliği İzleyin

1. Bir kullanıcıya ya da API çağıranına ulaşan hata, bir hata kimliği taşır.
2. `php coremio/errlog.php find <kimlik>` ile arayın.
3. O tek olayın kaydını alırsınız; kullanıcı bildirimini üzerinde çalışılabilir kılan şey budur.

## Referans

Tek giriş noktası, sekiz komut. Argümanlar sıralıdır, seçenekler herhangi bir sırada `--ad=değer` biçimindedir. Komutun okumadığı seçenek yok sayılır.

| Komut | Argüman | Seçenekler | Ne yapar |
| --- | --- | --- | --- |
| `help` | yok | yok | Komut listesi. Varsayılan da budur, `-h` da buna karşılık gelir. |
| `stats` | yok | yok | Seviye ve tipe göre toplamlar, bir de son yirmi dört saat. |
| `list` | yok | `--level` `--type` `--limit` `--offset` `--file` | Manifest'teki kayıtlar; en son görülen üstte, imza başına bir satır. |
| `find` | hata kimliği | yok | O kimlik için tüm log dosyalarını tarar ve kaydı gösterir. Eşleşme yoksa çıkış kodu 1. |
| `show` | imza | yok | Tek bir kaydı çözüp tamamını döker: istek, mesaj, bağlam, yığın, POST gövdesi. |
| `delete` | imza | `--force` | Tek bir log dosyasını ve manifest satırını siler. Zorlanmadıkça onay sorar. |
| `cleanup` | yok | `--days` `--force` | N günden eski dosyaları iki log tipinde de siler. Zorlanmadıkça onay sorar. |
| `rebuild` | yok | yok | Her log dosyasını taze bir manifest'e yeniden dizinler; dosyalar elle kopyalandıktan ya da silindikten sonra. |

İki sıralı argüman birbirinin yerine geçmez. İmza, bir çağrı noktasının 32 karakterlik parmak izidir ve her `list` satırının altında görünür. Hata kimliği ise hatayla karşılaşan kişiye gösterilen kısa koddur. İkisi de büyük küçük harf duyarsız eşleştirilir; birini diğerinin yerine vermek biçim kontrolüne takılır.

### Seçenekler

- **--level=AD**: Şunlardan biri: `FATAL`, `ERROR`, `WARNING`, `INFO`, `DEBUG`, `DEPRECATED`. Büyük küçük harf önemsizdir. Koşu başına tek seviye.
- **--type=AD**: `system` ya da `database`. İkisi ayrı saklanır ve `stats` onları ayrı sayar.
- **--limit=N**: Kaç satır basılacağı. Varsayılan 20, en az 1.
- **--offset=N**: Kaç satır atlanacağı; sayfalama için. Varsayılan 0.
- **--file=DESEN**: Kayıtlı yolun büyük küçük harf duyarsız bir alt dizesi, glob değil. Bir dizin parçası bütün bir alt sistemi daraltır.
- **--days=N**: `cleanup` için gün cinsinden saklama süresi. Varsayılan 30; 1'in altındaki bir değer komutu durdurur.
- **--force**: `delete` ve `cleanup` onayını atlar.

### Yazan Taraf

CLI'nin okuduğu her şey bu iki sınıf üzerinden yazıldı. İki yazma metodu da `find` komutunun geri aldığı hata kimliğini döndürür.

```php
// coremio/classes/MioException.php
public static function getInstance(): MioException;
public function logError(string $message, array $context = [], string $level = 'ERROR', string $type = 'system'): string;
public function logDatabaseError(string $query, string $error, array $params = []): string;
public function sanitizeData(array $data): array;

// coremio/classes/Logger.php
public function log(string $level, string $message, array $context = [], string $type = 'system'): string;
public static function stats(): array;
public static function entries(array $filters = [], int $start = 0, int $limit = 50, array $sort = []): array;
public static function entriesCount(array $filters = []): int;
public static function cleanup(int $daysToKeep = 30, ?int $cutoffTimestamp = null, string $type = 'system'): int;
public static function deleteOne(string $signature): bool;
public static function rebuild(): int;
```

`list` komutunun arkasındaki filtre dizisi, CLI'nin açtığından fazlasını kabul eder. Bilinmeyen bir anahtar yok sayılır, yani bir yazım hatası sonucu daraltmaz, sessizce genişletir.

| Filtre anahtarı | Değer | Eşleşme |
| --- | --- | --- |
| `levels` | seviye adlarından oluşan dizi | tam, büyük harfe çevrilir |
| `types` | `system` ya da `database` dizisi | tam |
| `signature` | dize | alt dize |
| `file` | dize | alt dize, büyük küçük harf duyarsız |
| `message` | dize | kayıtlı önizlemenin alt dizesi |
| `exception_class` | dize | alt dize |
| `request_uri` | dize | alt dize |
| `http_method` | `GET`, `POST` vb. | tam, büyük harfe çevrilir |
| `user_id` | int | tam; 0 ise yok sayılır |
| `ip` | dize | alt dize |
| `min_occurrence` | int | bu sayı ve üzeri |
| `start_datetime` / `end_datetime` | tarih çözücünün kabul ettiği her şey | son görülme üzerinde pencere |

Sıralama dizisi `['field' => 'last_seen', 'order' => 'desc']` biçimindedir. İşe yarayan alanlar `last_seen`, `first_seen`, `count` ve `level`; sonuncusunda sıralama alfabeye göre değil ciddiyete göre yapılır.

### Bir Hata Nasıl Görünür

| İstek | Teşhis açık | Teşhis kapalı |
| --- | --- | --- |
| Normal sayfa, ölümcül | Ayrıntılı hata sayfası | Genel hata sayfası |
| Normal sayfa, ölümcül değil | Sayfada bildirim | Görünür bir şey yok |
| AJAX ya da API | Ayrıntılı JSON hatası | Kimlikli JSON hatası |
| Kanca dinleyicisi | Yakalanır ve loglanır; akış her hâlükârda devam eder |  |
| Komut satırı | Günlüğe yazılır; çıktı tamponu mesajı gizleyebilir |  |

## Örnek

```bash
# Önce sağlık: düşen bir şey var mı?
php coremio/errlog.php stats

# En son beş ölümcül hata
php coremio/errlog.php list --level=FATAL --limit=5

# Yalnız tek bir alt sistemden çıkanlar, onarlık ikinci sayfa
php coremio/errlog.php list --file=modules/Servers --limit=10 --offset=10

# Yalnız veritabanı hataları
php coremio/errlog.php list --type=database --limit=5

# Tek bir imzanın tam kaydı
php coremio/errlog.php show f05bc1f49d2963a58a7c378dc3430c48

# Kullanıcı bir hata kimliği bildirdi; o olayı ara
php coremio/errlog.php find 25EE72D6527

# Düzeltilen imzayı at, kalanı bir haftaya kırp
php coremio/errlog.php delete f05bc1f49d2963a58a7c378dc3430c48 --force
php coremio/errlog.php cleanup --days=7 --force

# Log dosyaları elle taşındıktan ya da silindikten sonra yeniden dizinle
php coremio/errlog.php rebuild
```

Aynı kayıtlar PHP'den. Yazmanın döndürdüğü kimlik, CLI'nin geri okuduğu kimliktir.

```php
// Yazma: `errlog.php find` komutuna vereceğiniz kimliği döndürür.
$id = MioException::getInstance()->logError(
    'Provisioning refused by the panel',
    ['service_id' => 41, 'response' => $body],
    Logger::LEVEL_ERROR,
    Logger::TYPE_SYSTEM
);

// Okuma: `errlog.php list` komutunun bastığı satırların aynısı.
$rows = Logger::entries(
    ['levels' => [Logger::LEVEL_FATAL], 'file' => 'modules/Servers'],
    0,
    10,
    ['field' => 'count', 'order' => 'desc']
);

foreach ($rows as $row)
    echo $row['signature'], '  x', (int) ($row['count'] ?? 1), '  ',
         $row['file'] ?? '-', ':', (int) ($row['line'] ?? 0), '  ',
         $row['message_preview'] ?? '', PHP_EOL;

// Toplamlar: total, system, database, fatal, error, warning, info,
// last_24h, occurrences ve seviyeye göre ayrılmış recent[].
$health = Logger::stats();
```

## Tuzaklar

> **Sessiz bir komut satırı başarılı demek değildir**
> 
> Başlangıçta bir çıktı tamponu kurulur, yani standart çıktıya yazılan mesaj kaybolabilir. Teşhis çıktısını standart hataya yazın ve bir script'in hiçbir yanlış yapmadığına inanmadan önce günlüğe bakın.

> **Her uyarı bir disk yazımına mal olur**
> 
> İşleyici ölümcül olmayan uyarıları da kaydeder; yani döngü içinde okunan eksik bir dizi anahtarı adım başına bir yazıma dönüşür. Değerleri varsayılanla okuyun.

> **--force olmadan yıkıcı komutlar cevap bekler**
> 
> `delete` ve `cleanup` terminalden evet ya da hayır okur. Zamanlanmış bir işte ya da boruya bağlı bir kabukta cevap verecek kimse yoktur; komut ya bloke olur ya hiçbir şey yapmaz. Seçeneği bilerek verin.

> **Yüksek bir tekrar sayısı tek bir sayfa yüklemesi olabilir**
> 
> Kayıtlar imzaya göre gruplanır ve sayılır. İlk ve son görülme aynı ana denk geliyorsa o satır tek istekte defalarca çalışmıştır. Bu, döngü içindeki bir uyarının parmak izidir.

## İlgili Makaleler

- [Geliştirme Ortamı Kurulumu](https://dev.wisecp.com/tr/gelistirme-ortami-kurulumu)
- [Hata Yönetimi](https://dev.wisecp.com/tr/hata-yonetimi)
- [Kod Konvansiyonları](https://dev.wisecp.com/tr/kod-konvansiyonlari)
- [Çekirdek Yükseltmesini Atlatma](https://dev.wisecp.com/tr/cekirdek-yukseltmesini-atlatma)
