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

Que*_*ger 217 python logging config

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

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf
Run Code Online (Sandbox Code Playgroud)

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

import logging.config

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

logger.info('testing')
Run Code Online (Sandbox Code Playgroud)

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

Vin*_*jip 251

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

import logging
logger = logging.getLogger(__name__)
Run Code Online (Sandbox Code Playgroud)

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

logger.debug('My message with %s', 'variable data')
Run Code Online (Sandbox Code Playgroud)

如果需要在模块内细分日志活动,请使用eg

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')
Run Code Online (Sandbox Code Playgroud)

和日志记录loggerA,并loggerB适当.

在您的主程序或程序中,例如:

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()
Run Code Online (Sandbox Code Playgroud)

要么

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

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

请参阅此处以获取来自多个模块的日志记录,此处用于记录将由其他代码用作库模块的代码的配置.

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

  • 如果你看看我的例子,我已经在做你上面的建议了.我的问题是我如何集中这个日志记录初始化,这样我就不必重复这3个语句.另外,在你的例子中,你错过了'logging.config.fileConfig('logging.conf')'stmt.这个问题实际上是我担心的根本原因.你看,如果我在每个模块中启动记录器,我将不得不在每个模块中键入此stmt.这意味着在每个模块中跟踪conf文件的路径,这对我来说看起来不是最佳实践(想象一下在更改模块/包位置时的破坏). (17认同)
  • 如果在创建记录器后调用fileConfig,则无论是在相同模块还是在另一个模块中(例如,当您在文件顶部创建记录器时)都不起作用.日志记录配置仅适用于之后创建的记录器.因此,这种方法对多个模块不起作用或不可行.@Quest Monger:您始终可以创建另一个包含配置文件位置的文件..;) (4认同)
  • @Oxidator:不一定 - 查看`disable_existing_loggers`标志,默认为"True",但可以设置为"False". (2认同)
  • @Vinay Sajip,谢谢。对于在模块中工作但也在类之外工作的记录器,您有什么建议吗?由于导入是在调用主函数之前完成的,因此这些日志已经被记录。我想在主模块中的所有导入之前设置记录器是唯一的方法?如果您愿意,可以在 main 中覆盖该记录器。 (2认同)

Sta*_*kop 104

实际上每个记录器都是父包的记录器的子节点(即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)
Run Code Online (Sandbox Code Playgroud)

使用fileConfig:

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

logging.config.fileConfig('logging.conf')
Run Code Online (Sandbox Code Playgroud)

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

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

log.info("Hello logging!")
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅高级日志教程.

  • 到目前为止,这是问题的最简单的解决方案,更不用说它暴露和利用模块之间的父子关系,这是我作为一个菜鸟不知道的东西.丹科. (9认同)
  • 实际上,由于该问题与单独的模块有关,因此答案更为相关。 (2认同)
  • 也许是愚蠢的问题:如果 `__main__.py` 中没有记录器(例如,如果我想在没有记录器的脚本中使用该模块),则 `logging.getLogger(__name__)` 仍然会在模块中进行某种日志记录或者它会引发异常? (2认同)
  • @Bill 我不确定我是否理解你的问题。你的意思是你没有logging.basicConfig或logging.config.fileConfig?您绝对可以使用logging.getLogger并进行一些日志记录,它只是不会在任何地方打印任何内容。许多库都会进行日志记录,但他们将日志记录设置(例如日志消息的去向)留给了用户。 (2认同)
  • 最后。我有一个工作记录器,但它在 Windows 中与 joblib 并行运行失败。我猜这是对系统的手动调整——并行还有其他问题。但是,它肯定有效!谢谢 (2认同)

Yar*_*kee 19

我总是这样做.

使用单个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')
Run Code Online (Sandbox Code Playgroud)

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

from log_conf import Logger

Logger.logr.info("Hello World")
Run Code Online (Sandbox Code Playgroud)

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

  • 这没用.根记录器已经是单例.只需使用logging.info而不是Logger.logr.info. (33认同)

Ale*_*lig 16

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

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
Run Code Online (Sandbox Code Playgroud)

其它文件

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")
Run Code Online (Sandbox Code Playgroud)

  • 对于我的小项目来说,这是可行的解决方案。请注意,根记录器是一个单例,方便地增加了这个简单解决方案的结构。 (6认同)
  • 这是一个被低估的答案。如果您的项目只需要一个记录器,则无需使用“getLogger(__name__)”创建多个记录器。有了这个答案,您只需要一行即可导入/配置记录器。我也更喜欢在代码中使用“basicConfig”而不是“fileConfig(logging.conf)”,因为您可以进行动态配置。另一种变体是您可以删除“logger=logging”别名并直接使用“logging.info()”。或者您创建一个更短的别名,例如“log=logging”来使用“log.info()”。 (6认同)
  • 谢谢-简单而美妙。您能否以某种方式使此代码启用由主函数驱动的日志文件命名? (2认同)

Phi*_*hil 15

我想添加我的解决方案(它基于记录食谱和该线程中的其他文章和建议。但是我花了很长时间才弄清楚为什么它没有立即按我的预期工作。所以我创建了一个一个小测试项目来了解日志记录是如何工作的。

\n

既然我已经弄清楚了,我想分享我的解决方案,也许它可以对某人有所帮助。

\n

我知道我的一些代码可能不是最佳实践,但我仍在学习。我将这些print()功能留在了那里,因为我使用了它们,而日志记录没有按预期工作。这些已在我的其他应用程序中删除。我也欢迎对代码或结构的任何部分提供任何反馈。

\n

my_log_test 项目结构(从我从事的另一个项目克隆/简化)

\n
my_log_test\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __main__.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 daemon.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 common\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 my_logger.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 pkg1\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 mod1.py\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 pkg2\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 mod2.py\n
Run Code Online (Sandbox Code Playgroud)\n

要求

\n

有一些不同的事情或者我在我使用的组合中没有明确提到的事情:

\n
    \n
  • 主模块是daemon.py__main__.py
  • \n
  • 我希望能够在开发/测试时分别调用模块mod1.pymod2.py
  • \n
  • 此时我不想使用basicConfig()FileConfig()但保留它就像在日志记录手册中一样
  • \n
\n

所以基本上,这意味着,我需要(始终)和模块中以及(仅在直接调用它们时)初始化根记录器。daemon.pymod1.pymod2.py

\n

为了使几个模块中的初始化更容易,我创建了my_logger.py它,它的作用是食谱中描述的。

\n

我的错

\n

之前,我在该模块中的错误是使用 (模块记录器) 初始化记录器,logger = logging.getLogger(__name__)而不是使用logger = logging.getLogger()(获取记录器)。

\n

第一个问题是,当从daemon.py记录器的命名空间调用时设置为my_log_test.common.my_logger. mod1.py因此,具有“不匹配”命名空间的模块记录器my_log_test.pkg1.mod1无法附加到其他记录器,并且我不会看到 mod1 的任何日志输出。

\n

第二个“问题”是,我的主程序位于daemon.py而不是在__main__.py. 但对我来说毕竟不是真正的问题,但它增加了命名空间的混乱。

\n

工作溶液

\n

这是来自食谱,但位于单独的模块中。我还添加了一个logger_cleanup可以从守护进程调用的函数,以删除早于 x 天的日志。

\n
## my_logger.py\nfrom datetime import datetime\nimport time\nimport os\n\n## Init logging start \nimport logging\nimport logging.handlers\n\ndef logger_init():\n    print("print in my_logger.logger_init()")\n    print("print my_logger.py __name__: " +__name__)\n    path = "log/"\n    filename = "my_log_test.log"\n\n    ## get logger\n    #logger = logging.getLogger(__name__) ## this was my mistake, to init a module logger here\n    logger = logging.getLogger() ## root logger\n    logger.setLevel(logging.INFO)\n\n    # File handler\n    logfilename = datetime.now().strftime("%Y%m%d_%H%M%S") + f"_{filename}"\n    file = logging.handlers.TimedRotatingFileHandler(f"{path}{logfilename}", when="midnight", interval=1)\n    #fileformat = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")\n    fileformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")\n    file.setLevel(logging.INFO)\n    file.setFormatter(fileformat)\n\n    # Stream handler\n    stream = logging.StreamHandler()\n    #streamformat = logging.Formatter("%(asctime)s [%(levelname)s:%(module)s] %(message)s")\n    streamformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")\n    stream.setLevel(logging.INFO)\n    stream.setFormatter(streamformat)\n\n    # Adding all handlers to the logs\n    logger.addHandler(file)\n    logger.addHandler(stream)\n\n\ndef logger_cleanup(path, days_to_keep):\n    lclogger = logging.getLogger(__name__)\n    logpath = f"{path}"\n    now = time.time()\n    for filename in os.listdir(logpath):\n        filestamp = os.stat(os.path.join(logpath, filename)).st_mtime\n        filecompare = now - days_to_keep * 86400\n        if  filestamp < filecompare:\n            lclogger.info("Delete old log " + filename)\n            try:\n                os.remove(os.path.join(logpath, filename))\n            except Exception as e:\n                lclogger.exception(e)\n                continue\n
Run Code Online (Sandbox Code Playgroud)\n

运行 deamon.py (通过__main__.py)使用python3 -m my_log_test

\n
## __main__.py\nfrom  my_log_test import daemon\n\nif __name__ == \'__main__\':\n    print("print in __main__.py")\n    daemon.run()\n
Run Code Online (Sandbox Code Playgroud)\n

(直接)运行 deamon.py 使用python3 -m my_log_test.daemon

\n
## daemon.py\nfrom datetime import datetime\nimport time\nimport logging\nimport my_log_test.pkg1.mod1 as mod1\nimport my_log_test.pkg2.mod2 as mod2\n\n## init ROOT logger from my_logger.logger_init()\nfrom my_log_test.common.my_logger import logger_init\nlogger_init() ## init root logger\nlogger = logging.getLogger(__name__) ## module logger\n\ndef run():\n    print("print in daemon.run()")\n    print("print daemon.py __name__: " +__name__)\n    logger.info("Start daemon")\n    loop_count = 1\n    while True:\n        logger.info(f"loop_count: {loop_count}")\n        logger.info("do stuff from pkg1")\n        mod1.do1()\n        logger.info("finished stuff from pkg1")\n\n        logger.info("do stuff from pkg2")\n        mod2.do2()\n        logger.info("finished stuff from pkg2")\n\n        logger.info("Waiting a bit...")\n        time.sleep(30)\n\n\nif __name__ == \'__main__\':\n    try:\n        print("print in daemon.py if __name__ == \'__main__\'")\n        logger.info("running daemon.py as main")\n        run()\n    except KeyboardInterrupt as e:\n        logger.info("Program aborted by user")\n    except Exception as e:\n        logger.info(e)\n
Run Code Online (Sandbox Code Playgroud)\n

要(直接)运行 mod1.py 使用python3 -m my_log_test.pkg1.mod1

\n
## mod1.py\nimport logging\n# mod1_logger = logging.getLogger(__name__)\nmod1_logger = logging.getLogger("my_log_test.daemon.pkg1.mod1") ## for testing, namespace set manually\n\ndef do1():\n    print("print in mod1.do1()")\n    print("print mod1.py __name__: " +__name__)\n    mod1_logger.info("Doing someting in pkg1.do1()")\n\nif __name__ == \'__main__\':\n    ## Also enable this pkg to be run directly while in development with\n    ## python3 -m my_log_test.pkg1.mod1\n\n    ## init root logger\n    from my_log_test.common.my_logger import logger_init\n    logger_init() ## init root logger\n\n    print("print in mod1.py if __name__ == \'__main__\'")\n    mod1_logger.info("Running mod1.py as main")\n    do1()\n
Run Code Online (Sandbox Code Playgroud)\n

要(直接)运行 mod2.py 使用python3 -m my_log_test.pkg2.mod2

\n
## mod2.py\nimport logging\nlogger = logging.getLogger(__name__)\n\ndef do2():\n    print("print in pkg2.do2()")\n    print("print mod2.py __name__: " +__name__) # setting namespace through __name__\n    logger.info("Doing someting in pkg2.do2()")\n\nif __name__ == \'__main__\':\n    ## Also enable this pkg to be run directly while in development with\n    ## python3 -m my_log_test.pkg2.mod2\n\n    ## init root logger\n    from my_log_test.common.my_logger import logger_init\n    logger_init() ## init root logger\n\n    print("print in mod2.py if __name__ == \'__main__\'")\n    logger.info("Running mod2.py as main")\n    do2()\n
Run Code Online (Sandbox Code Playgroud)\n

如果有帮助的话很高兴。也很高兴收到反馈!

\n


phi*_*686 8

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

import logging
logger = logging.getLogger(__name__)
Run Code Online (Sandbox Code Playgroud)

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

#my_module
import logging

logger = logging.getLogger(__name__)

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

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')
Run Code Online (Sandbox Code Playgroud)

在您的主要模块中:

#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()
Run Code Online (Sandbox Code Playgroud)

现在,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')    
Run Code Online (Sandbox Code Playgroud)

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

  • 您似乎与[官方文档](https://docs.python.org/3/howto/logging.html#advanced-logging-tutorial)相矛盾:“命名记录器时使用的一个很好的约定是使用模块-级别记录器,在每个使用日志记录的模块中,命名如下:`logger =logging.getLogger(__name__)`' (4认同)
  • 在 `import my_module` 之前不能运行 `logging.config.fileConfig('logging.ini')` 吗?正如[在这个答案中]所建议的(/sf/answers/1101079031/)。 (2认同)
  • 不确定 - 但以这种方式混合导入和可执行代码肯定也被认为是不好的做法。您也不希望您的客户在导入之前必须检查是否需要配置日志记录,尤其是当有一个简单的替代方案时!想象一下,如果像 requests 这样广泛使用的库已经做到了这一点......! (2认同)

Tom*_*mmy 6

扔进另一个解决方案.

在我的模块的主init中,我有类似的东西:

# 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
Run Code Online (Sandbox Code Playgroud)

然后在每个班级我需要一个记录器,我这样做:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)
Run Code Online (Sandbox Code Playgroud)

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


dee*_*ank 5

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

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
Run Code Online (Sandbox Code Playgroud)

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

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")
Run Code Online (Sandbox Code Playgroud)


dee*_*ank 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)
Run Code Online (Sandbox Code Playgroud)

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

  • 记录模块已经处理了单例.logging.getLogger("Hello")将在所有模块中获得相同的记录器. (11认同)