From 75809aaafcbafc24b091b495930d2e4ec89468ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=20=D0=97=D0=B2=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B2?= Date: Thu, 1 Aug 2024 15:22:33 +0500 Subject: [PATCH] Fix formating message from zabbix now get Host, history url, host's ip and description of trigger Trying to fix logger, add logrotation by app --- telezab.py | 315 ++++++++++++++++++++++++++--------------------------- 1 file changed, 155 insertions(+), 160 deletions(-) diff --git a/telezab.py b/telezab.py index a22db40..abd89b6 100644 --- a/telezab.py +++ b/telezab.py @@ -5,6 +5,7 @@ from dotenv import load_dotenv import hashlib import logging from logging.config import dictConfig +import zipfile from threading import Thread, Lock, Timer import sqlite3 import time @@ -29,10 +30,13 @@ ENABLE_FILE_LOGGING = os.getenv('ENABLE_FILE_LOGGING', 'true').lower() in ['true # 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): def __init__(self, stream=None): @@ -44,29 +48,47 @@ class UTF8StreamHandler(logging.StreamHandler): if hasattr(stream, 'reconfigure'): stream.reconfigure(encoding='utf-8') -# Define handlers based on environment variables +# Конфигурация логирования handlers = {} if ENABLE_CONSOLE_LOGGING: handlers['console'] = { 'class': 'telezab.UTF8StreamHandler', - 'stream': 'ext://sys.stdout', # Output to console + 'stream': 'ext://sys.stdout', # Вывод в консоль 'formatter': 'console', } if ENABLE_FILE_LOGGING: handlers['file_errors'] = { - 'class': 'logging.FileHandler', + 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': LOG_PATH_ERRORS, + 'when': 'midnight', + 'interval': 1, + 'backupCount': 7, 'formatter': 'error', - 'encoding': 'utf-8', # Ensure UTF-8 encoding + 'encoding': 'utf-8', # Убедитесь, что используется кодировка UTF-8 } handlers['file_events'] = { - 'class': 'logging.FileHandler', + 'class': 'logging.handlers.TimedRotatingFileHandler', 'filename': LOG_PATH_EVENTS, + 'when': 'midnight', + 'interval': 1, + 'backupCount': 7, 'formatter': 'event', - 'encoding': 'utf-8', # Ensure UTF-8 encoding + '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', + } +} -# Configure logging +# Include flask_handlers in dictConfig dictConfig({ 'version': 1, 'formatters': { @@ -83,16 +105,63 @@ dictConfig({ 'format': '[%(asctime)s] EVENT %(module)s: %(message)s', }, }, - 'handlers': handlers, + 'handlers': {**handlers, **flask_handlers}, + 'loggers': { + 'default': { + 'level': 'INFO', + 'handlers': ['console', 'file_events'], + }, + 'error': { + 'level': 'ERROR', + 'handlers': ['console', 'file_errors'], + 'propagate': False, + }, + 'flask': { + 'level': 'INFO', + 'handlers': ['flask'], + 'propagate': False, + } + }, 'root': { 'level': 'INFO', - 'handlers': list(handlers.keys()), + 'handlers': ['console', 'file_events'], } }) + + +# Set Flask app logger to use the 'flask' logger + + +# Определение функции архивирования логов +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 TOKEN = os.getenv('TELEGRAM_TOKEN') ZABBIX_URL = os.getenv('ZABBIX_URL') @@ -120,54 +189,48 @@ user_states = {} user_timers = {} + + # Initialize SQLite database def init_db(): - with db_lock: - conn = sqlite3.connect('telezab.db') - cursor = conn.cursor() - - # Create events table - cursor.execute('''CREATE TABLE IF NOT EXISTS events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - hash TEXT UNIQUE, - data TEXT, - delivered BOOLEAN)''') - - # Create subscriptions table with username and active flag - cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions ( - chat_id INTEGER, - region_id TEXT, - username TEXT, - active BOOLEAN DEFAULT TRUE, - skip BOOLEAN DEFAULT FALSE, - UNIQUE(chat_id, region_id))''') - - # Create whitelist table - cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist ( - chat_id INTEGER PRIMARY KEY)''') - - # Create regions table with active flag - cursor.execute('''CREATE TABLE IF NOT EXISTS regions ( - region_id TEXT PRIMARY KEY, - region_name TEXT, - active BOOLEAN DEFAULT TRUE)''') - - # Create user events table for logging - cursor.execute('''CREATE TABLE IF NOT EXISTS user_events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - chat_id INTEGER, - username TEXT, - action TEXT, - timestamp TEXT)''') - - # Insert sample regions - cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES - ('01', 'Адыгея'), - ('02', 'Башкортостан (Уфа)'), - ('04', 'Алтай'), - ('19', 'Республика Хакасия')''') - - conn.commit() + try: + with db_lock: + conn = sqlite3.connect('telezab.db') + cursor = conn.cursor() + cursor.execute('''CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + hash TEXT UNIQUE, + data TEXT, + delivered BOOLEAN)''') + cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions ( + chat_id INTEGER, + region_id TEXT, + username TEXT, + active BOOLEAN DEFAULT TRUE, + skip BOOLEAN DEFAULT FALSE, + UNIQUE(chat_id, region_id))''') + cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist ( + chat_id INTEGER PRIMARY KEY)''') + cursor.execute('''CREATE TABLE IF NOT EXISTS regions ( + region_id TEXT PRIMARY KEY, + region_name TEXT, + active BOOLEAN DEFAULT TRUE)''') + cursor.execute('''CREATE TABLE IF NOT EXISTS user_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + chat_id INTEGER, + username TEXT, + action TEXT, + timestamp TEXT)''') + cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES + ('01', 'Адыгея'), + ('02', 'Башкортостан (Уфа)'), + ('04', 'Алтай'), + ('19', 'Республика Хакасия')''') + conn.commit() + logger.info("Database initialized successfully.") + except Exception as e: + error_logger.error(f"Error initializing database: {e}") + finally: conn.close() @@ -285,16 +348,20 @@ def format_regions_list(regions): return '\n'.join([f"{region_id} - {region_name}" for region_id, region_name in regions]) -# Log user events def log_user_event(chat_id, username, action): timestamp = time.strftime('%Y-%m-%d %H:%M:%S') - with db_lock: - conn = sqlite3.connect('telezab.db') - cursor = conn.cursor() - query = 'INSERT INTO user_events (chat_id, username, action, timestamp) VALUES (?, ?, ?, ?)' - app.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)) - conn.commit() + try: + with db_lock: + conn = sqlite3.connect('telezab.db') + cursor = conn.cursor() + 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}") + cursor.execute(query, (chat_id, username, action, timestamp)) + conn.commit() + logger.info(f"User event logged: {chat_id} ({username}) - {action} at {timestamp}.") + except Exception as e: + error_logger.error(f"Error logging user event: {e}") + finally: conn.close() @@ -717,43 +784,6 @@ def process_remove_region(message): bot.send_message(chat_id, "Неверный формат. Используйте: ") show_settings_menu(chat_id) - -# # Handle admin whitelist management commands -# def prompt_admin_for_whitelist(chat_id, action): -# if action == 'add': -# bot.send_message(chat_id, "Введите ID пользователя, которого хотите добавить в белый список") -# bot.register_next_step_handler_by_chat_id(chat_id, process_add_whitelist) -# elif action == 'remove': -# bot.send_message(chat_id, "Введите ID пользователя, которого хотите удалить из белого списка") -# bot.register_next_step_handler_by_chat_id(chat_id, process_remove_whitelist) -# -# -# def process_add_whitelist(message): -# chat_id = message.chat.id -# try: -# new_chat_id = int(message.text.split()[0]) -# add_to_whitelist(new_chat_id) -# bot.send_message(chat_id, f"Chat ID {new_chat_id} добавлен в белый список.") -# app.logger.info(f"Admin {chat_id} added {new_chat_id} to the whitelist.") -# log_user_event(new_chat_id, "N/A", f"Added to whitelist by {chat_id} (@{message.from_user.username})") -# except (IndexError, ValueError): -# bot.send_message(chat_id, "Неверный формат. Используйте: ") -# show_settings_menu(chat_id) - - -# def process_remove_whitelist(message): -# chat_id = message.chat.id -# try: -# remove_chat_id = int(message.text.split()[0]) -# remove_from_whitelist(remove_chat_id) -# bot.send_message(chat_id, f"Chat ID {remove_chat_id} удален из белого списка.") -# app.logger.info(f"Admin {chat_id} removed {remove_chat_id} from the whitelist.") -# log_user_event(remove_chat_id, "N/A", f"Removed from whitelist by {chat_id} (@{message.from_user.username})") -# except (IndexError, ValueError): -# bot.send_message(chat_id, "Неверный формат. Используйте: ") -# show_settings_menu(chat_id) - - # Handle displaying active subscriptions for a user def handle_my_subscriptions(message): chat_id = message.chat.id @@ -957,21 +987,29 @@ def format_message(data): try: status_emoji = "⚠️" if data['status'].upper() == "PROBLEM" else "✅" priority_map = { - 'High': 'Высокая', - 'Disaster': 'Авария' + '4': 'Высокая', + '5': 'Авария' } priority = priority_map.get(data['severity'], 'Неизвестно') - message = ( - f"{status_emoji} Host: {data['host']}\n" - f"Сообщение: {data['msg']}\n" - f"Дата получения: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))}\n" - f"Критичность: {priority}\n" - f"Теги: {data['tags']}\n" - f"Статус: {data['status']}" - ) - if 'link' in data: - message += f'\nСсылка на график: {data['link']}' - return message + + if data['status'].upper() == "PROBLEM": + message = ( + f"⚠️ {data['host']} ({data["ip"]})\n" + f"{data['msg']}\n" + f"Критичность: {data['severity']}" + ) + if 'link' in data: + message += f'\nURL: {data['link']}' + return message + else: + message = ( + f"✅ {data['host']} ({data["ip"]})\n" + f"{data['msg']}\n" + f"Критичность: {data['severity']}\n" + f"Проблема устранена!\n" + f"Время устранения проблемы: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))}" + ) + return message except KeyError as e: app.logger.error(f"Missing key in data: {e}") raise ValueError(f"Missing key in data: {e}") @@ -985,19 +1023,12 @@ def add_user(): if telegram_id and chat_id: add_to_whitelist(chat_id, telegram_id) + app.logger.info(f"User {telegram_id} added to whitelist.") return jsonify({"status": "success"}), 200 else: + app.logger.error("Invalid data received for adding user.") return jsonify({"status": "failure", "reason": "Invalid data"}), 400 - -# def format_message(data): -# return (f"Zabbix Alert\n" -# f"Host: {data['host']}\n" -# f"Item: {data['item']}\n" -# f"Trigger: {data['trigger']}\n" -# f"Value: {data['value']}") - - # Handle active triggers def handle_active_triggers(message): chat_id = message.chat.id @@ -1076,7 +1107,7 @@ def handle_group_selection(call): if triggers is None: bot.send_message(chat_id, "Не удалось подключиться к Zabbix API. Пожалуйста, попробуйте позже.") elif not triggers: - bot.send_message(chat_id, "Нет активных проблем по указанной группе за последние 24 часа.") + bot.send_message(chat_id, "Нет активных проблем по указанной группе уровня HIGH и DISASTER за последние 24 часа.") else: for trigger in triggers: bot.send_message(chat_id, trigger, parse_mode="html") @@ -1191,41 +1222,6 @@ def get_zabbix_triggers(group_id): logging.error(f"Error connecting to Zabbix API: {e}") return None -# def split_message(message): -# max_length = 4096 -# if len(message) <= max_length: -# return [message] -# -# parts = [] -# while len(message) > max_length: -# split_index = message[:max_length].rfind('\n') -# if split_index == -1: -# split_index = max_length -# parts.append(message[:split_index]) -# message = message[split_index:] -# -# parts.append(message) -# return parts - - -# def split_message_by_delimiter(messages, delimiter="---"): -# max_length = 4096 -# parts = [] -# current_part = "" -# -# for message in messages: -# if len(current_part) + len(message) + len(delimiter) <= max_length: -# if current_part: -# current_part += f"\n\n{delimiter}\n\n" -# current_part += message -# else: -# parts.append(current_part) -# current_part = message -# -# if current_part: -# parts.append(current_part) -# -# return parts async def send_group_messages(chat_id, messages): for message in messages: @@ -1245,7 +1241,6 @@ def simulate_event(message): } - app.logger.info(f"Simulating event: {test_event}") # Use requests to simulate a POST request response = requests.post('http://localhost:5000/webhook', json=test_event) @@ -1274,7 +1269,7 @@ def run_polling(): if __name__ == '__main__': init_db() - + 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()