我有第三方python控制台脚本,我不想修改这个源代码。
但我想配置由脚本及其库完成的日志记录。该脚本使用标准的python日志记录,但不支持它的配置。
脚本使用此模式:
import logging
logger=logging.getLogger(__name__)
用例:
如果我不想修改控制台脚本的来源,如何配置日志记录?
脚本通过cron
调用。
如果此脚本可以配置日志记录?
创建像this answer这样的包装器脚本对我来说不是解决方案。
linux进程层次结构如下所示:
Cron -> third_party_script
应该有任何"胶水","包装"或"脏兮兮的" cron和third_party_script
之间的脚本。
我想练习"关注点分离"。我希望能够在一个地方配置一次记录。这个配置应该被virtualenv的所有python代码使用。编写包装器将是一种解决方法。我想要一个解决方案。
答案 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):
答案 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)
简而言之,我们要做的是在执行主代码之前注入由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
可能在做事上略有不同。不过,看完这个答案之后
您应该能够检查venv
与
vitualenv
并了解如何处理它们。
site-packages
简短的答案是site-packages
是python安装第三方的地方
代码(如非stdlib代码)。有关更多信息,请阅读
this
以及提供的链接。
Python确实允许您在启动时自定义python解释器,即 在执行我们的主要代码/脚本/之前。例如,这可能很有用:
实现注入的方法是通过创建/修改sitecustomize.py
或
usercustomize.py
。您还可以使用“路径配置文件”(即*.pth
)文件
带有import语句,但由于以下原因,我在这里不介绍这种情况:
sitecustomize
/ usercustomize
。无论如何,如果您需要更多信息WRT到路径配置文件,则可以检查 PyMOTW,如果需要 将它们与导入语句一起使用的示例,请检查此blog post。
sitecustomize
和usercustomize
因此sitecustomize
和usercustomize
是默认情况下不存在的特殊文件,
但是如果我们创建它们,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_SITE
是False
。
出于完整性考虑,让我们完全禁用站点包:
$ 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
我们可以看到:
sitecustomize
和usercustomize
正在导入现在,如果我们禁用用户网站包,会发生什么?
$ python3 -s foo.py
-> /usr/lib/python3.7/site-packages/sitecustomize.py
Inside foo
这一次我们看到我们:
sitecustomize
。即使usercustomize
存在于
系统站点软件包python不会导入它!这个很重要!记住什么时候
我们讨论virtualenvs! (提示:这与ENABLE_USER_SITE
有关;您还记得吗
在这种情况下有什么价值?)sitecustomize
正在从系统站点程序包中导入最后,如果我们完全禁用站点软件包,显然usercustomize
和
sitecustomize
将被忽略:
$ python3 -S foo.py
Inside foo
好的,现在我们也将virtualenv引入游戏中。有两种类型的virtualenv:
--system-site-packages
创建的人。让我们创建两种类型的虚拟环境
python3 -mvenv venv_no_system
python3 -mvenv venv_system
让我们还将sitecustomize.py
和usercustomize.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
,
这意味着:
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_SITE
是True
,表示:
usercustomize
正在正常导入。但是还有另外一个区别。在这种情况下,我们有 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