переобут logger
This commit is contained in:
188
get_data.py
188
get_data.py
@@ -27,12 +27,13 @@ import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import getpass
|
||||
import argparse
|
||||
import logging
|
||||
import logging.config
|
||||
import threading
|
||||
import concurrent.futures
|
||||
import queue
|
||||
import platform
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -85,39 +86,55 @@ STATUS_CHECK_INTERVAL_SEC = 60 # проверять "онлайн?" раз
|
||||
ONLINE_FETCH_INTERVAL_SEC = 1 # когда матч онлайн, дергать три запроса каждую секунду
|
||||
POLL_INTERVAL_OFFLINE_SEC = 300 # резервный интервал сна при ошибках/до старта
|
||||
|
||||
USERNAME = getpass.getuser()
|
||||
|
||||
TELEGRAM_BOT_TOKEN = "7639240596:AAH0YtdQoWZSC-_R_EW4wKAHHNLIA0F_ARY"
|
||||
TELEGRAM_CHAT_ID = 228977654
|
||||
|
||||
myhost = platform.node()
|
||||
|
||||
# Настройка логгера
|
||||
def setup_logger(level: str = "INFO") -> logging.Logger:
|
||||
user = getpass.getuser()
|
||||
log_dir = "LOGS"
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
LOG_CONFIG = {
|
||||
"version": 1,
|
||||
"handlers": {
|
||||
"telegram": {
|
||||
"class": "telegram_handler.TelegramHandler",
|
||||
"level": "INFO",
|
||||
"token": TELEGRAM_BOT_TOKEN,
|
||||
"chat_id": TELEGRAM_CHAT_ID,
|
||||
"formatter": "telegram",
|
||||
},
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
"file": {
|
||||
"class": "logging.FileHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple",
|
||||
"filename": f"logs/GFX_{myhost}.log",
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
__name__: {"handlers": ["console", "file", "telegram"], "level": "DEBUG"},
|
||||
},
|
||||
"formatters": {
|
||||
"telegram": {
|
||||
"class": "telegram_handler.HtmlFormatter",
|
||||
"format": "%(levelname)s %(message)s",
|
||||
"use_emoji": "True",
|
||||
},
|
||||
"simple": {
|
||||
"class": "logging.Formatter",
|
||||
"format": "%(asctime)s %(levelname)-8s %(funcName)s() - %(message)s",
|
||||
"datefmt": "%d.%m.%Y %H:%M:%S",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
log_path = os.path.join(log_dir, f"{user}.log")
|
||||
|
||||
logger = logging.getLogger("game_watcher")
|
||||
logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
||||
logger.propagate = False
|
||||
|
||||
if not logger.handlers:
|
||||
fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
|
||||
|
||||
# потоковый вывод (консоль)
|
||||
sh = logging.StreamHandler(sys.stdout)
|
||||
sh.setFormatter(fmt)
|
||||
logger.addHandler(sh)
|
||||
|
||||
# запись в файл
|
||||
fh = logging.FileHandler(log_path, encoding="utf-8")
|
||||
fh.setFormatter(fmt)
|
||||
logger.addHandler(fh)
|
||||
|
||||
logger.info("Логи будут писаться в: %s", log_path)
|
||||
return logger
|
||||
logging.config.dictConfig(LOG_CONFIG)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.handlers[2].formatter.use_emoji = True
|
||||
|
||||
|
||||
# ==========================
|
||||
@@ -125,53 +142,6 @@ def setup_logger(level: str = "INFO") -> logging.Logger:
|
||||
# ==========================
|
||||
|
||||
|
||||
def send_telegram(message: str) -> None:
|
||||
"""
|
||||
Отправка уведомления в Telegram.
|
||||
Можно использовать как переменные окружения,
|
||||
так и значения, указанные в TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID.
|
||||
"""
|
||||
import requests
|
||||
logger = logging.getLogger("game_watcher")
|
||||
|
||||
# 1️⃣ Сначала пытаемся взять значения из переменных окружения
|
||||
token = os.getenv("TELEGRAM_BOT_TOKEN", TELEGRAM_BOT_TOKEN)
|
||||
chat_id = os.getenv("TELEGRAM_CHAT_ID", str(TELEGRAM_CHAT_ID))
|
||||
|
||||
if not token or not chat_id:
|
||||
logger.warning(
|
||||
"TELEGRAM_BOT_TOKEN/CHAT_ID не заданы — сообщение не отправлено: %s",
|
||||
message,
|
||||
)
|
||||
return
|
||||
|
||||
url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||
try:
|
||||
resp = requests.post(
|
||||
url, json={"chat_id": chat_id, "text": message}, timeout=10
|
||||
)
|
||||
resp.raise_for_status()
|
||||
logger.info("Сообщение успешно отправлено в Telegram: %s", message)
|
||||
except requests.HTTPError as e:
|
||||
logger.error("Ошибка HTTP при отправке в Telegram: %s | Ответ: %s", e, resp.text)
|
||||
except Exception as e:
|
||||
logger.error("Ошибка отправки в Telegram: %s", e)
|
||||
|
||||
|
||||
def notify_error(msg: str) -> None:
|
||||
user = getpass.getuser()
|
||||
full_msg = f"[{user}] ❗ Ошибка: {msg}"
|
||||
logging.getLogger("game_watcher").error(full_msg)
|
||||
# send_telegram(full_msg)
|
||||
|
||||
|
||||
def notify_info(msg: str) -> None:
|
||||
user = getpass.getuser()
|
||||
full_msg = f"[{user}] ℹ️ {msg}"
|
||||
logging.getLogger("game_watcher").info(full_msg)
|
||||
# send_telegram(full_msg)
|
||||
|
||||
|
||||
def fetch_json(url: str, params: dict | None = None, session: requests.Session | None = None) -> dict:
|
||||
"""
|
||||
GET JSON с таймаутом и внятными ошибками.
|
||||
@@ -1075,8 +1045,7 @@ def Json_Team_Generation(merged: dict, *, out_dir: str = "static", who: str | No
|
||||
def validate_league_or_die(league: str) -> str:
|
||||
league = (league or DEFAULT_LEAGUE).lower().strip()
|
||||
if league not in ALLOWED_LEAGUES:
|
||||
msg = f"Неверный тег лиги: '{league}'. Допустимо: {sorted(ALLOWED_LEAGUES)}"
|
||||
notify_error(msg)
|
||||
logger.warning(f"Неверный тег лиги: '{league}'. Допустимо: {sorted(ALLOWED_LEAGUES)}")
|
||||
sys.exit(2)
|
||||
return league
|
||||
|
||||
@@ -1086,12 +1055,12 @@ def get_last_season_or_die(league: str, lang: str) -> str:
|
||||
try:
|
||||
data = fetch_json(url)
|
||||
season = extract_last_season(data)
|
||||
logging.getLogger("game_watcher").info(
|
||||
"Последний сезон для %s: %s", league, season
|
||||
logging.info(
|
||||
f"Последний сезон для {league}: {season}"
|
||||
)
|
||||
return season
|
||||
except Exception as e:
|
||||
notify_error(f"Не получилось получить последний сезон для {league}: {e}")
|
||||
logger.warning(f"Не получилось получить последний сезон для {league}: {e}")
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
@@ -1101,10 +1070,10 @@ def get_team_schedule_or_die(league: str, season: str, team: str, lang: str) ->
|
||||
data = fetch_json(url)
|
||||
team_games = extract_team_schedule_for_season(data, team)
|
||||
if not team_games:
|
||||
notify_error(f"Для команды {team} не найдено игр в сезоне {season}.")
|
||||
logger.warning(f"Для команды {team} не найдено игр в сезоне {season}.")
|
||||
return team_games
|
||||
except Exception as e:
|
||||
notify_error(f"Не получилось получить расписание {league}/{season}: {e}")
|
||||
logger.warning(f"Не получилось получить расписание {league}/{season}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -1166,7 +1135,7 @@ class PostProcessor:
|
||||
Json_Team_Generation(merged, out_dir="static", who="team1")
|
||||
Json_Team_Generation(merged, out_dir="static", who="team2")
|
||||
except Exception as e:
|
||||
logging.getLogger("game_watcher").exception("Postproc failed: %s", e)
|
||||
logging.exception(f"Postproc failed: {e}")
|
||||
|
||||
def stop(self):
|
||||
self._stop.set()
|
||||
@@ -1179,7 +1148,7 @@ class OnlinePoller:
|
||||
self.lang = lang
|
||||
self._stop_event = threading.Event()
|
||||
self._thread: threading.Thread | None = None
|
||||
self._log = logging.getLogger("game_watcher")
|
||||
self._log = logging.info("start")
|
||||
self._on_update = on_update
|
||||
self._post = PostProcessor()
|
||||
|
||||
@@ -1204,7 +1173,7 @@ class OnlinePoller:
|
||||
if self._thread and self._thread.is_alive():
|
||||
self._stop_event.set()
|
||||
self._thread.join(timeout=2)
|
||||
self._log.info("Онлайн-поллер для игры %s остановлен.", self.game_id)
|
||||
self._log.info(f"Онлайн-поллер для игры {self.game_id} остановлен.")
|
||||
self._thread = None
|
||||
try:
|
||||
self._session.close()
|
||||
@@ -1249,7 +1218,7 @@ class OnlinePoller:
|
||||
len(ls) if isinstance(ls, dict) else "—",
|
||||
)
|
||||
except Exception as e:
|
||||
notify_error(f"Сбой online-поллера для игры {self.game_id}: {e}")
|
||||
logger.warning(f"Сбой online-поллера для игры {self.game_id}: {e}")
|
||||
# лёгкая задержка после ошибки, но не «наказание» на целую секунду
|
||||
time.sleep(0.2)
|
||||
|
||||
@@ -1273,8 +1242,7 @@ class OnlinePoller:
|
||||
self._log.info("Онлайн-поллер для игры %s запущен.", self.game_id)
|
||||
|
||||
def monitor_game_loop(league: str, game_id: str, lang:str, stop_event: threading.Event) -> None:
|
||||
log = logging.getLogger("game_watcher")
|
||||
notify_info(f"Старт мониторинга игры {game_id} ({league}).")
|
||||
logger.info(f"Старт мониторинга игры {game_id} ({league}).")
|
||||
poller = OnlinePoller(league, game_id, lang)
|
||||
was_online = False
|
||||
|
||||
@@ -1285,15 +1253,14 @@ def monitor_game_loop(league: str, game_id: str, lang:str, stop_event: threading
|
||||
is_finished = status in {"resultconfirmed", "result"}
|
||||
|
||||
if is_finished:
|
||||
log.info("Матч %s завершён. Останавливаем мониторинг.", game_id)
|
||||
notify_info(f"Матч {game_id} завершён.")
|
||||
logger.info(f"Матч {game_id} завершён. Останавливаем мониторинг.")
|
||||
break
|
||||
|
||||
if is_online and not was_online:
|
||||
log.info("Матч %s перешёл в онлайн. Запускаем быстрый опрос (1 сек).", game_id)
|
||||
logger.info(f"Матч {game_id} перешёл в онлайн. Запускаем быстрый опрос (1 сек).")
|
||||
poller.start()
|
||||
elif not is_online and was_online:
|
||||
log.info("Матч %s вышел из онлайна (или ещё не стартовал). Останавливаем быстрый опрос.", game_id)
|
||||
logger.info(f"Матч {game_id} вышел из онлайна (или ещё не стартовал). Останавливаем быстрый опрос.")
|
||||
poller.stop()
|
||||
|
||||
was_online = is_online
|
||||
@@ -1302,13 +1269,13 @@ def monitor_game_loop(league: str, game_id: str, lang:str, stop_event: threading
|
||||
stop_event.wait(STATUS_CHECK_INTERVAL_SEC)
|
||||
|
||||
except Exception as e:
|
||||
notify_error(f"Сбой проверки статуса матча {game_id}: {e}")
|
||||
logger.warning(f"Сбой проверки статуса матча {game_id}: {e}")
|
||||
# При ошибке — не дергаем быстро, подождём немного и повторим
|
||||
stop_event.wait(POLL_INTERVAL_OFFLINE_SEC)
|
||||
|
||||
# Гарантированно остановим быстрый опрос при завершении
|
||||
poller.stop()
|
||||
log.info("Мониторинг матча %s остановлен.", game_id)
|
||||
logger.info(f"Мониторинг матча {game_id} остановлен.")
|
||||
|
||||
|
||||
def next_midnight_local(now: datetime) -> datetime:
|
||||
@@ -1333,15 +1300,12 @@ def daily_rollover_loop(
|
||||
- выбираем сегодняшнюю игру или последний сыгранный
|
||||
- при наличии сегодняшней игры — перезапускаем монитор на неё
|
||||
"""
|
||||
log = logging.getLogger("game_watcher")
|
||||
while not stop_event.is_set():
|
||||
now = datetime.now(APP_TZ)
|
||||
wakeup_at = next_midnight_local(now)
|
||||
seconds = (wakeup_at - now).total_seconds()
|
||||
log.info(
|
||||
"Ежедневная перекладка: проснусь %s (через ~%d сек).",
|
||||
wakeup_at.isoformat(),
|
||||
int(seconds),
|
||||
logger.info(
|
||||
f"Ежедневная перекладка: проснусь {wakeup_at.isoformat()} (через {int(seconds)} сек)."
|
||||
)
|
||||
if stop_event.wait(seconds):
|
||||
break
|
||||
@@ -1351,7 +1315,7 @@ def daily_rollover_loop(
|
||||
season = season_getter(league, lang)
|
||||
games = schedule_getter(league, season, team, lang)
|
||||
if not games:
|
||||
notify_info(
|
||||
logger.info(
|
||||
f"Ежедневная проверка: у {team} нет игр в расписании сезона {season}."
|
||||
)
|
||||
continue
|
||||
@@ -1361,19 +1325,19 @@ def daily_rollover_loop(
|
||||
)
|
||||
if today_game:
|
||||
gid = today_game["game"]["id"]
|
||||
notify_info(
|
||||
logger.info(
|
||||
f"Сегодня у {team} есть игра: gameID={gid}. Перезапуск мониторинга."
|
||||
)
|
||||
monitor_mgr.restart(gid, lang)
|
||||
elif last_played:
|
||||
gid = last_played["game"]["id"]
|
||||
notify_info(
|
||||
logger.info(
|
||||
f"Сегодня у {team} нет игры. Последняя сыгранная: gameID={gid}. Мониторинг НЕ запускаем."
|
||||
)
|
||||
else:
|
||||
notify_info(f"Сегодня у {team} нет игры и нет предыдущих сыгранных.")
|
||||
logger.info(f"Сегодня у {team} нет игры и нет предыдущих сыгранных.")
|
||||
except Exception as e:
|
||||
notify_error(f"Ошибка ежедневной проверки: {e}")
|
||||
logger.warning(f"Ошибка ежедневной проверки: {e}")
|
||||
|
||||
|
||||
class MonitorManager:
|
||||
@@ -1426,8 +1390,7 @@ def main():
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
logger = setup_logger(args.log_level)
|
||||
logger.info("Запуск программы пользователем: %s", USERNAME)
|
||||
logger.info("Запуск программы пользователем: %s", myhost)
|
||||
logger.info("Запуск с параметрами: league=%s, team=%s, lang=%s", args.league, args.team, args.lang)
|
||||
|
||||
league = validate_league_or_die(args.league)
|
||||
@@ -1439,7 +1402,7 @@ def main():
|
||||
# 2) Получить расписание для команды
|
||||
team_games = get_team_schedule_or_die(league, season, team, args.lang)
|
||||
if not team_games:
|
||||
notify_error("Расписание пустое — работа завершена.")
|
||||
logger.warning("Расписание пустое — работа завершена.")
|
||||
sys.exit(4)
|
||||
|
||||
# 3) Найти сегодняшнюю или последнюю сыгранную игру
|
||||
@@ -1451,7 +1414,7 @@ def main():
|
||||
if today_game:
|
||||
# В исходном расписании предполагалось наличие game.id
|
||||
game_id = today_game["game"]["id"]
|
||||
notify_info(
|
||||
logger.info(
|
||||
f"Сегодня у {team} есть игра: gameID={game_id}. Запускаю мониторинг."
|
||||
)
|
||||
monitor_mgr.restart(game_id, args.lang)
|
||||
@@ -1470,15 +1433,16 @@ def main():
|
||||
)
|
||||
Json_Team_Generation(merged, out_dir="static", who="team1")
|
||||
Json_Team_Generation(merged, out_dir="static", who="team2")
|
||||
notify_info(
|
||||
# print(merged)
|
||||
logger.info(
|
||||
f"Сегодня у {team} нет игры. Последняя сыгранная: gameID={game_id}. Мониторинг не запускаю."
|
||||
)
|
||||
except Exception as e:
|
||||
logging.getLogger("game_watcher").exception(
|
||||
logging.exception(
|
||||
"Оффлайн-сохранение для gameID=%s упало: %s", game_id, e
|
||||
)
|
||||
else:
|
||||
notify_info(f"Сегодня у {team} нет игры и нет предыдущих сыгранных.")
|
||||
logger.info(f"Сегодня у {team} нет игры и нет предыдущих сыгранных.")
|
||||
|
||||
# 4) Ежедневная перекладка расписания
|
||||
stop_event = threading.Event()
|
||||
|
||||
Reference in New Issue
Block a user