在多个模块中使用Python日志记录

时间:2013-03-31 07:39:09

标签: python logging config

我有一个小python项目,具有以下结构 -

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

我计划使用默认日志记录模块将消息打印到stdout和日志文件。 要使用日志记录模块,需要进行一些初始化 -

import logging.config

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

logger.info('testing')

目前,我在开始记录消息之前在每个模块中执行此初始化。是否可以在一个地方只执行一次初始化,以便通过在整个项目中记录来重复使用相同的设置?

11 个答案:

答案 0 :(得分:224)

在每个模块中,最佳做法是使用如下定义的记录器:

import logging
logger = logging.getLogger(__name__)

靠近模块的顶部,然后在模块的其他代码中做例如。

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

如果您需要在模块内细分日志记录活动,请使用例如

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

并根据需要登录loggerAloggerB

在您的主程序中,例如:

def main():
    "your program code"

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

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

if __name__ == '__main__':
    main()

有关从多个模块进行日志记录的信息,请参阅here;有关将由其他代码用作库模块的代码的日志记录配置,请参见here

更新:调用fileConfig()时,如果您使用的是Python 2.6或更高版本,则可能需要指定disable_existing_loggers=False(有关详细信息,请参阅the docs) 。为了向后兼容,默认值为True,这会导致fileConfig()禁用所有现有记录器,除非在配置中明确命名它们或它们的祖先。将值设置为False时,现有记录器将保持不变。如果使用Python 2.7 / Python 3.2或更高版本,您可能希望考虑优于dictConfig()的{​​{1}} API,因为它可以更好地控制配置。

答案 1 :(得分:91)

实际上每个记录器都是父包的记录器的子节点(即package.subpackage.module继承package.subpackage)的配置,所以你需要做的只是配置根记录器。这可以通过{实现{3}}(您自己的记录器配置)或logging.config.fileConfig(设置根记录器)。设置记录您的输入模块(__main__.py或您想要运行的任何内容,例如main_script.py__init__.py同样适用)

使用basicConfig:

# package/__main__.py
import logging
import sys

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

使用fileConfig:

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

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

然后使用以下命令创建每个记录器:

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

log.info("Hello logging!")

有关详细信息,请参阅logging.basicConfig

答案 2 :(得分:20)

我总是这样做。

使用单个python文件将我的日志配置为名为“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')

在另一个模块中,只需导入配置。

from log_conf import Logger

Logger.logr.info("Hello World")

这是一种简单有效的记录单例模式。

答案 3 :(得分:7)

其中一些答案表明,您可以在模块的顶部执行

import logging
logger = logging.getLogger(__name__)

据我了解,这被认为是非常糟糕的做法。原因是文件配置将默认禁用所有现有记录器。 E.g。

#my_module
import logging

logger = logging.getLogger(__name__)

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

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

在你的主要模块中:

#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()

现在logging.ini中指定的日志将为空,因为fileconfig调用已禁用现有记录器。

虽然肯定可以解决这个问题(disable_existing_Loggers = False),但实际上你的库的许多客户端都不会知道这种行为,也不会收到你的日志。通过始终在本地调用logging.getLogger,使您的客户轻松。帽子提示:我从Victor Lin's Website了解了这种行为。

因此,良好的做法是始终在本地调用logging.getLogger。 E.g。

#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')    

此外,如果您在main中使用fileconfig,请设置disable_existing_loggers = False,以防您的库设计者使用模块级记录器实例。

答案 4 :(得分:7)

对我来说,在多个模块中使用日志记录库实例的一种简单方法是采用以下解决方案:

base_logger.py

import logging

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

其他文件

from base_logger import logger

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

答案 5 :(得分:5)

@ Yarkee的解决方案似乎更好。我想补充一点 -

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)

因此LoggerManager可以插入整个应用程序。 希望它有意义和有价值。

答案 6 :(得分:5)

投入另一个解决方案。

在我模块的主 init 中,我有类似的内容:

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

然后在每节课中我需要一个记录器,我这样做:

from [modname] import get_module_logger
logger = get_module_logger(__name__)

当错过日志时,您可以通过它们来自的模块区分它们的来源。

答案 7 :(得分:3)

你也可以想出这样的东西!

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

现在你可以在同一模块和整个项目中使用多个记录器,如果上面是在一个单独的模块中定义的,那么在其他模块中导入则需要记录。

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

答案 8 :(得分:2)

有几个答案。我最终得到了一个对我有意义的类似但又不同的解决方案,也许对您也很有意义。 我的主要目标是能够按其级别将日志传递给处理程序(将调试级别的日志传递至控制台,将警告及以上内容传递给文件):

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)

创建了一个名为logger.py的实用文件:

import logging

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

flask.app是flask中的硬编码值。应用程序记录器始终以flask.app作为模块名称。

现在,在每个模块中,我都可以在以下模式下使用它:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

这将以最小的努力为“ app.flask.MODULE_NAME”创建一个新日志。

答案 9 :(得分:1)

最佳做法是分别创建一个模块,该模块只有一个方法,我们的任务是将记录程序处理程序提供给调用方法。将此文件另存为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

现在,只要需要记录程序处理程序,就调用getlogger()方法。

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')

答案 10 :(得分:0)

是python的新手,所以我不知道这是否可取,但是对于不重写样板非常有用。

您的项目必须具有 init .py,以便可以将其作为模块加载

# 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)的建议来自here

然后在其他任何文件中使用记录器:

from [your module name here] import logger

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

注意事项:

  1. 您必须将文件作为模块运行,否则import [your module]将不起作用:
    • python -m [your module name].[your filename without .py]
  2. 程序入口点的记录器名称为__main__,但是使用__name__的任何解决方案都会出现此问题。