improve monitoring resilience
This commit is contained in:
@@ -4,3 +4,5 @@ MINECRAFT_HOST=example.org
|
||||
MINECRAFT_PORT=25565
|
||||
POLL_INTERVAL_SECONDS=30
|
||||
STATUS_FILE_PATH=data/status.json
|
||||
REQUEST_TIMEOUT_SECONDS=5
|
||||
OFFLINE_AFTER_FAILURES=2
|
||||
|
||||
@@ -19,6 +19,8 @@ MINECRAFT_HOST=example.org
|
||||
MINECRAFT_PORT=25565
|
||||
POLL_INTERVAL_SECONDS=30
|
||||
STATUS_FILE_PATH=data/status.json
|
||||
REQUEST_TIMEOUT_SECONDS=5
|
||||
OFFLINE_AFTER_FAILURES=2
|
||||
```
|
||||
3) Запустіть бота:
|
||||
```bash
|
||||
@@ -82,6 +84,8 @@ docker-compose up -d
|
||||
- Усі параметри задаються через змінні оточення (див. `.env.example`).
|
||||
- `POLL_INTERVAL_SECONDS` — частота опитування.
|
||||
- `STATUS_FILE_PATH` — куди писати JSON. Скрипт сам створить директорію, якщо її немає.
|
||||
- `REQUEST_TIMEOUT_SECONDS` — таймаут запиту до сервера (щоб не ловити «вічний» конект).
|
||||
- `OFFLINE_AFTER_FAILURES` — скільки послідовних помилок/таймаутів вважати перед тим, як оголосити офлайн.
|
||||
|
||||
## Перевірка
|
||||
- Запустіть бота і в Telegram відправте `/start`, потім `/status`, щоб побачити поточні дані.
|
||||
|
||||
16
config.py
16
config.py
@@ -12,6 +12,8 @@ class Settings:
|
||||
minecraft_port: int
|
||||
poll_interval_seconds: int
|
||||
status_file_path: str
|
||||
request_timeout_seconds: int
|
||||
offline_after_failures: int
|
||||
|
||||
|
||||
def load_settings() -> Settings:
|
||||
@@ -23,6 +25,8 @@ def load_settings() -> Settings:
|
||||
port = os.getenv("MINECRAFT_PORT", "25565")
|
||||
interval = os.getenv("POLL_INTERVAL_SECONDS", "30")
|
||||
status_file_path = os.getenv("STATUS_FILE_PATH", "data/status.json")
|
||||
request_timeout = os.getenv("REQUEST_TIMEOUT_SECONDS", "5")
|
||||
offline_after_failures = os.getenv("OFFLINE_AFTER_FAILURES", "2")
|
||||
|
||||
if not token:
|
||||
raise RuntimeError("TELEGRAM_BOT_TOKEN is required")
|
||||
@@ -41,6 +45,16 @@ def load_settings() -> Settings:
|
||||
except ValueError as exc:
|
||||
raise RuntimeError("POLL_INTERVAL_SECONDS must be an integer") from exc
|
||||
|
||||
try:
|
||||
parsed_timeout = int(request_timeout)
|
||||
except ValueError as exc:
|
||||
raise RuntimeError("REQUEST_TIMEOUT_SECONDS must be an integer") from exc
|
||||
|
||||
try:
|
||||
parsed_offline_after = int(offline_after_failures)
|
||||
except ValueError as exc:
|
||||
raise RuntimeError("OFFLINE_AFTER_FAILURES must be an integer") from exc
|
||||
|
||||
return Settings(
|
||||
telegram_bot_token=token,
|
||||
telegram_chat_id=int(chat_id),
|
||||
@@ -48,4 +62,6 @@ def load_settings() -> Settings:
|
||||
minecraft_port=parsed_port,
|
||||
poll_interval_seconds=parsed_interval,
|
||||
status_file_path=status_file_path,
|
||||
request_timeout_seconds=parsed_timeout,
|
||||
offline_after_failures=parsed_offline_after,
|
||||
)
|
||||
|
||||
2
main.py
2
main.py
@@ -29,6 +29,8 @@ async def main() -> None:
|
||||
status_file=Path(settings.status_file_path),
|
||||
poll_interval_seconds=settings.poll_interval_seconds,
|
||||
activity=activity,
|
||||
request_timeout_seconds=settings.request_timeout_seconds,
|
||||
offline_after_failures=settings.offline_after_failures,
|
||||
)
|
||||
dp.include_router(setup_handlers(monitor, activity))
|
||||
|
||||
|
||||
@@ -35,16 +35,25 @@ class MinecraftMonitor:
|
||||
status_file: Path,
|
||||
poll_interval_seconds: int,
|
||||
activity: ChatActivityTracker,
|
||||
request_timeout_seconds: int,
|
||||
offline_after_failures: int,
|
||||
) -> None:
|
||||
self.server = JavaServer(host, port=port)
|
||||
self.status_file = status_file
|
||||
self.poll_interval_seconds = poll_interval_seconds
|
||||
self.request_timeout_seconds = request_timeout_seconds
|
||||
self.offline_after_failures = max(1, offline_after_failures)
|
||||
self._previous_snapshot: Optional[ServerSnapshot] = None
|
||||
self.activity = activity
|
||||
self._consecutive_failures = 0
|
||||
|
||||
async def fetch_status(self) -> ServerSnapshot:
|
||||
try:
|
||||
response = await self.server.async_status()
|
||||
response = await asyncio.wait_for(
|
||||
self.server.async_status(),
|
||||
timeout=self.request_timeout_seconds,
|
||||
)
|
||||
self._consecutive_failures = 0
|
||||
motd = getattr(response.description, "to_plain", lambda: None)()
|
||||
player_names = [player.name for player in response.players.sample or []]
|
||||
return ServerSnapshot(
|
||||
@@ -57,7 +66,20 @@ class MinecraftMonitor:
|
||||
player_names=player_names,
|
||||
)
|
||||
except Exception:
|
||||
# Treat any exception as offline state but keep last known data safe.
|
||||
self._consecutive_failures += 1
|
||||
if self._consecutive_failures < self.offline_after_failures:
|
||||
# Возвращаем последнее известное состояние, помечая время обновления.
|
||||
if self._previous_snapshot:
|
||||
return ServerSnapshot(
|
||||
online=self._previous_snapshot.online,
|
||||
motd=self._previous_snapshot.motd,
|
||||
version=self._previous_snapshot.version,
|
||||
latency_ms=self._previous_snapshot.latency_ms,
|
||||
players_online=self._previous_snapshot.players_online,
|
||||
players_max=self._previous_snapshot.players_max,
|
||||
player_names=self._previous_snapshot.player_names,
|
||||
)
|
||||
# После порога считаем сервер офлайн.
|
||||
return ServerSnapshot(
|
||||
online=False,
|
||||
motd=None,
|
||||
|
||||
Reference in New Issue
Block a user