Configuración elegante de registro de Python en Django

101

Todavía tengo que encontrar una forma de configurar el registro de Python con Django con la que esté contento. Mis requisitos son bastante simples:

  • Diferentes controladores de registro para diferentes eventos, es decir, quiero poder iniciar sesión en diferentes archivos
  • Fácil acceso a los registradores en mis módulos. El módulo debería poder encontrar su registrador con poco esfuerzo.
  • Debería ser fácilmente aplicable a módulos de línea de comandos. Partes del sistema son procesos de demonio o de línea de comandos independientes. El registro debería poder utilizarse fácilmente con estos módulos.

Mi configuración actual es usar un logging.confarchivo y configurar el registro en cada módulo desde el que inicio sesión. No se siente bien.

¿Tiene una configuración de registro que le guste? Detalla: cómo configuras la configuración (la usas logging.confo configuras en el código), dónde / cuándo inicias los registradores, y cómo accedes a ellos en tus módulos, etc.

Parand
fuente
1
Puede que le resulte útil el siguiente screencast: ericholscher.com/blog/2008/aug/29/… . Además, Simon Willison ha propuesto un mejor soporte para iniciar sesión en Django (ver simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger
@Dominic Rodger - Ya puedes hacer un registro flexible de aplicaciones en Django, la propuesta de Simon principalmente para facilitar el registro en el interior de Django. Hay trabajo en marcha en Python para agregar una configuración basada en diccionario al registro de Python, de lo que Django puede beneficiarse.
Vinay Sajip

Respuestas:

57

La mejor forma que he encontrado hasta ahora es inicializar la configuración de registro en settings.py, en ningún otro lugar. Puede utilizar un archivo de configuración o hacerlo mediante programación paso a paso; solo depende de sus requisitos. La clave es que generalmente agrego los controladores que quiero al registrador raíz, usando niveles y, a veces, registrando.Filtros para obtener los eventos que quiero en los archivos apropiados, consola, syslogs, etc.Por supuesto, puede agregar controladores a cualquier otro registrador también, pero, en mi experiencia, no suele ser necesario hacerlo.

En cada módulo, defino un registrador usando

logger = logging.getLogger(__name__)

y utilícelo para registrar eventos en el módulo (y, si quiero diferenciarlo más) use un registrador que sea un hijo del registrador creado anteriormente.

Si mi aplicación se va a usar potencialmente en un sitio que no configura el inicio de sesión en settings.py, defino un NullHandler en algún lugar de la siguiente manera:

#someutils.py

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

null_handler = NullHandler()

y asegurarme de que se agregue una instancia a todos los registradores creados en los módulos de mis aplicaciones que usan el registro. (Nota: NullHandler ya está en el paquete de registro para Python 3.1 y estará en Python 2.7). Entonces:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

Esto se hace para garantizar que sus módulos funcionen bien en un sitio que no configura el inicio de sesión en settings.py, y que no reciba ningún molesto mensaje "No se pudieron encontrar controladores para el registrador XYZ" (que son advertencias sobre posibles registro mal configurado).

Hacerlo de esta manera cumple con los requisitos establecidos:

  • Puede configurar diferentes controladores de registro para diferentes eventos, como lo hace actualmente.
  • Fácil acceso a los registradores en sus módulos: use getLogger(__name__).
  • Fácilmente aplicable a módulos de línea de comandos, también se importan settings.py.

Actualización: tenga en cuenta que a partir de la versión 1.3, Django ahora incorpora soporte para el registro .

Vinay Sajip
fuente
¿No requerirá esto que cada módulo tenga un controlador definido en la configuración (no puede usar un controlador para que foo maneje foo.bar)? Vea la conversación que tuvimos hace años en groups.google.com/group/comp.lang.python/browse_thread/thread/…
andrew cooke
1
@andrew cooke: puede utilizar un controlador para foogestionar los eventos registrados foo.bar. Re. ese hilo, tanto fileConfig como dictConfig ahora tienen opciones para evitar la desactivación de registradores antiguos. Vea este problema: bugs.python.org/issue3136 , que se produjo un par de meses después de su problema bugs.python.org/issue2697 ; de todos modos, se solucionó desde junio de 2008.
Vinay Sajip
¿No sería mejor hacer de logger = someutils.getLogger(__name__)dónde someutils.getLoggerdevuelve el registrador logging.getLoggercon un null_handler ya agregado?
7yl4r
1
@ 7yl4r No necesita que todos los registradores tengan un NullHandleragregado, generalmente solo el registrador de nivel superior para la jerarquía de paquetes. Entonces eso sería una exageración, en mi opinión.
Vinay Sajip
122

Sé que esta ya es una respuesta resuelta, pero según django> = 1.3 hay una nueva configuración de registro.

Pasar de lo antiguo a lo nuevo no es automático, así que pensé en escribirlo aquí.

Y, por supuesto, consulte el documento de django para obtener más información.

Esta es la configuración básica, creada por defecto con django-admin createproject v1.3 - el kilometraje puede cambiar con las últimas versiones de django:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        }
    }
}

Esta estructura se basa en el dictConfig de registro estándar de Python , que dicta los siguientes bloques:

  • formatters - el valor correspondiente será un dict en el que cada clave es un id del formateador y cada valor es un dict que describe cómo configurar la instancia del formateador correspondiente.
  • filters - el valor correspondiente será un dict en el que cada clave es un ID de filtro y cada valor es un dict que describe cómo configurar la instancia de Filter correspondiente.
  • handlers- el valor correspondiente será un dict en el que cada clave es un identificador de controlador y cada valor es un dict que describe cómo configurar la instancia de Handler correspondiente. Cada controlador tiene las siguientes claves:

    • class(obligatorio). Este es el nombre completo de la clase de controlador.
    • level(Opcional). El nivel del manejador.
    • formatter(Opcional). El id del formateador para este controlador.
    • filters(Opcional). Una lista de identificadores de los filtros para este controlador.

Normalmente hago al menos esto:

  • agregar un archivo .log
  • configurar mis aplicaciones para escribir en este registro

Que se traduce en:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # I always add this handler to facilitate separating loggings
        'log_file':{
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'apps': { # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    },
}

editar

Ver las excepciones de solicitud ahora siempre se registran y el ticket # 16288 :

Actualicé la configuración de muestra anterior para incluir explícitamente el filtro correcto para mail_admins para que, de forma predeterminada, los correos electrónicos no se envíen cuando la depuración es Verdadero.

Deberías agregar un filtro:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse'
    }
},

y aplicarlo al controlador mail_admins:

    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }

De lo contrario django.core.handers.base.handle_uncaught_exception, no pasa errores al registrador 'django.request' si settings.DEBUG es True.

Si no hace esto en Django 1.5 obtendrá un

DeprecationWarning: No tiene filtros definidos en el controlador de registro 'mail_admins': agregando filtro implícito de depuración-solo falso

pero las cosas seguirán funcionando correctamente AMBOS en django 1.4 y django 1.5.

** fin de editar **

Ese conf está fuertemente inspirado por el ejemplo de conf en el documento de django, pero agregando la parte del archivo de registro.

A menudo también hago lo siguiente:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Luego, en mi código de Python, siempre agrego un NullHandler en caso de que no se defina ninguna configuración de registro. Esto evita advertencias para ningún Handler especificado. Especialmente útil para bibliotecas que no necesariamente se llaman solo en Django ( ref )

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[...]

logger.warning('etc.etc.')

¡Espero que esto ayude!

Stefano
fuente
Stefano, muchas gracias por la respuesta detallada, muy útil. Esto puede hacer que valga la pena actualizar a 1.3.
Parand
Parand, definitivamente vale la pena (¡en mi humilde opinión!) Pasar a django 1.3, aunque hay algunos puntos que debe tener en cuenta para una transición sin problemas: abra una nueva pregunta SO si se mete en problemas ;-)
Stefano
por cierto: todavía uso este tipo de configuraciones y el registro de archivos, ¡pero me mudé a centinela para la producción!
Stefano
@clime bueno, traté de explicarlo en la propia respuesta: en caso de que no se defina ninguna configuración de registro. Esto evita advertencias para ningún Handler especificado. Especialmente útil para bibliotecas que no necesariamente se llaman solo en Django (ref)
Stefano
No veo cómo se usa esta definición: 'nulo': {'nivel': 'DEPURACIÓN', 'clase': 'django.utils.log.NullHandler',}
clima
9

Inicializamos el registro en el nivel superior urls.pyutilizando un logging.iniarchivo.

La ubicación de la logging.inise proporciona en settings.py, pero eso es todo.

Cada módulo luego hace

logger = logging.getLogger(__name__)

Para distinguir las instancias de prueba, desarrollo y producción, tenemos diferentes archivos logging.ini. En su mayor parte, tenemos un "registro de consola" que va a stderr solo con errores. Tenemos un "registro de la aplicación" que usa un archivo de registro continuo que va a un directorio de registros.

S.Lott
fuente
Terminé usando esto, excepto inicializar en settings.py en lugar de urls.py
Parand
¿Cómo usa la configuración de settings.py en su archivo logging.ini? Por ejemplo, necesito la configuración BASE_DIR, para poder decirle dónde almacenar mis archivos de registro.
slypete
@slypete: No usamos configuraciones en logging.ini. Dado que el registro es en gran medida independiente, no usamos ninguna de las configuraciones de Django. Sí, existe la posibilidad de repetir algo. No, no hay mucha diferencia práctica.
S.Lott
En ese caso, haría un archivo logging.ini por separado en cada instalación de mi aplicación.
slypete
@slypete: Tiene un settings.py para cada instalación. También tiene un logging.ini para cada instalación. Además, probablemente también tenga un archivo de configuración de Apache para cada instalación. Más un archivo de interfaz wsgi. No estoy seguro de cuál es tu punto.
S.Lott
6

Actualmente estoy usando un sistema de registro, que yo mismo creé. Utiliza formato CSV para el registro.

django-csvlog

Este proyecto aún no tiene la documentación completa, pero estoy trabajando en ello.

Oduvan
fuente