Skip to content

log

log

Module configures the logger to log messages. It has a logger object that sends messages to different logging handlers (file and stream), a few classes for formatters.

Constants:

LOGGER: logging.Logger The logger object for sending messages to logging handlers. VERBOSITY_MAP: dict A dictionary mapping the verbosity level to the corresponding logging level. LOG_ENCODING: str The encoding type of the log file.

Functions:

def configure_logger( *, verbosity: int, logger_name: str, use_rich: bool, log_dir: Path | None = None, auto_rollover_sec: int = 24 * 60 * 60, max_log_files: int = 10, ) -> None: Configures the logger and creates logging handlers for file and stream. It takes the following parameters: * verbosity (int): The verbosity level of the logging messages. Must be an integer from 0 to 3. Default is 0. * logger_name (str): The name of the logger. Default is an empty string. * use_rich (bool): A flag to indicate whether to use the rich library to create a colorful log. Default is False. * log_dir (Path | None): The path to the directory where log files will be created. Default is the current working directory. * auto_rollover_sec (int): The time interval in seconds before rolling over the log file. Default is 86400 seconds (24 hours). * max_log_files (int): The maximum number of log files to keep. Default is 10.

Classes:

MarkupStripFormatter(logging.Formatter) A class to remove all formatting from the logging messages and return the plain text.

Functions:

Name Description
* format
NoHighlightRichHandler

A subclass of RichHandler to remove all rich formatting from the logging messages.

MarkupStripFormatter

Bases: Formatter

A logging formatter that strips rich markup tags from log messages before they are emitted.

Attributes:

Name Type Description
None

Methods:

Name Description
format

Return a formatted and stripped version of the log message.

Source code in cssfinder/log.py
class MarkupStripFormatter(logging.Formatter):
    """A logging formatter that strips `rich` markup tags from log messages before they
    are emitted.

    Attributes
    ----------
    None

    Methods
    -------
    format(record: logging.LogRecord) -> str:
        Return a formatted and stripped version of the log message.

    """

    def format(self, record: LogRecord) -> str:
        """Return the formatted string of a log record after stripping all formatting
        tags.

        _extended_summary_

        Parameters
        ----------
        record : LogRecord
            The log record to be formatted.

        Returns
        -------
        str
            The formatted string with all formatting tags removed.

        """
        string = super().format(record)
        # render strings with rich
        seg_list = Text.from_markup(string).render(Console())
        # but use only text part to get rid of all formatting
        return "".join(seg.text for seg in seg_list)

format

format(record: LogRecord) -> str

Return the formatted string of a log record after stripping all formatting tags.

extended_summary

Parameters:

Name Type Description Default
record LogRecord

The log record to be formatted.

required

Returns:

Type Description
str

The formatted string with all formatting tags removed.

Source code in cssfinder/log.py
def format(self, record: LogRecord) -> str:
    """Return the formatted string of a log record after stripping all formatting
    tags.

    _extended_summary_

    Parameters
    ----------
    record : LogRecord
        The log record to be formatted.

    Returns
    -------
    str
        The formatted string with all formatting tags removed.

    """
    string = super().format(record)
    # render strings with rich
    seg_list = Text.from_markup(string).render(Console())
    # but use only text part to get rid of all formatting
    return "".join(seg.text for seg in seg_list)

NoHighlightRichHandler

Bases: RichHandler

RichHandler subclass which permanently disables message highlighting (coloring of integers, strings, etc.).

Source code in cssfinder/log.py
class NoHighlightRichHandler(RichHandler):
    """RichHandler subclass which permanently disables message highlighting (coloring of
    integers, strings, etc.).
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.highlighter = None  # type: ignore[assignment]

configure_logger

configure_logger(
    *,
    verbosity: int,
    logger_name: str,
    use_rich: bool,
    log_dir: Path | None = None,
    auto_rollover_sec: int = 24 * 60 * 60,
    max_log_files: int = 10
) -> None

Configure a logger with a file handler and a console handler.

Parameters:

Name Type Description Default
verbosity int

The verbosity level for the console handler. The value should be between 0 and 3, with 0 being the least verbose and 3 being the most verbose.

required
logger_name str

The name of the logger.

required
use_rich bool

A flag indicating whether to use the rich console handler. If True, use the rich console handler; otherwise, use the standard console handler.

required
log_dir str or Path or None

The path to the directory where log files should be stored. If None, store the log files in the current working directory. The default is None.

None
auto_rollover_sec int

The number of seconds after which to rollover the log file. The default is 24 * 60 * 60.

24 * 60 * 60
max_log_files int

The maximum number of log files to keep. The default is 10.

10

Returns:

Type Description
None
Source code in cssfinder/log.py
def configure_logger(
    *,
    verbosity: int,
    logger_name: str,
    use_rich: bool,
    log_dir: Path | None = None,
    auto_rollover_sec: int = 24 * 60 * 60,
    max_log_files: int = 10,
) -> None:
    """Configure a logger with a file handler and a console handler.

    Parameters
    ----------
    verbosity : int
        The verbosity level for the console handler. The value should be between 0 and
        3, with 0 being the least verbose and 3 being the most verbose.
    logger_name : str
        The name of the logger.
    use_rich : bool
        A flag indicating whether to use the rich console handler. If True, use the rich
        console handler; otherwise, use the standard console handler.
    log_dir : str or Path or None, optional
        The path to the directory where log files should be stored. If None, store the
        log files in the current working directory. The default is None.
    auto_rollover_sec : int, optional
        The number of seconds after which to rollover the log file.
        The default is 24 * 60 * 60.
    max_log_files : int, optional
        The maximum number of log files to keep. The default is 10.

    Returns
    -------
    None

    """
    # Clamp verbosity between 0 and 3.
    verbosity = min(3, max(0, verbosity))

    logger = logging.getLogger()
    # global logger must have verbosity level set to DEBUG as we want out log files
    # to contain all log messages, and using any higher level would discard debug
    # messages before passing them to file handler.
    global_verbosity = logging.DEBUG
    logger.setLevel(global_verbosity)
    logger.handlers.clear()

    # Log file handler - fixed log level to DEBUG
    file_handler = _create_file_handler(
        logger_name,
        log_dir or Path.cwd() / "log",
        auto_rollover_sec,
        max_log_files,
    )
    logger.addHandler(file_handler)

    # Console logging handler - variable log level
    stream_handler = _create_stream_handler(verbosity, use_rich=use_rich)
    logger.addHandler(stream_handler)

    matplotlib_logger = getLogger("matplotlib")
    matplotlib_logger.setLevel(logging.WARNING)

    pandas_logger = getLogger("pandas")
    pandas_logger.setLevel(logging.WARNING)

enable_performance_logging

enable_performance_logging() -> None

Enable run time measurement and logging for run_project() function.

Source code in cssfinder/log.py
def enable_performance_logging() -> None:
    """Enable run time measurement and logging for run_project() function."""
    import time

    from cssfinder.api import run_project

    def run_project_wrapper(
        project: CSSFProject,
        tasks: list[str] | None = None,
        *,
        is_debug: bool = False,
        is_rich: bool = True,
        force_sequential: bool = False,
        max_parallel: int = -1,
    ) -> list[Task]:
        start_time = time.perf_counter()
        task_list = run_project(
            project,
            tasks,
            is_debug=is_debug,
            is_rich=is_rich,
            force_sequential=force_sequential,
            max_parallel=max_parallel,
        )
        end_time = time.perf_counter()
        execution_time = end_time - start_time

        perf_file = Path.cwd() / f"perf_{project.meta.name}.json"

        with FileLock(perf_file.with_suffix(".lock").as_posix()):
            if perf_file.exists():
                raw_content = perf_file.read_text(encoding="utf-8")
                try:
                    perf_index = json.loads(raw_content)
                except json.JSONDecodeError:
                    perf_index = {}
            else:
                perf_index = {}

            key = str.join("|", tasks) if tasks is not None else "None"
            perf_results_list = perf_index.get(key, [])
            perf_results_list.append(execution_time)
            perf_index[key] = perf_results_list

            serialized_perf_index = json.dumps(perf_index, indent=4)
            perf_file.write_text(serialized_perf_index, encoding="utf-8")

        print(f"Execution time: {execution_time:.6f} seconds")
        return task_list

    patch("cssfinder.api.run_project", new=run_project_wrapper).__enter__()