"""Loging utilities."""
import logging
from contextlib import contextmanager
from fnmatch import fnmatch
[docs]@contextmanager
def logs(logger, level=None, handler=None, close=True):
    """Emit logs at or above `level` in the encapsulated context, optionally using the
    specified `handler`.
    See `the Python logging cookbook
    <https://docs.python.org/3/howto/logging-cookbook.html#using-a-context-manager-for-selective-logging>`__
    for more information.
    Parameters
    ----------
    logger : :class:`logging.Logger`
        The logger to use for the encapsulated context.
    level : str or int, optional
        The minimum log levels to emit. The standard log levels "debug", "info",
        "warning", "error" and "critical" are supported, as are their corresponding
        level numbers (see :mod:`logging`).
    handler : :class:`logging.LogHandler`, optional
        The handler to add to `logger` for the encapsulated context.
    close : bool, optional
        Close `handler` once finished. Defaults to `True`.
    Examples
    --------
    Print debug logs during parsing, excluding compilation messages.
    >>> import logging
    >>> from finesse import Model
    >>> from finesse.utilities import logs
    >>> from finesse.utilities.logging import FinesseStreamHandler
    >>> handler = FinesseStreamHandler()
    >>> handler.exclude("finesse.script.compiler")
    >>> model = Model()
    >>> with logs(logging.getLogger(), level="debug", handler=handler):
    >>>     model.parse("laser l1 P=1")
    """
    old_level = None
    if level is not None:
        try:
            # Convert level name to uppercase.
            level = level.upper()
        except AttributeError:
            # Probably a number.
            pass
        old_level = logger.level
        logger.setLevel(level)
    if handler:
        logger.addHandler(handler)
    yield
    if level is not None:
        logger.setLevel(old_level)
    if handler:
        logger.removeHandler(handler)
        if close:
            handler.close() 
[docs]@contextmanager
def tracebacks(tracebacks=True):
    """Show or hide tracebacks in the encapsulated context.
    Some environments, such as Jupyterlab, hide tracebacks by default. This context
    allows tracebacks to be forcefully shown or hidden temporarily.
    Parameters
    ----------
    tracebacks : bool, optional
        Show tracebacks. Defaults to True.
    Examples
    --------
    Print tracebacks during a run.
    >>> from finesse import Model
    >>> from finesse.utilities import tracebacks
    >>> model = Model()
    >>> with tracebacks():
    >>>     model.parse("laser l1 L=1")
    """
    from .. import show_tracebacks
    old_tracebacks = show_tracebacks(tracebacks)
    yield
    show_tracebacks(old_tracebacks) 
[docs]class FinesseStreamHandler(logging.StreamHandler):
    """Finesse stream handler.
    This class provides a mechanism to exclude displayed log channels by wildcard. It is
    otherwise identical to :class:`logging.StreamHandler`.
    """
[docs]    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__excluded_channels = None
        self.reset_exclude_patterns() 
[docs]    def exclude(self, pattern):
        self.__excluded_channels.add(pattern) 
[docs]    def reset_exclude_patterns(self):
        """Empty the configured log channel exclude patterns, and return what was
        there."""
        old_excludes = set(self.__excluded_channels or [])
        self.__excluded_channels = set()
        return old_excludes 
[docs]    def filter(self, record):
        for pattern in self.__excluded_channels:
            if fnmatch(record.name, pattern):
                # Skip the record.
                return
        return record