Merge pull request 'Ожидаемые исходы скрапинга перестают быть ошибками' (#3488) from fix/3471-scraper-log-levels into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / build-browser (push) Successful in 43s
Deploy Trade-In / build-frontend (push) Successful in 2m32s
Deploy Trade-In / test (push) Successful in 6m37s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy (push) Successful in 2m5s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m41s
All checks were successful
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / build-browser (push) Successful in 43s
Deploy Trade-In / build-frontend (push) Successful in 2m32s
Deploy Trade-In / test (push) Successful in 6m37s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy (push) Successful in 2m5s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m41s
This commit is contained in:
commit
994eb79323
13 changed files with 39 additions and 26 deletions
|
|
@ -131,7 +131,7 @@ async def backfill_cian_price_history(
|
|||
# Fail-closed (#2616): пул пуст/недоступен в проде. Остальные листинги
|
||||
# упрутся в то же самое — рвём батч сразу, а не 50 раз по 5 секунд с
|
||||
# логом, который читается как «Циан нас блокирует».
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"cian_price_history: нет доступного прокси в пуле (%s) — батч прерван "
|
||||
"на listing_id=%s (обработано %d из %d)",
|
||||
exc,
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ async def verify_session(cookies: dict[str, str]) -> dict[str, Any] | None:
|
|||
# ИМЕННО для cian/нездоровы — НЕ уходим на settings.cian_proxy_url (тот самый
|
||||
# статичный узел мог быть источником бана, см. proxy_egress module docstring).
|
||||
# Явный отказ вместо слепого прохода через заведомо подозрительный egress.
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"Cian cookies verify: пул прокси исчерпан для cian (%s) — verify пропущен, "
|
||||
"cookies НЕ помечены протухшими, retry на следующем такте",
|
||||
exc,
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ def resolve_proxy_url(db: Session, source: str) -> str | None:
|
|||
|
||||
# Сценарий 2: пул РЕАЛЬНО не пуст, но для source не осталось ни одного
|
||||
# здорового/небаненного узла -- fail-closed (#2616), НЕ fallback на env.
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"proxy_egress: source=%s -- пул scrape_proxies НЕ пуст (%d узлов), но НИ ОДИН "
|
||||
"не прошёл фильтр для этого источника (banned_for_source=%d, "
|
||||
"unhealthy_or_disabled=%d) -- FAIL-CLOSED (#2616): отказ, БЕЗ обхода через "
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ async def backfill_yandex_addresses(
|
|||
# нездоровы — НЕ уходим на settings.scraper_proxy_url (см. proxy_egress module
|
||||
# docstring). Явный пропуск run'а вместо слепого прохода через egress, который
|
||||
# мог быть источником текущего инцидента.
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"yandex_address_backfill: пул прокси исчерпан для yandex (%s) — run "
|
||||
"пропущен, ни один листинг не обработан",
|
||||
exc,
|
||||
|
|
|
|||
|
|
@ -806,7 +806,7 @@ async def run_avito_detail_backfill(
|
|||
# и mark_failed с текстом про пул, как у домклика после #3283.
|
||||
if _caused_by_empty_pool(e):
|
||||
counters.failed += 1
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"avito_detail_backfill: run_id=%d СТОП — пул прокси пуст, "
|
||||
"к площадке не ходили. enriched=%d attempted=%d",
|
||||
run_id,
|
||||
|
|
@ -913,7 +913,7 @@ async def run_avito_detail_backfill(
|
|||
# ratio: прогон 5210 (14 блоков из 20, ровно порог) отпечатал
|
||||
# "ABORT -- 1 consecutive blocks" -- текущая серия в тот момент
|
||||
# действительно равнялась единице, но обрыв был не по ней.
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"avito_detail_backfill: run_id=%d ABORT -- %s, "
|
||||
"частая причина: %s. enriched=%d attempted=%d",
|
||||
run_id,
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ async def backfill_cian_history(
|
|||
if caused_by_no_proxy(exc):
|
||||
result.no_proxy_stop = True
|
||||
result.listings_failed_fetch += 1
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"cian_history_backfill: СТОП — пул прокси пуст, к площадке "
|
||||
"не ходили. listing_id=%s processed=%d succeeded=%d",
|
||||
listing_id,
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ async def run_domclick_detail_backfill(
|
|||
# ниже. Причина не теряется: в записи прогона стоит
|
||||
# no_proxy_stop=1 и mark_failed с текстом про пул.
|
||||
counters.failed += 1
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"domclick_detail_backfill: run_id=%d СТОП — пул прокси пуст, "
|
||||
"к площадке не ходили. enriched=%d attempted=%d",
|
||||
run_id,
|
||||
|
|
@ -572,7 +572,7 @@ async def run_domclick_detail_backfill(
|
|||
if consecutive_blocks >= max_consecutive_blocks:
|
||||
# #3196: причину больше не выдумываем и не молчим — печатаем
|
||||
# перепись диагнозов по HTTP-статусам этого прогона.
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"domclick_detail_backfill: run_id=%d ABORT -- %d consecutive "
|
||||
"blocks, диагнозы: %s. enriched=%d attempted=%d",
|
||||
run_id,
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ async def run_yandex_detail_backfill(
|
|||
)
|
||||
if consecutive_blocks >= max_consecutive_blocks:
|
||||
aborted_by_blocks = True
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"yandex_detail_backfill: run_id=%d ABORT -- %d consecutive "
|
||||
"non-200 responses. enriched=%d attempted=%d",
|
||||
run_id,
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ async def enrich_yandex_newbuilding_sweep(
|
|||
if caused_by_no_proxy(exc):
|
||||
result.no_proxy_stop = True
|
||||
result.failed_resolve += 1
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"yandex-nb-sweep: СТОП — пул прокси пуст, к площадке не ходили. "
|
||||
"house_id=%s processed=%d succeeded=%d",
|
||||
house_id,
|
||||
|
|
@ -419,7 +419,7 @@ async def enrich_yandex_newbuilding_sweep(
|
|||
if caused_by_no_proxy(exc): # #3197 — см. блок resolve выше
|
||||
result.no_proxy_stop = True
|
||||
result.failed_fetch += 1
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"yandex-nb-sweep: СТОП — пул прокси пуст, к площадке не ходили. "
|
||||
"house_id=%s processed=%d succeeded=%d",
|
||||
house_id,
|
||||
|
|
|
|||
|
|
@ -420,8 +420,10 @@ def test_all_candidates_banned_raises_pool_exhausted_not_env_fallback(
|
|||
assert exc_info.value.pool_total == 2
|
||||
assert exc_info.value.banned_for_source == 2
|
||||
assert exc_info.value.unhealthy_or_disabled == 0
|
||||
errors = [rec for rec in caplog.records if rec.levelname == "ERROR"]
|
||||
assert any("fail-closed" in rec.message.lower() for rec in errors)
|
||||
# fail-closed без здорового узла — штатный исход скрапинга, а не инцидент;
|
||||
# понижено до warning, чтобы не шуметь в GlitchTip (было logger.error).
|
||||
warnings = [rec for rec in caplog.records if rec.levelname == "WARNING"]
|
||||
assert any("fail-closed" in rec.message.lower() for rec in warnings)
|
||||
# НЕ должно быть "обход пула" / "static-fallback" в логах — env не тронут.
|
||||
assert not any("static-fallback" in rec.message for rec in caplog.records)
|
||||
|
||||
|
|
@ -429,8 +431,10 @@ def test_all_candidates_banned_raises_pool_exhausted_not_env_fallback(
|
|||
def test_exhausted_and_empty_pool_log_texts_are_distinct(
|
||||
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Регрессия на замечание ревью: "пуст" и "все отсеяны" — РАЗНЫЕ формулировки И
|
||||
разные уровни (WARNING vs ERROR), иначе их нельзя различить в логах/алертах."""
|
||||
"""Регрессия на замечание ревью: "пуст" и "все отсеяны" — РАЗНЫЕ формулировки,
|
||||
иначе их нельзя различить в логах/алертах. Оба сценария — штатный исход
|
||||
скрапинга, поэтому оба теперь warning (было WARNING vs ERROR), различимость
|
||||
держится на тексте, не на уровне."""
|
||||
now = datetime.now(UTC)
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
|
|
@ -452,8 +456,8 @@ def test_exhausted_and_empty_pool_log_texts_are_distinct(
|
|||
exhausted_messages = {rec.levelname: rec.message for rec in caplog.records}
|
||||
|
||||
assert "ERROR" not in empty_pool_messages
|
||||
assert "ERROR" in exhausted_messages
|
||||
assert empty_pool_messages.get("WARNING") != exhausted_messages.get("ERROR")
|
||||
assert "ERROR" not in exhausted_messages
|
||||
assert empty_pool_messages.get("WARNING") != exhausted_messages.get("WARNING")
|
||||
|
||||
|
||||
def test_disabled_proxy_raises_pool_exhausted() -> None:
|
||||
|
|
|
|||
|
|
@ -348,7 +348,9 @@ async def test_backfill_abort_log_has_no_ip_rate_limited_literal(caplog: Any) ->
|
|||
patch(_RESOLVE_PROXY_URL, MagicMock(return_value="http://test-proxy.local:8080")),
|
||||
patch(_FETCH, mock_fetch),
|
||||
patch(_SLEEP, new_callable=AsyncMock),
|
||||
caplog.at_level("ERROR"),
|
||||
# серия подтверждённых блоков площадки -- штатный исход скрапинга, не
|
||||
# инцидент; понижено до warning, чтобы не шуметь в GlitchTip.
|
||||
caplog.at_level("WARNING"),
|
||||
):
|
||||
await run_avito_detail_backfill(
|
||||
db, run_id=3, params={"batch_size": 10, "budget_sec": 3600, "max_consecutive_blocks": 5}
|
||||
|
|
@ -1354,7 +1356,9 @@ async def test_backfill_ratio_abort_log_names_the_ratio_not_the_streak(caplog: A
|
|||
patch(_FETCH, mock_fetch),
|
||||
patch(_SAVE, return_value=True),
|
||||
patch(_SLEEP, new_callable=AsyncMock),
|
||||
caplog.at_level("ERROR"),
|
||||
# ABORT по доле блоков -- штатный исход скрапинга, не инцидент; понижено
|
||||
# до warning, чтобы не шуметь в GlitchTip.
|
||||
caplog.at_level("WARNING"),
|
||||
):
|
||||
result = await run_avito_detail_backfill(
|
||||
db, run_id=110, params={"batch_size": total, "budget_sec": 3600}
|
||||
|
|
@ -1396,7 +1400,9 @@ async def test_backfill_safety_net_abort_log_names_the_streak(caplog: Any) -> No
|
|||
patch(_FETCH, mock_fetch),
|
||||
patch(_SAVE, return_value=True),
|
||||
patch(_SLEEP, new_callable=AsyncMock),
|
||||
caplog.at_level("ERROR"),
|
||||
# ABORT по safety-net серии -- штатный исход скрапинга, не инцидент;
|
||||
# понижено до warning, чтобы не шуметь в GlitchTip.
|
||||
caplog.at_level("WARNING"),
|
||||
):
|
||||
result = await run_avito_detail_backfill(
|
||||
db, run_id=111, params={"batch_size": total, "budget_sec": 3600}
|
||||
|
|
|
|||
|
|
@ -287,7 +287,9 @@ async def test_backfill_abort_log_has_no_qrator_literal(caplog: pytest.LogCaptur
|
|||
patch(_BROWSER_FETCHER, mock_bf_cls),
|
||||
patch(_FETCH, mock_fetch),
|
||||
patch(_SLEEP, new_callable=AsyncMock),
|
||||
caplog.at_level("ERROR"),
|
||||
# серия подтверждённых блоков площадки -- штатный исход скрапинга, не
|
||||
# инцидент; понижено до warning, чтобы не шуметь в GlitchTip.
|
||||
caplog.at_level("WARNING"),
|
||||
):
|
||||
await run_domclick_detail_backfill(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -446,8 +446,9 @@ async def test_verify_session_pool_exhausted_returns_source_unavailable_with_err
|
|||
) -> None:
|
||||
"""#2825 fail-closed (#2616): пул scrape_proxies исчерпан для cian (все узлы
|
||||
забанены/нездоровы) — session.get НЕ вызывается (никуда не ходим без egress),
|
||||
возвращается VERIFY_SOURCE_UNAVAILABLE_SENTINEL, но с ERROR-логом (не warning,
|
||||
отдельным от обычного network-error пути) — явная деградация, а не проглатывание."""
|
||||
возвращается VERIFY_SOURCE_UNAVAILABLE_SENTINEL, с явным логом (не тихое
|
||||
проглатывание). Штатный исход скрапинга -- warning, не error (было error;
|
||||
понижено, чтобы не шуметь в GlitchTip)."""
|
||||
from app.services.proxy_egress import ProxyPoolExhaustedError
|
||||
|
||||
def _raise(source: str) -> str | None:
|
||||
|
|
@ -470,8 +471,8 @@ async def test_verify_session_pool_exhausted_returns_source_unavailable_with_err
|
|||
|
||||
assert result is VERIFY_SOURCE_UNAVAILABLE_SENTINEL
|
||||
mock_session.get.assert_not_called()
|
||||
errors = [rec for rec in caplog.records if rec.levelname == "ERROR"]
|
||||
assert any("пул прокси исчерпан" in rec.message.lower() for rec in errors)
|
||||
warnings = [rec for rec in caplog.records if rec.levelname == "WARNING"]
|
||||
assert any("пул прокси исчерпан" in rec.message.lower() for rec in warnings)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue