apb_extra_utils.utils_logging
1# coding=utf-8 2# 3# Author: Ernesto Arredondo Martinez (ernestone@gmail.com) 4# Created: 7/6/19 18:23 5# Last modified: 7/6/19 18:21 6# Copyright (c) 2019 7 8import datetime 9import logging 10import logging.config 11import os 12from pathlib import Path 13import tempfile 14from operator import attrgetter 15 16from . import get_root_logger, get_environ 17from . import misc 18 19LOG_HANDLER = "LOG" 20REPORTS_HANDLER = 'REPORTS' 21CONSOLE_HANDLER = 'console' 22ENV_VAR_LOGS_DIR = "PYTHON_LOGS_DIR" 23 24 25def get_base_logger(nom_base_log=None, level=None, parent_func=False): 26 """ 27 Creates a logger for the context from where it is called with the logging level. 28 29 Args: 30 nom_base_log (str=None): Base name of the log. If not specified, it is obtained from the context where it is launched 31 level (int=logging.INFO): Logger level (logging.DEBUG, logging.INFO, logging.WARNING, ...) 32 parent_func (bool=False): If parent_func=True is indicated, then it returns the name of the context that calls the function 33 34 Returns: 35 logging.Logger: Logger instance for the function from where it is called 36 """ 37 if not level: 38 level = logging.INFO if get_environ() != 'dev' else logging.DEBUG 39 40 skip_ctxt = 1 41 if parent_func: 42 skip_ctxt += 1 43 44 if not nom_base_log: 45 nom_base_log = misc.caller_name(skip_ctxt) 46 47 root_logger = get_root_logger() 48 a_logger = root_logger.getChild(nom_base_log) 49 50 a_logger.setLevel(level) 51 a_logger.propagate = True 52 53 return a_logger 54 55 56def get_file_logger(nom_base_log=None, level=None, dir_log=None, parent_func=False, sufix_date=True, 57 separate_reports=True, encoding='utf-8'): 58 """ 59 Crea logger con FILEHANDLER (filehandlers si separate_reports a True) 60 61 Si nombre del log (nom_base_log) no se especifica, se crea nombre de log con el nombre del contexto desde donde 62 se llama y nombre de máquina si se puede obtener, y siempre la fecha actual 63 64 Args: 65 nom_base_log (str=None): Nombre base del log. Si no se especifica se obtiene del contexto donde se lanza 66 level (int=logging.INFO): Nivel del logger (logging.DEBUG, logging.INFO, logging.WARNING, ...) 67 dir_log (str, optional): Si se especifica, directorio donde guardar log 68 parent_func (bool=False): Si se indica parent_func=True entonces devuelve el nombre del 69 contexto que llama a la funcion 70 sufix_date (bool=True): 71 separate_reports (bool=False): Si se indica separate_reports=True entonces se creará un file 72 handler separado para el log de reports (logging.INFO) 73 encoding (str='utf-8'): Encoding del fichero de log 74 Returns: 75 logging.logger: Instancia de logger para la funcion desde donde se llama 76 """ 77 if not nom_base_log: 78 nom_base_log = misc.caller_name(1 if not parent_func else 2) 79 a_logger = get_base_logger(nom_base_log, level) 80 81 if not a_logger.handlers: 82 if not dir_log: 83 dir_log = logs_dir(True) 84 else: 85 misc.create_dir(dir_log) 86 87 sub_parts_nom = [] 88 if misc.machine_apb(): 89 sub_parts_nom.append(misc.machine_name()) 90 sub_parts_nom.append(nom_base_log) 91 if sufix_date: 92 sub_parts_nom.append(datetime.datetime.today().strftime('%Y%m%d_%H%M%S')) 93 94 path_base_log = os.path.normpath(os.path.join(dir_log, "-".join(sub_parts_nom))) 95 96 config_file_handlers = { 97 handler.name: handler for handler in root_handlers() 98 if handler.name != CONSOLE_HANDLER 99 } 100 101 def add_config_file_handler(handler, level_handler=None, sufix_handler=False): 102 """ 103 Add handler to logger 104 Args: 105 handler (logging.Handler): 106 level_handler (int, optional): If not specified, handler.level is used 107 sufix_handler (bool=False): If True, handler.name is added to path_log 108 """ 109 sufix_level = "" 110 if sufix_handler: 111 sufix_level = ".{}".format(handler.name.upper()) 112 113 path_log = ".".join(["{}{}".format(path_base_log, sufix_level), 114 "log"]) 115 116 a_file_handler = logging.FileHandler(path_log, mode="w", encoding=encoding, delay=True) 117 118 a_file_handler.setLevel(handler.level if not level_handler else level_handler) 119 for flt in handler.filters: 120 a_file_handler.addFilter(flt) 121 a_frm = handler.formatter 122 if a_frm: 123 a_file_handler.setFormatter(a_frm) 124 125 a_logger.addHandler(a_file_handler) 126 127 if separate_reports and a_logger.level <= logging.INFO: 128 if report_handler := config_file_handlers.get(REPORTS_HANDLER): 129 add_config_file_handler(report_handler, sufix_handler=True) 130 131 add_config_file_handler(config_file_handlers.get(LOG_HANDLER), level) 132 133 root_logger = get_root_logger() 134 root_level = root_logger.level 135 root_logger.setLevel(logging.INFO) 136 root_logger.info(f"Path prefix logs for FILE_LOGGER {nom_base_log}: '{path_base_log}'") 137 root_logger.setLevel(root_level) 138 139 return a_logger 140 141 142def get_handler_for_level(level): 143 """ 144 Devuelve el handler del logger root que se corresponde con el level de logging indicado 145 146 Args: 147 level (int): logging level 148 149 Returns: 150 logging.handler 151 152 """ 153 for hdl in root_handlers(): 154 if hdl.level <= level: 155 return hdl 156 157 158def root_handlers(desc=True): 159 """ 160 Devuelve los handlers definidos en el logger root 161 162 Returns: 163 164 """ 165 rl = get_root_logger() 166 sort_hdlrs = sorted(rl.handlers, key=attrgetter("level"), reverse=desc) 167 168 return sort_hdlrs 169 170 171def logs_dir(create=False): 172 """ 173 Devuelve el directorio donde se guardarán los LOGS a partir de la variable de entorno "PYTHON_LOGS_DIR". 174 Si no está informada devolverá el directorio de logs respecto al entorno de trabajo (misc.get_entorn()) 175 Entorno 'dev': %USERPROFILE%/PYTHON_LOGS/dev 176 'prod': %USERPROFILE%/PYTHON_LOGS/PROD 177 178 Si el usuario no puede acceder a dichos directorios, se devolverá el directorio temporal de usuario 179 %USERPROFILE%/AppData/Local/Temp/PYTHON_LOGS 180 181 Args: 182 create (bool=False): Si TRUE y el directorio NO existe entonces se intentará crear 183 184 Returns: 185 str: Retorna path con el directorio de LOGS 186 """ 187 path_logs_dir = os.getenv(ENV_VAR_LOGS_DIR, "").strip() 188 189 if path_logs_dir and create and not misc.create_dir(path_logs_dir): 190 get_root_logger().warning( 191 "No se ha podido usar el directorio de logs '{}'" 192 " indicado en la variable de entorno {}".format(path_logs_dir, 193 ENV_VAR_LOGS_DIR)) 194 path_logs_dir = None 195 196 if not path_logs_dir or not misc.is_dir_writable(path_logs_dir): 197 dir_base_logs = os.path.normpath(os.getenv("USERPROFILE", Path.home())) 198 199 if not misc.is_path_exists_or_creatable(dir_base_logs): 200 dir_base_logs = tempfile.gettempdir() 201 202 dir_base_logs = os.path.join(dir_base_logs, "PYTHON_LOGS") 203 if misc.get_environ() == "prod": 204 path_logs_dir = os.path.join(dir_base_logs, "PROD") 205 else: 206 path_logs_dir = os.path.join(dir_base_logs, "dev") 207 get_root_logger().warning(f'Usado por defecto el directorio de logs para el USERPROFILE: "{path_logs_dir}"') 208 209 if create: 210 misc.create_dir(path_logs_dir) 211 212 return path_logs_dir 213 214 215def logger_path_logs(a_logger=None, if_exist=True): 216 """ 217 Returns the file paths where a_logger file handlers put his entries 218 219 Args: 220 a_logger (logging.Logger=None): default is root logger 221 if_exist (bool=True): Returns the path if the file exists 222 223 Returns: 224 list 225 """ 226 if a_logger is None: 227 a_logger = get_root_logger() 228 229 path_logs = [] 230 231 for fn in [hdlr.baseFilename 232 for hdlr in a_logger.handlers if hasattr(hdlr, "baseFilename")]: 233 if not if_exist or os.path.exists(fn): 234 path_logs.append(fn) 235 236 return path_logs 237 238 239def filter_maker(level): 240 """ 241 Returns a filter for logging handlers 242 Args: 243 level (str|int): logging level 244 245 Returns: 246 filter 247 """ 248 if isinstance(level, str): 249 level = getattr(logging, level) 250 251 def filter(record): 252 return record.levelno <= level 253 254 return filter
26def get_base_logger(nom_base_log=None, level=None, parent_func=False): 27 """ 28 Creates a logger for the context from where it is called with the logging level. 29 30 Args: 31 nom_base_log (str=None): Base name of the log. If not specified, it is obtained from the context where it is launched 32 level (int=logging.INFO): Logger level (logging.DEBUG, logging.INFO, logging.WARNING, ...) 33 parent_func (bool=False): If parent_func=True is indicated, then it returns the name of the context that calls the function 34 35 Returns: 36 logging.Logger: Logger instance for the function from where it is called 37 """ 38 if not level: 39 level = logging.INFO if get_environ() != 'dev' else logging.DEBUG 40 41 skip_ctxt = 1 42 if parent_func: 43 skip_ctxt += 1 44 45 if not nom_base_log: 46 nom_base_log = misc.caller_name(skip_ctxt) 47 48 root_logger = get_root_logger() 49 a_logger = root_logger.getChild(nom_base_log) 50 51 a_logger.setLevel(level) 52 a_logger.propagate = True 53 54 return a_logger
Creates a logger for the context from where it is called with the logging level.
Arguments:
- nom_base_log (str=None): Base name of the log. If not specified, it is obtained from the context where it is launched
- level (int=logging.INFO): Logger level (logging.DEBUG, logging.INFO, logging.WARNING, ...)
- parent_func (bool=False): If parent_func=True is indicated, then it returns the name of the context that calls the function
Returns:
logging.Logger: Logger instance for the function from where it is called
57def get_file_logger(nom_base_log=None, level=None, dir_log=None, parent_func=False, sufix_date=True, 58 separate_reports=True, encoding='utf-8'): 59 """ 60 Crea logger con FILEHANDLER (filehandlers si separate_reports a True) 61 62 Si nombre del log (nom_base_log) no se especifica, se crea nombre de log con el nombre del contexto desde donde 63 se llama y nombre de máquina si se puede obtener, y siempre la fecha actual 64 65 Args: 66 nom_base_log (str=None): Nombre base del log. Si no se especifica se obtiene del contexto donde se lanza 67 level (int=logging.INFO): Nivel del logger (logging.DEBUG, logging.INFO, logging.WARNING, ...) 68 dir_log (str, optional): Si se especifica, directorio donde guardar log 69 parent_func (bool=False): Si se indica parent_func=True entonces devuelve el nombre del 70 contexto que llama a la funcion 71 sufix_date (bool=True): 72 separate_reports (bool=False): Si se indica separate_reports=True entonces se creará un file 73 handler separado para el log de reports (logging.INFO) 74 encoding (str='utf-8'): Encoding del fichero de log 75 Returns: 76 logging.logger: Instancia de logger para la funcion desde donde se llama 77 """ 78 if not nom_base_log: 79 nom_base_log = misc.caller_name(1 if not parent_func else 2) 80 a_logger = get_base_logger(nom_base_log, level) 81 82 if not a_logger.handlers: 83 if not dir_log: 84 dir_log = logs_dir(True) 85 else: 86 misc.create_dir(dir_log) 87 88 sub_parts_nom = [] 89 if misc.machine_apb(): 90 sub_parts_nom.append(misc.machine_name()) 91 sub_parts_nom.append(nom_base_log) 92 if sufix_date: 93 sub_parts_nom.append(datetime.datetime.today().strftime('%Y%m%d_%H%M%S')) 94 95 path_base_log = os.path.normpath(os.path.join(dir_log, "-".join(sub_parts_nom))) 96 97 config_file_handlers = { 98 handler.name: handler for handler in root_handlers() 99 if handler.name != CONSOLE_HANDLER 100 } 101 102 def add_config_file_handler(handler, level_handler=None, sufix_handler=False): 103 """ 104 Add handler to logger 105 Args: 106 handler (logging.Handler): 107 level_handler (int, optional): If not specified, handler.level is used 108 sufix_handler (bool=False): If True, handler.name is added to path_log 109 """ 110 sufix_level = "" 111 if sufix_handler: 112 sufix_level = ".{}".format(handler.name.upper()) 113 114 path_log = ".".join(["{}{}".format(path_base_log, sufix_level), 115 "log"]) 116 117 a_file_handler = logging.FileHandler(path_log, mode="w", encoding=encoding, delay=True) 118 119 a_file_handler.setLevel(handler.level if not level_handler else level_handler) 120 for flt in handler.filters: 121 a_file_handler.addFilter(flt) 122 a_frm = handler.formatter 123 if a_frm: 124 a_file_handler.setFormatter(a_frm) 125 126 a_logger.addHandler(a_file_handler) 127 128 if separate_reports and a_logger.level <= logging.INFO: 129 if report_handler := config_file_handlers.get(REPORTS_HANDLER): 130 add_config_file_handler(report_handler, sufix_handler=True) 131 132 add_config_file_handler(config_file_handlers.get(LOG_HANDLER), level) 133 134 root_logger = get_root_logger() 135 root_level = root_logger.level 136 root_logger.setLevel(logging.INFO) 137 root_logger.info(f"Path prefix logs for FILE_LOGGER {nom_base_log}: '{path_base_log}'") 138 root_logger.setLevel(root_level) 139 140 return a_logger
Crea logger con FILEHANDLER (filehandlers si separate_reports a True)
Si nombre del log (nom_base_log) no se especifica, se crea nombre de log con el nombre del contexto desde donde se llama y nombre de máquina si se puede obtener, y siempre la fecha actual
Arguments:
- nom_base_log (str=None): Nombre base del log. Si no se especifica se obtiene del contexto donde se lanza
- level (int=logging.INFO): Nivel del logger (logging.DEBUG, logging.INFO, logging.WARNING, ...)
- dir_log (str, optional): Si se especifica, directorio donde guardar log
- parent_func (bool=False): Si se indica parent_func=True entonces devuelve el nombre del contexto que llama a la funcion
- sufix_date (bool=True):
- separate_reports (bool=False): Si se indica separate_reports=True entonces se creará un file handler separado para el log de reports (logging.INFO)
- encoding (str='utf-8'): Encoding del fichero de log
Returns:
logging.logger: Instancia de logger para la funcion desde donde se llama
143def get_handler_for_level(level): 144 """ 145 Devuelve el handler del logger root que se corresponde con el level de logging indicado 146 147 Args: 148 level (int): logging level 149 150 Returns: 151 logging.handler 152 153 """ 154 for hdl in root_handlers(): 155 if hdl.level <= level: 156 return hdl
Devuelve el handler del logger root que se corresponde con el level de logging indicado
Arguments:
- level (int): logging level
Returns:
logging.handler
159def root_handlers(desc=True): 160 """ 161 Devuelve los handlers definidos en el logger root 162 163 Returns: 164 165 """ 166 rl = get_root_logger() 167 sort_hdlrs = sorted(rl.handlers, key=attrgetter("level"), reverse=desc) 168 169 return sort_hdlrs
Devuelve los handlers definidos en el logger root
Returns:
172def logs_dir(create=False): 173 """ 174 Devuelve el directorio donde se guardarán los LOGS a partir de la variable de entorno "PYTHON_LOGS_DIR". 175 Si no está informada devolverá el directorio de logs respecto al entorno de trabajo (misc.get_entorn()) 176 Entorno 'dev': %USERPROFILE%/PYTHON_LOGS/dev 177 'prod': %USERPROFILE%/PYTHON_LOGS/PROD 178 179 Si el usuario no puede acceder a dichos directorios, se devolverá el directorio temporal de usuario 180 %USERPROFILE%/AppData/Local/Temp/PYTHON_LOGS 181 182 Args: 183 create (bool=False): Si TRUE y el directorio NO existe entonces se intentará crear 184 185 Returns: 186 str: Retorna path con el directorio de LOGS 187 """ 188 path_logs_dir = os.getenv(ENV_VAR_LOGS_DIR, "").strip() 189 190 if path_logs_dir and create and not misc.create_dir(path_logs_dir): 191 get_root_logger().warning( 192 "No se ha podido usar el directorio de logs '{}'" 193 " indicado en la variable de entorno {}".format(path_logs_dir, 194 ENV_VAR_LOGS_DIR)) 195 path_logs_dir = None 196 197 if not path_logs_dir or not misc.is_dir_writable(path_logs_dir): 198 dir_base_logs = os.path.normpath(os.getenv("USERPROFILE", Path.home())) 199 200 if not misc.is_path_exists_or_creatable(dir_base_logs): 201 dir_base_logs = tempfile.gettempdir() 202 203 dir_base_logs = os.path.join(dir_base_logs, "PYTHON_LOGS") 204 if misc.get_environ() == "prod": 205 path_logs_dir = os.path.join(dir_base_logs, "PROD") 206 else: 207 path_logs_dir = os.path.join(dir_base_logs, "dev") 208 get_root_logger().warning(f'Usado por defecto el directorio de logs para el USERPROFILE: "{path_logs_dir}"') 209 210 if create: 211 misc.create_dir(path_logs_dir) 212 213 return path_logs_dir
Devuelve el directorio donde se guardarán los LOGS a partir de la variable de entorno "PYTHON_LOGS_DIR". Si no está informada devolverá el directorio de logs respecto al entorno de trabajo (misc.get_entorn()) Entorno 'dev': %USERPROFILE%/PYTHON_LOGS/dev 'prod': %USERPROFILE%/PYTHON_LOGS/PROD
Si el usuario no puede acceder a dichos directorios, se devolverá el directorio temporal de usuario %USERPROFILE%/AppData/Local/Temp/PYTHON_LOGS
Arguments:
- create (bool=False): Si TRUE y el directorio NO existe entonces se intentará crear
Returns:
str: Retorna path con el directorio de LOGS
216def logger_path_logs(a_logger=None, if_exist=True): 217 """ 218 Returns the file paths where a_logger file handlers put his entries 219 220 Args: 221 a_logger (logging.Logger=None): default is root logger 222 if_exist (bool=True): Returns the path if the file exists 223 224 Returns: 225 list 226 """ 227 if a_logger is None: 228 a_logger = get_root_logger() 229 230 path_logs = [] 231 232 for fn in [hdlr.baseFilename 233 for hdlr in a_logger.handlers if hasattr(hdlr, "baseFilename")]: 234 if not if_exist or os.path.exists(fn): 235 path_logs.append(fn) 236 237 return path_logs
Returns the file paths where a_logger file handlers put his entries
Arguments:
- a_logger (logging.Logger=None): default is root logger
- if_exist (bool=True): Returns the path if the file exists
Returns:
list
240def filter_maker(level): 241 """ 242 Returns a filter for logging handlers 243 Args: 244 level (str|int): logging level 245 246 Returns: 247 filter 248 """ 249 if isinstance(level, str): 250 level = getattr(logging, level) 251 252 def filter(record): 253 return record.levelno <= level 254 255 return filter
Returns a filter for logging handlers
Arguments:
- level (str|int): logging level
Returns:
filter