Python: registro personalizado en todos los módulos
logging centralized (1)
Tarea
Tengo una colección de scripts y me gustaría que produjeran mensajes de registro unificados con modificaciones mínimas en los módulos que registran los mensajes reales.
He escrito un pequeño módulo ''custom_logger'' al que planeo llamar desde la aplicación principal una vez, que devuelva un registrador, que luego seguiré usando.
Los submódulos que estaría importando en la aplicación solo deberían (o más bien deseo que lo hagan)
- solo debe "importar el registro como registro", de modo que no se requiera nada específico de mi sitio para que se ejecuten si alguien más lo encuentra útil.
- solo debe registrar los mensajes con log.info/error(''message '') sin agregarles nada específico del sitio
- debe usar el registrador ''root'' ya configurado con todo su formato y manejadores, y no afectar la configuración del registrador raíz
* custom_logger.py *
import logging
import logging.handlers
import os
import sys
def getLogger(name=''root'', loglevel=''INFO''):
logger = logging.getLogger(name)
# if logger ''name'' already exists, return it to avoid logging duplicate
# messages by attaching multiple handlers of the same type
if logger.handlers:
return logger
# if logger ''name'' does not already exist, create it and attach handlers
else:
# set logLevel to loglevel or to INFO if requested level is incorrect
loglevel = getattr(logging, loglevel.upper(), logging.INFO)
logger.setLevel(loglevel)
fmt = ''%(asctime)s %(filename)-18s %(levelname)-8s: %(message)s''
fmt_date = ''%Y-%m-%dT%T%Z''
formatter = logging.Formatter(fmt, fmt_date)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
if logger.name == ''root'':
logger.warning(''Running: %s %s'',
os.path.basename(sys.argv[0]),
'' ''.join(sys.argv[1:]))
return logger
Luego viene el submódulo que contiene algunos mensajes de prueba con ejemplos de lo que funciona y lo que no.
submódulo.py
import sys
import custom_logger
import logging
class SubClass(object):
def __init__(self):
# NOK (no idea why since by default (no name parameter), it should return the root logger)
#log = logging.getLogger()
#log.info(''message from SubClass / __init__'')
# OK (works as expected)
#log = logging.getLogger(''root'')
#log.info(''message from SubClass / __init__'')
# OK (works as expected)
log = custom_logger.getLogger(''root'')
log.info(''message from SubClass / __init__'')
def SomeMethod(self):
# OK but I''d have to define `log` for every method, which is unacceptable
# Please see question below all code snippets
log = custom_logger.getLogger(''root'')
log.info(''message from SubClass / SomeMethod'')
Y la aplicación principal: app.py Nada especial aquí:
#!/usr/bin/python
import custom_logger
import submodule
log = custom_logger.getLogger(''root'', loglevel=''DEBUG'')
log.debug(''debug message'')
log.info(''info message'')
log.warning(''warning message'')
log.error(''error message'')
a = submodule.SubClass() # this should produce a log message
a.SomeMethod() # so should this
Salida que estoy buscando y que estoy obteniendo, solo de una manera extremadamente fea:
% ./app.py
2013-04-08T03:07:46BST custom_logger.py WARNING : Running: app.py
2013-04-08T03:07:46BST app.py DEBUG : debug message
2013-04-08T03:07:46BST app.py INFO : info message
2013-04-08T03:07:46BST app.py WARNING : warning message
2013-04-08T03:07:46BST app.py ERROR : error message
2013-04-08T03:07:46BST submodule.py INFO : message from SubClass / __init__
2013-04-08T03:07:46BST submodule.py INFO : message from SubClass / SomeMethod
Quiero poder definir un registrador en app.py y luego, en los submódulos, solo uso la biblioteca de registro estándar de Python para hacer uso de un registrador ya configurado en app.py.
También, una solución fea: si coloco el siguiente código después de las importaciones en submodule.py:
log = custom_logger.getLogger(''root'')
se ejecutará antes de que mi registrador se configure en app.py, haciendo el submódulo de manera efectiva, no mi registro de configuración de la aplicación.
Otra solución que consideré: dentro del constuctor de la clase SubClass, podría definir
self.log = custom_logger.getLogger(''root'')
y luego use self.log.error (''algún error''). Debe haber una forma más agradable: si puede sugerir algo útil o señalar dónde entendí mal la documentación, ¡le estaría muy agradecido!
PD. Me he pasado un buen rato leyendo el howto de registro de Python (básico y avanzado) y el libro de cocina, así que si me he perdido algo útil, indíquelo.
¡Gracias!
Si desea cambiar el registrador raíz, puede usar getLogger()
todas partes, sin argumentos.
Con respecto a la configuración de la instancia solo en el módulo principal, puede crear una instancia de su registrador, agregar su propio controlador y usarlo en todos los otros submódulos (como lo hice a continuación).
Creé una clase que hereda el StreamHandler en custom_logger.py
:
class MyHandler(logging.StreamHandler):
def __init__(self):
logging.StreamHandler.__init__(self)
fmt = ''%(asctime)s %(filename)-18s %(levelname)-8s: %(message)s''
fmt_date = ''%Y-%m-%dT%T%Z''
formatter = logging.Formatter(fmt, fmt_date)
self.setFormatter(formatter)
Luego, en submodule.py
, puse el getLogger después de las importaciones y lo comenté en los métodos:
import sys
import logging
log = logging.getLogger(''root'')
class SubClass(object):
def __init__(self):
log.info(''message from SubClass / __init__'')
def SomeMethod(self):
log.info(''message from SubClass / SomeMethod'')
Luego, en app.py creé una instancia de Logger (que será la misma en todos los módulos) y agregué mi controlador, que formatea la salida:
#!/usr/bin/python
import logging
import custom_logger
import submodule
log = logging.getLogger(''root'')
log.setLevel(''DEBUG'')
log.addHandler(custom_logger.MyHandler())
log.debug(''debug message'')
log.info(''info message'')
log.warning(''warning message'')
log.error(''error message'')
a = submodule.SubClass() # this should produce a log message
a.SomeMethod() # so should this
Salida:
./app.py
2013-04-08T15:20:05EEST app.py DEBUG : debug message
2013-04-08T15:20:05EEST app.py INFO : info message
2013-04-08T15:20:05EEST app.py WARNING : warning message
2013-04-08T15:20:05EEST app.py ERROR : error message
2013-04-08T15:20:05EEST submodule.py INFO : message from SubClass / __init__
2013-04-08T15:20:05EEST submodule.py INFO : message from SubClass / SomeMethod