From 0bd3d70e12b56f12e0f47c2af6d12a8d3eea4659 Mon Sep 17 00:00:00 2001
From: kavore <161734431+kavore@users.noreply.github.com>
Date: Thu, 12 Feb 2026 10:48:48 +0300
Subject: [PATCH] feat(logging): add LOG_ADMIN_HIDE setting and update logging
functionality
Introduce a new configuration option `LOG_ADMIN_HIDE` to control the visibility of admin-generated events in the logs. Update the logging retrieval functions to respect this setting, ensuring that admin actions can be hidden from the "All message logs" UI and CSV exports. Additionally, enhance the README with detailed logging configuration options for better clarity.
---
.env.example | 1 +
README.md | 12 ++++++++++
bot/handlers/admin/logs_admin.py | 38 +++++++++++++++++++++-----------
bot/handlers/user/payment.py | 16 ++++++++++++--
config/settings.py | 5 +++++
db/dal/message_log_dal.py | 17 +++++++++-----
6 files changed, 69 insertions(+), 20 deletions(-)
diff --git a/.env.example b/.env.example
index d7e6c5a..73de37c 100644
--- a/.env.example
+++ b/.env.example
@@ -165,6 +165,7 @@ LOG_LEVEL=INFO #
LOG_STORE_MESSAGE_CONTENT=False # Store message/callback content in DB logs
LOG_STORE_RAW_UPDATES=False # Store raw update payload snippets in DB logs
LOG_EXPORT_INCLUDE_SENSITIVE=False # Include content/raw update columns in admin CSV export
+LOG_ADMIN_HIDE=False # Hide admin actions from "All message logs" UI and CSV export
# Admin Logging Configuration
LOG_CHAT_ID=-1001234567890 # Telegram chat/group ID for admin notifications
diff --git a/README.md b/README.md
index 79cc3b6..2fc3dd7 100644
--- a/README.md
+++ b/README.md
@@ -118,6 +118,18 @@
| `SEVERPAY_LIFETIME_MINUTES` | (Опционально) Время жизни платежной ссылки в минутах (30–4320). |
+
+ Настройки логирования
+
+ | Переменная | Описание | Пример |
+ | --- | --- | --- |
+ | `LOGS_PAGE_SIZE` | Количество записей на странице в разделе админ-логов. | `10` |
+ | `LOG_STORE_MESSAGE_CONTENT` | Сохранять ли содержимое сообщений/колбэков в БД логов (`true`/`false`). | `false` |
+ | `LOG_STORE_RAW_UPDATES` | Сохранять ли превью сырого Telegram update в БД логов (`true`/`false`). | `false` |
+ | `LOG_EXPORT_INCLUDE_SENSITIVE` | Добавлять ли в CSV экспорт чувствительные поля (`content`, `raw_update_preview`). | `false` |
+ | `LOG_ADMIN_HIDE` | Скрывать админские события (`ADMIN_IDS`) в интерфейсе «Все логи сообщений» и в CSV экспорте (`true`/`false`). Логи продолжают записываться в БД. | `true` |
+
+
Настройки подписок
diff --git a/bot/handlers/admin/logs_admin.py b/bot/handlers/admin/logs_admin.py
index 6316d29..fca23d0 100644
--- a/bot/handlers/admin/logs_admin.py
+++ b/bot/handlers/admin/logs_admin.py
@@ -171,9 +171,17 @@ async def view_all_logs_handler(callback: types.CallbackQuery,
await callback.answer("Error processing request.", show_alert=True)
return
+ hide_admin_events = bool(settings.LOG_ADMIN_HIDE)
logs_models = await message_log_dal.get_all_message_logs(
- session, settings.LOGS_PAGE_SIZE, page_idx * settings.LOGS_PAGE_SIZE)
- total_logs_count = await message_log_dal.count_all_message_logs(session)
+ session,
+ settings.LOGS_PAGE_SIZE,
+ page_idx * settings.LOGS_PAGE_SIZE,
+ hide_admin_events=hide_admin_events,
+ )
+ total_logs_count = await message_log_dal.count_all_message_logs(
+ session,
+ hide_admin_events=hide_admin_events,
+ )
await _display_formatted_logs(
target_message=callback.message,
@@ -344,8 +352,12 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
try:
# Get all logs (limit to 10000 for performance)
logs_models = await message_log_dal.get_all_message_logs(
- session, limit=10000, offset=0)
-
+ session,
+ limit=10000,
+ offset=0,
+ hide_admin_events=bool(settings.LOG_ADMIN_HIDE),
+ )
+
if not logs_models:
await callback.message.answer(_(
"admin_logs_csv_no_data"
@@ -355,7 +367,7 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
# Create CSV content
csv_buffer = io.StringIO()
csv_writer = csv.writer(csv_buffer, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
-
+
# Write header
headers = [
_("admin_csv_header_log_id"),
@@ -374,16 +386,16 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
_("admin_csv_header_raw_update_preview"),
])
csv_writer.writerow(headers)
-
+
# Write data rows
for log in logs_models:
# Format timestamp
timestamp_str = log.timestamp.strftime('%Y-%m-%d %H:%M:%S UTC') if log.timestamp else ''
-
+
# Clean content and raw_update_preview (remove newlines and quotes for CSV)
content_clean = (log.content or '').replace('\n', ' ').replace('\r', ' ').strip()
raw_update_clean = (log.raw_update_preview or '').replace('\n', ' ').replace('\r', ' ').strip()
-
+
row = [
log.log_id or '',
timestamp_str,
@@ -400,21 +412,21 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
raw_update_clean,
])
csv_writer.writerow(row)
-
+
# Create file
csv_content = csv_buffer.getvalue()
csv_buffer.close()
-
+
# Generate filename with current timestamp
now = datetime.now()
filename = f"message_logs_{now.strftime('%Y%m%d_%H%M%S')}.csv"
-
+
# Send as document
csv_file = types.BufferedInputFile(
csv_content.encode('utf-8-sig'), # BOM for Excel compatibility
filename=filename
)
-
+
await callback.message.answer_document(
csv_file,
caption=_(
@@ -423,7 +435,7 @@ async def export_logs_csv_handler(callback: types.CallbackQuery,
date=now.strftime('%Y-%m-%d %H:%M:%S')
)
)
-
+
except Exception as e:
logging.error(f"Error exporting logs to CSV: {e}", exc_info=True)
await callback.message.answer(_(
diff --git a/bot/handlers/user/payment.py b/bot/handlers/user/payment.py
index 0dd571e..53ebdd7 100644
--- a/bot/handlers/user/payment.py
+++ b/bot/handlers/user/payment.py
@@ -645,8 +645,20 @@ async def yookassa_webhook_route(request: web.Request):
yookassa_service,
lknpd_service)
if not processed:
- await session.rollback()
- return web.Response(status=503, text="yookassa_processing_failed_retry")
+ # process_successful_payment uses False for permanent business failures
+ # (e.g. user not found / metadata issues) and may have already updated
+ # the payment status. Commit the status and ACK the webhook to stop
+ # indefinite provider retries.
+ try:
+ await session.commit()
+ except Exception:
+ await session.rollback()
+ logging.exception(
+ "Failed to commit failure status for YooKassa payment %s",
+ payment_dict_for_processing.get("id"),
+ )
+ return web.Response(status=503, text="yookassa_processing_failed_retry")
+ return web.Response(status=200, text="ok")
await session.commit()
else:
logging.warning(
diff --git a/config/settings.py b/config/settings.py
index eea4f32..fb2087f 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -568,6 +568,11 @@ class Settings(BaseSettings):
description="Include content/raw update fields in admin CSV export",
)
+ LOG_ADMIN_HIDE: bool = Field(
+ default=False,
+ description="Hide admin-generated events from admin logs UI and CSV export",
+ )
+
@field_validator('LOG_LEVEL', mode='before')
@classmethod
def normalize_log_level(cls, v):
diff --git a/db/dal/message_log_dal.py b/db/dal/message_log_dal.py
index 576a1a3..6a0e16b 100644
--- a/db/dal/message_log_dal.py
+++ b/db/dal/message_log_dal.py
@@ -22,16 +22,23 @@ async def create_message_log(session: AsyncSession,
return None
-async def get_all_message_logs(session: AsyncSession, limit: int,
- offset: int) -> List[MessageLog]:
- stmt = select(MessageLog).order_by(
- MessageLog.timestamp.desc()).limit(limit).offset(offset)
+async def get_all_message_logs(session: AsyncSession,
+ limit: int,
+ offset: int,
+ hide_admin_events: bool = False) -> List[MessageLog]:
+ stmt = select(MessageLog)
+ if hide_admin_events:
+ stmt = stmt.where(MessageLog.is_admin_event.is_(False))
+ stmt = stmt.order_by(MessageLog.timestamp.desc()).limit(limit).offset(offset)
result = await session.execute(stmt)
return result.scalars().all()
-async def count_all_message_logs(session: AsyncSession) -> int:
+async def count_all_message_logs(session: AsyncSession,
+ hide_admin_events: bool = False) -> int:
stmt = select(func.count()).select_from(MessageLog)
+ if hide_admin_events:
+ stmt = stmt.where(MessageLog.is_admin_event.is_(False))
result = await session.execute(stmt)
return result.scalar_one()