#!/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")