Merge pull request #1649 from Fr1ngg/revert-1648-es7eok-bedolaga/fix-log-time-discrepancy-with-server-time

Revert "Improve timezone-aware logging"
This commit is contained in:
Egor
2025-11-01 04:54:03 +03:00
committed by GitHub
6 changed files with 32 additions and 169 deletions

View File

@@ -7,7 +7,6 @@ import html
from collections import defaultdict
from datetime import time
from typing import List, Optional, Union, Dict
from zoneinfo import ZoneInfo
from pydantic_settings import BaseSettings
from pydantic import field_validator, Field
from pathlib import Path
@@ -61,8 +60,6 @@ class Settings(BaseSettings):
SQLITE_PATH: str = "./data/bot.db"
LOCALES_PATH: str = "./locales"
TIMEZONE: str = Field(default_factory=lambda: os.getenv("TZ", "UTC"))
DATABASE_MODE: str = "auto"
@@ -1427,20 +1424,9 @@ class Settings(BaseSettings):
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"extra": "ignore"
"extra": "ignore"
}
@field_validator("TIMEZONE")
@classmethod
def validate_timezone(cls, value: str) -> str:
try:
ZoneInfo(value)
except Exception as exc: # pragma: no cover - defensive validation
raise ValueError(
f"Некорректный идентификатор часового пояса: {value}"
) from exc
return value
settings = Settings()
ENV_OVERRIDE_KEYS = set(settings.model_fields_set)

View File

@@ -15,7 +15,6 @@ from app.database.models import (
from app.database.crud.notification import clear_notifications
from app.utils.pricing_utils import calculate_months_from_days, get_remaining_months
from app.config import settings
from app.utils.timezone import format_local_datetime
logger = logging.getLogger(__name__)
@@ -1132,13 +1131,7 @@ async def check_and_update_subscription_status(
current_time = datetime.utcnow()
logger.info(
"🔍 Проверка статуса подписки %s, текущий статус: %s, дата окончания: %s, текущее время: %s",
subscription.id,
subscription.status,
format_local_datetime(subscription.end_date),
format_local_datetime(current_time),
)
logger.info(f"🔍 Проверка статуса подписки {subscription.id}, текущий статус: {subscription.status}, дата окончания: {subscription.end_date}, текущее время: {current_time}")
if (subscription.status == SubscriptionStatus.ACTIVE.value and
subscription.end_date <= current_time):

View File

@@ -7,7 +7,6 @@ from dataclasses import dataclass
from app.config import settings
from app.external.remnawave_api import RemnaWaveAPI, test_api_connection
from app.utils.cache import cache
from app.utils.timezone import format_local_datetime
logger = logging.getLogger(__name__)
@@ -46,9 +45,6 @@ class MaintenanceService:
def get_maintenance_message(self) -> str:
if self._status.auto_enabled:
last_check_display = format_local_datetime(
self._status.last_check, "%H:%M:%S", "неизвестно"
)
return f"""
🔧 Технические работы!
@@ -56,7 +52,7 @@ class MaintenanceService:
⏰ Мы работаем над восстановлением. Попробуйте через несколько минут.
🔄 Последняя проверка: {last_check_display}
🔄 Последняя проверка: {self._status.last_check.strftime('%H:%M:%S') if self._status.last_check else 'неизвестно'}
"""
else:
return settings.get_maintenance_message()
@@ -83,12 +79,7 @@ class MaintenanceService:
}
emoji = emoji_map.get(alert_type, "")
timestamp = format_local_datetime(
datetime.utcnow(), "%d.%m.%Y %H:%M:%S %Z"
)
formatted_message = (
f"{emoji} <b>ТЕХНИЧЕСКИЕ РАБОТЫ</b>\n\n{message}\n\n⏰ <i>{timestamp}</i>"
)
formatted_message = f"{emoji} <b>ТЕХНИЧЕСКИЕ РАБОТЫ</b>\n\n{message}\n\n⏰ <i>{datetime.now().strftime('%d.%m.%Y %H:%M:%S')}</i>"
return await notification_service._send_message(formatted_message)
@@ -161,14 +152,11 @@ class MaintenanceService:
await self._save_status_to_cache()
enabled_time = format_local_datetime(
self._status.enabled_at, "%d.%m.%Y %H:%M:%S %Z"
)
notification_msg = f"""Режим технических работ ВКЛЮЧЕН
📋 <b>Причина:</b> {self._status.reason}
🤖 <b>Автоматически:</b> {'Да' if auto else 'Нет'}
🕐 <b>Время:</b> {enabled_time}
🕐 <b>Время:</b> {self._status.enabled_at.strftime('%d.%m.%Y %H:%M:%S')}
Обычные пользователи временно не смогут использовать бота."""
@@ -209,13 +197,10 @@ class MaintenanceService:
else:
duration_str = f"\n⏱️ <b>Длительность:</b> {minutes}мин"
notification_time = format_local_datetime(
datetime.utcnow(), "%d.%m.%Y %H:%M:%S %Z"
)
notification_msg = f"""Режим технических работ ВЫКЛЮЧЕН
🤖 <b>Автоматически:</b> {'Да' if was_auto else 'Нет'}
🕐 <b>Время:</b> {notification_time}
🕐 <b>Время:</b> {datetime.utcnow().strftime('%d.%m.%Y %H:%M:%S')}
{duration_str}
Сервис снова доступен для пользователей."""
@@ -244,17 +229,14 @@ class MaintenanceService:
settings.get_maintenance_retry_attempts(),
)
await self._notify_admins(
f"""Мониторинг технических работ запущен
await self._notify_admins(f"""Мониторинг технических работ запущен
🔄 <b>Интервал проверки:</b> {settings.get_maintenance_check_interval()} секунд
🤖 <b>Автовключение:</b> {'Включено' if settings.is_maintenance_auto_enable() else 'Отключено'}
🎯 <b>Порог ошибок:</b> {self._max_consecutive_failures}
🔁 <b>Повторных попыток:</b> {settings.get_maintenance_retry_attempts()}
Система будет следить за доступностью API.""",
"info",
)
Система будет следить за доступностью API.""", "info")
return True
@@ -309,19 +291,13 @@ class MaintenanceService:
)
if not self._status.api_status:
recovery_time = format_local_datetime(
self._status.last_check, "%H:%M:%S %Z"
)
await self._notify_admins(
f"""API Remnawave восстановлено!
await self._notify_admins(f"""API Remnawave восстановлено!
✅ <b>Статус:</b> Доступно
🕐 <b>Время восстановления:</b> {recovery_time}
🕐 <b>Время восстановления:</b> {self._status.last_check.strftime('%H:%M:%S')}
🔄 <b>Неудачных попыток было:</b> {self._status.consecutive_failures}
API снова отвечает на запросы.""",
"success",
)
API снова отвечает на запросы.""", "success")
self._status.api_status = True
self._status.consecutive_failures = 0
@@ -345,19 +321,13 @@ API снова отвечает на запросы.""",
self._status.consecutive_failures += 1
if was_available:
detection_time = format_local_datetime(
self._status.last_check, "%H:%M:%S %Z"
)
await self._notify_admins(
f"""API Remnawave недоступно!
await self._notify_admins(f"""API Remnawave недоступно!
❌ <b>Статус:</b> Недоступно
🕐 <b>Время обнаружения:</b> {detection_time}
🕐 <b>Время обнаружения:</b> {self._status.last_check.strftime('%H:%M:%S')}
🔄 <b>Попытка:</b> {self._status.consecutive_failures}
Началась серия неудачных проверок API.""",
"error",
)
Началась серия неудачных проверок API.""", "error")
if (
self._status.consecutive_failures >= self._max_consecutive_failures
@@ -379,16 +349,12 @@ API снова отвечает на запросы.""",
logger.error(f"Ошибка проверки API: {e}")
if self._status.api_status:
error_time = format_local_datetime(datetime.utcnow(), "%H:%M:%S %Z")
await self._notify_admins(
f"""Ошибка при проверке API Remnawave
await self._notify_admins(f"""Ошибка при проверке API Remnawave
❌ <b>Ошибка:</b> {str(e)}
🕐 <b>Время:</b> {error_time}
🕐 <b>Время:</b> {datetime.utcnow().strftime('%H:%M:%S')}
Не удалось выполнить проверку доступности API.""",
"error",
)
Не удалось выполнить проверку доступности API.""", "error")
self._status.api_status = False
self._status.consecutive_failures += 1

View File

@@ -1,5 +1,6 @@
import asyncio
import logging
import os
import re
from contextlib import AsyncExitStack, asynccontextmanager
from datetime import datetime, timedelta
@@ -41,7 +42,6 @@ from app.database.models import (
from app.utils.subscription_utils import (
resolve_hwid_device_limit_for_payload,
)
from app.utils.timezone import get_local_timezone
logger = logging.getLogger(__name__)
@@ -59,7 +59,15 @@ class RemnaWaveService:
self._config_error: Optional[str] = None
self._panel_timezone = get_local_timezone()
tz_name = os.getenv("TZ", "UTC")
try:
self._panel_timezone = ZoneInfo(tz_name)
except Exception:
logger.warning(
"⚠️ Не удалось загрузить временную зону '%s'. Используется UTC.",
tz_name,
)
self._panel_timezone = ZoneInfo("UTC")
if not base_url:
self._config_error = "REMNAWAVE_API_URL не настроен"

View File

@@ -1,82 +0,0 @@
"""Timezone utilities for consistent local time handling."""
from __future__ import annotations
import logging
from datetime import datetime, timezone as dt_timezone
from functools import lru_cache
from typing import Optional
from zoneinfo import ZoneInfo
from app.config import settings
logger = logging.getLogger(__name__)
@lru_cache(maxsize=1)
def get_local_timezone() -> ZoneInfo:
"""Return the configured local timezone.
Falls back to UTC if the configured timezone cannot be loaded. The
fallback is logged once and cached for subsequent calls.
"""
tz_name = settings.TIMEZONE
try:
return ZoneInfo(tz_name)
except Exception as exc: # pragma: no cover - defensive branch
logger.warning(
"⚠️ Не удалось загрузить временную зону '%s': %s. Используем UTC.",
tz_name,
exc,
)
return ZoneInfo("UTC")
def to_local_datetime(dt: Optional[datetime]) -> Optional[datetime]:
"""Convert a datetime value to the configured local timezone."""
if dt is None:
return None
aware_dt = dt if dt.tzinfo is not None else dt.replace(tzinfo=dt_timezone.utc)
return aware_dt.astimezone(get_local_timezone())
def format_local_datetime(
dt: Optional[datetime],
fmt: str = "%Y-%m-%d %H:%M:%S %Z",
na_placeholder: str = "N/A",
) -> str:
"""Format a datetime value in the configured local timezone."""
localized = to_local_datetime(dt)
if localized is None:
return na_placeholder
return localized.strftime(fmt)
class TimezoneAwareFormatter(logging.Formatter):
"""Logging formatter that renders timestamps in the configured timezone."""
def __init__(self, *args, timezone_name: Optional[str] = None, **kwargs):
super().__init__(*args, **kwargs)
if timezone_name:
try:
self._timezone = ZoneInfo(timezone_name)
except Exception as exc: # pragma: no cover - defensive branch
logger.warning(
"⚠️ Не удалось загрузить временную зону '%s': %s. Используем UTC.",
timezone_name,
exc,
)
self._timezone = ZoneInfo("UTC")
else:
self._timezone = get_local_timezone()
def formatTime(self, record, datefmt=None): # noqa: N802 - inherited method name
dt = datetime.fromtimestamp(record.created, tz=self._timezone)
if datefmt:
return dt.strftime(datefmt)
return dt.strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]

18
main.py
View File

@@ -36,7 +36,6 @@ from app.services.system_settings_service import bot_configuration_service
from app.services.external_admin_service import ensure_external_admin_token
from app.services.broadcast_service import broadcast_service
from app.utils.startup_timeline import StartupTimeline
from app.utils.timezone import TimezoneAwareFormatter
class GracefulExit:
@@ -50,20 +49,13 @@ class GracefulExit:
async def main():
formatter = TimezoneAwareFormatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
timezone_name=settings.TIMEZONE,
)
file_handler = logging.FileHandler(settings.LOG_FILE, encoding='utf-8')
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
logging.basicConfig(
level=getattr(logging, settings.LOG_LEVEL),
handlers=[file_handler, stream_handler],
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(settings.LOG_FILE, encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)