Как организовать единое логирование и контроль лимитов при работе с несколькими внешними API

В моей практике почти каждый проект рано или поздно сталкивается с подключением нескольких сторонних API: сервисы оплаты, базы данных, распознавание, нейросетевые модели. У каждого провайдера — свои лимиты, свой формат ответов и свои коды ошибок.
Если вести логи и ограничения отдельно для каждого сервиса, поддержка быстро превращается в хаос. При сбое сложно отследить, на каком этапе упал запрос, какой провайдер исчерпал квоту и почему данные не дошли. Поделюсь рабочим подходом, который использую для малых и средних команд.

Что нужно сделать на уровне архитектуры

Основная идея — вынести всю работу с внешними вызовами в отдельный слой-обёртку. Он отвечает за три задачи:

  • Единый формат логирования всех исходящих запросов и ответов
  • Контроль лимитов частоты запросов для каждого внешнего провайдера
  • Повторные попытки при временных ошибках и корректная обработка сбоев

Такой слой не привязан к конкретному сервису, его легко расширять при подключении новых API.

Единое логирование

Каждый запрос должен иметь общий идентификатор (correlation_id), который передаётся через всю систему. В лог записываем одинаковый набор полей независимо от провайдера:

  • Время отправки и длительность запроса
  • Название внешнего сервиса и тип операции
  • correlation_id
  • HTTP-статус ответа
  • Краткое описание ошибки (при наличии)
  • Количество затраченных единиц квоты

Важное правило: никогда не записывайте в логи секретные ключи, токены и персональные данные. Тело запроса и ответа сохраняем только при отладке и временно.

Пример структуры лога:

{ "correlation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "service": "external_weather_api", "method": "GET", "status": 200, "duration_ms": 320, "error": null, "quota_spent": 1 }

Контроль лимитов запросов

Разные провайдеры выдают разные ограничения: запросы в минуту, токены в минуту или суточный лимит. Для сервисов с несколькими инстансами самое надёжное — хранить состояние лимитов в Redis.

Подходит алгоритм токенного ведра (token bucket). Перед отправкой проверяем, есть ли доступный токен. Если нет — запрос ставим в очередь или возвращаем ошибку ожидания.

Базовый пример на Python:

import redis import time r = redis.Redis(host="localhost", port=6379, db=0) def can_send_request(service_key: str, limit_per_minute: int) -> bool: key = f"rate:{service_key}" now = time.time() pipe = r.pipeline() pipe.zremrangebyscore(key, 0, now - 60) pipe.zadd(key, {now: now}) pipe.zcard(key) count = pipe.execute()[2] return count < limit_per_minute

Это не готовое production-решение, но хорошо показывает базовую логику распределённого ограничения.

Обработка ошибок и повторные попытки

Не все ошибки означают неисправность. Таймаут, временная перегрузка, сбой сети — такие случаи безопасно повторять. А ошибки авторизации или неверные параметры повторять нельзя.

Что рекомендую:

  • Заранее составить список кодов, для которых разрешён retry
  • Добавить экспоненциальную задержку между попытками
  • Предусмотреть fallback: при недоступности одного внешнего сервиса система должна продолжать работать по альтернативному сценарию

Итоговые выводы

При работе с множеством внешних API главное — не решать задачи для каждого провайдера отдельно. Общий слой с едиными логами, контролем лимитов и стандартной обработкой ошибок сильно снижает стоимость поддержки и упрощает диагностику инцидентов.

Такой подход подходит для большинства внутренних интеграций, корпоративных сервисов и продуктов, которые постепенно подключают новые внешние системы.

Если вам нужны готовые шаблоны логирования и полный пример реализации rate limiter на Python для продакшена — все материалы я собрал в телеграм-канале, ссылка в профиле.

В комментариях делитесь, какие проблемы чаще всего возникают у вас при интеграции сторонних API.

2