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,20 +189,19 @@ user_states = {}
user_timers = {} user_timers = {}
# Initialize SQLite database # Initialize SQLite database
def init_db(): def init_db():
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,
@ -141,33 +209,28 @@ 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.")
except Exception as e:
error_logger.error(f"Error initializing database: {e}")
finally:
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')
try:
with db_lock: with db_lock:
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 (?, ?, ?, ?)'
app.logger.debug(f"Executing query: {query} with chat_id={chat_id}, username={username}, action={action}, timestamp={timestamp}") 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}.")
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,20 +987,28 @@ 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'], 'Неизвестно')
if data['status'].upper() == "PROBLEM":
message = ( message = (
f"{status_emoji} Host: {data['host']}\n" f"⚠️ {data['host']} ({data["ip"]})\n"
f"Сообщение: {data['msg']}\n" f"{data['msg']}\n"
f"Дата получения: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(data['date_reception'])))}\n" f"Критичность: {data['severity']}"
f"Критичность: {priority}\n"
f"Теги: {data['tags']}\n"
f"Статус: {data['status']}"
) )
if 'link' in data: if 'link' in data:
message += f'\nСсылка на график: {data['link']}' 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 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}")
@ -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()