# @Email: jmaggio14@gmail.com
# @Website: https://www.imagepypelines.org/
# @License: https://github.com/jmaggio14/imagepypelines/blob/master/LICENSE
# @github: https://github.com/jmaggio14/imagepypelines
#
# Copyright (c) 2018 - 2020 Jeff Maggio, Jai Mehra, Ryan Hartzell
#
import logging
from termcolor import colored
import sys
# --------- enable terminal colors if we are in on a windows system ---------
import os
if os.name == 'nt':
import colorama
colorama.init()
del colorama
LOG_LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL,
}
"""Log level strings mapped to their numerical values"""
LOG_COLORS = {
'debug': 'cyan',
'info': None,
'warning': 'yellow',
'error': 'red',
'critical': 'red',
}
"""Module variable controlling the color of our logs, this can be modified to
suit the end user's needs or ignored entirely by setting
ip.MASTER_LOGGER.ENABLE_LOG_COLOR = False"""
LOG_TEXT_ATTRS = {
'debug':['bold'],
'info':None,
'warning':['bold'],
'error':None,
'critical':['bold']
}
"""Module variable controlling the text attributes of our logs, this can be
modified to suit the end user's needs or ignored entirely by setting
ip.MASTER_LOGGER.ENABLE_LOG_COLOR = False"""
# this is defined lower down in this file
MASTER_LOGGER = None
"""logging.Logger subclass that is the root of all loggers instantiated in
ImagePypelines"""
MASTER_ADAPTER = None
"""logging.LoggerAdapter subclass that wraps the master logger"""
# LOGGING_MANAGER = None
# """logging.Manager for all ImagePypelines loggers"""
# Define our new special Logger class that can be pickled
# (like the loggers of python 3.7)
[docs]class ImagepypelinesLogger( logging.getLoggerClass() ):
"""subclass of logging.Logger that can be pickled, also adds colored logging
outputs if desired. the color, functionality and text attributes can be
controlled by setting the module variables imagepypelines.LOG_COLORS,
imagepypelines.ENABLE_LOG_COLOR, imagepypelines.LOG_TEXT_ATTRS
Attributes:
ENABLE_LOG_COLOR(bool): class level variable controlling whether or not
to markup log output with ANSI color codes. True by default
"""
ENABLE_LOG_COLOR = True
def _color_msg(self, msg, level, LEVEL):
if self.isEnabledFor(LEVEL) and self.ENABLE_LOG_COLOR:
return colored(msg, LOG_COLORS[level], attrs=LOG_TEXT_ATTRS[level])
return msg
[docs] def debug(self, msg, *args, **kwargs):
msg = self._color_msg(msg, 'debug', logging.DEBUG)
return super().debug(msg, *args, **kwargs)
[docs] def info(self, msg, *args, **kwargs):
msg = self._color_msg(msg, 'info', logging.INFO)
return super().info(msg, *args, **kwargs)
[docs] def warning(self, msg, *args, **kwargs):
msg = self._color_msg(msg, 'warning', logging.WARNING)
return super().warning(msg, *args, **kwargs)
[docs] def error(self, msg, *args, **kwargs):
msg = self._color_msg(msg, 'error', logging.ERROR)
return super().error(msg, *args, **kwargs)
[docs] def critical(self, msg, *args, **kwargs):
msg = self._color_msg(msg, 'critical', logging.CRITICAL)
return super().critical(msg, *args, **kwargs)
[docs] def getChild(self,*args,**kwargs):
# make a formatter for the child logger
child = super().getChild(*args,**kwargs)
if not len(self.handlers):
ch = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s | %(name)s [ %(levelname)8s ]: %(message)s')
ch.setFormatter(formatter)
child.addHandler(ch)
return child
[docs] def setLevel(self, level, *args, **kwargs):
level = LOG_LEVELS.get(level, level)
super().setLevel(level, *args, **kwargs)
# JEFF: modified from here https://github.com/python/cpython/blob/ca7b504a4d4c3a5fde1ee4607b9501c2bab6e743/Lib/logging/__init__.py
def __reduce__(self):
if self.name == 'ImagePypelines':
return make_master, (self.level,)
return get_logger, (self.name,)
class ImagepypelinesLoggerAdapter(logging.LoggerAdapter):
def getChild(self, name, *args, **kwargs):
logger = self.logger.getChild(name, *args, **kwargs)
adapter = ImagepypelinesLoggerAdapter(logger, self.extra)
return adapter
[docs]def get_master_logger():
if MASTER_ADAPTER:
return MASTER_ADAPTER
metadata = {'pipeline_id':'master',
'pipeline_uuid':'master',
'pipeline_name':'master'}
return ImagepypelinesLoggerAdapter(make_master(),metadata)
def make_master(level=logging.INFO):
"""creates the master logger if it doesn't exist, returns it if it does"""
if MASTER_LOGGER:
return MASTER_LOGGER
# create our ImagePypelines master logger
# set our subclass as the root of all child loggers
master = ImagepypelinesLogger('ImagePypelines')
manager = logging.Manager(master)
manager.setLoggerClass(ImagepypelinesLogger)
master.manager = manager
ch = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s | %(name)s [ %(levelname)8s ]: %(message)s')
ch.setFormatter(formatter)
master.addHandler(ch)
master.setLevel(level)
return master
MASTER_LOGGER = make_master()
MASTER_ADAPTER = get_master_logger()
[docs]def get_logger(name, pipeline=None, parent=MASTER_LOGGER):
"""Creates a new child logging adapter from the given parent (root logger by
default)
Args:
name(str): the name of the new child logger
pipeline(Block,Pipeline): the Pipeline object associated with this logger
(optional)
parent(logging.LoggerAdapter,logging.Logger): parent logger to spawn a
new logger off of
Returns:
ImagepypelinesLoggerAdapter: a new child logger adapter with conn ids for
id, uuid, and name of the Pipeline (if applicable)
"""
if isinstance(parent, logging.LoggerAdapter):
parent = parent.logger
logger = parent.getChild(name)
if pipeline:
metadata = {'pipeline_id':pipeline.id,
'pipeline_uuid':pipeline.uuid,
'pipeline_name':pipeline.name}
else:
metadata = {'pipeline_id':None,
'pipeline_uuid':None,
'pipeline_name':None}
adapter = ImagepypelinesLoggerAdapter(logger, metadata)
return adapter
[docs]def set_log_level(log_level):
"""sets the global master logger level"""
global MASTER_LOGGER
log_level = LOG_LEVELS.get(log_level, log_level)
MASTER_LOGGER.setLevel(log_level)
# END