Логи и мониторинг
Учимся видеть, что происходит в проде: пишем логи так, чтобы их можно было искать, строим метрики, настраиваем алерты и разбираем инцидент по шагам.
Cloud Logging собирает и хранит логи всех сервисов проекта, Cloud Monitoring — числовые метрики, графики и алерты. Вместе они отвечают на два вопроса: «всё ли хорошо прямо сейчас?» и «что именно сломалось полчаса назад?».
Зачем это на практике
Проект без наблюдаемости живёт по такому сценарию: пользователь пишет в поддержку «не могу оплатить», поддержка пишет разработчику, разработчик заходит в консоль, листает простыню логов, ничего не находит, просит «а можете ещё раз попробовать и сказать точное время?». Через два часа выясняется, что упал сторонний платёжный шлюз.
Проект с наблюдаемостью живёт иначе: в 14:02 в Telegram падает алерт «доля 5xx на shop-api выше 1%», разработчик открывает Log Explorer, за 30 секунд фильтрует ошибки, видит card_declined и таймауты к внешнему API, и в 14:10 уже знает причину. Разница не в уме разработчика — в подготовленной заранее инфраструктуре.
Про деньги: Cloud Logging бесплатен до заметного объёма (порядка 50 GiB приёма логов на проект в месяц), дальше берёт плату за каждый гигабайт. Стандартные метрики GCP (запросы, задержки, CPU) бесплатны, алерты тоже. Пугать должен именно объём логов — о нём в конце урока.
Пишите структурные логи, а не текст
Cloud Run, GKE, Cloud Functions забирают в Logging всё, что приложение печатает в stdout. Если печатать обычный текст, вы получите поле textPayload и сможете искать по нему разве что подстрокой. Если печатать JSON — Logging разберёт его в jsonPayload, и каждое поле станет фильтруемым.
Есть два зарезервированных ключа: severity (уровень: DEBUG, INFO, WARNING, ERROR, CRITICAL) и message (текст, который показывается в списке). Всё остальное — ваши поля.
import json
def log(severity, message, **fields):
entry = {
"severity": severity,
"message": message,
"time": "2026-07-14T10:00:00Z",
**fields,
}
print(json.dumps(entry, ensure_ascii=False))
log("INFO", "заказ создан", order_id=1042, user_id=7)
log("ERROR", "оплата не прошла", order_id=1042, code="card_declined")
Результат:
{"severity": "INFO", "message": "заказ создан", "time": "2026-07-14T10:00:00Z", "order_id": 1042, "user_id": 7}
{"severity": "ERROR", "message": "оплата не прошла", "time": "2026-07-14T10:00:00Z", "order_id": 1042, "code": "card_declined"}
Одна строка = одна JSON-запись. Теперь в Log Explorer можно спросить «покажи все события по заказу 1042» — и получить ответ, а не 40 минут чтения простыни. В боевом коде вместо самописной функции берут библиотеку структурного логирования и обязательно кладут в запись идентификатор запроса (trace), чтобы склеивать события одного пользовательского обращения.
Log Explorer: язык запросов
Поиск по логам — это не «строка поиска», а маленький язык фильтров. Условия соединяются через AND, OR, NOT.
resource.type="cloud_run_revision"
resource.labels.service_name="shop-api"
severity>=ERROR
jsonPayload.order_id=1042
timestamp>="2026-07-14T13:50:00Z"
Здесь severity>=ERROR означает «ERROR и всё, что серьёзнее» — уровни сравнимы между собой. Полнотекстовый поиск тоже есть: просто напишите слово в кавычках, например "card_declined", — но по конкретному полю фильтр и быстрее, и точнее.
То же самое из терминала — удобно, когда нужно быстро посмотреть последние ошибки, не открывая браузер:
gcloud logging read \
'resource.type="cloud_run_revision" AND severity>=ERROR' \
--freshness=1h --limit=20 --format=json
# живой хвост логов сервиса
gcloud beta run services logs tail shop-api --region=europe-west1
Метрики и алерты
Логи отвечают на вопрос «что случилось». Метрики — на вопрос «сколько и как часто». Cloud Monitoring уже собирает за вас десятки метрик без единой строки кода: run.googleapis.com/request_count, request_latencies, использование CPU и памяти.
Свою метрику можно вырастить прямо из логов — это называется log-based metric: Logging считает, сколько записей подошло под фильтр, и превращает счёт в числовой ряд.
gcloud logging metrics create payment_failures \
--description="Неудачные оплаты" \
--log-filter='resource.type="cloud_run_revision" AND jsonPayload.code="card_declined"'
Дальше на метрику вешают alerting policy — правило «если значение выше порога дольше N минут, разбуди человека». Политику можно кликнуть в UI, а можно описать файлом и держать в git рядом с Terraform:
displayName: "Много 5xx на shop-api"
combiner: OR
conditions:
- displayName: "5xx чаще 5 раз в минуту в течение 5 минут"
conditionThreshold:
filter: 'metric.type="run.googleapis.com/request_count" AND resource.type="cloud_run_revision" AND metric.labels.response_code_class="5xx"'
comparison: COMPARISON_GT
thresholdValue: 5
duration: 300s
aggregations:
- alignmentPeriod: 60s
perSeriesAligner: ALIGN_RATE
notificationChannels:
- projects/my-shop/notificationChannels/1234567890
Обратите внимание на duration: 300s. Без него алерт сработает на единственный случайный 5xx в три часа ночи. Порог «держится N минут» отсекает шум — а шумный алерт быстро перестают читать, и это опаснее, чем отсутствие алерта.
Отдельно стоит завести uptime check: Google раз в минуту дёргает ваш URL из нескольких регионов мира. Он ловит то, чего не видят внутренние метрики, — например, протухший TLS-сертификат или сломанную балансировку.
Сколько ошибок мы вообще имеем право допустить
Чтобы порог не был взят с потолка, команды считают бюджет ошибок: если вы обещали доступность 99,9%, то 0,1% запросов имеют право упасть — и это конкретное число.
total_requests = 1_000_000 # запросов за месяц
failed = 4_200 # из них ответили 5xx
slo = 0.999 # обещанная доступность
budget = total_requests * (1 - slo)
spent = failed / budget * 100
print(f"Бюджет ошибок за месяц: {budget:.0f} запросов")
print(f"Фактически ошибок: {failed}")
print(f"Бюджет израсходован на {spent:.1f}%")
Результат:
Бюджет ошибок за месяц: 1000 запросов
Фактически ошибок: 4200
Бюджет израсходован на 420.0%
Бюджет перерасходован вчетверо — значит, следующий спринт уходит не на фичи, а на надёжность. В этом и смысл цифры: она превращает спор «релизить или чинить» в арифметику.
Как это работает: разбор инцидента по шагам
Сложился реальный сценарий: 14:02 пришёл алерт по 5xx. Что делает дежурный.
- Ограничить время. В Log Explorer выставить окно 13:50–14:10. Не «последний час» — узкое окно вокруг события, иначе утонете в записях.
- Отсечь лишнее. Фильтр по сервису и
severity>=ERROR. Из тысяч строк остаются десятки. - Найти начало. Отсортировать по возрастанию времени и посмотреть первую ошибку. Последняя ошибка — обычно следствие; интересна первая.
- Проверить, что менялось. Cloud Run хранит ревизии: если в 13:58 приехал деплой, а в 14:00 полезли 5xx, вы уже нашли причину. Логи деплоя — там же, в Logging.
- Склеить историю одного запроса. По полю
trace(или своемуrequest_id) достать все записи одного обращения — от входа до падения. - Остановить кровь. Сначала откат, потом расследование. Cloud Run позволяет перевести трафик на предыдущую ревизию одной командой.
# посмотреть ревизии и увидеть, какая приехала перед инцидентом
gcloud run revisions list --service=shop-api --region=europe-west1
# вернуть весь трафик на предыдущую ревизию
gcloud run services update-traffic shop-api \
--region=europe-west1 \
--to-revisions=shop-api-00041-xyz=100
Под капотом Logging — это не «файл с логами», а индексированное хранилище (log buckets) с маршрутизацией. Каждая запись проходит через sinks: правила «эти логи в основное хранилище, эти в BigQuery для аналитики, эти выбросить». Именно sinks дают контроль над деньгами.
Частые ошибки
- Логировать всё подряд на DEBUG в проде. Сервис на 500 RPS с подробным логом каждого запроса легко даёт десятки гигабайт в месяц — и бесплатный лимит кончается, а счёт растёт. Лечится exclusion-фильтром в sink: например, выбрасывать успешные health-check'и, которые не несут информации.
- Секреты в логах. Печать всего тела запроса рано или поздно занесёт в Logging пароль, токен или номер карты. Логи хранятся, индексируются и доступны всей команде — считайте их публичными.
- Алерт без
duration. Срабатывает на любой чих, команда привыкает игнорировать уведомления, и настоящая авария проходит незамеченной. - Алерт есть, канала уведомления нет. Политика создана, но
notificationChannelsпуст — инцидент честно фиксируется в консоли, куда никто не смотрит. - Текстовые логи вместо JSON. Через полгода вы не сможете отфильтровать события по
order_id, потому что его нет как поля. - Мониторить только CPU. Пользователю всё равно, какая у вас загрузка процессора. Смотрите на «золотые сигналы»: доля ошибок, задержка (обязательно p95/p99, не среднее), трафик, насыщение ресурсов.
- Расследовать вместо отката. Пока вы ищете причину, пользователи получают 500-е. Сначала верните рабочую ревизию, потом разбирайтесь.
Итоги
- Пишите в stdout JSON-строки с полями
severityиmessage— Logging сам разложит их вjsonPayload, и логи станут искомыми. - Log Explorer — язык фильтров (
resource.type,severity>=ERROR, свои поля), а не строка поиска. То же доступно черезgcloud logging read. - Log-based metric превращает записи логов в числовой ряд, на который можно повесить алерт.
- У алерта обязательны порог,
durationи канал уведомления. Шумный алерт хуже, чем никакого. - Инцидент: сузить окно → отфильтровать ошибки → найти первую → проверить последний деплой → откатить трафик → расследовать.
- Главная денежная утечка в наблюдаемости — объём логов. Exclusion-фильтры и разумный уровень логирования в проде экономят реальные деньги.