2009-02-07 5 views
28

Hoy estaba pensando en un proyecto de Python que escribí hace un año donde usé logging bastante extensamente. Recuerdo haber tenido que comentar un montón de llamadas de registro en escenarios de bucle interno (el código del 90%) debido a la sobrecarga (hotshot indicó que era uno de mis principales cuellos de botella).¿Cómo puedo quitar las llamadas de registro de Python sin comentarlas?

Me pregunto ahora si hay alguna forma canónica de eliminar programáticamente las llamadas de registro en las aplicaciones de Python sin comentar ni descomentar todo el tiempo. Creo que podría usar la inspección/recompilación o la manipulación de código byte para hacer algo como esto y solo apuntar a los objetos de código que están causando los cuellos de botella. De esta manera, se podría añadir un manipulador como un paso posterior a la recopilación y el uso de un archivo de configuración centralizada, así:

[Leave ERROR and above] 
my_module.SomeClass.method_with_lots_of_warn_calls 

[Leave WARN and above] 
my_module.SomeOtherClass.method_with_lots_of_info_calls 

[Leave INFO and above] 
my_module.SomeWeirdClass.method_with_lots_of_debug_calls 

Por supuesto, te gustaría usarlo con moderación y, probablemente, con granularidad por función - solo para objetos de código que han mostrado logging como un cuello de botella. Alguien sabe de algo como esto?

Nota: Hay algunas cosas que hacen que esto sea más difícil de realizar de manera efectiva debido a la escritura dinámica y el enlace tardío. Por ejemplo, cualquier llamada a un método llamado debug puede tener que ser envuelto con un if not isinstance(log, Logger). En cualquier caso, supongo que todos los detalles menores se pueden superar, ya sea por acuerdo de caballeros o por alguna verificación en tiempo de ejecución. :-)

+0

¿utiliza el mismo registrador de nivel de raíz? es decir, logging.getLogger()? Si es así, debes arreglarlo primero. De lo contrario, proporcione algunas de las llamadas de getLogger en esos módulos. –

+0

@ S.Lott: utilizo LOG = logging.getLogger (__ name__) por módulo, luego llamo LOG.debug (msg) y similares. Aunque realmente no veo cómo es relevante. – cdleary

Respuesta

20

Qué acerca del uso logging.disable?

También he encontrado que tuve que usar logging.isEnabledFor si el mensaje de registro es costoso crear.

+0

logging.isEnabledFor podría funcionar, ya que creo que fueron las llamadas a los métodos de Logger las que miren la hora. Me gusta esto como una solución potencial, a pesar de que es molesto hacer una creación condicional. Voy a tratar de desenterrar el código base anterior y ver si esto funciona. – cdleary

+1

Al final, así es exactamente como se implementa 'Logger.debug': http://svn.python.org/view/python/tags/r26/Lib/logging/__init__.py?view=markup – cdleary

+1

En realidad, mirando el código 'logging' aún más, esta es probablemente la optimización que necesito. 'getEffectiveLevel' atraviesa la jerarquía del registrador, mientras que' disable' eliminará ese cruce. ¡Gracias! – cdleary

2

Como un atajo imperfecto, ¿qué tal burlarse de logging en módulos específicos usando algo como MiniMock?

Por ejemplo, si my_module.py fue:

import logging 
class C(object): 
    def __init__(self, *args, **kw): 
     logging.info("Instantiating") 

debería reemplazar el uso de my_module con:

from minimock import Mock 
import my_module 
my_module.logging = Mock('logging') 
c = my_module.C() 

Sólo se tendría que hacer esto una vez, antes de la importación inicial de la módulo.

Obtener el comportamiento específico de nivel sería lo suficientemente simple burlando métodos específicos, o tener logging.getLogger devolviendo un objeto falso con algunos métodos impotentes y otros delegando en el módulo real logging.

En la práctica, es probable que desee reemplazar MiniMock con algo más simple y más rápido; al menos algo que no imprime el uso de stdout! Por supuesto, esto no maneja el problema del módulo A importando logging del módulo B (y por lo tanto A también importa la granularidad del registro de B) ...

Esto nunca será tan rápido como no ejecutar las instrucciones de registro en todo, pero debería ser mucho más rápido que recorrer todo el camino hasta las profundidades del módulo de registro solo para descubrir que este registro no debería registrarse después de todo.

+0

Esto realmente suena mucho más sano que la manipulación de códigos de bytes, ya que las técnicas de manipulación de códigos de bytes no necesariamente se trasladarán a través de las máquinas virtuales. El único problema que veo es que solo se puede averiguar * qué método lo llamó mediante la inspección de rastreo de pila, que también sería lento, si no me equivoco. – cdleary

+0

Ah, está bien ... así que estaba pensando que los objetos simulados serían muy tontos, sin preocuparse (o saber) de dónde fueron llamados. Solo tendrías una granularidad de configuración por módulo, no por función. Estoy temo que elegir a cuál de sus módulos inyectará objetos simulados es su única flexibilidad. –

1

usted podría intentar algo como esto:

# Create something that accepts anything 
class Fake(object): 
    def __getattr__(self, key): 
     return self 
    def __call__(self, *args, **kwargs): 
     return True 

# Replace the logging module 
import sys 
sys.modules["logging"] = Fake() 

En esencia, sustituye a (o se llena inicialmente en) el espacio para el módulo de registro con una instancia de Fake que simplemente se toma en nada. Debe ejecutar el código anterior (¡solo una vez!) Antes de intentar utilizar el módulo de registro en cualquier lugar.Aquí está una prueba:

import logging 

logging.basicConfig(level=logging.DEBUG, 
        format='%(asctime)s %(levelname)-8s %(message)s', 
        datefmt='%a, %d %b %Y %H:%M:%S', 
        filename='/temp/myapp.log', 
        filemode='w') 
logging.debug('A debug message') 
logging.info('Some information') 
logging.warning('A shot across the bows') 

Con lo anterior, nada en absoluto se registra, como era de esperar.

+0

¿No tiene forma de activar o desactivar el registro selectivo por módulo o por nivel? –

+0

Esto ni siquiera acelera las cosas ... – radtek

1

que haría uso de algún decorador de lujo de registro, o un grupo de ellos:

def doLogging(logTreshold): 
    def logFunction(aFunc): 
     def innerFunc(*args, **kwargs): 
      if LOGLEVEL >= logTreshold: 
       print ">>Called %s at %s"%(aFunc.__name__, time.strftime("%H:%M:%S")) 
       print ">>Parameters: ", args, kwargs if kwargs else "" 
      try: 
       return aFunc(*args, **kwargs) 
      finally: 
       print ">>%s took %s"%(aFunc.__name__, time.strftime("%H:%M:%S")) 
     return innerFunc 
    return logFunction 

Todo lo que necesita es declarar constante LOGLEVEL en cada módulo (o simplemente a nivel mundial y simplemente importar en todos los módulos) y entonces se puede utilizar de esta manera:

@doLogging(2.5) 
def myPreciousFunction(one, two, three=4): 
    print "I'm doing some fancy computations :-)" 
    return 

Y si LOGLEVEL no es menor que 2,5 podrás obtener una salida como esta:

>>Called myPreciousFunction at 18:49:13 
>>Parameters: (1, 2) 
I'm doing some fancy computations :-) 
>>myPreciousFunction took 18:49:13 

Como puede ver, se necesita algo de trabajo para un mejor manejo de kwargs, por lo que los valores predeterminados se imprimirán si están presentes, pero esa es otra cuestión.

Probablemente debería utilizar algún logger módulo en lugar de primas print declaraciones, pero quería centrarse en la idea decorador y evitar hacer el código demasiado tiempo.

De todos modos, con este decorador obtienes el registro de función, arbitrariamente muchos niveles de registro, facilidad de aplicación a la nueva función y para deshabilitar el registro, solo necesitas configurar LOGLEVEL. Y puede definir diferentes flujos de salida/archivos para cada función si lo desea. Puede escribir doLogging como:

def doLogging(logThreshold, outStream=sys.stdout): 
     ..... 
     print >>outStream, ">>Called %s at %s" etc. 

Y utilizar archivos de registro definidos por función.

+0

Logo cosas desde * dentro * del método utilizando el módulo 'logging' - su enfoque solo puede registrar cosas que son visibles externamente (args, kwargs, valor de retorno y tiempo de ejecución) – cdleary

+0

Ha escrito sobre el uso del inicio de sesión en la granularidad por función, y su ejemplo de pseudo-código también sugiere algo como esto. Mi enfoque puede ampliarse: puede agregar un parámetro de palabra clave adicional a su función. Y ese parámetro adicional será un logger u objeto ficticio, proporcionado por el decorador :-) – Abgan

+0

No es una solución ideal, estoy de acuerdo, pero - los decoradores son altamente extensibles ¿no? – Abgan

1

Este es un problema en mi proyecto, así - el registro termina en los informes de perfil bastante consistente.

He utilizado el módulo de _ast antes en un tenedor de pyflakes (http://github.com/kevinw/pyflakes) ... y es definitivamente posible hacer lo que usted sugiere en su pregunta - para inspeccionar e inyectar guardias antes de las llamadas a métodos de registro (con tu advertencia de que tendrías que hacer alguna comprobación del tipo de tiempo de ejecución). Ver http://pyside.blogspot.com/2008/03/ast-compilation-from-python.html para un ejemplo simple.

Editar: acabo de notar MetaPython en mi alimentación planetpython.org - el caso del ejemplo el uso es la eliminación de declaraciones de registro en tiempo de importación.

Tal vez la mejor solución sería que alguien volver a implementar el registro como un módulo C, pero no sería el primero en saltar a una oportunidad ...: p

5

También he visto afirman usado de esta manera.

assert logging.warn('disable me with the -O option') is None 

(Supongo que advertir siempre devuelve ninguno ..si no, usted obtendrá una AssertionError

Pero en realidad eso es sólo una manera divertida de hacer esto:

if __debug__: logging.warn('disable me with the -O option') 

Cuando se ejecuta un script con esa línea en ella con la opción -O, la línea se eliminarse del código .pyo optimizado. Si, en cambio, tuvieras tu propia variable, como en el siguiente, tendrás un condicional que siempre se ejecutará (sin importar el valor de la variable), aunque un condicional debería ejecutarse más rápido que una llamada de función:

my_debug = True 
... 
if my_debug: logging.warn('disable me by setting my_debug = False') 

si mi comprensión de depura es correcta, parece una buena manera de deshacerse de las llamadas de registro innecesarias. La otra cara de la moneda es que también deshabilita todas sus afirmaciones, por lo que es un problema si necesita las aseveraciones.

+0

Sí, poner una afirmación frente a cada llamada es demasiado feo. Poner un 'if my_debug' al frente de todo es igualmente indeseable y poco elegante. Esperaba algo para quitarlos/modificarlos automáticamente. :-(Además, siempre use 'is None' para una comparación explícita con None. :-) – cdleary

0

Me gusta la solución 'if __debug_', excepto que ponerla en frente de cada llamada es un poco molesto y desagradable. Tuve el mismo problema y lo superé al escribir una secuencia de comandos que analiza automáticamente los archivos de origen y reemplaza las declaraciones de registro con las declaraciones de paso (y las declaraciones comentadas de las declaraciones de registro). También puede deshacer esta conversión.

Lo uso cuando despliegue código nuevo en un entorno de producción cuando hay muchas declaraciones de registro que no necesito en una configuración de producción y que están afectando el rendimiento.

Puede encontrar el script aquí: http://dound.com/2010/02/python-logging-performance/

1

:-) Se utilizó para llamar a que un preprocesador y aunque preprocesador de C tenía algunos de esos capablities, el "rey de la colina" fue el preprocesador para mainframe de IBM PL /YO. Brindaba un amplio soporte de idiomas en el preprocesador (asignaciones completas, condicionales, bucles, etc.) y era posible escribir "programas que escribían programas" utilizando solo PL/I PP.

Escribí muchas aplicaciones con un sofisticado programa sofisticado y rastreo de datos (no teníamos un depurador decente para un proceso de back-end en ese momento) para su uso en desarrollo y prueba que luego, cuando se compilaba con el "indicador de tiempo de ejecución" simplemente eliminó todo el código de seguimiento sin ningún impacto en el rendimiento.

Creo que la idea del decorador es buena. Puede escribir un decorador para envolver las funciones que necesitan registro. Luego, para la distribución en tiempo de ejecución, el decorador se convierte en un "no-op" que elimina las declaraciones de depuración.

Jon R

5

Uso pypreprocessor

que también se pueden encontrar en PYPI (Python Package Index) y se encontraron usando PIP.

Aquí está un ejemplo de uso básico:

from pypreprocessor import pypreprocessor 

pypreprocessor.parse() 

#define nologging 

#ifdef nologging 
...logging code you'd usually comment out manually... 
#endif 

En esencia, el preprocesador comentarios fuera código de la forma en que estaba haciendo de forma manual antes. Simplemente lo hace sobre la marcha condicionalmente, dependiendo de lo que defina.

También puede eliminar todas las directivas del preprocesador y el código comentado del código posprocesado al agregar 'pypreprocessor.removeMeta = True' entre la importación y las declaraciones parse().

La salida de bytecode (.pyc) archivo contendrá la salida optimizada.

SideNote: pypreprocessor es compatible con python2x y python3k.

Descargo de responsabilidad: soy el autor de pypreprocessor.

+0

En general, no soy un gran admirador de las directivas de preprocesador. He estado al borde del vacío en ese enfoque, cuando ningún Unix hacía las cosas del mismo modo que cualquier otro Unix, y no era bonito. OTOH, es fantástico que la característica se pueda agregar como un módulo externo. Y en algunos casos ... puede ser justo lo que necesita. –

+0

@Jonathan Tampoco lo soy, pero hay algunos casos de nicho en los que puede reducir drásticamente el mantenimiento. Originalmente lo creé para poder ejecutar los códigos py2x y py3x en los mismos archivos (desarrollo paralelo para múltiples versiones) pero el lexer invalida el código antes de que el preprocesador pueda incluso ejecutarse. Para casos como estos debería funcionar sin problemas. Básicamente se abre solo, lee el código, comenta las ramas innecesarias del código y ejecuta el resultado modificado. Es bastante hacky pero funciona sorprendentemente bien, especialmente porque también puedes guardar el código posprocesado como un archivo. –

+0

Envolver mi código en el bloque '#define nologging' me da' NameError: nombre global 'logging' no está definido' – radtek

1

Actualmente estoy haciendo un proyecto que usa un registro extensivo para probar la lógica y los tiempos de ejecución de una API de análisis de datos usando la biblioteca Pandas.

Encontré esta cadena con un problema similar, p. ¿Cuál es la sobrecarga en las instrucciones logging.debug incluso si el nivel logging.basicConfig está establecido en level = logging.WARNING

He recurrido a escribir el siguiente script para comentar o descomentar el registro de depuración antes de la implementación:

import os 
import fileinput 

comment = True 

# exclude files or directories matching string 
fil_dir_exclude = ["__","_archive",".pyc"] 

if comment : 
    ## Variables to comment 
    source_str = 'logging.debug' 
    replace_str = '#logging.debug' 
else : 
    ## Variables to uncomment 
    source_str = '#logging.debug' 
    replace_str = 'logging.debug' 

# walk through directories 
for root, dirs, files in os.walk('root/directory') : 
    # where files exist 
    if files: 
     # for each file 
     for file_single in files : 
      # build full file name 
      file_name = os.path.join(root,file_single) 
      # exclude files with matching string 
      if not any(exclude_str in file_name for exclude_str in fil_dir_exclude) : 
       # replace string in line 
       for line in fileinput.input(file_name, inplace=True): 
        print "%s" % (line.replace(source_str, replace_str)), 

Este es un archivo de recursividad que excluye los archivos basándose en una lista de criterios y realiza una en su lugar replace basan en una respuesta encontrar aquí: Search and replace a line in a file in Python

Cuestiones relacionadas