使用登录多个模块


257

我有一个具有以下结构的小型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')

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


3
回应您对我的回答的评论:您不必fileConfig在每个执行日志记录的模块中调用,除非if __name__ == '__main__'所有逻辑模块中都有逻辑。如果软件包是库,则prost的答案不是一个好习惯,尽管它可能对您有用-除了添加以外,不应配置库软件包中的日志记录NullHandler
Vinay Sajip 2013年

1
prost暗示我们需要在每个模块中调用import和logger stmts,而仅在主模块中调用fileconfig stmt。难道与您所说的不一样吗?
Quest Monger

6
普罗斯特说你应该把日志配置代码放在package/__init__.py。通常这不是您放置if __name__ == '__main__'代码的地方。另外,prost的示例看起来像将在导入时无条件调用配置代码,这对我来说不合适。通常,除非在导入__main__时,否则日志配置代码应在一个地方完成,并且不应作为导入的副作用发生。
Vinay Sajip

没错,我完全错过了他的代码示例中的“#package / __ init__.py”行。感谢您指出这一点以及您的耐心等待。
Quest Monger

1
那么,如果您有多个会if __name__ == '__main__'怎样?(如果情况如此,则没有明确提及)
kon psych

Answers:


293

最佳实践是在每个模块中都定义一个记录器,如下所示:

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

这里用于从多个模块记录,并在这里用于代码日志配置将被用作其它的代码库模块。

更新:调用时fileConfig(),您可能想指定disable_existing_loggers=False是否使用Python 2.6或更高版本(有关更多信息,请参阅文档)。缺省值是True为了向后兼容,fileConfig()除非所有现有记录器或其祖先在配置中被明确命名,否则它将被禁用。将该值设置为False,将保留现有记录器。如果使用的是Python 2.7 / Python 3.2或更高版本,则不妨考虑该dictConfig()API更好,fileConfig()因为它可以更好地控制配置。


21
如果您看一下我的例子,我已经在做您上面的建议了。我的问题是我如何集中此日志记录初始化,这样我就不必重复这3条语句。另外,在您的示例中,您错过了'logging.config.fileConfig('logging.conf')'stmt。这实际上是我担心的根本原因。您会看到,如果我在每个模块中都启动了记录器,则必须在每个模块中键入此stmt。这将意味着跟踪每个模块中conf文件的路径,这对我而言似乎不是最佳实践(在更改模块/程序包位置时会造成严重破坏)。
Quest Monger

4
如果在创建记录器后调用fileConfig,则无论是在同一模块中还是在另一个模块中(例如,在文件顶部创建记录器时)均不起作用。日志记录配置仅适用于之后创建的记录器。因此,这种方法行不通或对多个模块都不可行。@Quest Monger:您始终可以创建另一个保存配置文件位置的文件。;)
Vincent Ketelaars 2013年

2
@Oxidator:不一定-请参阅默认设置的disable_existing_loggers标记,True但可以将其设置为False
Vinay Sajip 2013年

1
@Vinay Sajip,谢谢。您是否对在模块中但在类之外工作的记录器有建议?由于导入是在调用main函数之前完成的,因此这些日志将已经被记录。我想在主模块中的所有导入之前设置记录器是唯一的方法吗?然后,您可以根据需要覆盖主记录器。
Vincent Ketelaars 2013年

1
如果我希望所有模块特定的记录器的记录级别都不同于默认的“警告”,是否需要在每个模块上进行该设置?说,我希望所有模块都记录在INFO中。
拉吉2016年

127

实际上,每个记录器都是父级程序包记录器的子级(即package.subpackage.module从继承配置package.subpackage),因此您只需要做的就是配置根记录器。这可以通过logging.config.fileConfig(您自己的记录器配置)或logging.basicConfig(设置根记录器)来实现。在您的输入模块中登录安装程序(__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!")

有关更多信息,请参见高级日志记录教程


15
到目前为止,这是解决该问题的最简单方法,更不用说它公开并利用了模块之间的父子关系,而我作为一个菜鸟却没有意识到。丹科
Quest Monger 2013年

你是对的。就像vinay在他的文章中指出的那样,您的解决方案是正确的,只要不在init .py模块中即可。当我将其应用到主模块(入口点)时,您的解决方案确实起作用了。
Quest Monger

2
实际上,由于该问题与单独的模块有关,因此答案更为相关。
Jan Sila '18

1
可能是愚蠢的问题:如果没有记录器__main__.py(例如,如果我想在没有记录器的脚本中使用该模块)logging.getLogger(__name__)仍会在该模块中进行某种记录,还是会引发异常?
比尔

1
最后。我有一个工作正常的记录器,但是在Windows中使用joblib并行运行时记录器失败。我猜这是对系统的手动调整-Parallel还有其他问题。但是,它肯定有效!谢谢
B Furtado

17

我总是这样做如下。

使用一个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")

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


1
感谢您详细介绍单例模式。我当时计划实施此操作,但是@prost解决方案要简单得多,并且非常适合我的需求。但是,我确实看到您的解决方案很有用,因为它是具有多个入口点(而不是主入口)的较大项目。丹科
Quest Monger

46
这没用。根记录器已经是单例。只需使用logging.info而不是Logger.logr.info。

9

这些答案中有几个建议您在模块的顶部进行操作

import logging
logger = logging.getLogger(__name__)

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

#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的网站上

因此,好的做法是始终在本地调用logging.getLogger。例如

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

另外,如果在主目录中使用fileconfig,请设置disable_existing_loggers = False,以防万一您的库设计人员使用模块级记录器实例。


你能不能运行logging.config.fileConfig('logging.ini')之前import my_module?正如这个答案所建议的。
lucid_dreamer '17

不确定-但是以这种方式混合导入和可执行代码肯定也被认为是不好的做法。您也不希望您的客户端在导入之前必须检查它们是否需要配置日志记录,尤其是在有其他选择的情况下!想象一下,如果像请求这样的广泛使用的库已经做到了……。
phil_20686

“不确定-但是以这种方式混合导入和可执行代码肯定也被认为是不好的做法。” -为什么?
lucid_dreamer

我不太清楚这为什么不好。我不完全理解您的例子。您可以发布此示例的配置并显示一些用法吗?
lucid_dreamer

1
您似乎与官方文档相矛盾:“命名记录器时要使用的一个很好的约定是在使用日志记录的每个模块中使用模块级记录器,命名如下:logger = logging.getLogger(__name__)'
iron9

8

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

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")

7

抛出另一种解决方案。

在我模块的init .py中,我有类似以下内容:

# 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

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

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

如果缺少日志,则可以通过其来源来区分其来源。


“我的模块的主要初始化”是什么意思?还有“然后在每个班级,我需要一个记录器,我要做:”?您能否提供一个名为_module.py的示例,以及从模块caller_module.py导入该示例的示例?请参阅此答案,以获取我要询问的格式的灵感。不要试图光顾。我试图理解您的答案,如果您以这种方式写,我会知道的。
lucid_dreamer

1
我澄清了@lucid_dreamer。
汤米

4

您也可以提出这样的建议!

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")

4

@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可以插入整个应用程序。希望它有意义和有价值。


11
日志记录模块已经处理了单例。logging.getLogger(“ Hello”)将在所有模块中获得相同的记录器。

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”创建一个新日志。


2

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

1
如果您没有任何其他参数,则很好。但是,如果说,您--debug在应用程序中具有选项,并希望基于此参数在应用程序中的所有记录器中设置日志记录级别...
The Godfather

@TheGodfather是的,用这种方法很难做到。在这种情况下,我们可以做的是创建一个类,该类在创建对象时将格式化程序作为参数,并具有类似的功能来返回记录程序处理程序。您对此有何看法?
Mousam Singh

是的,我做了类似的事情,使它get_logger(level=logging.INFO)返回某种单例,因此当它第一次从主应用程序中调用时,它会以适当的级别初始化记录器和处理程序,然后将相同的logger对象返回给所有其他方法。
教父

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)建议来自这里

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

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__都会出现该问题。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.