配置第三方脚本的日志记录

时间:2015-04-30 08:17:59

标签: python logging

我有第三方python控制台脚本,我不想修改这个源代码。

但我想配置由脚本及其库完成的日志记录。该脚本使用标准的python日志记录,但不支持它的配置。

脚本使用此模式:

import logging
logger=logging.getLogger(__name__)

用例:

  • 我希望忽略文件foo.py的INFO消息。
  • 我想在记录消息中包含PID。

如果我不想修改控制台脚本的来源,如何配置日志记录?

脚本通过cron调用。

如果此脚本可以配置日志记录?

重要

创建像this answer这样的包装器脚本对我来说不是解决方案。

linux进程层次结构如下所示:

Cron -> third_party_script

应该有任何"胶水","包装"或"脏兮兮的" cron和third_party_script之间的脚本。

为什么要突兀/ netpicking?

我想练习"关注点分离"。我希望能够在一个地方配置一次记录。这个配置应该被virtualenv的所有python代码使用。编写包装器将是一种解决方法。我想要一个解决方案。

5 个答案:

答案 0 :(得分:7)

库不应该配置日志记录 - 这取决于应用程序开发人员。 Inbar Rose的答案不是完全正确。如果您引用的模块名为foo,则其__name__来电中对getLogger的引用将传入foo。因此,在您的配置代码中,您需要执行等效的

logging.getLogger('foo').setLevel(logging.WARNING)

要在日志中包含PID,只需确保为格式化程序使用适当的格式字符串,即包含%(process)d的字符串。一个简单的例子是:

logging.basicConfig(format='%(process)d %(message)s')

请注意,您无法同时从多个进程写入同一日志文件 - 如果要执行此操作,可能需要考虑an alternative approach

更新:应用程序开发人员是编写不是库的Python代码的人,但是可以通过例如用户或其他脚本通过命令行或其他创建Python进程的方法。

要使用我上面发布的代码,只要它是一个库,就不需要包装或修改第三方代码。例如,在调用第三方库的主脚本中:

if __name__ == '__main__':
    # configure logging here
    # sets the third party's logger to do WARNING or greater
    # replace 'foo' with whatever the top-level package name your
    # third party package uses
    logging.getLogger('foo').setLevel(logging.WARNING)
    # set any other loggers to use INFO or greater,
    # unless otherwise configured explicitly
    logging.basicConfig(level=logging.INFO, format='%(process)d %(message)s')
    # now call the main function (or else inline code here)
    main()

如果第三方代码通过cron运行,那么它不是库代码 - 它是一个应用程序,你可能运气不好。

答案 1 :(得分:6)

几个月前我问过这个问题。不幸的是,我没有得到满意的答案。

使用日志记录和设置日志之间的区别对我来说非常重要。

这是我的解决方案:在我们的上下文中,我们设置了一个在usercustomize.py中调用的方法的日志记录。

这样,可选插件可以使用日志记录而无需进行设置。

这几乎解决了我的所有需求。

到目前为止,我发现没有比usercustomize.py更好的方法了。我的完美解决方案是我称之为virtualenvcustomize.py的东西:如果解释器加载virtualenv,则会运行一些初始化代码。到目前为止,我找不到这样的钩子。如果您有解决方案,请告诉我。

答案 2 :(得分:5)

几种可能性:

<强>包装

如果你可以编辑你的cron表,你可以在python中创建一个小脚本来获取lib记录器,删除现有的日志处理程序并在其上挂钩自定义处理程序:

# Assumes the lib defines a logger object
from third_party_lib import *

# Note this assumes only one handler was defined by the lib
logger.removeHandler(logger.handlers[0])

# Then we can hook our custom format handler
custom_handler = logging.StreamHandler(sys.stdout)
custom_handler.setFormatter(logging.Formatter(format = '%(asctime)s %(levelname)s %(name)s %(process)d: %(message)s', None))
logger.addHandler(custom_handler)
logger.setLevel(logging.WARNING)

另外请记住,假设lib没有在路上重新声明记录器。

动态代码修改

如果您无法修改cron调用,则可以进行动态代码编辑,但这相当于手动编辑文件(hacky):

  • 获取包含记录器配置的第三方文件
  • 修改并保存修改后的版本
  • cron作业使用第三方代码启动任务
  • 执行cron作业后,将文件恢复到原始状态。

答案 3 :(得分:2)

您可以更改该记录器的最低日志级别。

logging.getLogger(__name__).setLevel(logging.WARNING)

现在,仅显示警告及以上。没有信息,也没有调查。

另外,您还可以更改格式。 %(process)d是PID。

log_format = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(process)d: %(message)s', '%H:%M:%S')
logging.getLogger(__name__).setFormatter(log_format)

所有在一起:

log_format = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(process)d: %(message)s', '%H:%M:%S')
log_handle = logging.getLogger(__name__)
log_handle.setLevel(logging.WARNING)
log_handle.setFormatter(log_format)

注意:您应该使用相关的日志处理程序替换代码中的__name__

答案 4 :(得分:1)

tl; dr

简而言之,我们要做的是在执行主代码之前注入由python解释器执行的代码。

实现此目标的最佳方法是创建一个virtualenv并添加 sitecustomize.py在virtualenv的网站程序包中。

演示

我们假设我们要运行的应用程序名为my_app.py, 记录器具有相同的名称。

$ cat my_app.py

import logging
logger = logging.getLogger("my_app")

logger.debug("A debug message")
logger.info("An info message")
logger.warning("A warning message")
logger.error("An error message")

运行my_app.py仅应显示严重性为> WARNING的消息(其中 是python日志记录中的默认行为)。

$ python my_app.py

A warning message
An error message

现在让我们创建一个虚拟环境

python3 -m venv my_venv

然后将sitecustomize.py添加到virtualenv的站点包中。

$ cat my_venv/lib/python3.7/site-packages/sitecustomize.py

import logging

# Setup logging for my_app
# We will only setup a console handler
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setFormatter(
    logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
)
logger.addHandler(ch)

现在让我们尝试使用virtualenv运行my_app.py

$ ./my_venv/bin/python my_app.py

2019-01-25 16:03:16,815 - my_app - DEBUG - A debug message
2019-01-25 16:03:16,815 - my_app - INFO - An info message
2019-01-25 16:03:16,815 - my_app - WARNING - A warning message
2019-01-25 16:03:16,815 - my_app - ERROR - An error message

仅此而已) 我们获得了正确的日志记录,而无需修改my_app.py或编写包装器!

现在,如果您想知道为什么这是最佳方法,请继续阅读。

(真的)很长的答案

在理解为什么使用virtualenv + sitecustomize.py是正确的方法之前 对于这个问题,我们需要做一个简短的介绍。

注意:我将假设您使用 venv模块使用 stdlib的site.py。的 virtuaelnv库,使用自己的库 site.py 可能在做事上略有不同。不过,看完这个答案之后 您应该能够检查venvvitualenv并了解如何处理它们。

什么是site-packages

简短的答案是site-packages是python安装第三方的地方 代码(如非stdlib代码)。有关更多信息,请阅读 this 以及提供的链接。

如何注入代码?

Python确实允许您在启动时自定义python解释器,即 在执行我们的主要代码/脚本/之前。例如,这可能很有用:

  • code-coverage
  • 配置文件
  • 并通常插入代码,就像我们需要解决的那样。

实现注入的方法是通过创建/修改sitecustomize.pyusercustomize.py。您还可以使用“路径配置文件”(即*.pth)文件 带有import语句,但由于以下原因,我在这里不介绍这种情况:

  • 这些导入语句感觉很糟糕
  • 我不相信与之相比,它具有任何真正的优势 sitecustomize / usercustomize
  • 我想让事情保持简单

无论如何,如果您需要更多信息WRT到路径配置文件,则可以检查 PyMOTW,如果需要 将它们与导入语句一起使用的示例,请检查此blog post

sitecustomizeusercustomize

因此sitecustomizeusercustomize是默认情况下不存在的特殊文件, 但是如果我们创建它们,python将在开始执行之前自动导入它们 我们的代码。我们可以创建以下文件:

  • 在全球站点软件包中(例如/usr/lib/python3.7/site-packages/
  • 或在用户网站包装上(例如~/.local/lib/python3.7/site-packages/

sitecustomize总是在usercustomize之前导入。如果缺少任何一个文件, ImportError被静默忽略。

为安全起见,如果用户或组ID与 有效ID,然后禁用了用户网站包 (source)。 而且,python解释器的CLI参数为completely disable site-packages(系统和用户 个)或disable user site-packages。假如说 我们没有ID不匹配,并且我们不使用任何CLI标志,因此用户 站点包的优先级高于系统站点包。因此,如果我们同时拥有:

~/.local/lib/python3.7/site-packages/sitecustomize.py
/usr/lib/python3.7/site-packages/sitecustomize.py

第一个是将要导入的。我们实际上可以检查sys.path 通过执行site.py模块来获得优先级:

$ python3 -msite

sys.path = [
    '/tmp/test',
    '/usr/lib/python37.zip',
    '/usr/lib/python3.7',
    '/usr/lib/python3.7/lib-dynload',
    '/home/username/.local/lib/python3.7/site-packages',      # user site-packages
    '/usr/lib/python3.7/site-packages',                       # system site-packages
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True

此处的重要信息是ENABLE_USER_SITE的值。如果是True,则 用户站点包已启用。如果为False,那么我们只能使用全局站点包。 例如。如果我们使用python -s

$ python3 -s -msite

sys.path = [
    '/tmp/test',
    '/usr/lib/python37.zip',
    '/usr/lib/python3.7',
    '/usr/lib/python3.7/lib-dynload',
    '/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False

请注意,在这种情况下,ENABLE_USER_SITEFalse

出于完整性考虑,让我们完全禁用站点包:

$ python3 -S -msite

sys.path = [
    '/tmp/test',
    '/usr/lib/python37.zip',
    '/usr/lib/python3.7',
    '/usr/lib/python3.7/lib-dynload',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: None

实验

为了更好地理解这一点,让我们做一个实验。首先让我们创建 系统站点和用户站点软件包中的usercustomize sitecustomize个模块。

警告:我们将在系统站点程序包中创建文件。这将成为;这将是 干扰您的python发行版。 注意记住,将其删除 他们完成后。

# system site packages
echo 'print(f"-> {__file__}")' | sudo tee /usr/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | sudo tee /usr/lib/python3.7/site-packages/sitecustomize.py

# user site packages
echo 'print(f"-> {__file__}")' | tee ~/.local/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ~/.local/lib/python3.7/site-packages/sitecustomize.py

我们还要创建一个python模块:

echo 'print("Inside foo")' | tee foo.py

现在让我们执行foo.py

$ python3 foo.py

-> /home/username/.local/lib/python3.7/site-packages/sitecustomize.py
-> /home/username/.local/lib/python3.7/site-packages/usercustomize.py
Inside foo

我们可以看到:

  • 两者 sitecustomizeusercustomize正在导入
  • 它们是从用户站点软件包
  • 导入的

现在,如果我们禁用用户网站包,会发生什么?

$ python3 -s foo.py

-> /usr/lib/python3.7/site-packages/sitecustomize.py
Inside foo

这一次我们看到我们:

    仅导入
  • sitecustomize。即使usercustomize存在于 系统站点软件包python不会导入它!这个很重要!记住什么时候 我们讨论virtualenvs! (提示:这与ENABLE_USER_SITE有关;您还记得吗 在这种情况下有什么价值?)
  • sitecustomize正在从系统站点程序包中导入

最后,如果我们完全禁用站点软件包,显然usercustomizesitecustomize将被忽略:

$ python3 -S foo.py

Inside foo

什么是virtualenvs?

好的,现在我们也将virtualenv引入游戏中。有两种类型的virtualenv:

  • 普通人
  • --system-site-packages创建的人。

让我们创建两种类型的虚拟环境

python3 -mvenv venv_no_system
python3 -mvenv venv_system

让我们还将sitecustomize.pyusercustomize.py模块创建到 virtualenv的网站软件包:

echo 'print(f"-> {__file__}")' | tee ./venv_no_system/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_no_system/lib/python3.7/site-packages/sitecustomize.py

echo 'print(f"-> {__file__}")' | tee ./venv_system/lib/python3.7/site-packages/usercustomize.py
echo 'print(f"-> {__file__}")' | tee ./venv_system/lib/python3.7/site-packages/sitecustomize.py

让我们看一下区别:

$ ./venv_no_system/bin/python -msite

/tmp/test/venv_no_system/lib/python3.7/site-packages/sitecustomize.py
sys.path = [
    '/tmp/test',
    '/usr/lib/python37.zip',
    '/usr/lib/python3.7',
    '/usr/lib/python3.7/lib-dynload',
    '/tmp/test/venv_no_system/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False

我们在这里看到了什么?在 normal virtualenv的ENABLE_USER_SITE上是False, 这意味着:

  1. 用户站点包被忽略
  2. sitecustomize正在导入!即我们不能使用usercustomize来 注入代码!

我们还可以看到,virtualenv代替了我们的全局站点软件包 拥有一个(即/tmp/test/venv_no_system/lib/python3.7/site-packages)。

现在让我们重复一次,但这一次是使用使用系统的virtualenv 网站软件包:

$ ./venv_system/bin/python -msite

-> /home/username/.local/lib/python3.7/site-packages/sitecustomize.py
-> /home/username/.local/lib/python3.7/site-packages/usercustomize.py
sys.path = [
    '/tmp/test',
    '/usr/lib/python37.zip',
    '/usr/lib/python3.7',
    '/usr/lib/python3.7/lib-dynload',
    '/tmp/test/venv_system/lib/python3.7/site-packages',
    '/home/username/.local/lib/python3.7/site-packages',
    '/usr/lib/python3.7/site-packages',
]
USER_BASE: '/home/username/.local' (exists)
USER_SITE: '/home/username/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True

在这种情况下,行为是不同的...

ENABLE_USER_SITETrue,表示:

  1. 已启用用户网站包
  2. usercustomize正在正常导入。

但是还有另外一个区别。在这种情况下,我们有 3 个网站程序包 目录。 virtualenv是优先级更高的那个,其次是 用户站点包,而系统站点包是最后一个。

那该怎么用?

我认为有三种选择:

  1. 使用系统python安装
  2. 使用常规的virtualenv
  3. 使用带有系统站点软件包的virtualenv

我认为在大多数情况下,例如在普通服务器/桌面上,进行修改 通常应避免安装系统python。至少在* nix上,太多 事情取决于Python。我真的很不愿意改变它的行为。可能 短暂的或静态的“系统”(例如,在容器内部)都是例外。

就虚拟环境而言,除非我们知道我们将需要系统 网站套件,我认为坚持目前的做法并使用正常 那些。如果我们坚持这一点,那么为了在脚本获得之前注入代码 执行,我们只有一个选择:

  

要在virtuaelenv的站点程序包中添加sitecustomize.py

清理

# remove the virtualenvs
rm -rf my_venv
rm -rf venv_system
rm -rf venv_no_system

# remove our usercustomize.py and sitecustomize.py
sudo rm /usr/lib/python3.7/site-packages/sitecustomize.py
sudo rm /usr/lib/python3.7/site-packages/usercustomize.py
rm ~/.local/lib/python3.7/site-packages/sitecustomize.py
rm ~/.local/lib/python3.7/site-packages/usercustomize.py

# remove the modules
rm foo.py
rm my_app.py