GinJuniorTechnical

Как Gin обрабатывает паники и что такое Recovery middleware?

Recovery — встроенный middleware Gin, который ловит паники через defer/recover, возвращает клиенту HTTP 500 и логирует стек вызовов, не останавливая сервер. Подключается через gin.Default() или явно r.Use(gin.Recovery()).

Recovery middleware в Gin

Recovery — встроенный middleware Gin, который перехватывает паники (panic) в обработчиках и middleware, предотвращая аварийное завершение всего HTTP-сервера. После перехвата паники Recovery возвращает клиенту ответ 500 Internal Server Error и записывает стек вызовов в лог.

Как подключить Recovery

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	// gin.Default() включает Logger + Recovery автоматически
	r := gin.Default()

	// Или вручную через gin.New():
	// r := gin.New()
	// r.Use(gin.Logger())
	// r.Use(gin.Recovery())

	r.GET("/panic", func(c *gin.Context) {
		panic("something went wrong!")
	})

	r.GET("/ok", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	r.Run(":8080")
}

После запроса GET /panic сервер вернёт 500, но продолжит обрабатывать GET /ok — паника не убивает процесс.

Что делает Recovery внутри

Recovery оборачивает вызов c.Next() в defer с recover(). При панике он:

  • Вызывает recover() и получает значение паники.
  • Проверяет, не разорвано ли соединение клиентом (brokenPipe).
  • Логирует стек вызовов через debug.Stack().
  • Возвращает 500, если ответ ещё не был записан.

Кастомный Recovery с логированием

import (
	"log/slog"
	"net/http"
	"runtime/debug"

	"github.com/gin-gonic/gin"
)

func CustomRecovery() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				stack := debug.Stack()
				slog.Error("panic recovered",
					"error", err,
					"path", c.Request.URL.Path,
					"method", c.Request.Method,
					"stack", string(stack),
				)
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
					"error": "internal server error",
				})
			}
		}()
		c.Next()
	}
}

func main() {
	r := gin.New()
	r.Use(CustomRecovery())
	r.Run(":8080")
}

RecoveryWithWriter — запись стека в произвольный io.Writer

import (
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.New()
	// Пишем стек паники в stderr
	r.Use(gin.RecoveryWithWriter(os.Stderr))
	r.Run(":8080")
}

Подводные камни

  • Recovery не перехватывает панику в goroutine. Если хендлер запускает go func() { panic(...) }(), Recovery не поможет — goroutine упадёт и убьёт процесс. Добавляйте собственный defer/recover внутри каждой goroutine.
  • Паника после записи заголовков. Если c.JSON() уже был вызван до паники, Recovery не сможет изменить статус ответа — клиент получит частичный ответ с кодом 200.
  • gin.Default() подключает Recovery автоматически. Добавление ещё одного r.Use(gin.Recovery()) удвоит Recovery в цепочке. Проверяйте, не используете ли вы оба варианта одновременно.
  • Стек трейс в продакшене. Стандартный Recovery выводит полный стек в stdout. В продакшене перехватывайте панику кастомным Recovery и отправляйте в Sentry/Datadog, а не в открытый лог.
  • brokenPipe паники логируются на уровне warn, не error. Gin специально обрабатывает разрыв соединения как ненастоящую ошибку сервера — убедитесь, что ваш кастомный Recovery делает то же самое.
  • Порядок middleware важен. Recovery должен быть первым в цепочке — только тогда он перехватит паники из всех последующих middleware и хендлеров.

Common mistakes

  • Давать ответ про Recovery middleware в Gin только на уровне определения, не показывая поведение в реальном приложении.
  • Игнорировать границы ответственности вокруг темы «Recovery middleware в Gin»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
  • Не связывать Recovery middleware в Gin с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.

What the interviewer is testing

  • Точно объясняет, что именно делает Recovery middleware в Gin и где это используется в Go-коде.
  • Связывает Recovery middleware в Gin с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
  • Не изобретает API и опирается на реальные контракты официальной документации.

Sources

Related topics