用户警告的堆栈跟踪

6
我在日志中看到了这样的警告信息:

我在日志中看到了这样的警告信息:

py.warnings.__init__: WARNING .../bs4/__init__.py:219: UserWarning: "foo" 
  looks like a filename, not markup. You should probably open this file 
  and pass the filehandle into Beautiful Soup

这条信息并没有提供太多帮助。

我想要查看发生问题的堆栈追踪。

请不要深入讨论此警告的内容。这个问题与Beautiful Soup无关 :-)

一个简单的解决方案是修改第三方代码(bs4/__init__.py 的第219行),并添加类似以下的内容:

import traceback
logger.warn('Exc at ...\n%s' % ''.join(traceback.format_stack()))

但我想避免这种情况。原因如下:

  • 这是来自生产系统的警告。我不想改变源代码。
  • 下次发生这样的警告时,我希望能立即看到堆栈跟踪。

是否有一个 Python 标志或设置可以更改,以便不仅可以看到一行,而且可以看到整个堆栈跟踪?我需要上面的框架来进行调试。

在此环境中使用 Python 2.7。

2个回答

3
您需要执行以下操作:
  1. Create if USER_SITE does not exists: issue python -c "import site; site._script()", see USER_SITE variable contents
  2. Place a file usercustomize.py in that directory with the following code:

    import traceback
    import warnings
    
    
    _old_warn = warnings.warn
    def warn(*args, **kwargs):
        tb = traceback.extract_stack()
        _old_warn(*args, **kwargs)
        print("".join(traceback.format_list(tb)[:-1]))
    warnings.warn = warn
    

    Credits to this answer for the code.

按照通常的方式运行代码。我的测试代码:

import warnings

def f():
    warnings.warn("foz")

f()

在操作之前:

$ python test_warn.py
test_warn.py:4: UserWarning: foz
  warnings.warn("foz")

之后:

$ python test_warn.py
<USER_SITE_REDACTED>/usercustomize.py:6: UserWarning: foz
  _old_warn(*args, **kwargs)
  File "test_warn.py", line 6, in <module>
    f()
  File "test_warn.py", line 4, in f
    warnings.warn("foz")

好的,我明白了。据我所知,这被称为猴子补丁。是的,我认为usercustomize.py是执行此类猴子补丁的好地方。谢谢。 - guettli
1
@guettli,这是猴子补丁。虽然这可能是使用它的合法情况,因为源代码不会被更改,并且干扰不会改变任何逻辑 - 只是有效地添加了更多日志记录。 - RebelWithoutAPulse

2

如果我想找到一个警告的根源,通常会将 Warnings 提升为 Exceptions

在你的情况下,你可以简单地使用 warnings.simplefilter 或者 warnings.filterwarnings

例如:

import warnings

def func():
    warnings.warn('abc', UserWarning)
    
def func2():
    func()
    
# Here I promote all UserWarnings to exceptions, but you could also use "warnings.filterwarnings"
# to only promote warnings from a specified module or matching a specified message.
# You may need to check which is most useful/appropriate for you.
warnings.simplefilter("error", UserWarning)   # you might need to reset this later :)
func2()

这将会给出完整的回溯信息:

---------------------------------------------------------------------------
UserWarning                               Traceback (most recent call last)
<ipython-input-11-be791e1071e7> in <module>()
      8 
      9 warnings.simplefilter("error", UserWarning)
---> 10 func2()

<ipython-input-11-be791e1071e7> in func2()
      5 
      6 def func2():
----> 7     func()
      8 
      9 warnings.simplefilter("error", UserWarning)

<ipython-input-11-be791e1071e7> in func()
      2 
      3 def func():
----> 4     warnings.warn('abc', UserWarning)
      5 
      6 def func2():

UserWarning: abc

如果您想调试这个问题,您可以轻松地挂接Python的pdb在最后遇到的异常上:

import pdb

pdb.pm()

导致:
> <ipython-input-11-be791e1071e7>(4)func()
-> warnings.warn('abc', UserWarning)
(Pdb) _________________

这将启动对最近遇到的异常的事后分析。这将使您能够浏览帧并检查变量等。
您还询问了一个标志,确实有一个标志可以启用“警告处理”,即 -W 标志。它非常类似于 warnings.filterwarnings 函数。为方便起见,我在此处复制了 -W 标志的文档:

Warning control. Python’s warning machinery by default prints warning messages to sys.stderr. A typical warning message has the following form:

file:line: category: message

By default, each warning is printed once for each source line where it occurs. This option controls how often warnings are printed.

Multiple -W options may be given; when a warning matches more than one option, the action for the last matching option is performed. Invalid -W options are ignored (though, a warning message is printed about invalid options when the first warning is issued).

Starting from Python 2.7, DeprecationWarning and its descendants are ignored by default. The -Wd option can be used to re-enable them.

Warnings can also be controlled from within a Python program using the warnings module.

The simplest form of argument is one of the following action strings (or a unique abbreviation) by themselves:

  • ignore

    Ignore all warnings.

  • default

    Explicitly request the default behavior (printing each warning once per source line).

  • all

    Print a warning each time it occurs (this may generate many messages if a warning is triggered repeatedly for the same source line, such as inside a loop).

  • module

    Print each warning only the first time it occurs in each module.

  • once

    Print each warning only the first time it occurs in the program.

  • error:

    Raise an exception instead of printing a warning message.

The full form of argument is:

action:message:category:module:line

Here, action is as explained above but only applies to messages that match the remaining fields. Empty fields match all values; trailing empty fields may be omitted. The message field matches the start of the warning message printed; this match is case-insensitive. The category field matches the warning category. This must be a class name; the match tests whether the actual warning category of the message is a subclass of the specified warning category. The full class name must be given. The module field matches the (fully-qualified) module name; this match is case-sensitive. The line field matches the line number, where zero matches all line numbers and is thus equivalent to an omitted line number.


我猜你的回答中没有解决这个问题:下一次出现这样的警告,我想立即看到堆栈跟踪。 - guettli
如果将它们提升为异常,您将立即看到回溯信息。或者我误解了您的问题? - MSeifert
我昨天误解了你的解决方案。现在我想我理解了。你的做法是“引发异常而不是打印警告信息”。但我想避免这种情况,因为警告会在生产环境中出现,而迄今为止在开发环境中无法重复。所以我想在这种情况下避免从警告切换到异常。 - guettli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接