2026-01-01 18:48:25 +01:00
|
|
|
import logging
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
import functools
|
|
|
|
|
import asyncio
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from typing import Any, Callable
|
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
|
|
class KubyLogger:
|
|
|
|
|
def __init__(self, name="KubyBot"):
|
|
|
|
|
self.logger = logging.getLogger(name)
|
|
|
|
|
self.logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
# Créer le format de log
|
|
|
|
|
formatter = logging.Formatter(
|
|
|
|
|
'[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s:%(lineno)d] %(message)s',
|
|
|
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Console handler
|
|
|
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
|
|
|
console_handler.setFormatter(formatter)
|
|
|
|
|
console_handler.setLevel(logging.DEBUG)
|
|
|
|
|
self.logger.addHandler(console_handler)
|
|
|
|
|
|
|
|
|
|
# File handler
|
|
|
|
|
file_handler = logging.FileHandler('kuby_logs.log')
|
|
|
|
|
file_handler.setFormatter(formatter)
|
|
|
|
|
file_handler.setLevel(logging.DEBUG)
|
|
|
|
|
self.logger.addHandler(file_handler)
|
|
|
|
|
|
|
|
|
|
# Error file handler
|
|
|
|
|
error_handler = logging.FileHandler('kuby_errors.log')
|
|
|
|
|
error_handler.setLevel(logging.ERROR)
|
|
|
|
|
error_handler.setFormatter(formatter)
|
|
|
|
|
self.logger.addHandler(error_handler)
|
|
|
|
|
|
|
|
|
|
# Audit file handler for security events
|
|
|
|
|
audit_handler = logging.FileHandler('kuby_audit.log')
|
|
|
|
|
audit_handler.setLevel(logging.INFO)
|
|
|
|
|
audit_formatter = logging.Formatter(
|
|
|
|
|
'[AUDIT] [%(asctime)s] [%(levelname)s] [%(funcName)s] %(message)s',
|
|
|
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
)
|
|
|
|
|
audit_handler.setFormatter(audit_formatter)
|
|
|
|
|
self.logger.addHandler(audit_handler)
|
2026-02-07 16:03:16 +01:00
|
|
|
|
|
|
|
|
# GitLab handler for automated error reporting
|
|
|
|
|
try:
|
|
|
|
|
from utils.gitlab_client import gitlab_client
|
|
|
|
|
class GitLabHandler(logging.Handler):
|
|
|
|
|
def __init__(self, client):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.client = client
|
|
|
|
|
self.last_errors = {}
|
|
|
|
|
self.cooldown = 3600 # 1 hour cooldown per unique error
|
|
|
|
|
|
|
|
|
|
def emit(self, record):
|
|
|
|
|
if record.levelno >= logging.ERROR:
|
|
|
|
|
# Extract message and first line of traceback if available
|
|
|
|
|
msg = record.getMessage()
|
|
|
|
|
error_key = msg
|
|
|
|
|
if record.exc_info:
|
|
|
|
|
error_key += str(record.exc_info[1])
|
|
|
|
|
|
|
|
|
|
current_time = time.time()
|
|
|
|
|
if error_key in self.last_errors and current_time - self.last_errors[error_key] < self.cooldown:
|
|
|
|
|
return # Skip if same error within cooldown
|
|
|
|
|
|
|
|
|
|
self.last_errors[error_key] = current_time
|
|
|
|
|
|
|
|
|
|
# Create issue (Fire and forget, we don't want to block logging)
|
|
|
|
|
description = f"**Type:** {record.levelname}\n"
|
|
|
|
|
description += f"**Message:** {msg}\n"
|
|
|
|
|
description += f"**Source:** {record.name}.{record.funcName}:{record.lineno}\n\n"
|
|
|
|
|
if record.exc_info:
|
|
|
|
|
description += f"**Traceback:**\n```python\n{traceback.format_exc()}\n```"
|
|
|
|
|
|
|
|
|
|
title = f"[AUTO] Error: {msg[:50]}..."
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
if loop.is_running():
|
|
|
|
|
loop.create_task(self.client.create_issue(title, description, labels=["bug", "auto-report"]))
|
|
|
|
|
else:
|
|
|
|
|
# Fallback if loop is not running (rare for this bot)
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
gitlab_handler = GitLabHandler(gitlab_client)
|
|
|
|
|
gitlab_handler.setLevel(logging.ERROR)
|
|
|
|
|
self.logger.addHandler(gitlab_handler)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f"Failed to initialize GitLabHandler: {e}")
|
|
|
|
|
|
2026-01-01 18:48:25 +01:00
|
|
|
def debug(self, message, **kwargs):
|
|
|
|
|
self.logger.debug(message, **kwargs)
|
|
|
|
|
|
|
|
|
|
def info(self, message, **kwargs):
|
|
|
|
|
self.logger.info(message, **kwargs)
|
|
|
|
|
|
|
|
|
|
def warning(self, message, **kwargs):
|
|
|
|
|
self.logger.warning(message, **kwargs)
|
|
|
|
|
|
|
|
|
|
def error(self, message, **kwargs):
|
|
|
|
|
self.logger.error(message, **kwargs)
|
|
|
|
|
|
|
|
|
|
def critical(self, message, **kwargs):
|
|
|
|
|
self.logger.critical(message, **kwargs)
|
|
|
|
|
|
|
|
|
|
def audit(self, message, **kwargs):
|
|
|
|
|
"""Special audit logging for security events"""
|
|
|
|
|
self.logger.log(25, f"AUDIT: {message}", **kwargs)
|
|
|
|
|
|
|
|
|
|
def performance(self, message, duration=None, **kwargs):
|
|
|
|
|
"""Performance logging with timing information"""
|
|
|
|
|
if duration is not None:
|
|
|
|
|
self.logger.info(f"PERFORMANCE: {message} - Duration: {duration:.3f}s", **kwargs)
|
|
|
|
|
else:
|
|
|
|
|
self.logger.info(f"PERFORMANCE: {message}", **kwargs)
|
|
|
|
|
|
|
|
|
|
# Performance decorator
|
|
|
|
|
def log_performance(func: Callable) -> Callable:
|
|
|
|
|
"""Decorator to log function performance"""
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
async def async_wrapper(*args, **kwargs):
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
kuby_logger.debug(f"Entering {func.__name__} with args: {args}, kwargs: {kwargs}")
|
|
|
|
|
try:
|
|
|
|
|
result = await func(*args, **kwargs)
|
|
|
|
|
duration = time.time() - start_time
|
|
|
|
|
kuby_logger.performance(f"Completed {func.__name__}", duration=duration)
|
|
|
|
|
return result
|
|
|
|
|
except Exception as e:
|
|
|
|
|
duration = time.time() - start_time
|
|
|
|
|
kuby_logger.error(f"Error in {func.__name__} after {duration:.3f}s: {str(e)}")
|
|
|
|
|
kuby_logger.error(traceback.format_exc())
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
def sync_wrapper(*args, **kwargs):
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
kuby_logger.debug(f"Entering {func.__name__} with args: {args}, kwargs: {kwargs}")
|
|
|
|
|
try:
|
|
|
|
|
result = func(*args, **kwargs)
|
|
|
|
|
duration = time.time() - start_time
|
|
|
|
|
kuby_logger.performance(f"Completed {func.__name__}", duration=duration)
|
|
|
|
|
return result
|
|
|
|
|
except Exception as e:
|
|
|
|
|
duration = time.time() - start_time
|
|
|
|
|
kuby_logger.error(f"Error in {func.__name__} after {duration:.3f}s: {str(e)}")
|
|
|
|
|
kuby_logger.error(traceback.format_exc())
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
if asyncio.iscoroutinefunction(func):
|
|
|
|
|
return async_wrapper
|
|
|
|
|
return sync_wrapper
|
|
|
|
|
|
|
|
|
|
# Instance globale du logger
|
|
|
|
|
kuby_logger = KubyLogger()
|