Skip to content

logging

lacuna.utils.logging

Consistent logging and user message formatting for Lacuna.

Provides a unified system for displaying progress, success, warnings, and errors to users with consistent formatting across all modules.

ConsoleLogger

Consistent console logger for user-facing messages.

Uses the standard Python logging module for output, ensuring consistent formatting with timestamps and module names across all Lacuna modules.

Parameters:

Name Type Description Default
verbose bool

If True, print messages. If False, silent mode (no output).

True
width int

Width for section headers

70
indent str

Indentation string for nested messages

" "
name str

Logger name for the Python logging module

"lacuna"

Examples:

>>> logger = ConsoleLogger(verbose=True)
>>> logger.section("PROCESSING DATA")
2026-01-15 10:00:00 - lacuna - INFO - ============================================
2026-01-15 10:00:00 - lacuna - INFO - PROCESSING DATA
2026-01-15 10:00:00 - lacuna - INFO - ============================================
>>> logger.info("Loading connectome...")
2026-01-15 10:00:00 - lacuna - INFO - Loading connectome...
>>> logger.success("Analysis complete", details={"subjects": 10, "time": 42.3})
2026-01-15 10:00:00 - lacuna - INFO - Analysis complete
2026-01-15 10:00:00 - lacuna - INFO -   subjects: 10
2026-01-15 10:00:00 - lacuna - INFO -   time: 42.3
Source code in src/lacuna/utils/logging.py
class ConsoleLogger:
    """
    Consistent console logger for user-facing messages.

    Uses the standard Python logging module for output, ensuring consistent
    formatting with timestamps and module names across all Lacuna modules.

    Parameters
    ----------
    verbose : bool, default=True
        If True, print messages. If False, silent mode (no output).
    width : int, default=70
        Width for section headers
    indent : str, default="  "
        Indentation string for nested messages
    name : str, default="lacuna"
        Logger name for the Python logging module

    Examples
    --------
    >>> logger = ConsoleLogger(verbose=True)
    >>> logger.section("PROCESSING DATA")
    2026-01-15 10:00:00 - lacuna - INFO - ============================================
    2026-01-15 10:00:00 - lacuna - INFO - PROCESSING DATA
    2026-01-15 10:00:00 - lacuna - INFO - ============================================

    >>> logger.info("Loading connectome...")
    2026-01-15 10:00:00 - lacuna - INFO - Loading connectome...

    >>> logger.success("Analysis complete", details={"subjects": 10, "time": 42.3})
    2026-01-15 10:00:00 - lacuna - INFO - Analysis complete
    2026-01-15 10:00:00 - lacuna - INFO -   subjects: 10
    2026-01-15 10:00:00 - lacuna - INFO -   time: 42.3
    """

    def __init__(
        self,
        verbose: bool = False,
        width: int = 70,
        indent: str = "  ",
        name: str = "lacuna.analysis",
    ):
        """Initialize console logger."""
        self.verbose = verbose
        self.width = width
        self.indent = indent
        self._logger = logging.getLogger(name)

    def _log(self, message: str, level: int = logging.INFO) -> None:
        """
        Log message if verbose mode is enabled.

        Parameters
        ----------
        message : str
            Message to log
        level : int
            Logging level (default: INFO)
        """
        if self.verbose:
            self._logger.log(level, message)

    def section(self, title: str) -> None:
        """
        Print a major section header.

        Parameters
        ----------
        title : str
            Section title

        Examples
        --------
        >>> logger.section("ANALYSIS PIPELINE")
        """
        if self.verbose:
            separator = "=" * self.width
            self._log("")
            self._log(separator)
            self._log(title)
            self._log(separator)

    def subsection(self, title: str) -> None:
        """
        Print a minor subsection header.

        Parameters
        ----------
        title : str
            Subsection title

        Examples
        --------
        >>> logger.subsection("Loading data")
        """
        if self.verbose:
            separator = "-" * self.width
            self._log("")
            self._log(separator)
            self._log(title)
            self._log(separator)

    def debug(self, message: str, indent_level: int = 0) -> None:
        """
        Print a debug message (only shown with -vv verbosity).

        Parameters
        ----------
        message : str
            Debug message
        indent_level : int, default=0
            Indentation level (0, 1, 2, ...)

        Examples
        --------
        >>> logger.debug("Computing correlation maps")
        """
        indent = self.indent * indent_level
        self._log(f"{indent}{message}", level=logging.DEBUG)

    def info(self, message: str, indent_level: int = 0) -> None:
        """
        Print an informational message.

        Parameters
        ----------
        message : str
            Information message
        indent_level : int, default=0
            Indentation level (0, 1, 2, ...)

        Examples
        --------
        >>> logger.info("Loading mask information...")
        """
        indent = self.indent * indent_level
        self._log(f"{indent}{message}")

    def success(
        self,
        message: str,
        details: dict | None = None,
        indent_level: int = 0,
    ) -> None:
        """
        Print a success message with optional details.

        Parameters
        ----------
        message : str
            Success message
        details : dict, optional
            Dictionary of key-value pairs to display
        indent_level : int, default=0
            Indentation level

        Examples
        --------
        >>> logger.success("Analysis complete", details={"time": 42.3, "subjects": 10})
        """
        indent = self.indent * indent_level
        self._log(f"{indent}{message}")

        if details and self.verbose:
            detail_indent = self.indent * (indent_level + 1)
            for key, value in details.items():
                # Format numbers nicely
                if isinstance(value, float):
                    formatted_value = f"{value:.2f}"
                elif isinstance(value, int) and value >= 1000:
                    formatted_value = f"{value:,}"
                else:
                    formatted_value = str(value)

                self._log(f"{detail_indent}{key}: {formatted_value}")

    def warning(self, message: str, indent_level: int = 0) -> None:
        """
        Print a warning message.

        Parameters
        ----------
        message : str
            Warning message
        indent_level : int, default=0
            Indentation level

        Examples
        --------
        >>> logger.warning("Mask size smaller than expected")
        """
        indent = self.indent * indent_level
        self._log(f"{indent}{message}", level=logging.WARNING)

    def error(self, message: str, indent_level: int = 0) -> None:
        """
        Print an error message.

        Parameters
        ----------
        message : str
            Error message
        indent_level : int, default=0
            Indentation level

        Examples
        --------
        >>> logger.error("Failed to load connectome")
        """
        indent = self.indent * indent_level
        self._log(f"{indent}{message}", level=logging.ERROR)

    def progress(
        self,
        message: str,
        current: int | None = None,
        total: int | None = None,
        percent: float | None = None,
        indent_level: int = 0,
    ) -> None:
        """
        Print a progress update.

        Parameters
        ----------
        message : str
            Progress message
        current : int, optional
            Current item number
        total : int, optional
            Total items
        percent : float, optional
            Completion percentage (0-100)
        indent_level : int, default=0
            Indentation level

        Examples
        --------
        >>> logger.progress("Processing batch", current=3, total=10)
        >>> logger.progress("Loading data", percent=65.5)
        """
        indent = self.indent * indent_level
        progress_str = f"{indent}{message}"

        if current is not None and total is not None:
            progress_str += f" [{current}/{total}]"
        elif percent is not None:
            progress_str += f" [{percent:.1f}%]"

        self._log(progress_str)

    def result_summary(self, title: str, metrics: dict, indent_level: int = 0) -> None:
        """
        Print a formatted summary of results.

        Parameters
        ----------
        title : str
            Summary title
        metrics : dict
            Dictionary of metric name: value pairs
        indent_level : int, default=0
            Indentation level

        Examples
        --------
        >>> logger.result_summary("Analysis Results", {
        ...     "Mean correlation": 0.4523,
        ...     "Std correlation": 0.1234,
        ...     "Range": "[-0.45, 0.89]"
        ... })
        """
        indent = self.indent * indent_level
        self._log(f"{indent}{title}:")

        detail_indent = self.indent * (indent_level + 1)
        for key, value in metrics.items():
            if isinstance(value, float):
                formatted_value = f"{value:.4f}"
            elif isinstance(value, int) and value >= 1000:
                formatted_value = f"{value:,}"
            else:
                formatted_value = str(value)

            self._log(f"{detail_indent}{key}: {formatted_value}")

    def blank_line(self) -> None:
        """Print a blank line for spacing."""
        if self.verbose:
            self._log("")

__init__(verbose=False, width=70, indent=' ', name='lacuna.analysis')

Initialize console logger.

Source code in src/lacuna/utils/logging.py
def __init__(
    self,
    verbose: bool = False,
    width: int = 70,
    indent: str = "  ",
    name: str = "lacuna.analysis",
):
    """Initialize console logger."""
    self.verbose = verbose
    self.width = width
    self.indent = indent
    self._logger = logging.getLogger(name)

blank_line()

Print a blank line for spacing.

Source code in src/lacuna/utils/logging.py
def blank_line(self) -> None:
    """Print a blank line for spacing."""
    if self.verbose:
        self._log("")

debug(message, indent_level=0)

Print a debug message (only shown with -vv verbosity).

Parameters:

Name Type Description Default
message str

Debug message

required
indent_level int

Indentation level (0, 1, 2, ...)

0

Examples:

>>> logger.debug("Computing correlation maps")
Source code in src/lacuna/utils/logging.py
def debug(self, message: str, indent_level: int = 0) -> None:
    """
    Print a debug message (only shown with -vv verbosity).

    Parameters
    ----------
    message : str
        Debug message
    indent_level : int, default=0
        Indentation level (0, 1, 2, ...)

    Examples
    --------
    >>> logger.debug("Computing correlation maps")
    """
    indent = self.indent * indent_level
    self._log(f"{indent}{message}", level=logging.DEBUG)

error(message, indent_level=0)

Print an error message.

Parameters:

Name Type Description Default
message str

Error message

required
indent_level int

Indentation level

0

Examples:

>>> logger.error("Failed to load connectome")
Source code in src/lacuna/utils/logging.py
def error(self, message: str, indent_level: int = 0) -> None:
    """
    Print an error message.

    Parameters
    ----------
    message : str
        Error message
    indent_level : int, default=0
        Indentation level

    Examples
    --------
    >>> logger.error("Failed to load connectome")
    """
    indent = self.indent * indent_level
    self._log(f"{indent}{message}", level=logging.ERROR)

info(message, indent_level=0)

Print an informational message.

Parameters:

Name Type Description Default
message str

Information message

required
indent_level int

Indentation level (0, 1, 2, ...)

0

Examples:

>>> logger.info("Loading mask information...")
Source code in src/lacuna/utils/logging.py
def info(self, message: str, indent_level: int = 0) -> None:
    """
    Print an informational message.

    Parameters
    ----------
    message : str
        Information message
    indent_level : int, default=0
        Indentation level (0, 1, 2, ...)

    Examples
    --------
    >>> logger.info("Loading mask information...")
    """
    indent = self.indent * indent_level
    self._log(f"{indent}{message}")

progress(message, current=None, total=None, percent=None, indent_level=0)

Print a progress update.

Parameters:

Name Type Description Default
message str

Progress message

required
current int

Current item number

None
total int

Total items

None
percent float

Completion percentage (0-100)

None
indent_level int

Indentation level

0

Examples:

>>> logger.progress("Processing batch", current=3, total=10)
>>> logger.progress("Loading data", percent=65.5)
Source code in src/lacuna/utils/logging.py
def progress(
    self,
    message: str,
    current: int | None = None,
    total: int | None = None,
    percent: float | None = None,
    indent_level: int = 0,
) -> None:
    """
    Print a progress update.

    Parameters
    ----------
    message : str
        Progress message
    current : int, optional
        Current item number
    total : int, optional
        Total items
    percent : float, optional
        Completion percentage (0-100)
    indent_level : int, default=0
        Indentation level

    Examples
    --------
    >>> logger.progress("Processing batch", current=3, total=10)
    >>> logger.progress("Loading data", percent=65.5)
    """
    indent = self.indent * indent_level
    progress_str = f"{indent}{message}"

    if current is not None and total is not None:
        progress_str += f" [{current}/{total}]"
    elif percent is not None:
        progress_str += f" [{percent:.1f}%]"

    self._log(progress_str)

result_summary(title, metrics, indent_level=0)

Print a formatted summary of results.

Parameters:

Name Type Description Default
title str

Summary title

required
metrics dict

Dictionary of metric name: value pairs

required
indent_level int

Indentation level

0

Examples:

>>> logger.result_summary("Analysis Results", {
...     "Mean correlation": 0.4523,
...     "Std correlation": 0.1234,
...     "Range": "[-0.45, 0.89]"
... })
Source code in src/lacuna/utils/logging.py
def result_summary(self, title: str, metrics: dict, indent_level: int = 0) -> None:
    """
    Print a formatted summary of results.

    Parameters
    ----------
    title : str
        Summary title
    metrics : dict
        Dictionary of metric name: value pairs
    indent_level : int, default=0
        Indentation level

    Examples
    --------
    >>> logger.result_summary("Analysis Results", {
    ...     "Mean correlation": 0.4523,
    ...     "Std correlation": 0.1234,
    ...     "Range": "[-0.45, 0.89]"
    ... })
    """
    indent = self.indent * indent_level
    self._log(f"{indent}{title}:")

    detail_indent = self.indent * (indent_level + 1)
    for key, value in metrics.items():
        if isinstance(value, float):
            formatted_value = f"{value:.4f}"
        elif isinstance(value, int) and value >= 1000:
            formatted_value = f"{value:,}"
        else:
            formatted_value = str(value)

        self._log(f"{detail_indent}{key}: {formatted_value}")

section(title)

Print a major section header.

Parameters:

Name Type Description Default
title str

Section title

required

Examples:

>>> logger.section("ANALYSIS PIPELINE")
Source code in src/lacuna/utils/logging.py
def section(self, title: str) -> None:
    """
    Print a major section header.

    Parameters
    ----------
    title : str
        Section title

    Examples
    --------
    >>> logger.section("ANALYSIS PIPELINE")
    """
    if self.verbose:
        separator = "=" * self.width
        self._log("")
        self._log(separator)
        self._log(title)
        self._log(separator)

subsection(title)

Print a minor subsection header.

Parameters:

Name Type Description Default
title str

Subsection title

required

Examples:

>>> logger.subsection("Loading data")
Source code in src/lacuna/utils/logging.py
def subsection(self, title: str) -> None:
    """
    Print a minor subsection header.

    Parameters
    ----------
    title : str
        Subsection title

    Examples
    --------
    >>> logger.subsection("Loading data")
    """
    if self.verbose:
        separator = "-" * self.width
        self._log("")
        self._log(separator)
        self._log(title)
        self._log(separator)

success(message, details=None, indent_level=0)

Print a success message with optional details.

Parameters:

Name Type Description Default
message str

Success message

required
details dict

Dictionary of key-value pairs to display

None
indent_level int

Indentation level

0

Examples:

>>> logger.success("Analysis complete", details={"time": 42.3, "subjects": 10})
Source code in src/lacuna/utils/logging.py
def success(
    self,
    message: str,
    details: dict | None = None,
    indent_level: int = 0,
) -> None:
    """
    Print a success message with optional details.

    Parameters
    ----------
    message : str
        Success message
    details : dict, optional
        Dictionary of key-value pairs to display
    indent_level : int, default=0
        Indentation level

    Examples
    --------
    >>> logger.success("Analysis complete", details={"time": 42.3, "subjects": 10})
    """
    indent = self.indent * indent_level
    self._log(f"{indent}{message}")

    if details and self.verbose:
        detail_indent = self.indent * (indent_level + 1)
        for key, value in details.items():
            # Format numbers nicely
            if isinstance(value, float):
                formatted_value = f"{value:.2f}"
            elif isinstance(value, int) and value >= 1000:
                formatted_value = f"{value:,}"
            else:
                formatted_value = str(value)

            self._log(f"{detail_indent}{key}: {formatted_value}")

warning(message, indent_level=0)

Print a warning message.

Parameters:

Name Type Description Default
message str

Warning message

required
indent_level int

Indentation level

0

Examples:

>>> logger.warning("Mask size smaller than expected")
Source code in src/lacuna/utils/logging.py
def warning(self, message: str, indent_level: int = 0) -> None:
    """
    Print a warning message.

    Parameters
    ----------
    message : str
        Warning message
    indent_level : int, default=0
        Indentation level

    Examples
    --------
    >>> logger.warning("Mask size smaller than expected")
    """
    indent = self.indent * indent_level
    self._log(f"{indent}{message}", level=logging.WARNING)

MessageType

Bases: Enum

Types of messages that can be displayed.

Source code in src/lacuna/utils/logging.py
class MessageType(Enum):
    """Types of messages that can be displayed."""

    INFO = ""  # General information
    SUCCESS = ""  # Operation completed successfully
    WARNING = ""  # Warning message
    ERROR = ""  # Error message
    PROGRESS = ""  # Progress update
    SECTION = "="  # Section header
    SUBSECTION = "-"  # Subsection header

log_error(message, verbose=False)

Print an error message.

Source code in src/lacuna/utils/logging.py
def log_error(message: str, verbose: bool = False) -> None:
    """Print an error message."""
    logger = ConsoleLogger(verbose=verbose)
    logger.error(message)

log_info(message, verbose=False)

Print an info message.

Source code in src/lacuna/utils/logging.py
def log_info(message: str, verbose: bool = False) -> None:
    """Print an info message."""
    logger = ConsoleLogger(verbose=verbose)
    logger.info(message)

log_progress(message, current=None, total=None, verbose=False)

Print a progress message.

Source code in src/lacuna/utils/logging.py
def log_progress(
    message: str,
    current: int | None = None,
    total: int | None = None,
    verbose: bool = False,
) -> None:
    """Print a progress message."""
    logger = ConsoleLogger(verbose=verbose)
    logger.progress(message, current=current, total=total)

log_section(title, width=70, verbose=False)

Print a section header.

Source code in src/lacuna/utils/logging.py
def log_section(title: str, width: int = 70, verbose: bool = False) -> None:
    """Print a section header."""
    logger = ConsoleLogger(verbose=verbose, width=width)
    logger.section(title)

log_success(message, details=None, verbose=False)

Print a success message.

Source code in src/lacuna/utils/logging.py
def log_success(message: str, details: dict | None = None, verbose: bool = False) -> None:
    """Print a success message."""
    logger = ConsoleLogger(verbose=verbose)
    logger.success(message, details=details)

log_warning(message, verbose=False)

Print a warning message.

Source code in src/lacuna/utils/logging.py
def log_warning(message: str, verbose: bool = False) -> None:
    """Print a warning message."""
    logger = ConsoleLogger(verbose=verbose)
    logger.warning(message)