有谁知道任何具有良好日志记录实现的模块的好例子?
\n\n我一直在用几种不同的方式进行日志记录,但我不确定哪种方式最Pythonic。
\n\n对于脚本,这就是我一直在做的事情:
\n\nimport logging\n\nLOGGER = logging.getLogger(__program__)\nSTREAM = logging.StreamHandler()\nFORMATTER = logging.Formatter('(%(name)s) %(levelname)s: %(message)s')\nSTREAM.setFormatter(FORMATTER)\nLOGGER.addHandler(STREAM)\n\ndef main():\n LOGGER.warning('This is a warning message.')\nRun Code Online (Sandbox Code Playgroud)\n\n这是在全局命名空间中执行的,我可以调用LOGGER从任何地方调用。
对于模块来说,上述解决方案并不是一个好主意,因为代码是在导入时执行的。所以对于模块,我一直称之为_logging()函数来设置日志记录。
def _logging():\n import logging\n\n global logger\n logger = logging.getLogger(__program__)\n stream = logging.StreamHandler()\n formatter = logging.Formatter('(%(name)s) %(levelname)s: %(message)s')\n stream.setFormatter(formatter)\n logger.addHandler(stream)\n\ndef main():\n _logging()\n logger.warning('This is a warning message.')\nRun Code Online (Sandbox Code Playgroud)\n\n由于logger是全局的,我可以在任何需要的地方调用它。然而,pylint会发出全局变量未定义的警告。它被定义为全局变量记录器 在模块级别未定义 当通过 \xe2\x80\x9cglobal\xe2\x80\x9d 语句定义变量但该变量未在模块范围中定义时使用但我不太确定时使用为什么这是一个问题。
或者我应该打电话给_logger()尽早调用该函数(减去全局函数),然后在需要的地方创建记录器?
def _logging():\n import logging\n\n logger = logging.getLogger(__program__)\n stream = logging.StreamHandler()\n formatter = logging.Formatter('(%(name)s) %(levelname)s: %(message)s')\n stream.setFormatter(formatter)\n logger.addHandler(stream)\n\ndef main():\n _logging()\n logger = logging.getLogger(__program__)\n logger.warning('This is a warning message.')\nRun Code Online (Sandbox Code Playgroud)\n\n最后一种技术似乎是最干净的,尽管是最乏味的,特别是因为我经常从几十个小类、函数、方法等中进行日志记录。是否有已经在这个领域开辟道路的人员/模块的例子?
\n如果我理解正确的话,您将分别在每个模块中配置日志记录。这是不必要的,也不符合logging模块的设计。
我认为日志记录的关键是理解logging模块是 Python 进程中的一个有状态对象。至少对我来说,在这种洞察之后,在大多数情况下只有一种明显的方法来进行日志记录。
您应该在程序开始时配置日志记录。定义处理程序、格式化程序等,只要没有显式覆盖,配置就会保留在整个程序中。
所有进行日志记录的模块都可以在logging导入后立即定义一个全局记录器。无需将其放入函数中。正如文档还建议的那样,最好根据模块名称(包括包路径)命名每个记录器:
import logging
logger = logging.getLogger(__name__)
Run Code Online (Sandbox Code Playgroud)
了解程序中的记录器形成层次结构也很重要。默认情况下,记录器将记录传播给其父级。这意味着底部有一个记录器(根)可以获取所有记录,除非您配置一些记录器来防止这种情况发生。通常,仅配置根记录器就足够了。
更具体一点,让我们编写一个包含两个模块的程序,one.py和two.py。 one.py包含一个函数main,该函数将作为程序的入口点。我们将使用dictConfig配置日志记录,这使我们可以将日志记录配置与其余代码很好地分开。我们将配置字典放在一个单独的 YAML 文件中,如下所示:
# logging_config.yaml
version: 1
formatters:
brief:
format: '%(message)s'
default:
format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
handlers:
console:
class: logging.StreamHandler
formatter: brief
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
formatter: default
filename: example.log
maxBytes: 1024
backupCount: 3
loggers:
two:
level: INFO
handlers: [file]
propagate: False
root:
level: INFO
handlers: [console]
Run Code Online (Sandbox Code Playgroud)
这些片段大部分改编自文档。在此配置中,我们定义将来自记录器的 INFO 级别以上的所有内容two记录到文件中。来自的记录two也不会进一步传播。如果根记录器高于 INFO 级别,则所有到达根记录器的内容都会被输入到控制台。
模块的定义one可以是这样的:
# one.py
import logging
import logging.config
import yaml
def configure_logging(filename):
with open(filename) as f:
config = yaml.load(f)
logging.config.dictConfig(config)
def main():
configure_logging('logging_config.yaml')
from two import func
logger = logging.getLogger(__name__)
logger.info('Starting the program')
func()
logger.info('Finished')
Run Code Online (Sandbox Code Playgroud)
这里一个棘手的细节是,我们two仅在设置配置后才导入模块并定义记录器。这样做是因为默认情况下dictConfig禁用所有现有记录器。
最后,这是 module 的定义two:
# two.py
import logging
logger = logging.getLogger(__name__)
def func():
logger.info('Doing stuff')
Run Code Online (Sandbox Code Playgroud)
现在,如果我们运行该程序,我们会得到以下输出:
>>> import one
>>> one.main()
Starting the program
Finished
Run Code Online (Sandbox Code Playgroud)
日志文件example.log包含以下行:
2015-06-07 15:04:15 INFO two Doing stuff
Run Code Online (Sandbox Code Playgroud)
优秀的日志记录示例可以在 Python 文档中找到: