Usando el registro en múltiples módulos

257

Tengo un pequeño proyecto de Python que tiene la siguiente estructura:

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Planeo usar el módulo de registro predeterminado para imprimir mensajes en stdout y un archivo de registro. Para usar el módulo de registro, se requiere algo de inicialización:

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

Actualmente, realizo esta inicialización en cada módulo antes de comenzar a registrar mensajes. ¿Es posible realizar esta inicialización solo una vez en un lugar, de modo que la misma configuración se reutilice al iniciar sesión en todo el proyecto?

Quest Monger
fuente
3
En respuesta a su comentario sobre mi respuesta: no tiene que llamar fileConfiga todos los módulos que registran, a menos que tenga if __name__ == '__main__'lógica en todos ellos. La respuesta de prost no es una buena práctica si el paquete es una biblioteca, aunque podría funcionar para usted: no debe configurar el inicio de sesión en los paquetes de la biblioteca, aparte de agregar a NullHandler.
Vinay Sajip
1
prost implicaba que debíamos llamar a los stmts de importación y registrador en cada módulo, y solo llamar a los stmt de fileconfig en el módulo principal. ¿No es similar a lo que estás diciendo?
Quest Monger
66
prost dice que deberías poner el código de configuración de inicio de sesión package/__init__.py. Ese no es normalmente el lugar donde pones el if __name__ == '__main__'código. Además, el ejemplo de prost parece que llamará al código de configuración incondicionalmente en la importación, lo que no me parece correcto. En general, el código de configuración de registro debe hacerse en un lugar y no debe ocurrir como un efecto secundario de la importación, excepto cuando está importando __main__.
Vinay Sajip
tienes razón, me perdí totalmente la línea '# package / __ init__.py' en su código de muestra. gracias por señalar eso y tu paciencia.
Quest Monger
1
Entonces, ¿qué pasa si tienes múltiples if __name__ == '__main__'? (no se menciona explícitamente en cuestión si este es el caso)
kon psych

Respuestas:

293

La mejor práctica es, en cada módulo, tener un registrador definido de esta manera:

import logging
logger = logging.getLogger(__name__)

cerca de la parte superior del módulo, y luego en otro código del módulo, p. ej.

logger.debug('My message with %s', 'variable data')

Si necesita subdividir la actividad de registro dentro de un módulo, use p. Ej.

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

e inicie sesión en loggerAy loggerBsegún corresponda.

En su programa o programas principales, por ejemplo:

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

o

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

Vea aquí para iniciar sesión desde múltiples módulos, y aquí para registrar la configuración del código que otro código utilizará como módulo de biblioteca.

Actualización: al llamar fileConfig(), es posible que desee especificar disable_existing_loggers=Falsesi está utilizando Python 2.6 o posterior (consulte los documentos para obtener más información). El valor predeterminado es Truepara la compatibilidad con versiones anteriores, lo que hace que todos los registradores existentes se deshabiliten a fileConfig()menos que ellos o sus antepasados ​​se mencionen explícitamente en la configuración. Con el valor establecido en False, los registradores existentes se dejan solos. Si usa Python 2.7 / Python 3.2 o posterior, es posible que desee considerar la dictConfig()API, que es mejor que fileConfig()ya que le da más control sobre la configuración.

Vinay Sajip
fuente
21
Si miras mi ejemplo, ya estoy haciendo lo que sugieres arriba. mi pregunta era cómo centralizo esta inicialización de registro de modo que no tenga que repetir esas 3 declaraciones. también, en su ejemplo, se perdió el 'logging.config.fileConfig (' logging.conf ')' stmt. Este problema es en realidad la causa principal de mi preocupación. usted ve, si he iniciado el registrador en cada módulo, tendría que escribir este stmt en cada módulo. eso significaría rastrear la ruta del archivo conf en cada módulo, lo que no me parece una práctica recomendada (imagínese el caos al cambiar la ubicación del módulo / paquete).
Quest Monger
44
Si llama a fileConfig después de crear el registrador, ya sea en el mismo módulo o en otro (por ejemplo, cuando crea el registrador en la parte superior del archivo) no funciona. La configuración de registro solo se aplica a los registradores creados después. Por lo tanto, este enfoque no funciona o no es una opción viable para múltiples módulos. @Quest Monger: Siempre puedes crear otro archivo que contenga la ubicación del archivo de configuración ...;)
Vincent Ketelaars
2
@Oxidator: No necesariamente: vea la disable_existing_loggersbandera que está Truepor defecto pero se puede establecer en False.
Vinay Sajip
1
@Vinay Sajip, gracias. ¿Tiene recomendaciones para los registradores que funcionan en módulos pero también fuera de clases? Como las importaciones se realizan antes de llamar a la función principal, esos registros ya se habrán registrado. ¿Supongo que configurar su registrador antes de todas las importaciones en el módulo principal es la única manera? Este registrador podría sobrescribirse en main, si lo desea.
Vincent Ketelaars
1
Si deseo que todos los registradores específicos de mi módulo tengan un nivel de registro diferente al AVISO predeterminado, ¿tendré que hacer esa configuración en cada módulo? Digamos, quiero que todos mis módulos inicien sesión en INFO.
Raj
128

En realidad, cada registrador es un elemento secundario del registrador de paquetes del padre (es decir, package.subpackage.modulehereda la configuración package.subpackage), por lo que todo lo que necesita hacer es configurar el registrador raíz. Esto puede lograrse mediante logging.config.fileConfig(su propia configuración para registradores) o logging.basicConfig(establece el registrador raíz) Configurar el registro en su módulo de entrada ( __main__.pyo lo que quiera ejecutar, por ejemplo main_script.py. __init__.pyTambién funciona)

usando basicConfig:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

usando fileConfig:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

y luego crea cada registrador usando:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

Para obtener más información, consulte Tutorial avanzado de registro .

Stan Prokop
fuente
15
Esta es, con mucho, la solución más simple para el problema, sin mencionar que expone y aprovecha la relación padre-hijo entre los módulos, algo que yo, como novato, desconocía. danke
Quest Monger
tienes razón. y como vinay señaló en su publicación, su solución es correcta siempre que no esté en el módulo init .py. su solución funcionó cuando la apliqué al módulo principal (punto de entrada).
Quest Monger
2
En realidad, una respuesta mucho más relevante ya que la pregunta se refiere a módulos separados.
Jan Sila
1
Quizás una pregunta tonta: si no hay un registrador en __main__.py(por ejemplo, si quiero usar el módulo en un script que no tiene registrador), ¿ logging.getLogger(__name__)seguirá haciendo algún tipo de registro en el módulo o generará una excepción?
Bill
1
Finalmente. Tenía un registrador que funcionaba, pero falló en Windows porque las ejecuciones paralelas con joblib. Supongo que este es un ajuste manual del sistema, algo más está mal con Parallel. Pero, seguramente funciona! Gracias
B Furtado
17

Siempre lo hago como a continuación.

Usar un solo archivo de Python para configurar mi registro como patrón singleton que se llama ' log_conf.py'

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

En otro módulo, solo importa la configuración.

from log_conf import Logger

Logger.logr.info("Hello World")

Este es un patrón singleton para registrar, simple y eficientemente.

Yarkee
fuente
1
gracias por detallar el patrón singleton. Estaba planeando implementar esto, pero la solución @prost es mucho más simple y se adapta perfectamente a mis necesidades. Sin embargo, veo que su solución es útil en proyectos más grandes que tienen múltiples puntos de entrada (que no sean principales). danke
Quest Monger
46
Esto es inútil El registrador raíz ya es un singleton. Simplemente use logging.info en lugar de Logger.logr.info.
Pod
9

Varias de estas respuestas sugieren que en la parte superior de un módulo usted lo hace

import logging
logger = logging.getLogger(__name__)

Tengo entendido que esto se considera una muy mala práctica . La razón es que la configuración del archivo deshabilitará todos los registradores existentes de forma predeterminada. P.ej

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

Y en tu módulo principal:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

Ahora el registro especificado en logging.ini estará vacío, ya que el registro existente fue deshabilitado por la llamada fileconfig.

Si bien es posible evitar esto (disable_existing_Loggers = False), de manera realista, muchos clientes de su biblioteca no conocerán este comportamiento y no recibirán sus registros. Facilite a sus clientes siempre llamando a logging.getLogger localmente. Sugerencia: aprendí sobre este comportamiento en el sitio web de Victor Lin .

Por lo tanto, una buena práctica es llamar siempre a logging.getLogger localmente. P.ej

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

Además, si usa fileconfig en su main, configure disable_existing_loggers = False, en caso de que los diseñadores de su biblioteca usen instancias de registrador a nivel de módulo.

phil_20686
fuente
¿No puedes correr logging.config.fileConfig('logging.ini')antes import my_module? Como se sugiere en esta respuesta .
lucid_dreamer
No estoy seguro, pero definitivamente también se consideraría una mala práctica mezclar las importaciones y el código ejecutable de esa manera. Tampoco desea que sus clientes tengan que verificar si necesitan configurar el registro antes de importar, ¡especialmente cuando hay una alternativa trivial! ¡Imagínese si una biblioteca ampliamente utilizada como solicitudes hubiera hecho eso ...!
phil_20686
"No estoy seguro, pero definitivamente también se consideraría una mala práctica mezclar las importaciones y el código ejecutable de esa manera". - ¿por qué?
lucid_dreamer
No tengo muy claro por qué eso es malo. Y no entiendo completamente tu ejemplo. ¿Puedes publicar tu configuración para este ejemplo y mostrar algún uso?
lucid_dreamer
1
Parece que contradice los documentos oficiales : 'Una buena convención para nombrar a los registradores es usar un registrador a nivel de módulo, en cada módulo que usa el registro, denominado de la siguiente manera: logger = logging.getLogger(__name__)'
iron9
9

Para mí, una forma simple de usar una instancia de la biblioteca de registro en varios módulos fue la siguiente solución:

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

Otros archivos

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")
Alex Jolig
fuente
7

Tirando en otra solución.

En init .py de mi módulo tengo algo como:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

Luego, en cada módulo necesito un registrador, hago:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Cuando se pierden los registros, puede diferenciar su fuente por el módulo del que provienen.

Tommy
fuente
¿Qué significa "inicio principal de mi módulo"? Y "Entonces, en cada clase necesito un registrador, hago:"? ¿Puede proporcionar una muestra llamada_module.py, y un ejemplo de su uso como una importación desde el módulo caller_module.py? Vea esta respuesta para obtener una inspiración del formato sobre el que estoy preguntando. No trato de ser condescendiente. Estoy tratando de entender tu respuesta y sé que lo haría si la escribieras de esa manera.
lucid_dreamer
1
@lucid_dreamer aclaré.
Tommy
4

¡También podrías pensar en algo como esto!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

Ahora podría usar varios registradores en el mismo módulo y en todo el proyecto si lo anterior se define en un módulo separado e importado en otros módulos donde se requiere el registro.

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")
deeshank
fuente
4

La solución de @ Yarkee parecía mejor. Me gustaría agregarle algo más:

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

Por lo tanto, LoggerManager puede conectarse a toda la aplicación. Espero que tenga sentido y valor.

deeshank
fuente
11
El módulo de registro ya se ocupa de singletons. logging.getLogger ("Hola") obtendrá el mismo registrador en todos sus módulos.
Pod
2

Hay varias respuestas Terminé con una solución similar pero diferente que tiene sentido para mí, tal vez también tenga sentido para usted. Mi objetivo principal era poder pasar los registros a los controladores por su nivel (registros de nivel de depuración a la consola, advertencias y más arriba a los archivos):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

creó un buen archivo util llamado logger.py:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

el flask.app es un valor codificado en el matraz. el registrador de aplicaciones siempre comienza con flask.app como nombre del módulo.

ahora, en cada módulo, puedo usarlo en el siguiente modo:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Esto creará un nuevo registro para "app.flask.MODULE_NAME" con el mínimo esfuerzo.

Ben Yitzhaki
fuente
2

La mejor práctica sería crear un módulo por separado que tenga un solo método cuya tarea sea asignar un controlador de registro al método de llamada. Guarde este archivo como m_logger.py

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

Ahora llame al método getlogger () siempre que se necesite el controlador del registrador.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')
Mousam Singh
fuente
1
Esto es bueno si no tiene ningún parámetro adicional. Pero si, digamos, tiene una --debugopción en la aplicación y desea establecer el nivel de registro en todos los registradores de su aplicación en función de este parámetro ...
The Godfather
@TheGodfather Sí, esto es difícil de lograr con esta metodología. Lo que podemos hacer en esta situación es crear una clase para la cual tomaría el formateador como parámetro en el momento de la creación del objeto y tendría una función similar para devolver el controlador del registrador. ¿Cuáles son sus puntos de vista sobre esto?
Mousam Singh
Sí, hice algo similar, hecho get_logger(level=logging.INFO)para devolver algún tipo de singleton, por lo que cuando llamó por primera vez desde la aplicación principal, inicializa el registrador y los controladores con el nivel adecuado y luego devuelve el mismo loggerobjeto a todos los demás métodos.
El padrino
0

Nuevo en Python, así que no sé si esto es aconsejable, pero funciona muy bien para no volver a escribir repetitivo.

Su proyecto debe tener un init .py para que pueda cargarse como un módulo

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1) sugerencia proviene de aquí

Luego, para usar su registrador en cualquier otro archivo:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

Advertencias:

  1. Debe ejecutar sus archivos como módulos, de lo contrario import [your module]no funcionará:
    • python -m [your module name].[your filename without .py]
  2. El nombre del registrador para el punto de entrada de su programa será __main__, pero cualquier solución que use __name__tendrá ese problema.
npjohns
fuente