30

今天我在考虑我一年前写的一个 Python 项目,我在该项目中使用logging得非常广泛。hotshot我记得由于开销(表明这是我最大的瓶颈之一),不得不在类似内部循环的场景(90% 的代码)中注释掉很多日志调用。

我现在想知道是否有一些规范的方法可以以编程方式去除 Python 应用程序中的日志记录调用,而无需一直注释和取消注释。我认为您可以使用检查/重新编译或字节码操作来执行此类操作,并且仅针对导致瓶颈的代码对象。这样,您可以添加一个操纵器作为编译后步骤并使用集中配置文件,如下所示:

[Leave ERROR and above]
my_module.SomeClass.method_with_lots_of_warn_calls

[Leave WARN and above]
my_module.SomeOtherClass.method_with_lots_of_info_calls

[Leave INFO and above]
my_module.SomeWeirdClass.method_with_lots_of_debug_calls

当然,您可能希望谨慎地使用它,并且可能以每个函数的粒度- 仅用于已显示logging为瓶颈的代码对象。有人知道这样的事情吗?

注意:由于动态类型和后期绑定,有一些事情使得以高性能方式执行此操作更加困难。例如,对名为的方法的任何调用都debug可能必须用if not isinstance(log, Logger). 无论如何,我假设所有的小细节都可以通过君子协议或一些运行时检查来克服。:-)

4

10 回答 10

22

使用logging.disable怎么样?

我还发现如果创建日志消息的成本很高,我必须使用logging.isEnabledFor 。

于 2009-02-08T17:33:41.710 回答
5

使用pypreprocessor

也可以在PYPI(Python 包索引)上找到并使用 pip 获取。

这是一个基本的用法示例:

from pypreprocessor import pypreprocessor

pypreprocessor.parse()

#define nologging

#ifdef nologging
...logging code you'd usually comment out manually...
#endif

本质上,预处理器以您之前手动执行的方式注释掉代码。它只是根据您的定义有条件地即时执行。

您还可以通过在 import 和 parse() 语句之间添加“pypreprocessor.removeMeta = True”来删除所有预处理器指令并从后处理代码中注释掉代码。

字节码输出 (.pyc) 文件将包含优化的输出。

旁注:pypreprocessor 与 python2x 和 python3k 兼容。

免责声明:我是 pypreprocessor 的作者。

于 2011-03-16T03:36:10.827 回答
4

我也看到过以这种方式使用的断言。

assert logging.warn('disable me with the -O option') is None

(我猜警告总是不返回。如果没有,你会得到一个 AssertionError

但实际上这只是一种有趣的方式:

if __debug__: logging.warn('disable me with the -O option')

当您使用 -O 选项运行包含该行的脚本时,该行将从优化的 .pyo 代码中删除。相反,如果您有自己的变量,如下所示,您将有一个始终执行的条件(无论变量是什么值),尽管条件应该比函数调用执行得更快:

my_debug = True
...
if my_debug: logging.warn('disable me by setting my_debug = False')

所以如果我对调试的理解是正确的,这似乎是摆脱不必要的日志调用的好方法。另一方面是它还会禁用所有断言,因此如果您需要断言,这是一个问题。

于 2009-04-04T07:22:20.253 回答
2

作为一个不完美的捷径,使用MiniMock 之logging类的东西在特定模块中模拟怎么样?

例如,如果my_module.py是:

import logging
class C(object):
    def __init__(self, *args, **kw):
        logging.info("Instantiating")

您将使用以下内容替换您的使用my_module

from minimock import Mock
import my_module
my_module.logging = Mock('logging')
c = my_module.C()

在模块的初始导入之前,您只需要这样做一次。

logging.getLogger通过模拟特定方法或返回一个模拟对象,其中一些方法无效而其他方法委托给真实logging模块,获得特定于级别的行为会很简单。

在实践中,您可能希望用更简单、更快的东西替换 MiniMock;至少不会将使用情况打印到标准输出的东西!当然,这并不能解决模块 Alogging从模块 B 导入的问题(因此 A 也导入了 B 的日志粒度)......

这永远不会像根本不运行日志语句那样快,但应该比一直深入到日志模块的深处发现根本不应该记录该记录要快得多。

于 2009-02-07T00:27:15.963 回答
1

你可以尝试这样的事情:

# Create something that accepts anything
class Fake(object):
    def __getattr__(self, key):
        return self
    def __call__(self, *args, **kwargs):
        return True

# Replace the logging module
import sys
sys.modules["logging"] = Fake()

Fake它本质上用一个简单地接受任何东西的实例替换(或最初填充)日志记录模块的空间。在尝试在任何地方使用日志记录模块之前,您必须运行上述代码(只需一次!)。这是一个测试:

import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)-8s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/temp/myapp.log',
                    filemode='w')
logging.debug('A debug message')
logging.info('Some information')
logging.warning('A shot across the bows')

如上所述,正如所料,根本没有记录任何内容。

于 2009-02-07T01:30:41.780 回答
1

我会使用一些花哨的日志装饰器,或者其中的一堆:

def doLogging(logTreshold):
    def logFunction(aFunc):
        def innerFunc(*args, **kwargs):
            if LOGLEVEL >= logTreshold:
                print ">>Called %s at %s"%(aFunc.__name__, time.strftime("%H:%M:%S"))
                print ">>Parameters: ", args, kwargs if kwargs else "" 
            try:
                return aFunc(*args, **kwargs)
            finally:
                print ">>%s took %s"%(aFunc.__name__, time.strftime("%H:%M:%S"))
        return innerFunc
    return logFunction

您只需要在每个模块中声明 LOGLEVEL 常量(或者只是全局声明并在所有模块中导入它),然后您可以像这样使用它:

@doLogging(2.5)
def myPreciousFunction(one, two, three=4):
    print "I'm doing some fancy computations :-)"
    return

如果 LOGLEVEL 不小于 2.5,您将获得如下输出:

>>Called myPreciousFunction at 18:49:13
>>Parameters:  (1, 2) 
I'm doing some fancy computations :-)
>>myPreciousFunction took 18:49:13

如您所见,需要做一些工作才能更好地处理 kwargs,因此如果存在默认值,将打印它们,但这是另一个问题。

您可能应该使用一些logger模块而不是原始print语句,但我想专注于装饰器的想法并避免使代码太长。

无论如何 - 使用这样的装饰器,您可以获得功能级日志记录,任意多个日志级别,易于应用到新功能,并且禁用日志记录您只需要设置 LOGLEVEL。如果您愿意,您可以为每个函数定义不同的输出流/文件。您可以将 doLogging 编写为:

 def doLogging(logThreshold, outStream=sys.stdout):
      .....
      print >>outStream, ">>Called %s at %s" etc.

并利用基于每个功能定义的日志文件。

于 2009-02-07T17:59:32.550 回答
1

这也是我的项目中的一个问题——记录最终会非常一致地记录在分析器报告中。

我之前在 PyFlakes ( http://github.com/kevinw/pyflakes ) 的一个分支中使用了 _ast 模块......而且绝对可以按照你在问题中的建议去做——在之前检查和注入警卫调用日志记录方法(您承认必须进行一些运行时类型检查)。有关简单示例,请参见http://pyside.blogspot.com/2008/03/ast-compilation-from-python.html

编辑:我刚刚在我的 planetpython.org 提要上注意到MetaPython——示例用例是在导入时删除日志语句。

也许最好的解决方案是让某人将日志记录重新实现为 C 模块,但我不会是第一个抓住这样一个......机会的人:p

于 2009-03-06T22:38:39.300 回答
1

:-) 我们曾经称它为预处理器,尽管 C 的预处理器具有其中的一些功能,但“山中之王”是 IBM 大型机 PL/I 的预处理器。它在预处理器中提供了广泛的语言支持(完全赋值、条件、循环等),并且可以仅使用 PL/I PP 编写“编写程序的程序”。

我编写了许多具有成熟的复杂程序和数据跟踪的应用程序(当时我们没有用于后端进程的合适调试器)用于开发和测试,然后在使用适当的“运行时标志”编译时只需干净地剥离所有跟踪代码,而不会影响性能。

我认为装饰器的想法是一个好主意。您可以编写一个装饰器来包装需要记录的函数。然后,对于运行时分发,装饰器变成了“无操作”,消除了调试语句。

乔恩·R

于 2010-05-11T03:43:26.800 回答
1

我目前正在做一个项目,该项目使用大量日志记录来测试使用 Pandas 库的数据分析 API 的逻辑和执行时间。

我发现这个字符串有类似的问题 - 例如,即使 logging.basicConfig 级别设置为 level=logging.WARNING,logging.debug 语句的开销是多少

我已经编写了以下脚本来在部署之前注释掉或取消注释调试日志:

import os
import fileinput

comment = True

# exclude files or directories matching string
fil_dir_exclude = ["__","_archive",".pyc"]

if comment :
    ## Variables to comment
    source_str = 'logging.debug'
    replace_str = '#logging.debug'
else :
    ## Variables to uncomment
    source_str = '#logging.debug'
    replace_str = 'logging.debug'

# walk through directories
for root, dirs, files in os.walk('root/directory') :
    # where files exist
    if files:
        # for each file
        for file_single in files :
            # build full file name
            file_name = os.path.join(root,file_single)
            # exclude files with matching string
            if not any(exclude_str in file_name for exclude_str in fil_dir_exclude) :
                # replace string in line
                for line in fileinput.input(file_name, inplace=True):
                    print "%s" % (line.replace(source_str, replace_str)),

这是一个文件递归,它根据条件列表排除文件,并根据此处找到的答案执行就地替换:Search and replace a line in a file in Python

于 2014-06-09T12:35:04.693 回答
0

我喜欢 'if __debug_' 解决方案,只是把它放在每次调用之前有点分散注意力和丑陋。我遇到了同样的问题,并通过编写一个脚本来克服它,该脚本自动解析您的源文件并用 pass 语句替换日志语句(并注释掉日志语句的副本)。它还可以撤消此转换。

当我将新代码部署到生产环境中时,我会使用它,因为在生产环境中有很多我不需要的日志语句并且它们正在影响性能。

你可以在这里找到脚本:http: //dound.com/2010/02/python-logging-performance/

于 2010-02-07T21:16:58.140 回答