İçeriğe geç
Muhammet Şafak
en
Soran: Oğuz Cevaplandı:

Go HTTP sunucusunda panic'i recovery middleware ile nasıl yönetirim?


Soru

Go ile yazılı HTTP API'mizde nil pointer dereference yüzünden beklenmedik bir panic oluştu. Bu panic tüm sürecin aniden kapanmasına ve o an içeride olan tüm HTTP isteklerinin drop edilmesine sebep oldu. Go'nun `recover()` mekanizmasıyla panic'leri yakalayan, loglayan ve istemciye zarif bir 500 dönen merkezi bir middleware'i nasıl kurarım?

Cevap

Kısa cevap: net/http bir handler’daki panic’i zaten kendisi recover eder; yani tek bir kötü istek süreci öldürmez. Ama istemciye düzgün bir yanıt da gitmez: bağlantı kopar ve stack trace yalnızca sunucunun hata loguna yazılır; varsayılanda bu stderr’dir. Recovery middleware’i kontrollü bir 500, request id’li yapılandırılmış log ve bir metrik için gerekir — ve http.ErrAbortHandler’ı yeniden fırlatmalıdır.

Kısa cevap

Asıl mesele şu: sunucunun kendi recover’ı bir güvenlik ağıdır, hata yönetimi değil. Handler’daki bir nil deref, binlerce sağlıklı isteği sizinle birlikte öldürmez; ama panic eden isteğin sahibi cevap yerine kopmuş bir bağlantı görür ve geriye yalnızca stderr’de ham bir stack trace kalır. Süreciniz gerçekten kapandıysa o güvenlik ağının dışında bir panic arayın: go ile açılmış bir goroutine’de kimsenin recover etmediği panic programı sonlandırır. Servisi standart kütüphaneyle kurmanın detayını Go’da HTTP servisi yazmak yazısında anlatmıştım.

Neden

  1. Yerleşik recover ne yanıt verir ne sinyal. Panic eden bir handler için sunucu panic’i recover eder, stack trace’i hata loguna yazar ve ağ bağlantısını kapatır ya da HTTP/2 RST_STREAM gönderir. İstemci 500 görmez, log satırında request id yoktur, hiçbir metrik artmaz.

  2. Recover yalnızca kendi goroutine’ini yakalar. Sunucunun recover’ı handler’ı çalıştıran goroutine’i kapsar. Handler içinde go func(){...}() açtıysanız, oradaki panic ne sunucunun ne de middleware’inizin recover’ına takılır — ve bütün süreci sonlandırır; içerideki tüm istekleri drop eden durum budur.

  3. Sessizce yutulan panic, recover edilmemesi kadar tehlikelidir. Metrik yoksa aynı bug günlerce gizli kalır.

Ne yapmalı

  1. Her isteği defer recover() ile sarın. Middleware içinde defer func(){ if r := recover(); r != nil { /* logla + 500 yaz */ } }() kurun.

  2. Bu middleware’i zincirin en dışına koyun. Recovery en dıştaki katman olmalı ki kendisinden sonra gelen tüm handler ve middleware’lerin panic’lerini sarsın.

  3. Stack trace’i + request id’yi loglayın, ama içini sızdırmayın. debug.Stack() ile stack trace’i ve isteğin id’sini loglayın; istemciye generic bir 500 dönün.

  4. http.ErrAbortHandler’ı yeniden fırlatın. Yakalanan değer http.ErrAbortHandler ise 500 yazmak yerine aynı değeri yeniden fırlatın. Handler’ların yanıtı bilerek yarıda kesmek için kullandığı işaret değeri budur; yeniden fırlatmak sunucunun bu değere uyguladığı davranışı korur: istemci yarım kalmış bir yanıt görür, stack trace loglanmaz.

  5. Panic’i görünür kılın — bir metrik basın. Her recover’da bir sayaç artırın.

  6. Açtığınız her goroutine’e kendi recover’ını verin. Aksi hâlde süreç yine ölür.

Sonuç: Ben olsam kontrollü bir 500 dönen, request id ile loglayan, panic’i sayan ve http.ErrAbortHandler’ı yeniden fırlatan bir istek başı recover middleware’i + her goroutine için ayrı recover kurardım. İki uyarı: recover, beklenmedik bug’lar (nil deref) içindir, hata dönmenin yerine geçmez — beklenen hataları error ile dönün. Ve buna rağmen süreci yeniden başlatan bir supervisor tutun.

İlgili Yazılar

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi