diff --git a/tardis/io/logger/logger2.py b/tardis/io/logger/logger2.py deleted file mode 100644 index 3f1aa9abbaf..00000000000 --- a/tardis/io/logger/logger2.py +++ /dev/null @@ -1,286 +0,0 @@ -import logging -import re -import panel as pn -from dataclasses import dataclass, field -import asyncio -import concurrent.futures -import threading -from IPython.display import display -import os - -pn.extension('terminal') - -PYTHON_WARNINGS_LOGGER = logging.getLogger("py.warnings") - -def get_environment(): - """Determine the execution environment""" - try: - import IPython - ipython = IPython.get_ipython() - - if ipython is None: - return 'standard' - - # Check for VSCode specific environment variables - if any(x for x in ('VSCODE_PID', 'VSCODE') if x in os.environ): - return 'vscode' - - # Check if running in Jupyter notebook - if 'IPKernelApp' in ipython.config: - return 'jupyter' - - return 'standard' - except: - return 'standard' - -def create_output_widget(height=300): - return pn.widgets.Terminal( - "", - options={"cursorBlink": True}, - height=height, - sizing_mode='stretch_width' - ) - -log_outputs = { - "WARNING/ERROR": create_output_widget(), - "INFO": create_output_widget(), - "DEBUG": create_output_widget(), - "ALL": create_output_widget(), -} - -tab_order = ["ALL", "WARNING/ERROR", "INFO", "DEBUG"] -logger_widget = pn.Tabs( - *[(title, log_outputs[title]) for title in tab_order], - height=350, - sizing_mode='stretch_width' -) - - -@dataclass -class LoggingConfig: - LEVELS: dict[str, int] = field(default_factory=lambda: { - "NOTSET": logging.NOTSET, - "DEBUG": logging.DEBUG, - "INFO": logging.INFO, - "WARNING": logging.WARNING, - "ERROR": logging.ERROR, - "CRITICAL": logging.CRITICAL, - }) - - COLORS: dict[int | str, str] = field(default_factory=lambda: { - logging.INFO: "#D3D3D3", - logging.WARNING: "orange", - logging.ERROR: "red", - logging.CRITICAL: "orange", - logging.DEBUG: "blue", - "default": "black", - }) - - DEFAULT_LEVEL = "INFO" - DEFAULT_SPECIFIC_STATE = False - - -LOGGING_LEVELS = LoggingConfig().LEVELS - - -class AsyncEmitLogHandler(logging.Handler): - def __init__(self, log_outputs, colors, display_widget=True): - super().__init__() - self.log_outputs = log_outputs - self.colors = colors - self.environment = get_environment() - self.main_thread_id = threading.get_ident() - self.futures = [] - self.display_widget = display_widget - - # Only set up async handling for GUI environments when display_widget is True - if self.display_widget and self.environment in ['jupyter', 'vscode']: - self.loop = asyncio.new_event_loop() - self.thread = threading.Thread(target=self._run_event_loop, daemon=True) - self.thread.start() - - # Only set up display handle for Jupyter - if self.environment == 'jupyter': - self.display_handle = display(logger_widget, display_id=True) - - def _run_event_loop(self): - """Runs event loop in separate thread""" - asyncio.set_event_loop(self.loop) - self.loop.run_forever() - - def emit(self, record): - log_entry = self.format(record) - - if not self.display_widget or self.environment == 'standard': - stream_handler = logging.StreamHandler() - stream_handler.setFormatter(logging.Formatter("%(name)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)d)")) - stream_handler.emit(record) - return - - clean_log_entry = self._remove_ansi_escape_sequences(log_entry) - html_output = self._format_html_output(clean_log_entry, record) - - future = asyncio.run_coroutine_threadsafe( - self._async_emit(record.levelno, html_output), - self.loop - ) - self.futures.append(future) - - def close(self): - if self.display_widget and self.environment in ['jupyter', 'vscode']: - self.loop.call_soon_threadsafe(self.loop.stop) - self.thread.join(timeout=5) - - # Clean up any remaining tasks in the loop - pending = asyncio.all_tasks(self.loop) - for task in pending: - task.cancel() - - # Run the event loop one last time to finalize all pending tasks - self.loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) - self.loop.close() - super().close() - - @staticmethod - def _remove_ansi_escape_sequences(text): - """Remove ANSI escape sequences from string.""" - ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]") - return ansi_escape.sub("", text) - - def _format_html_output(self, log_entry, record): - """Format log entry as HTML with appropriate styling.""" - color = self.colors.get(record.levelno, self.colors["default"]) - parts = log_entry.split(" ", 2) - if len(parts) > 2: - prefix, levelname, message = parts - return f'{prefix} {levelname} {message}' - return log_entry - - async def _async_emit(self, level, html_output): - level_to_output = { - logging.WARNING: "WARNING/ERROR", - logging.ERROR: "WARNING/ERROR", - logging.INFO: "INFO", - logging.DEBUG: "DEBUG" - } - - # Remove HTML wrapping since we're using Terminal now - clean_text = re.sub('<[^<]+?>', '', html_output) + '\n' - - # Update specific level output - output_key = level_to_output.get(level) - if output_key: - self.log_outputs[output_key].write(clean_text) - - # Update ALL output - self.log_outputs["ALL"].write(clean_text) - - if self.environment == 'jupyter': - self.display_handle.update(logger_widget.embed()) - - -class TARDISLogger: - def __init__(self): - self.config = LoggingConfig() - self.logger = logging.getLogger("tardis") - - def configure_logging(self, log_level, tardis_config, specific_log_level=None): - if "debug" in tardis_config: - specific_log_level = tardis_config["debug"].get( - "specific_log_level", specific_log_level - ) - logging_level = log_level or tardis_config["debug"].get( - "log_level", "INFO" - ) - if log_level and tardis_config["debug"].get("log_level"): - self.logger.debug( - "log_level is defined both in Functional Argument & YAML Configuration {debug section}, " - f"log_level = {log_level.upper()} will be used for Log Level Determination" - ) - else: - tardis_config["debug"] = {} - logging_level = log_level or self.config.DEFAULT_LEVEL - specific_log_level = specific_log_level or self.config.DEFAULT_SPECIFIC_STATE - - logging_level = logging_level.upper() - if logging_level not in self.config.LEVELS: - raise ValueError( - f"Passed Value for log_level = {logging_level} is Invalid. Must be one of the following {list(self.config.LEVELS.keys())}" - ) - - logger = logging.getLogger("tardis") - tardis_loggers = [ - logging.getLogger(name) - for name in logging.root.manager.loggerDict - if name.startswith("tardis") - ] - - if logging_level in self.config.LEVELS: - for logger in tardis_loggers: - logger.setLevel(self.config.LEVELS[logging_level]) - - if logger.filters: - for filter in logger.filters: - for logger in tardis_loggers: - logger.removeFilter(filter) - - if specific_log_level: - filter_log = LogFilter([self.config.LEVELS[logging_level], logging.INFO, logging.DEBUG]) - for logger in tardis_loggers: - logger.addFilter(filter_log) - else: - for filter in logger.filters: - for logger in tardis_loggers: - logger.removeFilter(filter) - - - def setup_widget_logging(self, display_widget=True): - """ - Set up widget-based logging interface. - - Parameters - ---------- - display_widget : bool, optional - Whether to display the widget in GUI environments (default: True) - """ - self.widget_handler = AsyncEmitLogHandler( - log_outputs, - self.config.COLORS, - display_widget=display_widget - ) - self.widget_handler.setFormatter( - logging.Formatter("%(name)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)d)") - ) - - self._configure_handlers() - - def _configure_handlers(self): - """Configure logging handlers.""" - logging.captureWarnings(True) - - for logger in [self.logger, logging.getLogger()]: - for handler in logger.handlers[:]: - logger.removeHandler(handler) - - self.logger.addHandler(self.widget_handler) - PYTHON_WARNINGS_LOGGER.addHandler(self.widget_handler) - - -class LogFilter: - """Filter for controlling which log levels are displayed.""" - def __init__(self, log_levels): - self.log_levels = log_levels - - def filter(self, log_record): - return log_record.levelno in self.log_levels - - -def logging_state(log_level, tardis_config, specific_log_level=None, display_widget=True): - logger = TARDISLogger() - logger.configure_logging(log_level, tardis_config, specific_log_level) - logger.setup_widget_logging(display_widget=display_widget) - - if display_widget and get_environment() == 'vscode': - display(logger_widget) - - return logger_widget if (display_widget and get_environment() in ['jupyter', 'vscode']) else None