Files
remnawave-bedolaga-telegram…/app/utils/timezone.py
Fringg 1f0fef114b refactor: complete structlog migration with contextvars, kwargs, and logging hardening
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
  via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
  to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
  so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
  270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
  for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
  style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
  and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
  to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
  switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
  inline.py, and admin/payments.py
2026-02-16 09:18:12 +03:00

67 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Timezone utilities for consistent local time handling."""
from __future__ import annotations
from datetime import UTC, datetime
from functools import lru_cache
from zoneinfo import ZoneInfo
import structlog
from app.config import settings
logger = structlog.get_logger(__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("⚠️ Не удалось загрузить временную зону '': . Используем UTC.", tz_name=tz_name, exc=exc)
return ZoneInfo('UTC')
def panel_datetime_to_naive_utc(dt: datetime) -> datetime:
"""Convert a panel datetime to naive UTC.
Panel API returns local time with a misleading UTC offset (+00:00 / Z).
This strips the offset, interprets the raw value as panel-local time,
then converts to naive UTC for database storage.
"""
naive = dt.replace(tzinfo=None)
localized = naive.replace(tzinfo=get_local_timezone())
return localized.astimezone(ZoneInfo('UTC')).replace(tzinfo=None)
def to_local_datetime(dt: datetime | None) -> datetime | None:
"""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=UTC)
return aware_dt.astimezone(get_local_timezone())
def format_local_datetime(
dt: datetime | None,
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)