Ваши шаги по расследованию и устранению ошибки 500 (Internal Server Error)?

Ответ

Ошибка 500 (Internal Server Error) — это общий код ответа, означающий, что на сервере произошла непредвиденная ошибка. Мой план действий для её диагностики будет следующим:

Шаг 1: Сбор информации (не трогая прод)

  1. Проверить логи приложения: Это первое и самое важное место. Искать записи об ошибках (ERROR, FATAL), паниках (panic) и их стектрейсы, которые произошли в момент получения 500-й ошибки.
  2. Проверить логи веб-сервера (Nginx, Apache): Если приложение работает за прокси-сервером, ошибка может быть на его уровне (например, upstream sent invalid header).
  3. Системы мониторинга и алертинга (Prometheus, Grafana, Sentry): Проанализировать дашборды. Возможно, есть всплеск нагрузки на CPU/RAM, нехватка дискового пространства, или ошибка уже была автоматически зафиксирована и сгруппирована в Sentry.

Шаг 2: Воспроизведение и локализация

  1. Попытаться воспроизвести ошибку: Если возможно, воспроизвести ошибку в тестовом или staging-окружении с теми же входными данными. Это самый безопасный способ для отладки.
  2. Проанализировать запрос: Какой именно эндпоинт, с какими параметрами, заголовками и телом запроса вызывает ошибку?
  3. Проверить внешние зависимости: Убедиться, что базы данных, кэши (Redis), брокеры сообщений (RabbitMQ, Kafka) и другие внешние API, к которым обращается сервис, доступны и работают корректно.

Шаг 3: Анализ кода и исправление

  1. Анализ стектрейса: Если есть panic, стектрейс точно укажет на место в коде, где произошла проблема. В Go для перехвата паник в HTTP-обработчиках используется recover.
    func recoveryMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            defer func() {
                if err := recover(); err != nil {
                    log.Printf("recovered from panic: %vn%s", err, debug.Stack())
                    http.Error(w, "Internal Server Error", http.StatusInternalServerError)
                }
            }()
            next.ServeHTTP(w, r)
        })
    }
  2. Анализ недавних изменений: Проверить последние коммиты и деплои. Очень часто причина кроется в недавно добавленном коде.
  3. Логирование запросов/ответов: Если причина неясна, можно временно добавить middleware для детального логирования входящих запросов и ответов, чтобы поймать проблемный случай.
    func loggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Printf("Request: %s %s", r.Method, r.URL.Path)
            next.ServeHTTP(w, r)
        })
    }

Шаг 4: Внедрение исправления

  1. Написать исправление и тесты: Устранить причину ошибки и написать регрессионный тест, который бы падал без этого исправления, чтобы предотвратить повторение ошибки в будущем.
  2. Выкатить исправление: Развернуть исправленную версию приложения.

Ответ 18+ 🔞

А, ну это классика, ёпта! Ошибка 500 — это когда сервер такой: «Ой, блядь, всё, пиздец, я не могу». И он тебе вместо ответа как бы говорит: «Сам разбирайся, я в ахуе». Но мы-то не такие, мы сейчас эту манду с ушами разберём по косточкам.

Шаг 1: Сначала посмотрим, что наговорил этот обалдуй-сервер

  1. Логи приложения — наш главный свидетель. Там надо искать крики «ERROR», «FATAL» и особенно «panic». Это как найти на месте преступления записку «Я, Герасим, утопил Муму». Стектрейс — это вообще золото, он покажет, в каком именно месте кода всё накрылось медным тазом.
  2. Логи веб-сервера (Nginx/Apache). А вдруг это не наше приложение обосралось, а какой-то прокси-сервер рядом? Там бывает такое: «upstream sent invalid header while reading response header from upstream». Ну, типа, наш сервис прислал какую-то дичь в заголовках, и Nginx просто офигел.
  3. Системы мониторинга (Prometheus, Grafana, Sentry). Это как камеры наблюдения. Глянем — может, у нас CPU зашкаливает, память кончилась или диск переполнился? А Sentry — она вообще умная, она уже могла сгруппировать ошибки и сказать: «Смотри, чувак, вот тут у тебя уже 500 раз на этом месте падало, ты чё, слепой?».

Шаг 2: Пытаемся понять, как это воспроизвести, не сломав прод

  1. Воспроизведение — это святое. Надо попробовать на тестовом стенде натравить на сервис те же самые данные, что и в проде. Если повторится — овердохуища, можно спокойно ковыряться.
  2. Какой запрос был-то? Какой эндпоинт, какие параметры, что в теле? Может, клиент прислал JSON, в котором вместо числа — строка «пизда», а наш код этого не ожидал.
  3. А внешние сервисы живы? База данных не легла? Redis не ответил? Внешний API не начал возвращать хуйню вместо ответа? Сервер иногда падает, потому что кто-то другой его подвёл — классическая история предательства.

Шаг 3: Лезем в код с фонарём и отвёрткой

  1. Стектрейс — наш лучший друг. Если была паника, то она, как труп, укажет прямо на место преступления. В Go, чтобы такие паники не убивали весь сервер, умные люди придумали recover. Вот смотри, как это выглядит, когда мы ловим панику, как какого-то мартышлюшку:
    func recoveryMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            defer func() {
                if err := recover(); err != nil {
                    log.Printf("recovered from panic: %vn%s", err, debug.Stack())
                    http.Error(w, "Internal Server Error", http.StatusInternalServerError)
                }
            }()
            next.ServeHTTP(w, r)
        })
    }

    Видишь? Мы ловим панику, пишем в лог «ой, блядь, тут такое было», и отдаём пользователю цивилизованную 500-ку, а не просто обрываем соединение.

  2. Что меняли недавно? Абсолютно всегда, в 99% случаев, ошибка появляется после последнего деплоя. Кто-то что-то «улучшил». Надо смотреть диффы последних коммитов — там обычно и сидит виновник, этот пидарас шерстяной.
  3. Добавим логирования, если совсем темно. Можно на время воткнуть промежуточный слой, который будет записывать все входящие запросы. Иногда только так и поймаешь тот один хитрый запрос, который всё ломает.
    func loggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Printf("Request: %s %s", r.Method, r.URL.Path)
            next.ServeHTTP(w, r)
        })
    }

Шаг 4: Чиним и выкатываем, чтобы больше не позориться

  1. Чиним баг и пишем тест. Нашли причину — исправили. А главное — пишем тест, который падает без нашего фикса. Чтобы в будунии какой-нибудь полупидор не «отрефакторил» код и снова не вернул эту ошибку. Это называется — поставить хуй в пробирку.
  2. Выкатываем исправление. Аккуратно, по всем правилам, на прод. И смотрим, чтобы наши графики ошибок в мониторинге резко пошли вниз, а не нахуй вверх.

Вот и весь план, ёпта. Главное — не паниковать, как тот сервер. Системно собирать улики, воспроизводить и бить точно в причину. А то бывает, начинают тыкать палкой в рандомные места — только хуже делают.