pyros_logger.py 8.49 KB
#!/usr/bin/env python3

"""LOGGER

"""


# Standard library imports
import logging
# https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers
from logging.handlers import SMTPHandler, TimedRotatingFileHandler
import sys
import os
#import logging_tree


# Third party imports
# None

# Local application imports
# None

# Exported Objects (to modules that use this logger)
log: logging.Logger = None


# ./../logs/
#LOGFILES_PATH = __file__ + os.sep + '..' + os.sep + '..' + os.sep + 'logs'
#LOGFILES_PATH = os.path.join(os.path.dirname(__file__), '..', 'logs')
_LOGFILES_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'logs')
#LOGFILES_PATH = LOGFILES_PATH + os.sep + 'logs'


# https://docs.python.org/3/howto/logging-cookbook.html#filters-contextual
class _FilterOnDebugModeAndNoLevelINFO(logging.Filter):

    def filter(self, logRecord):
        debug_mode = int(os.environ.get('PYROS_DEBUG', 0))
        #print("debug_mode:", debug_mode)
        #print(debug_mode)
        #return True
        # On affiche TOUJOURS sur la console SAUF level debug si mode DEBUG is OFF 
        #print("level name", logging.getLevelName(logRecord.levelno))
        #if logRecord.levelno != 10: return True 

        #'''
        if logging.getLevelName(logRecord.levelno) == 'INFO': return False 
        if logging.getLevelName(logRecord.levelno) == 'DEBUG': return (debug_mode == 1)
        #'''
        
        return True
        # On n'affiche sur la console QUE si mode DEBUG is ON
        #return (debug_mode == 1)


class _FilterOnLogLevelMax(logging.Filter):

    def __init__(self, level):
        self.__level_max = level

    def filter(self, logRecord):
        return logRecord.levelno <= self.__level_max


def _formatter_full():
    return logging.Formatter('%(asctime)s - %(levelname)-8s - %(filename)-12s - %(processName)s - %(threadName)s : %(lineno)-5s - %(message)s')

def _formatter_mini():
    return logging.Formatter('%(message)s')


def _handler_console(minloglevel: int, a_filter: logging.Filter, formatter: logging.Formatter):
    # Console should print ALL messages (starting from lower level DEBUG)
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(minloglevel)
    #handler.setLevel(logging.DEBUG)
    #formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
    #formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s : %(lineno)s - %(message)s')
    #formatter = logging.Formatter('%(name)-12s - %(relativeCreated)5d - %(asctime)s - %(levelname)-8s - %(filename)s - %(processName)s - %(threadName)s : %(lineno)s - %(message)s')
    # pyroslogger  -  3364 - 2022-01-04 11:41:59,672 - INFO     - Agent.py - MainProcess - MainThread : 999 - in set active
    ##formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(processName)s - %(threadName)s : %(lineno)s - %(message)s')
    handler.setFormatter(formatter)
    handler.addFilter(a_filter)
    #handler.addFilter(DebugIsOnFilter())
    return handler


def _handler_file(minloglevel: int, suffix: str, filepath: str, a_filter: logging.Filter=None):
    # Log File should contain ONLY messages starting from level WARNING (ERROR ?)
    #f_handler = logging.FileHandler('file.log')
    handler = TimedRotatingFileHandler(filepath+os.sep + f"pyroslog_{suffix}.log", when='midnight')
    handler.setLevel(minloglevel)
    # Create formatter and add it to handler
    #f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    #f_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s : %(lineno)s - %(message)s')
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(processName)s - %(threadName)s : %(lineno)s - %(message)s')
    handler.setFormatter(formatter)
    if a_filter: handler.addFilter(a_filter)
    return handler


# public for Agent.py
def handler_filebyagent(minloglevel: int, agent_name: str, filepath: str=None):
    if filepath is None: filepath = _LOGFILES_PATH
    # Create agent folder if does not exist
    agentDir = os.path.join(filepath, agent_name)
    while not os.path.isdir(agentDir): os.mkdir(agentDir)

    # Log File should contain ONLY messages starting from level WARNING (ERROR ?)
    #f_handler = logging.FileHandler('file.log')
    #f_handler = TimedRotatingFileHandler(filepath+os.sep+'agentslog.log', when='midnight')
    #handler = TimedRotatingFileHandler(filepath+os.sep+agent_name+'.log', when='midnight')
    handler = TimedRotatingFileHandler(os.path.join(agentDir,agent_name+'.log'), when='midnight')
    handler.setLevel(minloglevel)
    # Create formatter and add it to handler
    #f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    #f_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s : %(lineno)s - %(message)s')
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(processName)s - %(threadName)s : %(lineno)s - %(message)s')
    handler.setFormatter(formatter)
    return handler


def _handler_db():
    # DB should contain ONLY messages starting from level ERROR
    # TODO:
    pass


def _handler_email(minloglevel: int):
    # Email should be sent ONLY for messages starting from level CRITICAL
    # http://sametmax.com/envoi-dun-email-par-logging-en-cas-de-plantage-dun-script-python-ou-comment-faire-bouffer-uxe9-a-smtphandler/
    # TODO: a ameliorer avec article ci-dessus pour UNICODE
    m_handler = SMTPHandler(
        # Host et port
        ('SMTP.GMAIL.COM', 587),
        # From
        "MOI@GMAIL.COM",
        # To (liste)
        ["QUELQU.UN@QUELQUE.PART"],
        # Sujet du message
        "Erreur critique dans module MMM",
        # pour l'authentification
        credentials = ("MONEMAIL@GMAIL.COM", "MONSUPERPASSWORD"),
        secure = ()
    )
    m_handler.setLevel(minloglevel)
    m_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    m_handler.setFormatter(m_format)
    return m_handler




#def get_logger(logger_name: str='pyroslogger'):
###def set_logger_config(logger_name: str='pyroslogger'):

###global logger


# Basic configuration
'''
#logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG, filename='client.log', filemode='a', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logging.debug("Client instanciated")
'''

# Advanced configuration
# https://docs.python.org/3/library/logging.html#logging.getLogger
#logger = logging.getLogger(__name__)
##logger = logging.getLogger(log_name)
log = logging.getLogger('pyroslogger')

# If not set, default level is NOTSET (all messages are logged)
# The defined levels, in order of increasing severity : DEBUG, INFO, WARNING, ERROR, CRITICAL    
log.setLevel(logging.DEBUG) # better to have too much log than not enough
#log.setLevel(logging.ERROR) # better to have too much log than not enough

# Create and add handlers to the logger
# - log to console for all levels except "info" (and "debug" only if DEBUG is ON)
log.addHandler(_handler_console(logging.DEBUG, _FilterOnDebugModeAndNoLevelINFO(), _formatter_full()))
# - log to console only for "info" level
log.addHandler(_handler_console(logging.INFO, _FilterOnLogLevelMax(logging.INFO), _formatter_mini()))
# - log to file only for "info" level
log.addHandler(_handler_file(logging.INFO, 'info', _LOGFILES_PATH, _FilterOnLogLevelMax(logging.INFO)))
# - log to file only from "warning" level
log.addHandler(_handler_file(logging.WARN, 'error', _LOGFILES_PATH))
##logger.addHandler(handler_filebyagent(logging.INFO, LOGFILES_PATH))
# TODO:
####logger.addHandler(get_email_handler(logging.ERROR))
##logger.addHandler(db_handler)

# with this pattern, it's rarely necessary to propagate the error up to parent
##logger.propagate = False

##return logger


log.debug("log files are logged to:" + _LOGFILES_PATH)


'''
# Shortcuts for logger:
#def log_d(msg:str): log.debug(msg)
def log_d(msg:str, *args, **kwargs): log.debug(msg, *args, **kwargs)
def log_i(msg:str, *args, **kwargs): log.info(msg, *args, **kwargs)
def log_w(msg:str, *args, **kwargs): log.warning(msg, *args, **kwargs)
def log_e(msg:str, *args, **kwargs): log.error(msg, *args, **kwargs)
def log_c(msg:str, *args, **kwargs): log.critical(msg, *args, **kwargs)
'''


if __name__ == "__main__":

    set_logger_config()
    #my_logger = get_logger("my_module_name")
    #my_logger = get_logger()

    #logging_tree.printout()  

    log.debug("a debug message")
    log.info("a info message")
    log.warning("a warn message")
    log.error("a error message")
    log.critical("a critical message")