|
from enum import Enum |
|
from typing import Optional, Dict, Any, Union |
|
from colorama import Fore, Back, Style, init |
|
import time |
|
import os |
|
from datetime import datetime |
|
|
|
class LogLevel(Enum): |
|
DEBUG = 1 |
|
INFO = 2 |
|
SUCCESS = 3 |
|
WARNING = 4 |
|
ERROR = 5 |
|
|
|
class AsyncLogger: |
|
""" |
|
Asynchronous logger with support for colored console output and file logging. |
|
Supports templated messages with colored components. |
|
""" |
|
|
|
DEFAULT_ICONS = { |
|
'INIT': 'β', |
|
'READY': 'β', |
|
'FETCH': 'β', |
|
'SCRAPE': 'β', |
|
'EXTRACT': 'β ', |
|
'COMPLETE': 'β', |
|
'ERROR': 'Γ', |
|
'DEBUG': 'β―', |
|
'INFO': 'βΉ', |
|
'WARNING': 'β ', |
|
} |
|
|
|
DEFAULT_COLORS = { |
|
LogLevel.DEBUG: Fore.LIGHTBLACK_EX, |
|
LogLevel.INFO: Fore.CYAN, |
|
LogLevel.SUCCESS: Fore.GREEN, |
|
LogLevel.WARNING: Fore.YELLOW, |
|
LogLevel.ERROR: Fore.RED, |
|
} |
|
|
|
def __init__( |
|
self, |
|
log_file: Optional[str] = None, |
|
log_level: LogLevel = LogLevel.DEBUG, |
|
tag_width: int = 10, |
|
icons: Optional[Dict[str, str]] = None, |
|
colors: Optional[Dict[LogLevel, str]] = None, |
|
verbose: bool = True |
|
): |
|
""" |
|
Initialize the logger. |
|
|
|
Args: |
|
log_file: Optional file path for logging |
|
log_level: Minimum log level to display |
|
tag_width: Width for tag formatting |
|
icons: Custom icons for different tags |
|
colors: Custom colors for different log levels |
|
verbose: Whether to output to console |
|
""" |
|
init() |
|
self.log_file = log_file |
|
self.log_level = log_level |
|
self.tag_width = tag_width |
|
self.icons = icons or self.DEFAULT_ICONS |
|
self.colors = colors or self.DEFAULT_COLORS |
|
self.verbose = verbose |
|
|
|
|
|
if log_file: |
|
os.makedirs(os.path.dirname(os.path.abspath(log_file)), exist_ok=True) |
|
|
|
def _format_tag(self, tag: str) -> str: |
|
"""Format a tag with consistent width.""" |
|
return f"[{tag}]".ljust(self.tag_width, ".") |
|
|
|
def _get_icon(self, tag: str) -> str: |
|
"""Get the icon for a tag, defaulting to info icon if not found.""" |
|
return self.icons.get(tag, self.icons['INFO']) |
|
|
|
def _write_to_file(self, message: str): |
|
"""Write a message to the log file if configured.""" |
|
if self.log_file: |
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] |
|
with open(self.log_file, 'a', encoding='utf-8') as f: |
|
|
|
clean_message = message.replace(Fore.RESET, '').replace(Style.RESET_ALL, '') |
|
for color in vars(Fore).values(): |
|
if isinstance(color, str): |
|
clean_message = clean_message.replace(color, '') |
|
f.write(f"[{timestamp}] {clean_message}\n") |
|
|
|
def _log( |
|
self, |
|
level: LogLevel, |
|
message: str, |
|
tag: str, |
|
params: Optional[Dict[str, Any]] = None, |
|
colors: Optional[Dict[str, str]] = None, |
|
base_color: Optional[str] = None, |
|
**kwargs |
|
): |
|
""" |
|
Core logging method that handles message formatting and output. |
|
|
|
Args: |
|
level: Log level for this message |
|
message: Message template string |
|
tag: Tag for the message |
|
params: Parameters to format into the message |
|
colors: Color overrides for specific parameters |
|
base_color: Base color for the entire message |
|
""" |
|
if level.value < self.log_level.value: |
|
return |
|
|
|
|
|
if params: |
|
try: |
|
|
|
formatted_message = message.format(**params) |
|
|
|
|
|
if colors: |
|
for key, color in colors.items(): |
|
|
|
if key in params: |
|
value_str = str(params[key]) |
|
formatted_message = formatted_message.replace( |
|
value_str, |
|
f"{color}{value_str}{Style.RESET_ALL}" |
|
) |
|
|
|
except KeyError as e: |
|
formatted_message = f"LOGGING ERROR: Missing parameter {e} in message template" |
|
level = LogLevel.ERROR |
|
else: |
|
formatted_message = message |
|
|
|
|
|
color = base_color or self.colors[level] |
|
log_line = f"{color}{self._format_tag(tag)} {self._get_icon(tag)} {formatted_message}{Style.RESET_ALL}" |
|
|
|
|
|
if self.verbose or kwargs.get("force_verbose", False): |
|
print(log_line) |
|
|
|
|
|
self._write_to_file(log_line) |
|
|
|
def debug(self, message: str, tag: str = "DEBUG", **kwargs): |
|
"""Log a debug message.""" |
|
self._log(LogLevel.DEBUG, message, tag, **kwargs) |
|
|
|
def info(self, message: str, tag: str = "INFO", **kwargs): |
|
"""Log an info message.""" |
|
self._log(LogLevel.INFO, message, tag, **kwargs) |
|
|
|
def success(self, message: str, tag: str = "SUCCESS", **kwargs): |
|
"""Log a success message.""" |
|
self._log(LogLevel.SUCCESS, message, tag, **kwargs) |
|
|
|
def warning(self, message: str, tag: str = "WARNING", **kwargs): |
|
"""Log a warning message.""" |
|
self._log(LogLevel.WARNING, message, tag, **kwargs) |
|
|
|
def error(self, message: str, tag: str = "ERROR", **kwargs): |
|
"""Log an error message.""" |
|
self._log(LogLevel.ERROR, message, tag, **kwargs) |
|
|
|
def url_status( |
|
self, |
|
url: str, |
|
success: bool, |
|
timing: float, |
|
tag: str = "FETCH", |
|
url_length: int = 50 |
|
): |
|
""" |
|
Convenience method for logging URL fetch status. |
|
|
|
Args: |
|
url: The URL being processed |
|
success: Whether the operation was successful |
|
timing: Time taken for the operation |
|
tag: Tag for the message |
|
url_length: Maximum length for URL in log |
|
""" |
|
self._log( |
|
level=LogLevel.SUCCESS if success else LogLevel.ERROR, |
|
message="{url:.{url_length}}... | Status: {status} | Time: {timing:.2f}s", |
|
tag=tag, |
|
params={ |
|
"url": url, |
|
"url_length": url_length, |
|
"status": success, |
|
"timing": timing |
|
}, |
|
colors={ |
|
"status": Fore.GREEN if success else Fore.RED, |
|
"timing": Fore.YELLOW |
|
} |
|
) |
|
|
|
def error_status( |
|
self, |
|
url: str, |
|
error: str, |
|
tag: str = "ERROR", |
|
url_length: int = 50 |
|
): |
|
""" |
|
Convenience method for logging error status. |
|
|
|
Args: |
|
url: The URL being processed |
|
error: Error message |
|
tag: Tag for the message |
|
url_length: Maximum length for URL in log |
|
""" |
|
self._log( |
|
level=LogLevel.ERROR, |
|
message="{url:.{url_length}}... | Error: {error}", |
|
tag=tag, |
|
params={ |
|
"url": url, |
|
"url_length": url_length, |
|
"error": error |
|
} |
|
) |