2009-06-10 18 views
11

Así que ahora mismo tenemos un montón de scripts de Python y estamos tratando de consolidarlos y arreglarlos y redundancias. Una de las cosas que intentamos hacer es garantizar que todo sys.stdout/sys.stderr vaya al módulo de registro de Python.redirigiendo sys.stdout al registro de Python

Ahora lo más importante es, queremos que el siguiente impreso:

[<ERROR LEVEL>] | <TIME> | <WHERE> | <MSG> 

Ahora todos sys.stdout/Mensajes sys.stderr más o menos en todos los mensajes de error pitón están en el formato de [NIVEL ] - MSG, que están escritos usando sys.stdout/sys.stderr. Puedo analizar la multa, en mi contenedor sys.stdout y en el contenedor sys.stderr. Luego llame al nivel de registro correspondiente, dependiendo de la entrada analizada.

Así que, básicamente, tenemos un paquete llamado foo, y un subpaquete llamado log. En __init__.py definimos el siguiente:

def initLogging(default_level = logging.INFO, stdout_wrapper = None, \ 
       stderr_wrapper = None): 
    """ 
     Initialize the default logging sub system 
    """ 
    root_logger = logging.getLogger('') 
    strm_out = logging.StreamHandler(sys.__stdout__) 
    strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_TIME_FORMAT, \ 
              DEFAULT_LOG_TIME_FORMAT)) 
    root_logger.setLevel(default_level) 
    root_logger.addHandler(strm_out) 

    console_logger = logging.getLogger(LOGGER_CONSOLE) 
    strm_out = logging.StreamHandler(sys.__stdout__) 
    #strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_MSG_FORMAT, \ 
    #          DEFAULT_LOG_TIME_FORMAT)) 
    console_logger.setLevel(logging.INFO) 
    console_logger.addHandler(strm_out) 

    if stdout_wrapper: 
     sys.stdout = stdout_wrapper 
    if stderr_wrapper: 
     sys.stderr = stderr_wrapper 


def cleanMsg(msg, is_stderr = False): 
    logy = logging.getLogger('MSG') 
    msg = msg.rstrip('\n').lstrip('\n') 
    p_level = r'^(\s+)?\[(?P<LEVEL>\w+)\](\s+)?(?P<MSG>.*)$' 
    m = re.match(p_level, msg) 
    if m: 
     msg = m.group('MSG') 
     if m.group('LEVEL') in ('WARNING'): 
      logy.warning(msg) 
      return 
     elif m.group('LEVEL') in ('ERROR'): 
      logy.error(msg) 
      return 
    if is_stderr: 
     logy.error(msg) 
    else: 
     logy.info(msg) 

class StdOutWrapper: 
    """ 
     Call wrapper for stdout 
    """ 
    def write(self, s): 
     cleanMsg(s, False) 

class StdErrWrapper: 
    """ 
     Call wrapper for stderr 
    """ 
    def write(self, s): 
     cleanMsg(s, True) 

Ahora nos llamarían a esto en una de las secuencias de comandos, por ejemplo:

import foo.log 
foo.log.initLogging(20, foo.log.StdOutWrapper(), foo.log.StdErrWrapper()) 
sys.stdout.write('[ERROR] Foobar blew') 

cual se convertiría en un mensaje de registro de errores. Al igual que:

[ERROR] | 20090610 083215 | __init__.py | Foobar Blew 

Ahora el problema es cuando lo hacemos, el módulo en el que se registra el mensaje de error es ahora el __init__ (correspondiente a foo.log.__init__.py archivo), que en contra del propósito.

Intenté hacer una copia profunda/mínima de los objetos stderr/stdout, pero eso no hace nada, todavía dice que el mensaje del módulo ocurrió en __init__.py. ¿Cómo puedo hacerlo para que esto no suceda?

Respuesta

6

El problema es que el módulo de registro está buscando una sola capa en la pila de llamadas para encontrar quién lo llamó, pero ahora su función es una capa intermedia en ese punto (aunque esperaba que informara cleanMsg, no __init__, ya que es allí donde llamas al registro()). En cambio, necesita que suba dos niveles, o bien pase quién es la persona que llama en el mensaje registrado. Puede hacerlo inspeccionando el marco de pila usted mismo y agarrando la función de llamada, insertándola en el mensaje.

Para localizar a su marco de llamada, puede utilizar el módulo de inspección:

import inspect 
f = inspect.currentframe(N) 

se verá hasta N tramas, y devolver el puntero de marco. es decir, la persona que llama de inmediato es el cuadro actual (1), pero es posible que tenga que subir otro cuadro si este es el método stdout.write. Una vez que tenga el marco de llamada, puede obtener el objeto de código de ejecución y observar el nombre del archivo y la función asociados.por ejemplo:

code = f.f_code 
caller = '%s:%s' % (code.co_filename, code.co_name) 

También puede tener que poner un poco de código para manejar código de llamada en usted (es decir, funciones o comandos internos C.) no pitón, ya que éstos pueden carecer de objetos f_code.

Como alternativa, siguiendo mikej's answer, podría usar el mismo enfoque en una clase de registrador personalizada que hereda del registro. Logger que reemplaza a findCaller para navegar varios fotogramas hacia arriba, en lugar de uno.

2

Creo que el problema es que sus mensajes de registro reales están siendo creados por los logy.error y logy.info llamadas en cleanMsg, de ahí que el método es la fuente de los mensajes de registro y que están viendo esto como __init__.py

Si Busque en la fuente de Python lib/logging/__init__.py y verá un método definido llamado findCaller que es lo que el módulo de registro usa para derivar a la persona que llama de una solicitud de registro.
¿Quizás puede anular esto en su objeto de registro para personalizar el comportamiento?

Cuestiones relacionadas