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
This commit is contained in:
Влад Зверев 2024-08-01 15:22:33 +05:00
parent 3838645d7e
commit 75809aaafc

View File

@ -5,6 +5,7 @@ from dotenv import load_dotenv
import hashlib import hashlib
import logging import logging
from logging.config import dictConfig from logging.config import dictConfig
import zipfile
from threading import Thread, Lock, Timer from threading import Thread, Lock, Timer
import sqlite3 import sqlite3
import time import time
@ -29,10 +30,13 @@ ENABLE_FILE_LOGGING = os.getenv('ENABLE_FILE_LOGGING', 'true').lower() in ['true
# Define log paths # Define log paths
LOG_PATH_ERRORS = os.getenv('LOG_PATH_ERRORS', 'logs/errors.log') LOG_PATH_ERRORS = os.getenv('LOG_PATH_ERRORS', 'logs/errors.log')
LOG_PATH_EVENTS = os.getenv('LOG_PATH_EVENTS', 'logs/events.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 # 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_ERRORS), exist_ok=True)
os.makedirs(os.path.dirname(LOG_PATH_EVENTS), 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):
@ -44,29 +48,47 @@ class UTF8StreamHandler(logging.StreamHandler):
if hasattr(stream, 'reconfigure'): if hasattr(stream, 'reconfigure'):
stream.reconfigure(encoding='utf-8') stream.reconfigure(encoding='utf-8')
# Define handlers based on environment variables # Конфигурация логирования
handlers = {} handlers = {}
if ENABLE_CONSOLE_LOGGING: if ENABLE_CONSOLE_LOGGING:
handlers['console'] = { handlers['console'] = {
'class': 'telezab.UTF8StreamHandler', 'class': 'telezab.UTF8StreamHandler',
'stream': 'ext://sys.stdout', # Output to console 'stream': 'ext://sys.stdout', # Вывод в консоль
'formatter': 'console', 'formatter': 'console',
} }
if ENABLE_FILE_LOGGING: if ENABLE_FILE_LOGGING:
handlers['file_errors'] = { handlers['file_errors'] = {
'class': 'logging.FileHandler', 'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': LOG_PATH_ERRORS, 'filename': LOG_PATH_ERRORS,
'when': 'midnight',
'interval': 1,
'backupCount': 7,
'formatter': 'error', 'formatter': 'error',
'encoding': 'utf-8', # Ensure UTF-8 encoding 'encoding': 'utf-8', # Убедитесь, что используется кодировка UTF-8
} }
handlers['file_events'] = { handlers['file_events'] = {
'class': 'logging.FileHandler', 'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': LOG_PATH_EVENTS, 'filename': LOG_PATH_EVENTS,
'when': 'midnight',
'interval': 1,
'backupCount': 7,
'formatter': 'event', '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({ dictConfig({
'version': 1, 'version': 1,
'formatters': { 'formatters': {
@ -83,16 +105,63 @@ dictConfig({
'format': '[%(asctime)s] EVENT %(module)s: %(message)s', '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': { 'root': {
'level': 'INFO', '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 # Initialize Flask application
app = Flask(__name__) 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')
@ -120,54 +189,48 @@ user_states = {}
user_timers = {} user_timers = {}
# Initialize SQLite database # Initialize SQLite database
def init_db(): def init_db():
with db_lock: try:
conn = sqlite3.connect('telezab.db') with db_lock:
cursor = conn.cursor() conn = sqlite3.connect('telezab.db')
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)''') cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions (
chat_id INTEGER,
# Create subscriptions table with username and active flag region_id TEXT,
cursor.execute('''CREATE TABLE IF NOT EXISTS subscriptions ( username TEXT,
chat_id INTEGER, active BOOLEAN DEFAULT TRUE,
region_id TEXT, skip BOOLEAN DEFAULT FALSE,
username TEXT, UNIQUE(chat_id, region_id))''')
active BOOLEAN DEFAULT TRUE, cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist (
skip BOOLEAN DEFAULT FALSE, chat_id INTEGER PRIMARY KEY)''')
UNIQUE(chat_id, region_id))''') cursor.execute('''CREATE TABLE IF NOT EXISTS regions (
region_id TEXT PRIMARY KEY,
# Create whitelist table region_name TEXT,
cursor.execute('''CREATE TABLE IF NOT EXISTS whitelist ( active BOOLEAN DEFAULT TRUE)''')
chat_id INTEGER PRIMARY KEY)''') cursor.execute('''CREATE TABLE IF NOT EXISTS user_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
# Create regions table with active flag chat_id INTEGER,
cursor.execute('''CREATE TABLE IF NOT EXISTS regions ( username TEXT,
region_id TEXT PRIMARY KEY, action TEXT,
region_name TEXT, timestamp TEXT)''')
active BOOLEAN DEFAULT TRUE)''') cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES
('01', 'Адыгея'),
# Create user events table for logging ('02', 'Башкортостан (Уфа)'),
cursor.execute('''CREATE TABLE IF NOT EXISTS user_events ( ('04', 'Алтай'),
id INTEGER PRIMARY KEY AUTOINCREMENT, ('19', 'Республика Хакасия')''')
chat_id INTEGER, conn.commit()
username TEXT, logger.info("Database initialized successfully.")
action TEXT, except Exception as e:
timestamp TEXT)''') error_logger.error(f"Error initializing database: {e}")
finally:
# Insert sample regions
cursor.execute('''INSERT OR IGNORE INTO regions (region_id, region_name) VALUES
('01', 'Адыгея'),
('02', 'Башкортостан (Уфа)'),
('04', 'Алтай'),
('19', 'Республика Хакасия')''')
conn.commit()
conn.close() 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]) 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): def log_user_event(chat_id, username, action):
timestamp = time.strftime('%Y-%m-%d %H:%M:%S') timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
with db_lock: try:
conn = sqlite3.connect('telezab.db') with db_lock:
cursor = conn.cursor() conn = sqlite3.connect('telezab.db')
query = 'INSERT INTO user_events (chat_id, username, action, timestamp) VALUES (?, ?, ?, ?)' cursor = conn.cursor()
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}, action={action}, timestamp={timestamp}") query = 'INSERT INTO user_events (chat_id, username, action, timestamp) VALUES (?, ?, ?, ?)'
cursor.execute(query, (chat_id, username, action, timestamp)) logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}, action={action}, timestamp={timestamp}")
conn.commit() 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() conn.close()
@ -717,43 +784,6 @@ def process_remove_region(message):
bot.send_message(chat_id, "Неверный формат. Используйте: <region_id>") bot.send_message(chat_id, "Неверный формат. Используйте: <region_id>")
show_settings_menu(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, "Неверный формат. Используйте: <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, "Неверный формат. Используйте: <chat_id>")
# show_settings_menu(chat_id)
# Handle displaying active subscriptions for a user # Handle displaying active subscriptions for a user
def handle_my_subscriptions(message): def handle_my_subscriptions(message):
chat_id = message.chat.id chat_id = message.chat.id
@ -957,21 +987,29 @@ def format_message(data):
try: try:
status_emoji = "⚠️" if data['status'].upper() == "PROBLEM" else "" status_emoji = "⚠️" if data['status'].upper() == "PROBLEM" else ""
priority_map = { priority_map = {
'High': 'Высокая', '4': 'Высокая',
'Disaster': 'Авария' '5': 'Авария'
} }
priority = priority_map.get(data['severity'], 'Неизвестно') priority = priority_map.get(data['severity'], 'Неизвестно')
message = (
f"{status_emoji} Host: {data['host']}\n" if data['status'].upper() == "PROBLEM":
f"Сообщение: {data['msg']}\n" message = (
f"Дата получения: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))}\n" f"⚠️ {data['host']} ({data["ip"]})\n"
f"Критичность: {priority}\n" f"{data['msg']}\n"
f"Теги: {data['tags']}\n" f"Критичность: {data['severity']}"
f"Статус: {data['status']}" )
) if 'link' in data:
if 'link' in data: message += f'\nURL: {data['link']}'
message += f'\nСсылка на график: {data['link']}' return message
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: except KeyError as e:
app.logger.error(f"Missing key in data: {e}") app.logger.error(f"Missing key in data: {e}")
raise ValueError(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: if telegram_id and chat_id:
add_to_whitelist(chat_id, telegram_id) add_to_whitelist(chat_id, telegram_id)
app.logger.info(f"User {telegram_id} added to whitelist.")
return jsonify({"status": "success"}), 200 return jsonify({"status": "success"}), 200
else: else:
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
# 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 # Handle active triggers
def handle_active_triggers(message): def handle_active_triggers(message):
chat_id = message.chat.id chat_id = message.chat.id
@ -1076,7 +1107,7 @@ def handle_group_selection(call):
if triggers is None: if triggers is None:
bot.send_message(chat_id, "Не удалось подключиться к Zabbix API. Пожалуйста, попробуйте позже.") bot.send_message(chat_id, "Не удалось подключиться к Zabbix API. Пожалуйста, попробуйте позже.")
elif not triggers: elif not triggers:
bot.send_message(chat_id, "Нет активных проблем по указанной группе за последние 24 часа.") bot.send_message(chat_id, "Нет активных проблем по указанной группе уровня HIGH и DISASTER за последние 24 часа.")
else: else:
for trigger in triggers: for trigger in triggers:
bot.send_message(chat_id, trigger, parse_mode="html") 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}") logging.error(f"Error connecting to Zabbix API: {e}")
return None 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): async def send_group_messages(chat_id, messages):
for message in messages: for message in messages:
@ -1245,7 +1241,6 @@ def simulate_event(message):
} }
app.logger.info(f"Simulating event: {test_event}") app.logger.info(f"Simulating event: {test_event}")
# Use requests to simulate a POST request # Use requests to simulate a POST request
response = requests.post('http://localhost:5000/webhook', json=test_event) response = requests.post('http://localhost:5000/webhook', json=test_event)
@ -1274,7 +1269,7 @@ def run_polling():
if __name__ == '__main__': if __name__ == '__main__':
init_db() init_db()
print('Bootstrap wait...')
# Start Flask app in a separate thread # 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() Thread(target=app.run, kwargs={'port': 5000, 'host': '0.0.0.0', 'debug': True, 'use_reloader': False}, daemon=True).start()