Hata Yönetimi

9 görüntülenme Markdown

Tek bir işleyici her şeyi yakalar ve çağıranın ne göreceğine karar verir. Hata yanıtı kurmak yerine fırlatmanızın sebebi budur; sessiz bir arıza da neredeyse her zaman yakalanmış bir arızadır.

Genel Bakış

Başlangıç; PHP hataları, yakalanmamış exception'lar ve ölümcül kapanışlar için işleyiciler kurar. O noktadan sonra hiçbir şey kendi başına düşmez. İşleyici kaydeder, sonra isteğin beklediği biçimde gösterir: bir HTML sayfası ya da bir JSON gövdesi.

Operation'ın içinde ikinci bir katman vardır. Dağıtıcı metodunuzu bir try bloğuna sarar; böylece fırlattığınız exception, metniniz mesajı olacak şekilde hata yanıtına dönüşür. Bir değişikliğin hata yolu bundan ibarettir: fırlat ve yazmayı bırak.

Referans

Hata Bildirme

Operation'lar Mesajı çeviri katmanından gelen bir exception fırlatın. Dağıtıcı onu hata yanıtına çevirir.
Modüller Operation'ın yaptığı gibi fırlatın. Çağıran yakalar ve mesajı gösterir. Bir $error özelliğine yazıp false döndüren eski bir modül, önceki ana sürümden kalma bir deseni taşıyordur. Taban sınıflar bunu hâlâ fırlatmaya çevirir, ama yeni kod böyle yazılmaz.
Yardımcılar Yanlış değerlendirilen bir değer döndürüp kararı çağırana bırakın ya da çağıran her zaman bir operation ise fırlatın.

Kaydetme

MioException, getInstance() ile alınan bir örnek
public static function getInstance(): MioException;
public function initialize(): void;

public function logError(string $message, array $context = [], string $level = Logger::LEVEL_ERROR, string $type = Logger::TYPE_SYSTEM): string;
public function logDatabaseError(string $query, string $error, array $params = []): string;
public function sanitizeData(array $data, int $depth = 0): array;   // $depth iç özyineleme; siz geçmezsiniz

public static function showErrors(): void;
Logger, çoğunlukla statikleriyle kullanılır
public static function getInstance(): Logger;
public function log(string $level, string $message, array $context = [], string $type = self::TYPE_SYSTEM): string;

// Seviye başına bir statik, hepsi aynı biçimde.
public static function debug(string $message, array $context = [], string $type = self::TYPE_SYSTEM): string;
public static function info(string $message, array $context = [], string $type = self::TYPE_SYSTEM): string;
public static function warning(string $message, array $context = [], string $type = self::TYPE_SYSTEM): string;
public static function error(string $message, array $context = [], string $type = self::TYPE_SYSTEM): string;
public static function fatal(string $message, array $context = [], string $type = self::TYPE_SYSTEM): string;

public static function database(string $query, string $error, array $params = []): string;

// Geri çağrımı koşar, fırlattığı her şeyi kaydeder ve yeniden fırlatmak yerine null döner.
public static function safe(callable $callback, array $context = [], string $errorMessage = 'Operation failed');
MioException::getInstance() İşleyici. Başlangıçta initialize() ile kurulan bir singleton.
logError() Bir hatayı bağlamıyla kaydeder ve kimliği döndürür; bu, çağırana gösterilen kimliğin ta kendisidir.
logDatabaseError() Sorgu için aynısı. İfade birinci, mesaj ikinci sıradadır; kurduğu bağlam ['query' => ..., 'parameters' => ...] biçimindedir.
sanitizeData() Hassas değerleri değiştirilmiş bir kopya döndürür. Bir şey loglamadan önce istek verisini buradan geçirin.
Logger::error() Günlük kullanımdaki biçim, seviye başına bir statik. İşleyicinin metoduyla aynı sonucu verir, singleton'a uzanmadan.
Logger::safe() İsteği kendisiyle birlikte düşürmemesi gereken işler için; örneğin sayfa gösterimi içindeki bir sağlayıcı çağrısı.
Logger::findById() Bir Hata Kimliği'nin arkasındaki kayıt, yoksa null. Destek talebi o kimlikle gelir; müşterinin mesajından saklanan bağlama böyle ulaşırsınız.

Seviyeler ve Tipler

İkisi de düz dizedir ve güvenle yazılabilecek tek yazım sabitlerdir. Her kayıt çağrısı kimliği dize olarak döndürür. En az altı karaktere tamamlanmış büyük harfli onaltılık sayı, ardından üç rakam. Yani sabit genişlikte değil, 4F59C9BC644 gibi. Metin olarak saklayın, uzunluğuna göre doğrulama yapmayın.

SabitDeğerNe için
Logger::LEVEL_DEBUGDEBUGGeliştirme izleri; yayına giden bir yolda kullanılmaz.
Logger::LEVEL_INFOINFOSonradan doğrulanmak istenebilecek bir şey oldu.
Logger::LEVEL_WARNINGWARNINGToparlandı, kalitesi düştü, yeniden denendi.
Logger::LEVEL_ERRORERRORVarsayılan. İşlem kendisinden isteneni yapmadı.
Logger::LEVEL_FATALFATALİstek devam edemez. Kapanış işleyicisi yazar.
Logger::LEVEL_DEPRECATEDDEPRECATEDMotorun kullanımdan kaldırma uyarıları; ayıklanabilsin diye ayrı tutulur.
Logger::TYPE_SYSTEMsystemVarsayılan tip; sorgu olmayan her şey.
Logger::TYPE_DATABASEdatabaseSorgu hataları. Gürültülü tek bir sorgu diğerlerini gömmesin diye ayrı günlükte tutulur.

Maskeleme Gerçekte Neyi Yakalar

Küçük harfe çevrilmiş anahtar adı şunlardan birini içeriyorsa değer maskelenir: password, pass, passwd, pwd, secret, token, key, auth, api_key, private_key. Alt dize testidir, iç içe dizilere özyinelemeli uygulanır ve yalnız anahtarlara bakar.

giren ve çıkan
$in = [
    'email'      => '[email protected]',
    'Password'   => 's3cr3t',            // büyük-küçük harf gözetmeden eşleşir
    'api_token'  => 'abc123',            // "token" içeriyor
    'keyword'    => 'hosting',           // "key" içeriyor - o da maskelenir
    'server'     => ['auth_user' => 'root', 'port' => 22],
];

$out = MioException::getInstance()->sanitizeData($in);

// [
//     'email'     => '[email protected]',
//     'Password'  => '***REDACTED***',
//     'api_token' => '***REDACTED***',
//     'keyword'   => '***REDACTED***',
//     'server'    => ['auth_user' => '***REDACTED***', 'port' => 22],
// ]

Çağıran Ne Görür

DurumYanıt
Operation içinde fırlatılan exception{"status":"error","message":"<mesajınız>"}
AJAX isteği sırasında işleyicinin yakaladığı hata{"status":"error","message":"Error ID: <kimlik>: <metin>","error_id":"<kimlik>"}, teşhisler açıkken ayrıca bir debug alanı
Sayfa sırasında ölümcül hataHata sayfası; ayrıntı yalnız teşhisler açıkken
Ölümcül olmayan uyarıKaydedilir; sayfada yalnız teşhisler açıkken görünür
Komut satırında her şeyKaydedilir; mesaj çıktı tamponu tarafından yutulabilir

Örnek

hata vereceğiniz iki yerde hata vermek
// Operation'da: fırlatın, cevabı dağıtıcı versin.
public function delete_widget(Operation $operation): bool
{
    $operation->demo();

    $id = (int) Filter::init("POST/id", "rnumbers");
    if (!$id) throw new Exception(Language::gc("widgets/error-id-required"));

    if (!$this->model->delete($id)) throw new Exception(Language::gc("widgets/error-delete-failed"));

    return $operation->output(['status' => "successful"]);
}

// Modülde: fırlatın. Çağıran yakalar ve mesajı yüzeye çıkarır.
public function create(): array|false
{
    $response = $this->api->create($params);

    if (!($response['success'] ?? false))
        throw new Exception((string) ($response['message'] ?? $this->lang["err-provider"]));

    return ['status' => 'SUCCESS'];
}
diğer taraf: kaydetme ve tarayıcının aldığı yanıt
// Kendiniz ele aldığınız bir şeyi kaydetme. Önce maskeleyin; geri dönen kimlik,
// operatörün okuyacağı mesajda anılacak kimliktir.
$mio = MioException::getInstance();
$id  = $mio->logError("Sağlayıcı transferi reddetti", $mio->sanitizeData($_POST), Logger::LEVEL_WARNING);

throw new Exception(Language::gc("services/error-transfer", ['{id}' => $id]));

// Tarayıcı bunun ardından operation dağıtıcısından şunu alır:
//   {"status":"error","message":"Transfer başarısız. Referans: 4F59C9BC644"}
//
// Komut satırında geri okumak:
//   php coremio/errlog.php list --limit=10
//   php coremio/errlog.php show <imza>

Tuzaklar

Yan yana üç çağrı, üç farklı ilk argüman

logError() mesajla, logDatabaseError() sorguyla başlar ve mesajı ikinci sırada alır, Logger::log() ise seviyeyle başlar. Veritabanı çağrısında ilk ikisini yer değiştirmek sessizdir: mesajı bir SQL ifadesi, kayıtlı sorgusu bir hata metni olan bir günlük kaydı elde edersiniz.

Her uyarı bir disk yazımıdır

İşleyici ölümcül olmayan uyarıları da kaydeder ve her kayıt bir yazımdır. Döngü içinde okunan eksik bir dizi anahtarı bu yüzden adım başına bir yazıma mal olur; değerlerin umutla değil varsayılanla okunmasının sebebi budur.

Loglamadan önce maskeleyin, önce de eşleşme kuralını okuyun

Günlük, isteği aşan ve etrafta kopyalanan bir dosyadır. Maskeleme anahtar adları üzerinde bir alt dize testidir, yani bir yönde cömert diğer yönde kördür. card ya da answer adlı bir alan hiç dokunulmadan geçer. Ham istek verisini geçip listenin sizin alanınızı kapsadığını varsaymayın.

Sessiz arıza da kaydedilir

Ekranda bir şey olmaması, işleyicinin onu göstermemeye karar verdiği anlamına gelir; hiçbir şey olmadığı anlamına değil. Bir kod yoluna hiç ulaşılmadığı sonucuna varmadan önce günlüğü okuyun.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.