"""Deal with loggers"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import logging
import os
import six
import vdat.config as vdatconfig
# store all the loggers used by cure tasks so that they and the handlers are
# created only once
_cure_loggers = {}
[docs]def setup_main_logger(conf, name='logger'):
"""Setup the main vdat logger.
Add to the logger called ``name`` a standard output and a file logger
Parameters
----------
conf : :class:`~pyhetdex.tools.configuration.ConfigParser` instance
configuration options,
"""
log = logging.getLogger(name)
# force debug to make handlers decide
log.setLevel(_to_log_level(conf.get('logging',
'logger_level')))
# create and add the handlers
for section in ['logging.main.stdout', 'logging.main.file']:
sconf = conf_to_dict(conf, section) # get the content of the section
h = make_handler(sconf)
log.addHandler(h)
[docs]def make_logger(logger, conf=None, section=None):
"""Create a handler using the instructions in the configuration ``section``
and add it to the ``logger``.
It looks for the following options:
* ``remove_old``: if True remove all the previous handlers before
adding the new one, default ``False``
* ``remove_std``: if True remove all the previous
:class:`~logging.StreamHandler``, default ``False``
* the ones used by :func:`make_handler`
Parameters
----------
logger : :class:`~logging.Logger` instance
logger
conf : :class:`~pyhetdex.tools.configuration.ConfigParser` instance,\
optional
configuration options, if ``None`` use the default ``VDAT``
configuration object
section : string, default
name of the section containing the information to build the logger;
defaults to ``logging.name``, where ``name`` is the logger name
"""
if not conf:
conf = vdatconfig.get_config('main')
if not section:
section = "logging.{}".format(logger.name)
dconf = conf_to_dict(conf, section)
# get the class to use to get rid of old handlers
parent_class = None
if dconf.get('remove_std', False):
parent_class = logging.StreamHandler
if dconf.get('remove_old', False):
parent_class = logging.Handler
# get rid of old handlers
if parent_class:
for h in logger.handlers:
if isinstance(h, parent_class):
logger.removeHandler(h)
# set the logger level
logger.setLevel(_to_log_level(dconf.get('logger_level')))
handler = make_handler(dconf)
if handler:
logger.addHandler(handler)
[docs]def astropy_handlers(conf):
"""Tweak the astropy logger removing the existing (stream) handlers and
setting a new one if required
Parameters
----------
conf : :class:`~pyhetdex.tools.configuration.ConfigParser` instance
configuration options,
"""
from astropy import log as astropy_log
make_logger(astropy_log, conf=conf, section='logging.astropy')
[docs]def ginga_handlers(conf):
"""Create/update the ginga logger removing existing handlers
setting a new one if required
Parameters
----------
conf : :class:`~pyhetdex.tools.configuration.ConfigParser` instance
configuration options,
"""
make_logger(logging.getLogger('ginga'), conf=conf)
[docs]def cure_logger(name):
"""Get the logger with ``name``.
The first time this function is called with a ``name``, the logger writing
to a file in the log directory and called ``name.log`` is created
Parameters
----------
name : string
name of the logger
Returns
-------
:class:`logging.Logger` instance
string
name of the file where the logs are saved
.. todo::
redo it when finishing the implementation of the command interpreter
"""
try:
return _cure_loggers[name]
except KeyError:
# Create logger for the cure task
conf = vdatconfig.get_config('main')
log = logging.getLogger(name)
# first clear all the handlers to avoid problems with handlers
# inherited from parent loggers
for h in log.handlers:
log.removeHandler(h)
if conf.getboolean('logging', 'cure_enabled'):
log_dir = conf.get('logging', 'logdir')
cure_name = name + conf.get("logging", "cure_ext")
cure_level = _get_handler_level(conf, "logging", "cure_level")
cure_logform = conf.get("logging", "cure_logform")
hf = _file_handler(log_dir, cure_name, cure_level, cure_logform)
log.addHandler(hf)
full_name = os.path.join(log_dir, cure_name)
else:
full_name = "No cure file logging enabled"
_cure_loggers[name] = [log, full_name]
return _cure_loggers[name]
[docs]def setup_command_loggers():
"""Set up the loggers for the commands connected or connectible to
buttons"""
main_config = vdatconfig.get_config('main')
command_config = vdatconfig.get_config('commands')
section = 'logging.commands'
for k, v in six.iteritems(command_config):
# loop through all the commands and create a logger accordingly
try:
exe_name = v['is_alias_of']
except KeyError:
exe_name = k
file_name = '{}{}'.format(exe_name, main_config.get(section,
'extension'))
main_config.set(section, 'file_name', file_name)
logger = logging.getLogger(name=k)
make_logger(logger, conf=main_config, section=section)
[docs]def conf_to_dict(conf, section, from_parent=['logdir', 'logger_level']):
"""Extract the ``section`` and convert it to a dictionary.
If ``from_parent`` is a non empty list, it will get the name of the parent
section, e.g. if ``section=logging.main.stdout``, the parent section is
``logging``. Then if any element in ``from_parent`` doesn't exist in
``section``, it tries to get to from the parent section. If it doesn't
exist there, it's skipped.
Parameters
----------
conf : :class:`~pyhetdex.tools.configuration.ConfigParser` instance
configuration options,
section : string
name of the section in the configuration where to find all the info
Returns
-------
dict
"""
dconf = dict(conf.items(section))
if from_parent:
parent_section = section.split('.')[0]
for fp in from_parent:
if fp not in dconf and conf.has_option(parent_section, fp):
dconf[fp] = conf.get(parent_section, fp)
return dconf
[docs]def make_handler(conf):
"""Create a handler following the instructions contained in ``section``
It looks for the following options:
* enabled: if False, doesn't create a logger and returns None; default
False
* file_name: if not found, a :class:`~logging.StreamHandler` is created, if
exists, a :class:`~logging.FileHandler` is created
* logdir: directory where the log file goes; default '.'
* format: formatting of the message, default %(levelname)s: %(message)s
* level: level of the handler, default INFO
Parameters
----------
conf : dict
configuration options for the handler; it can be created using
:func:`conf_to_dict`
Returns
-------
None, if enabled is ``False``
:class:`~logging.StreamHandler`: if not file name is given
:class:`~logging.FileHandler`: otherwise
"""
if not conf.get('enabled', False):
return None
file_name = conf.get('file_name', None)
if file_name:
logdir = conf.get('logdir', os.curdir)
if not os.path.exists(logdir):
os.mkdir(logdir)
handler = logging.FileHandler(os.path.join(logdir, file_name))
else:
handler = logging.StreamHandler()
logform = conf.get('format', '%(levelname)s: %(message)s')
handler.setFormatter(logging.Formatter(logform))
handler.setLevel(_to_log_level(conf.get("level", logging.INFO)))
return handler
[docs]def _to_log_level(level, default=logging.INFO):
"""Try to convert ``level`` to int or to extract the level
``level`` from the logging module. If both fail, return the default.
See `here
<https://docs.python.org/3.4/library/logging.html?highlight=logging#logging-levels>`_
for more info about logging levels.
Parameters
----------
level : string
numerical or literal value of the level
Returns
-------
int
handler level
"""
try:
return int(level)
except ValueError: # it's not an integer
pass
return getattr(logging, level, default)