Configurar logging en python y django con yaml


Os paso el esquema de logging que utilizo en mis aplicaciones por si os puede servir de ayuda. Lo he ido montando a partir de diversas fuentes y experiencias.

Un punto importante es dejar el fichero de configuración del logging fuera del programa para independizar mejor el sistema de logging de la aplicación.

Este puede ser un ejemplo del fichero de configuración en yaml (logger.conf):


version: 1
formatters:
  brief:
    format: '%(name)s - %(levelname)s - %(message)s'
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  detailed:
    format: '%(asctime)s %(module)-17s line:%(lineno)-4d %(levelname)-8s %(message)s'
  email:
    format: 'Timestamp: %(asctime)s\nModule: %(module)s\n Line: %(lineno)d\nMessage: %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: brief
    stream: ext://sys.stdout
  file:
    class: logging.handlers.RotatingFileHandler
    level: INFO
    formatter: detailed
    filename: app.log
    mode: a
    maxBytes: 10485760
    backupCount: 5
  smtp:
    class: logging.handlers.SMTPHandler
    level: ERROR
    formatter: email
    mailhost: smtp.xxxxx.es
    fromaddr: yyy@xxxx.es
    toaddrs:  yyyy@xxxx.es
    subject:  Error en aplicacion XX
root:
  level: DEBUG
  handlers: [console]

Se observan los formateadores de los mensajes y los handlers para la cónsola, fichero y correo. Por defecto el logging sale por cónsola, si queremos que los errores salgan por email handlers de root sería [console, smtp], para fichero que rotara cada maxBytes [file], etc... Realmente cómodo.

Para cargar la configuración, primero parseamos el yaml (PyYAML) y luego lo enviamos a dictConfig, que realiza la configuración así:


import yaml
import logging


#nos aseguramos de que dictConfig esté presente (python 2.7), sino, lo cargamos de la libreria de django 



try: 
    from logging.config import dictConfig 
except ImportError: 
    from django.utils.dictconfig import dictConfig

# Cargamos la configuracion del fichero, la parseamos y la enviamos a dictConf
try:
    logging.config.dictConfig(yaml.load(file('logger.conf','r')))
except Exception, e:
    print "Error configurating logger ", str(e)

Ahora ya podemos el usar el sistema. En general, es conveniente nombrar cada logger con el nombre de su módulo para que cuando haya muchos mensajes sepamos cuál los está enviando, para ello podemos usar el valor de __name__, así:

logger=logging.getLogger(__name__) # __name__ contiene el nombre del modulo
logger.debug("blah blah")

Si queremos que cada clase tenga su logger privado, tenemos que tomar una pequeña precaución para evitar un error si otros usan una librería nuestra con otro sistema de handles,

Declaramos una clase "nula":

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

y ahora, en la clase hacemos

class Clase():
   def __init__(self):
      self.__logger=logging.getLogger(__name__)
      self.__logger.addHandler(NullHandler())
      self.__logger.debug("clase iniciada")

Como veis es un tema un poco denso pero que una vez bien configurado nos será imprescindible para trazar correctamente las aplicaciones. Espero que os haya ayudado.