Add Log rotation

Add logging for Telebot and Flask
Add routing for Enable debugging for Flask and Telebot separately
This commit is contained in:
Влад Зверев 2024-08-02 11:09:40 +05:00
parent 41487ef5c6
commit 730de38cee

View File

@ -1,8 +1,10 @@
import os import os
from flask import Flask, request, jsonify from flask import Flask, request, jsonify
import telebot import schedule
from dotenv import load_dotenv from dotenv import load_dotenv
import hashlib import hashlib
import telebot
from telebot import logger
import logging import logging
from logging.config import dictConfig from logging.config import dictConfig
import zipfile import zipfile
@ -23,21 +25,6 @@ import re
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
# Read environment variables for logging configuration
ENABLE_CONSOLE_LOGGING = os.getenv('ENABLE_CONSOLE_LOGGING', 'true').lower() in ['true', '1', 'yes']
ENABLE_FILE_LOGGING = os.getenv('ENABLE_FILE_LOGGING', 'true').lower() in ['true', '1', 'yes']
# Define log paths
LOG_PATH_ERRORS = os.getenv('LOG_PATH_ERRORS', 'logs/errors.log')
LOG_PATH_EVENTS = os.getenv('LOG_PATH_EVENTS', 'logs/events.log')
LOG_PATH_FLASK = os.getenv('LOG_PATH_FLASK', 'logs/flask.log')
LOG_ARCHIVE_PATH = os.getenv('LOG_ARCHIVE_PATH', 'logs/archive')
# Create log directories if they do not exist
os.makedirs(os.path.dirname(LOG_PATH_ERRORS), exist_ok=True)
os.makedirs(os.path.dirname(LOG_PATH_EVENTS), exist_ok=True)
os.makedirs(LOG_ARCHIVE_PATH, exist_ok=True)
class UTF8StreamHandler(logging.StreamHandler): class UTF8StreamHandler(logging.StreamHandler):
def __init__(self, stream=None): def __init__(self, stream=None):
super().__init__(stream) super().__init__(stream)
@ -48,120 +35,90 @@ class UTF8StreamHandler(logging.StreamHandler):
if hasattr(stream, 'reconfigure'): if hasattr(stream, 'reconfigure'):
stream.reconfigure(encoding='utf-8') stream.reconfigure(encoding='utf-8')
# Конфигурация логирования
handlers = {}
if ENABLE_CONSOLE_LOGGING:
handlers['console'] = {
'class': 'telezab.UTF8StreamHandler',
'stream': 'ext://sys.stdout', # Вывод в консоль
'formatter': 'console',
}
if ENABLE_FILE_LOGGING:
handlers['file_errors'] = {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': LOG_PATH_ERRORS,
'when': 'midnight',
'interval': 1,
'backupCount': 7,
'formatter': 'error',
'encoding': 'utf-8', # Убедитесь, что используется кодировка UTF-8
}
handlers['file_events'] = {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': LOG_PATH_EVENTS,
'when': 'midnight',
'interval': 1,
'backupCount': 7,
'formatter': 'event',
'encoding': 'utf-8', # Убедитесь, что используется кодировка UTF-8
}
# Configure Flask logger
flask_handlers = {
'flask': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': LOG_PATH_FLASK,
'when': 'midnight',
'interval': 1,
'backupCount': 7,
'formatter': 'default',
'encoding': 'utf-8',
}
}
# Include flask_handlers in dictConfig # Определение пути к основному лог-файлу
LOG_FILE = 'logs/app.log'
# Определение функции архивирования логов
def archive_old_logs():
# Получаем дату предыдущего дня
yesterday_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
# Проверяем существует ли основной лог-файл
if os.path.exists(LOG_FILE):
# Путь к архиву в той же папке
archive_name = f"app_{yesterday_date}.zip"
archive_path = os.path.join(os.path.dirname(LOG_FILE), archive_name)
# Создание архива и добавление лог-файла
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.write(LOG_FILE, arcname=os.path.basename(LOG_FILE))
# Удаление старого лог-файла после архивирования
os.remove(LOG_FILE)
class FilterByMessage(logging.Filter):
def filter(self, record):
# Фильтруем сообщения, содержащие 'Received 1 new updates'
return 'Received ' not in record.getMessage()
# Initialize Flask application
app = Flask(__name__)
# Настройка логирования
dictConfig({ dictConfig({
'version': 1, 'version': 1,
'formatters': { 'formatters': {
'default': { 'default': {
'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s', 'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s',
}, },
},
'handlers': {
'console': { 'console': {
'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s', 'class': 'telezab.UTF8StreamHandler', # Замените на путь к вашему классу UTF8StreamHandler
'stream': 'ext://sys.stdout', # Вывод в консоль
'formatter': 'default',
'filters': ['filter_by_message']
}, },
'error': { 'file': {
'format': '[%(asctime)s] ERROR %(module)s: %(message)s', 'class': 'logging.FileHandler',
}, 'filename': 'app.log', # Запись в файл
'event': { 'formatter': 'default',
'format': '[%(asctime)s] EVENT %(module)s: %(message)s', 'encoding': 'utf-8', # Кодировка файла
},
},
'filters': {
'filter_by_message': {
'()': FilterByMessage,
}, },
}, },
'handlers': {**handlers, **flask_handlers},
'loggers': { 'loggers': {
'default': {
'level': 'INFO',
'handlers': ['console', 'file_events'],
},
'error': {
'level': 'ERROR',
'handlers': ['console', 'file_errors'],
'propagate': False,
},
'flask': { 'flask': {
'level': 'INFO', 'level': 'DEBUG',
'handlers': ['flask'], 'handlers': ['console', 'file'],
'propagate': False, 'propagate': False,
} },
'telebot': {
'level': 'DEBUG',
'handlers': ['console', 'file'],
'propagate': False,
},
}, },
'root': { 'root': {
'level': 'INFO', 'level': 'DEBUG',
'handlers': ['console', 'file_events'], 'handlers': ['console', 'file'],
} }
}) })
# Настройка уровня логирования для Flask
app.logger.setLevel(logging.DEBUG)
# Set Flask app logger to use the 'flask' logger # Настройка pyTelegramBotAPI logger
telebot.logger = logging.getLogger('telebot')
# Определение функции архивирования логов
def archive_old_logs():
yesterday_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
for log_file in [LOG_PATH_ERRORS, LOG_PATH_EVENTS]:
log_dir, log_filename = os.path.split(log_file)
for filename in os.listdir(log_dir):
if filename.startswith(log_filename) and filename != log_filename:
log_file_path = os.path.join(log_dir, filename)
archive_name = f"{log_filename}_{yesterday_date}.zip"
archive_path = os.path.join(LOG_ARCHIVE_PATH, archive_name)
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.write(log_file_path, arcname=filename)
os.remove(log_file_path)
# Create loggers
logger = logging.getLogger('default')
error_logger = logging.getLogger('error')
# Call archive_old_logs function after logging setup
archive_old_logs()
# Initialize Flask application
app = Flask(__name__)
app.logger.handlers = []
app.logger.propagate = True
flask_logger = logging.getLogger('flask')
app.logger.addHandler(flask_logger.handlers[0])
# Get the token from environment variables # Get the token from environment variables
TOKEN = os.getenv('TELEGRAM_TOKEN') TOKEN = os.getenv('TELEGRAM_TOKEN')
ZABBIX_URL = os.getenv('ZABBIX_URL') ZABBIX_URL = os.getenv('ZABBIX_URL')
@ -191,17 +148,22 @@ user_timers = {}
# Initialize SQLite database
def init_db(): def init_db():
global st
st = datetime.now()
try: try:
with db_lock: with db_lock:
conn = sqlite3.connect('telezab.db') conn = sqlite3.connect('telezab.db')
cursor = conn.cursor() cursor = conn.cursor()
# Create events table
cursor.execute('''CREATE TABLE IF NOT EXISTS events ( cursor.execute('''CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
hash TEXT UNIQUE, hash TEXT UNIQUE,
data TEXT, data TEXT,
delivered BOOLEAN)''') delivered BOOLEAN)''')
# Create subscriptions table with username and active flag
cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions ( cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions (
chat_id INTEGER, chat_id INTEGER,
region_id TEXT, region_id TEXT,
@ -209,29 +171,39 @@ def init_db():
active BOOLEAN DEFAULT TRUE, active BOOLEAN DEFAULT TRUE,
skip BOOLEAN DEFAULT FALSE, skip BOOLEAN DEFAULT FALSE,
UNIQUE(chat_id, region_id))''') UNIQUE(chat_id, region_id))''')
# Create whitelist table
cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist ( cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist (
chat_id INTEGER PRIMARY KEY)''') chat_id INTEGER PRIMARY KEY)''')
# Create regions table with active flag
cursor.execute('''CREATE TABLE IF NOT EXISTS regions ( cursor.execute('''CREATE TABLE IF NOT EXISTS regions (
region_id TEXT PRIMARY KEY, region_id TEXT PRIMARY KEY,
region_name TEXT, region_name TEXT,
active BOOLEAN DEFAULT TRUE)''') active BOOLEAN DEFAULT TRUE)''')
# Create user events table for logging
cursor.execute('''CREATE TABLE IF NOT EXISTS user_events ( cursor.execute('''CREATE TABLE IF NOT EXISTS user_events (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
chat_id INTEGER, chat_id INTEGER,
username TEXT, username TEXT,
action TEXT, action TEXT,
timestamp TEXT)''') timestamp TEXT)''')
# Insert sample regions
cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES
('01', 'Адыгея'), ('01', 'Адыгея'),
('02', 'Башкортостан (Уфа)'), ('02', 'Башкортостан (Уфа)'),
('04', 'Алтай'), ('04', 'Алтай'),
('19', 'Республика Хакасия')''') ('19', 'Республика Хакасия')''')
conn.commit() conn.commit()
logger.info("Database initialized successfully.") app.logger.info("Database initialized successfully.")
except Exception as e: except Exception as e:
error_logger.error(f"Error initializing database: {e}") app.logger.error(f"Error initializing database: {e}")
finally: finally:
conn.close() conn.close()
app.logger.info(f"init_db completed in {datetime.now() - st}")
# Hash the incoming data # Hash the incoming data
@ -245,7 +217,7 @@ def is_whitelisted(chat_id):
conn = sqlite3.connect('telezab.db') conn = sqlite3.connect('telezab.db')
cursor = conn.cursor() cursor = conn.cursor()
query = 'SELECT COUNT(*) FROM whitelist WHERE chat_id = ?' query = 'SELECT COUNT(*) FROM whitelist WHERE chat_id = ?'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}")
cursor.execute(query, (chat_id,)) cursor.execute(query, (chat_id,))
count = cursor.fetchone()[0] count = cursor.fetchone()[0]
conn.close() conn.close()
@ -258,7 +230,7 @@ def add_to_whitelist(chat_id, username):
conn = sqlite3.connect('telezab.db') conn = sqlite3.connect('telezab.db')
cursor = conn.cursor() cursor = conn.cursor()
query = 'INSERT OR IGNORE INTO whitelist (chat_id, username) VALUES (?, ?)' query = 'INSERT OR IGNORE INTO whitelist (chat_id, username) VALUES (?, ?)'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}")
cursor.execute(query, (chat_id, username)) cursor.execute(query, (chat_id, username))
conn.commit() conn.commit()
conn.close() conn.close()
@ -270,7 +242,7 @@ def remove_from_whitelist(chat_id):
conn = sqlite3.connect('telezab.db') conn = sqlite3.connect('telezab.db')
cursor = conn.cursor() cursor = conn.cursor()
query = 'DELETE FROM whitelist WHERE chat_id = ?' query = 'DELETE FROM whitelist WHERE chat_id = ?'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}")
cursor.execute(query, (chat_id,)) cursor.execute(query, (chat_id,))
conn.commit() conn.commit()
conn.close() conn.close()
@ -355,12 +327,12 @@ def log_user_event(chat_id, username, action):
conn = sqlite3.connect('telezab.db') conn = sqlite3.connect('telezab.db')
cursor = conn.cursor() cursor = conn.cursor()
query = 'INSERT INTO user_events (chat_id, username, action, timestamp) VALUES (?, ?, ?, ?)' query = 'INSERT INTO user_events (chat_id, username, action, timestamp) VALUES (?, ?, ?, ?)'
logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}, action={action}, timestamp={timestamp}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}, action={action}, timestamp={timestamp}")
cursor.execute(query, (chat_id, username, action, timestamp)) cursor.execute(query, (chat_id, username, action, timestamp))
conn.commit() conn.commit()
logger.info(f"User event logged: {chat_id} ({username}) - {action} at {timestamp}.") telebot.logger.info(f"User event logged: {chat_id} ({username}) - {action} at {timestamp}.")
except Exception as e: except Exception as e:
error_logger.error(f"Error logging user event: {e}") telebot.logger.error(f"Error logging user event: {e}")
finally: finally:
conn.close() conn.close()
@ -397,7 +369,8 @@ def reset_settings_timer(chat_id):
def transition_to_notification_mode(chat_id): def transition_to_notification_mode(chat_id):
set_user_state(chat_id, NOTIFICATION_MODE) set_user_state(chat_id, NOTIFICATION_MODE)
bot.send_message(chat_id, "Вы были автоматически переведены в режим получения уведомлений.") bot.send_message(chat_id, "Вы были автоматически переведены в режим получения уведомлений.")
app.logger.info(f"User {chat_id} automatically transitioned to notification mode.") show_main_menu(chat_id)
telebot.logger.info(f"User {chat_id} automatically transitioned to notification mode.")
# Main menu for users # Main menu for users
@ -434,7 +407,7 @@ def handle_start(message):
set_user_state(chat_id, NOTIFICATION_MODE) set_user_state(chat_id, NOTIFICATION_MODE)
show_main_menu(chat_id) show_main_menu(chat_id)
app.logger.info(f"User {chat_id} ({username}) started with command /start.") telebot.logger.info(f"User {chat_id} ({username}) started with command /start.")
# Handle menu button presses # Handle menu button presses
@ -498,7 +471,7 @@ def handle_subscribe(message):
chat_id = message.chat.id chat_id = message.chat.id
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
app.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {chat_id}")
return return
username = message.from_user.username username = message.from_user.username
@ -529,16 +502,16 @@ def process_subscription(message, chat_id, username):
bot.send_message(chat_id, f"Регион с ID {region_id} не существует или недопустимый формат. Введите только существующие номера регионов.") bot.send_message(chat_id, f"Регион с ID {region_id} не существует или недопустимый формат. Введите только существующие номера регионов.")
return show_settings_menu(chat_id) return show_settings_menu(chat_id)
query = 'INSERT OR IGNORE INTO subscriptions (chat_id, region_id, username, active) VALUES (?, ?, ?, TRUE)' query = 'INSERT OR IGNORE INTO subscriptions (chat_id, region_id, username, active) VALUES (?, ?, ?, TRUE)'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}, username={username}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}, username={username}")
cursor.execute(query, (chat_id, region_id, username)) cursor.execute(query, (chat_id, region_id, username))
if cursor.rowcount == 0: if cursor.rowcount == 0:
query = 'UPDATE subscriptions SET active = TRUE WHERE chat_id = ? AND region_id = ?' query = 'UPDATE subscriptions SET active = TRUE WHERE chat_id = ? AND region_id = ?'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}")
cursor.execute(query, (chat_id, region_id)) cursor.execute(query, (chat_id, region_id))
conn.commit() conn.commit()
conn.close() conn.close()
bot.send_message(chat_id, f"Подписка на регионы: {', '.join(region_ids)} оформлена.") bot.send_message(chat_id, f"Подписка на регионы: {', '.join(region_ids)} оформлена.")
app.logger.info(f"User {chat_id} ({username}) subscribed to regions: {', '.join(region_ids)}.") telebot.logger.info(f"User {chat_id} ({username}) subscribed to regions: {', '.join(region_ids)}.")
log_user_event(chat_id, username, f"Subscribed to regions: {', '.join(region_ids)}") log_user_event(chat_id, username, f"Subscribed to regions: {', '.join(region_ids)}")
show_settings_menu(chat_id) show_settings_menu(chat_id)
@ -549,7 +522,7 @@ def handle_unsubscribe(message):
chat_id = message.chat.id chat_id = message.chat.id
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
app.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {chat_id}")
return return
user_regions = get_user_subscribed_regions(chat_id) user_regions = get_user_subscribed_regions(chat_id)
@ -562,6 +535,7 @@ def handle_unsubscribe(message):
bot.register_next_step_handler_by_chat_id(chat_id, process_unsubscription, chat_id) bot.register_next_step_handler_by_chat_id(chat_id, process_unsubscription, chat_id)
# Пример функции, которая использует bot и log
def process_unsubscription(message, chat_id): def process_unsubscription(message, chat_id):
if message.text.lower() == 'отмена': if message.text.lower() == 'отмена':
bot.send_message(chat_id, "Действие отменено.") bot.send_message(chat_id, "Действие отменено.")
@ -579,13 +553,13 @@ def process_unsubscription(message, chat_id):
bot.send_message(chat_id, f"Регион с ID {region_id} не существует или недопустимый формат. Введите только номера регионов, на которые вы подписаны.") bot.send_message(chat_id, f"Регион с ID {region_id} не существует или недопустимый формат. Введите только номера регионов, на которые вы подписаны.")
return show_settings_menu(chat_id) return show_settings_menu(chat_id)
query = 'UPDATE subscriptions SET active = FALSE WHERE chat_id = ? AND region_id = ?' query = 'UPDATE subscriptions SET active = FALSE WHERE chat_id = ? AND region_id = ?'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with chat_id={chat_id}, region_id={region_id}")
cursor.execute(query, (chat_id, region_id)) cursor.execute(query, (chat_id, region_id))
conn.commit() conn.commit()
conn.close() conn.close()
bot.send_message(chat_id, f"Отписка от регионов: {', '.join(region_ids)} оформлена.") bot.send_message(chat_id, f"Отписка от регионов: {', '.join(region_ids)} оформлена.")
app.logger.info(f"User {chat_id} unsubscribed from regions: {', '.join(region_ids)}.") telebot.logger.info(f"User {chat_id} unsubscribed from regions: {', '.join(region_ids)}.")
username = message.from_user.username if message.from_user.username else "N/A" username = "@" + message.from_user.username if message.from_user.username else "N/A"
log_user_event(chat_id, username, f"Unsubscribed from regions: {', '.join(region_ids)}") log_user_event(chat_id, username, f"Unsubscribed from regions: {', '.join(region_ids)}")
show_settings_menu(chat_id) show_settings_menu(chat_id)
@ -663,7 +637,7 @@ def process_register(message, chat_id, username):
reply_markup=markup reply_markup=markup
) )
bot.send_message(chat_id, "Запрос отправлен администратору для одобрения.") bot.send_message(chat_id, "Запрос отправлен администратору для одобрения.")
app.logger.info(f"User {chat_id} ({username}) requested registration.") telebot.logger.info(f"User {chat_id} ({username}) requested registration.")
else: else:
bot.send_message(chat_id, "Некорректный выбор. Регистрация отменена.") bot.send_message(chat_id, "Некорректный выбор. Регистрация отменена.")
show_main_menu(chat_id) show_main_menu(chat_id)
@ -688,17 +662,17 @@ def process_add_region(message):
conn = sqlite3.connect('telezab.db') conn = sqlite3.connect('telezab.db')
cursor = conn.cursor() cursor = conn.cursor()
query = 'SELECT region_name, active FROM regions WHERE region_id = ?' query = 'SELECT region_name, active FROM regions WHERE region_id = ?'
app.logger.debug(f"Executing query: {query} with region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with region_id={region_id}")
cursor.execute(query, (region_id,)) cursor.execute(query, (region_id,))
result = cursor.fetchone() result = cursor.fetchone()
if result: if result:
existing_region_name, active = result existing_region_name, active = result
if existing_region_name == region_name: if existing_region_name == region_name:
query = 'UPDATE regions SET active = TRUE WHERE region_id = ?' query = 'UPDATE regions SET active = TRUE WHERE region_id = ?'
app.logger.debug(f"Executing query: {query} with region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with region_id={region_id}")
cursor.execute(query, (region_id,)) cursor.execute(query, (region_id,))
bot.send_message(chat_id, f"Регион {region_id} - {region_name} активирован.") bot.send_message(chat_id, f"Регион {region_id} - {region_name} активирован.")
app.logger.info(f"Admin {chat_id} reactivated region {region_id} - {region_name}.") telebot.logger.info(f"Admin {chat_id} reactivated region {region_id} - {region_name}.")
else: else:
markup = telebot.types.InlineKeyboardMarkup() markup = telebot.types.InlineKeyboardMarkup()
markup.add(telebot.types.InlineKeyboardButton(text="Заменить", callback_data=f"replace_{region_id}_{region_name}")) markup.add(telebot.types.InlineKeyboardButton(text="Заменить", callback_data=f"replace_{region_id}_{region_name}"))
@ -707,10 +681,10 @@ def process_add_region(message):
bot.send_message(chat_id, f"Регион {region_id} уже существует с названием {existing_region_name}. Хотите заменить его или активировать старый регион?", reply_markup=markup) bot.send_message(chat_id, f"Регион {region_id} уже существует с названием {existing_region_name}. Хотите заменить его или активировать старый регион?", reply_markup=markup)
else: else:
query = 'INSERT OR IGNORE INTO regions (region_id, region_name) VALUES (?, ?)' query = 'INSERT OR IGNORE INTO regions (region_id, region_name) VALUES (?, ?)'
app.logger.debug(f"Executing query: {query} with region_id={region_id}, region_name={region_name}") telebot.logger.debug(f"Executing query: {query} with region_id={region_id}, region_name={region_name}")
cursor.execute(query, (region_id, region_name)) cursor.execute(query, (region_id, region_name))
bot.send_message(chat_id, f"Регион {region_id} - {region_name} добавлен.") bot.send_message(chat_id, f"Регион {region_id} - {region_name} добавлен.")
app.logger.info(f"Admin {chat_id} added region {region_id} - {region_name}.") telebot.logger.info(f"Admin {chat_id} added region {region_id} - {region_name}.")
conn.commit() conn.commit()
conn.close() conn.close()
except (IndexError, ValueError): except (IndexError, ValueError):
@ -736,19 +710,19 @@ def handle_region_action(call):
cursor = conn.cursor() cursor = conn.cursor()
if action == "replace": if action == "replace":
query = 'UPDATE regions SET region_name = ?, active = TRUE WHERE region_id = ?' query = 'UPDATE regions SET region_name = ?, active = TRUE WHERE region_id = ?'
app.logger.debug(f"Executing query: {query} with region_name={region_name}, region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with region_name={region_name}, region_id={region_id}")
cursor.execute(query, (region_name, region_id)) cursor.execute(query, (region_name, region_id))
bot.send_message(chat_id, f"Регион {region_id} обновлен до {region_name} и активирован.") bot.send_message(chat_id, f"Регион {region_id} обновлен до {region_name} и активирован.")
app.logger.info(f"Admin {chat_id} replaced and reactivated region {region_id} with {region_name}.") telebot.logger.info(f"Admin {chat_id} replaced and reactivated region {region_id} with {region_name}.")
elif action == "reactivate": elif action == "reactivate":
query = 'UPDATE regions SET active = TRUE WHERE region_id = ?' query = 'UPDATE regions SET active = TRUE WHERE region_id = ?'
app.logger.debug(f"Executing query: {query} with region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with region_id={region_id}")
cursor.execute(query, (region_id,)) cursor.execute(query, (region_id,))
bot.send_message(chat_id, f"Регион {region_id} активирован.") bot.send_message(chat_id, f"Регион {region_id} активирован.")
app.logger.info(f"Admin {chat_id} reactivated region {region_id}.") telebot.logger.info(f"Admin {chat_id} reactivated region {region_id}.")
elif action == "cancel_region": elif action == "cancel_region":
bot.send_message(chat_id, "Действие отменено.") bot.send_message(chat_id, "Действие отменено.")
app.logger.info(f"Admin {chat_id} canceled region action.") telebot.logger.info(f"Admin {chat_id} canceled region action.")
conn.commit() conn.commit()
conn.close() conn.close()
bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None) bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None)
@ -771,15 +745,15 @@ def process_remove_region(message):
return show_settings_menu(chat_id) return show_settings_menu(chat_id)
query = 'UPDATE regions SET active = FALSE WHERE region_id = ?' query = 'UPDATE regions SET active = FALSE WHERE region_id = ?'
app.logger.debug(f"Executing query: {query} with region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with region_id={region_id}")
cursor.execute(query, (region_id,)) cursor.execute(query, (region_id,))
query = 'UPDATE subscriptions SET active = FALSE WHERE region_id = ? AND active = TRUE' query = 'UPDATE subscriptions SET active = FALSE WHERE region_id = ? AND active = TRUE'
app.logger.debug(f"Executing query: {query} with region_id={region_id}") telebot.logger.debug(f"Executing query: {query} with region_id={region_id}")
cursor.execute(query, (region_id,)) cursor.execute(query, (region_id,))
conn.commit() conn.commit()
conn.close() conn.close()
bot.send_message(chat_id, f"Регион {region_id} теперь неактивен и все активные подписки обновлены.") bot.send_message(chat_id, f"Регион {region_id} теперь неактивен и все активные подписки обновлены.")
app.logger.info(f"Admin {chat_id} set region {region_id} to inactive and updated subscriptions.") telebot.logger.info(f"Admin {chat_id} set region {region_id} to inactive and updated subscriptions.")
except IndexError: except IndexError:
bot.send_message(chat_id, "Неверный формат. Используйте: <region_id>") bot.send_message(chat_id, "Неверный формат. Используйте: <region_id>")
show_settings_menu(chat_id) show_settings_menu(chat_id)
@ -789,7 +763,7 @@ def handle_my_subscriptions(message):
chat_id = message.chat.id chat_id = message.chat.id
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
app.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {chat_id}")
return return
user_regions = get_user_subscribed_regions(chat_id) user_regions = get_user_subscribed_regions(chat_id)
@ -807,7 +781,7 @@ def handle_active_regions(message):
chat_id = message.chat.id chat_id = message.chat.id
if not is_whitelisted(chat_id): if not is_whitelisted(chat_id):
bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.") bot.send_message(chat_id, "Вы не авторизованы для использования этого бота.")
app.logger.info(f"Unauthorized access attempt by {chat_id}") telebot.logger.info(f"Unauthorized access attempt by {chat_id}")
return return
regions = get_sorted_regions() # Используем функцию для получения отсортированных регионов regions = get_sorted_regions() # Используем функцию для получения отсортированных регионов
@ -855,7 +829,7 @@ async def consume_from_queue():
await send_notification_message(chat_id, message_text) await send_notification_message(chat_id, message_text)
channel.basic_ack(method_frame.delivery_tag) channel.basic_ack(method_frame.delivery_tag)
except Exception as e: except Exception as e:
app.logger.error(f"Error sending message from queue: {e}") telebot.logger.error(f"Error sending message from queue: {e}")
# Optionally, you can nack the message to requeue it # Optionally, you can nack the message to requeue it
# channel.basic_nack(method_frame.delivery_tag) # channel.basic_nack(method_frame.delivery_tag)
@ -869,13 +843,13 @@ async def send_message(chat_id, message, is_notification=False):
await run_in_executor(bot.send_message, chat_id, message) await run_in_executor(bot.send_message, chat_id, message)
except telebot.apihelper.ApiTelegramException as e: except telebot.apihelper.ApiTelegramException as e:
if "429" in str(e): if "429" in str(e):
app.logger.warning(f"Rate limit exceeded for chat_id {chat_id}. Retrying...") telebot.logger.warning(f"Rate limit exceeded for chat_id {chat_id}. Retrying...")
await asyncio.sleep(1) await asyncio.sleep(1)
await send_message(chat_id, message, is_notification) await send_message(chat_id, message, is_notification)
else: else:
app.logger.error(f"Failed to send message to {chat_id}: {e}") telebot.logger.error(f"Failed to send message to {chat_id}: {e}")
except Exception as e: except Exception as e:
app.logger.error(f"Error sending message to {chat_id}: {e}") telebot.logger.error(f"Error sending message to {chat_id}: {e}")
await check_telegram_api() await check_telegram_api()
finally: finally:
if is_notification: if is_notification:
@ -897,11 +871,11 @@ async def check_telegram_api():
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get('https://api.telegram.org') as response: async with session.get('https://api.telegram.org') as response:
if response.status == 200: if response.status == 200:
app.logger.info("Telegram API is reachable.") telebot.logger.info("Telegram API is reachable.")
else: else:
app.logger.error("Telegram API is not reachable.") telebot.logger.error("Telegram API is not reachable.")
except Exception as e: except Exception as e:
app.logger.error(f"Error checking Telegram API: {e}") telebot.logger.error(f"Error checking Telegram API: {e}")
def extract_region_number(host): def extract_region_number(host):
@ -1029,6 +1003,48 @@ def add_user():
app.logger.error("Invalid data received for adding user.") app.logger.error("Invalid data received for adding user.")
return jsonify({"status": "failure", "reason": "Invalid data"}), 400 return jsonify({"status": "failure", "reason": "Invalid data"}), 400
# Обработчик для переключения уровня логирования Flask
@app.route('/debug/flask', methods=['POST'])
def toggle_flask_debug():
try:
data = request.get_json()
level = data.get('level', 'DEBUG').upper()
if level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
return jsonify({'status': 'error', 'message': 'Invalid log level'}), 400
log_level = getattr(logging, level, logging.DEBUG)
app.logger.setLevel(log_level)
# Обновление уровня логирования для каждого обработчика
for handler in app.logger.handlers:
handler.setLevel(log_level)
return jsonify({'status': 'success', 'level': level})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
# Обработчик для переключения уровня логирования Telebot
@app.route('/debug/telebot', methods=['POST'])
def toggle_telebot_debug():
try:
data = request.get_json()
level = data.get('level', 'DEBUG').upper()
if level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
return jsonify({'status': 'error', 'message': 'Invalid log level'}), 400
log_level = getattr(logging, level, logging.DEBUG)
telebot.logger.setLevel(log_level)
# Обновление уровня логирования для каждого обработчика
for handler in telebot.logger.handlers:
handler.setLevel(log_level)
return jsonify({'status': 'success', 'level': level})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
# Handle active triggers # Handle active triggers
def handle_active_triggers(message): def handle_active_triggers(message):
chat_id = message.chat.id chat_id = message.chat.id
@ -1090,7 +1106,7 @@ def handle_region_selection(call):
bot.send_message(chat_id, f"Найдены следующие группы хостов для региона {region_id}:", reply_markup=markup) bot.send_message(chat_id, f"Найдены следующие группы хостов для региона {region_id}:", reply_markup=markup)
except Exception as e: except Exception as e:
logging.error(f"Error connecting to Zabbix API: {e}") telebot.logger.error(f"Error connecting to Zabbix API: {e}")
bot.send_message(chat_id, "Не удалось подключиться к Zabbix API. Пожалуйста, попробуйте позже.") bot.send_message(chat_id, "Не удалось подключиться к Zabbix API. Пожалуйста, попробуйте позже.")
bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None) bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None)
@ -1113,7 +1129,7 @@ def handle_group_selection(call):
bot.send_message(chat_id, trigger, parse_mode="html") bot.send_message(chat_id, trigger, parse_mode="html")
time.sleep(1/5) time.sleep(1/5)
except Exception as e: except Exception as e:
logging.error(f"Error processing group selection: {e}") telebot.logger.error(f"Error processing group selection: {e}")
bot.send_message(chat_id, "Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже.") bot.send_message(chat_id, "Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже.")
bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None) bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None)
@ -1219,7 +1235,7 @@ def get_zabbix_triggers(group_id):
return trigger_messages return trigger_messages
except Exception as e: except Exception as e:
logging.error(f"Error connecting to Zabbix API: {e}") telebot.logger.error(f"Error connecting to Zabbix API: {e}")
return None return None
@ -1264,20 +1280,34 @@ def simulate_triggers(message):
def run_polling(): def run_polling():
bot.polling(none_stop=True, interval=0) bot.polling(non_stop=True, interval=0)
# Запуск Flask-приложения
def run_flask():
app.run(port=5000, host='0.0.0.0', debug=True, use_reloader=False)
if __name__ == '__main__': def schedule_jobs():
schedule.every().day.at("00:00").do(archive_old_logs)
while True:
schedule.run_pending()
time.sleep(60) # Проверять раз в минуту
# Основная функция для запуска
def main():
# Инициализация базы данных
init_db() init_db()
print('Bootstrap wait...') print('Bootstrap wait...')
# Start Flask app in a separate thread
Thread(target=app.run, kwargs={'port': 5000, 'host': '0.0.0.0', 'debug': True, 'use_reloader': False}, daemon=True).start()
# Start bot polling in a separate thread # Запуск Flask и бота в отдельных потоках
Thread(target=run_flask, daemon=True).start()
Thread(target=run_polling, daemon=True).start() Thread(target=run_polling, daemon=True).start()
# Запуск планировщика задач в отдельном потоке
Thread(target=schedule_jobs, daemon=True).start()
# Запуск асинхронных задач
asyncio.run(consume_from_queue())
# Start async message consumer if __name__ == '__main__':
loop = asyncio.get_event_loop() main()
loop.create_task(consume_from_queue())
loop.run_forever()