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

> Zincirin en dışına `defer recover()` koyun, stack'i request id ile loglayıp generic 500 dönün, metrik basın, her goroutine'e kendi recover'ını verin.

- Soruldu: 2026-06-01
- Yanıtlandı: 2026-06-04
- Soran: Oğuz
- Etiketler: dayaniklilik, go
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/go-da-panic-yonetimi-ve-recovery-middleware/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**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?


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](/tr/blog/go-da-http-servisi-yazmak-standart-kutuphane-yeter-mi/) 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

- [Go'da HTTP servisi yazmak: standart kütüphane yeter mi](/tr/blog/go-da-http-servisi-yazmak-standart-kutuphane-yeter-mi/) — Blog
- [Kanal tampon boyutu seçimi backpressure sorunlarını gizler mi, nasıl karar vermeliyim?](https://muhammetsafak.com/tr/sor-bakalim/kanal-tampon-boyutu-backpressure-sorunlarini-gizler-karar-vermeliyim/) — Sor Bakalım
- [Container'daki Go worker'ım SIGTERM almıyor ve graceful kapanmıyor, bu bir PID 1 sorunu mu?](https://muhammetsafak.com/tr/sor-bakalim/containerdaki-go-workerim-sigterm-almiyor-pid-1-sorunu/) — Sor Bakalım
- [Birbirine bağımlı birden fazla işi, ilk hata hepsini iptal edecek şekilde errgroup ile mi yürütmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/birbirine-bagimli-birden-fazla-isi-ilk-hata-hepsini-iptal-edecek-sekilde/) — Sor Bakalım
