Source code for qudi.core.logger

# -*- coding: utf-8 -*-
"""
This package provides the qudi logging facility. It facilitates Pythonic logging by joining Qt log
into native Python logging. Also installs a logging handler that emits a Qt Signal to tap into
every time a log event is registered.
Automatically installs all important logging handlers for qudi to work but also allows for
registering (and removal) of additional handlers.
Installs a rotating log file handler to write log messages to disk.


.. Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
.. distribution and on <https://github.com/Ulm-IQO/qudi-core/>
..
.. This file is part of qudi.
..
.. Qudi is free software: you can redistribute it and/or modify it under the terms of
.. the GNU Lesser General Public License as published by the Free Software Foundation,
.. either version 3 of the License, or (at your option) any later version.
..
.. Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
.. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
.. See the GNU Lesser General Public License for more details.
..
.. You should have received a copy of the GNU Lesser General Public License along with qudi.
.. If not, see <https://www.gnu.org/licenses/>.
"""

__all__ = (
    'clear_handlers',
    'get_handler',
    'get_file_handler',
    'get_logger',
    'get_record_table_model',
    'get_signal_handler',
    'get_stderr_handler',
    'init_record_model_handler',
    'init_rotating_file_handler',
    'register_handler',
    'set_log_level',
    'unregister_handler',
)

import os
import logging
import warnings
from logging.handlers import RotatingFileHandler
from PySide2.QtCore import qInstallMessageHandler

from .handlers import LogSignalHandler, LogTableModelHandler, qt_message_handler


# global variables
# Keep track of all handlers that have been registered to the qudi
_handlers = dict()
# default handlers for qudi root logger
_signal_handler = None
_file_handler = None
_stream_handler = None
_table_model_handler = None
# The qudi root logger for all loggers created with this module API
_qudi_root_logger = None

# Register Qt message handler
qInstallMessageHandler(qt_message_handler)

# initialize logging module
logging.basicConfig(format='%(message)s', level=logging.WARNING)
logging.addLevelName(logging.CRITICAL, 'critical')
logging.addLevelName(logging.ERROR, 'error')
logging.addLevelName(logging.WARNING, 'warning')
logging.addLevelName(logging.INFO, 'info')
logging.addLevelName(logging.DEBUG, 'debug')
logging.addLevelName(logging.NOTSET, 'not set')
logging.captureWarnings(True)

# set level of stream handler which logs to stderr
if len(logging.getLogger().handlers) < 1:
    _stream_handler = logging.StreamHandler()
    logging.getLogger().addHandler(_stream_handler)
else:
    _stream_handler = logging.getLogger().handlers[0]
_stream_handler.setLevel(logging.WARNING)

# Create qudi root logger
_qudi_root_logger = logging.getLogger('qudi')
_qudi_root_logger.setLevel(logging.INFO)
# _qudi_root_logger.propagate = False

# Create and register signal handler in root logger
_signal_handler = LogSignalHandler()
logging.getLogger().addHandler(_signal_handler)


def register_handler(name, handler, silent=False):
    global _handlers
    if name in _handlers:
        if silent:
            unregister_handler(name)
        else:
            raise KeyError(
                f'Unable to register new logging handler. Handler by name "{name}" '
                f'already registered.'
            )
    logging.getLogger().addHandler(handler)
    _handlers[name] = handler


def unregister_handler(name, silent=False):
    global _handlers
    handler = _handlers.pop(name, None)
    if handler is None:
        if not silent:
            raise KeyError(
                f'Unable to unregister logging handler. No handler registered by name "{name}".'
            )
    else:
        logging.getLogger().removeHandler(handler)


def clear_handlers():
    global _handlers
    root_logger = logging.getLogger()
    for name in tuple(_handlers):
        root_logger.removeHandler(_handlers.pop(name))


def get_handler(name):
    return _handlers.get(name, None)


[docs] def get_logger(name): return _qudi_root_logger.getChild(name.split('qudi.', 1)[-1])
def set_log_level(level): _signal_handler.setLevel(level) _qudi_root_logger.setLevel(level) if _table_model_handler is not None: _table_model_handler.setLevel(level) if _file_handler is not None: _file_handler.setLevel(level) def get_record_table_model(): return None if _table_model_handler is None else _table_model_handler.table_model def get_signal_handler(): return _signal_handler def get_stderr_handler(): return _stream_handler def get_file_handler(): return _file_handler def init_record_model_handler(max_records=10000): global _table_model_handler if _table_model_handler is not None: logging.getLogger().removeHandler(_table_model_handler) _table_model_handler = None _table_model_handler = LogTableModelHandler( level=_qudi_root_logger.level, max_records=max_records ) logging.getLogger().addHandler(_table_model_handler) def init_rotating_file_handler( path='', filename='qudi.log', max_bytes=1024**3, backup_count=5 ): global _file_handler # Remove file handler if it has already been registered if _file_handler is not None: _file_handler.doRollover() logging.getLogger().removeHandler(_file_handler) _file_handler = None filepath = os.path.join(path, filename) session_limit = 1000 session_count = 1 while session_count <= session_limit: try: # Start new file if old logfiles exist do_rollover = os.path.exists(filepath) and os.stat(filepath).st_size > 0 _file_handler = RotatingFileHandler( filepath, maxBytes=max_bytes, backupCount=backup_count ) _file_handler.setFormatter( logging.Formatter( '%(asctime)s %(levelname)s %(name)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', ) ) _file_handler.setLevel(_qudi_root_logger.level) if do_rollover: _file_handler.doRollover() except PermissionError: session_count += 1 if session_count > session_limit: warnings.warn( f'Unable to initialize logger rotating file handler. OS denied ' f'access to log file or there are more than {session_limit:d} qudi ' f'sessions running.' ) return split_filename = filename.rsplit('.', 1) if len(split_filename) == 2: new_filename = ( f'{split_filename[0]}_session{session_count:d}.{split_filename[1]}' ) else: new_filename = f'{filename}_session{session_count:d}' filepath = os.path.join(path, new_filename) else: logging.getLogger().addHandler(_file_handler) break