# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""This module defines a logging class based on the astropy logging module."""
import sys
import logging
from logging import Logger
from astropy.utils import find_current_module
try:
from PyQt6 import QtWidgets
except ImportError:
HAS_PYQT6 = False
QtWidgets = None
else:
HAS_PYQT6 = True
__all__ = ['log', 'EyeLogger', 'StreamLogger',
'StatusLogger', 'DialogLogger']
# Initialize by calling init_log()
log = None
def _init_log():
"""
Initializes the Eye log.
In most circumstances, this is called automatically when
importing sofia_redux.visualization.
"""
global log
orig_logger_cls = logging.getLoggerClass()
logging.setLoggerClass(EyeLogger)
try:
log = logging.getLogger('eye')
log._set_defaults()
finally:
logging.setLoggerClass(orig_logger_cls)
return log
[docs]
class EyeLogger(Logger):
"""
Set up the Eye logging.
This class is based on the astropy logger, but keeping only the
record handling and some default setting functionality.
"""
[docs]
def makeRecord(self, name, level, pathname, lineno, msg, args, exc_info,
func=None, extra=None, sinfo=None):
if extra is None:
extra = {}
if 'origin' not in extra:
current_module = find_current_module(1, finddiff=[True, 'logging'])
if current_module is not None:
extra['origin'] = current_module.__name__
else:
extra['origin'] = 'unknown'
return Logger.makeRecord(self, name, level, pathname, lineno, msg,
args, exc_info, func=func, extra=extra,
sinfo=sinfo)
def _set_defaults(self):
"""Reset logger to its initial state."""
# Remove all previous handlers
for handler in self.handlers[:]:
self.removeHandler(handler)
# Set levels
self.setLevel('DEBUG')
# Set up the stdout handler
sh = StreamLogger()
self.addHandler(sh)
[docs]
class StreamLogger(logging.StreamHandler):
"""
Log handler for logging messages to stdout or stderr streams.
A specialized StreamHandler that logs INFO and DEBUG messages to
stdout, and all other messages to stderr. Also provides color
coding of the output.
"""
[docs]
def emit(self, record):
"""
Emit log messages to terminal.
Parameters
----------
record : `logging.LogRecord`
The log record, with an additional 'origin' attribute
attached by `EyeLogger.makeRecord`.
"""
if record.levelno <= logging.INFO:
stream = sys.stdout
else:
stream = sys.stderr
if record.levelno < logging.DEBUG:
print(record.levelname, end='', file=stream)
else:
# Import utils.console only if necessary and at the latest because
# the import takes a significant time [#4649]
from astropy.utils.console import color_print
if record.levelno < logging.INFO:
color_print(record.levelname, 'magenta', end='', file=stream)
elif record.levelno < logging.WARN:
color_print(record.levelname, 'green', end='', file=stream)
elif record.levelno < logging.ERROR:
color_print(record.levelname, 'brown', end='', file=stream)
else:
color_print(record.levelname, 'red', end='', file=stream)
record.message = f"{record.msg} [{record.origin:s}]"
print(": " + record.message, file=stream)
[docs]
class StatusLogger(logging.Handler):
def __init__(self, status_bar):
"""
Log handler for logging info messages to a status bar.
Parameters
----------
status_bar : `PyQt6.QtWidgets.QStatusBar`
Status bar widget to display to.
"""
super().__init__()
self.status_bar = status_bar
[docs]
def emit(self, record):
"""
Display an INFO message in the status bar.
Parameters
----------
record : `logging.LogRecord`
The log record, with an additional 'origin' attribute
attached.
"""
if record.levelno == logging.INFO:
msg = str(record.msg)
try:
self.status_bar.showMessage(msg, 5000)
except RuntimeError: # pragma: no cover
# can happen in race conditions, if app is
# closed before log completes
pass
[docs]
class DialogLogger(logging.Handler):
def __init__(self, parent):
"""
Log handler for logging error messages to a dialog box.
Parameters
----------
parent : PyQt6.QtWidgets.QWidget
"""
super().__init__()
self.parent = parent
[docs]
def emit(self, record):
"""
Display a WARNING or ERROR message in a dialog box.
Parameters
----------
record : `logging.LogRecord`
The log record, with an additional 'origin' attribute
attached by `EyeLogger.makeRecord`.
"""
# no op if no pyqt
if not HAS_PYQT6: # pragma: no cover
return
# don't show dialog box if parent isn't up yet
if not self.parent.isVisible():
return
msg = str(record.msg)
if record.levelno == logging.WARNING:
QtWidgets.QMessageBox.warning(self.parent, 'WARNING', msg)
elif record.levelno == logging.ERROR:
QtWidgets.QMessageBox.critical(self.parent, 'ERROR', msg)