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
LOG_HANDLER = 'LOG'
REPORTS_HANDLER = 'REPORTS'
CONSOLE_HANDLER = 'console'
ENV_VAR_LOGS_DIR = 'PYTHON_LOGS_DIR'
def get_base_logger(nom_base_log=None, level=None, parent_func=False):
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

def get_file_logger( nom_base_log=None, level=None, dir_log=None, parent_func=False, sufix_date=True, separate_reports=True, encoding='utf-8'):
 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

def get_handler_for_level(level):
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

def root_handlers(desc=True):
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:

def logs_dir(create=False):
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

def logger_path_logs(a_logger=None, if_exist=True):
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

def filter_maker(level):
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