Как понять причину 500 ошибки на REST-сервере
Что проверяют
Есть ли у вас порядок действий при инциденте, а не только общие слова про логи. Интервьюеру важно, что вы умеете от кода ответа дойти до конкретной строки. Следом часто спрашивают, что будет с сервером на Go, если в хендлере случится паника.
Как отвечать
Идите от внешнего к внутреннему:
- сузить область: один эндпоинт или все, с какого момента, совпадает ли с деплоем. Графики RED-метрик по маршрутам в Prometheus и Grafana отвечают на это за минуту;
- найти запрос: request id или trace id попадает в заголовок ответа и в каждую строку лога, по нему находите конкретную ошибку в централизованных логах;
- читать структурированный лог: в Go есть стандартный
log/slog, также встречаютсяzapиzerolog. Ошибка обёрнута с контекстом черезfmt.Errorf("load user %d: %w", id, err), тогда в логе видна цепочка, а не голое «connection refused»; - посмотреть трассировку: OpenTelemetry покажет, какой спан упал: запрос в базу, вызов соседнего сервиса или сам хендлер;
- воспроизвести: повторить запрос на стенде и закрепить тестом через
httptest.
Про паники. net/http сам перехватывает панику в хендлере, пишет стек в лог сервера и рвёт соединение, так что клиент увидит обрыв, а не ответ. Поэтому ставят recover-middleware: оно ловит панику, пишет стек через debug.Stack(), возвращает 500 и увеличивает метрику. Паника в горутине, запущенной из хендлера, так не ловится и роняет весь процесс.
Типичные причины: nil-указатель, исчерпанный пул database/sql, истёкший контекст, неожиданный ответ внешнего API.
Где спотыкаются
Отдают клиенту err.Error() через http.Error. Наружу утекают SQL-запросы и внутренние адреса; клиенту нужен общий текст и request id, детали идут в лог.
Возвращают 500 на всё подряд, включая невалидный JSON от клиента. Это 400 или 422, и такие ответы засоряют алерты на реальные сбои.
Логируют ошибку на каждом уровне стека, и одна ошибка появляется в логе пять раз. Правильнее обернуть её и залогировать один раз там, где она обрабатывается.