简体   繁体   English

Python HTTP 请求和调试级别记录到日志文件

[英]Python HTTP request and debug level logging to the log file

I have a hard time to get DEBUG level logs in a log file for HTTP request like those from console like:我很难在 HTTP 请求的日志文件中获取 DEBUG 级别日志,例如来自控制台的请求:

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): URI:443
DEBUG:urllib3.connectionpool:URL:443 "POST /endpoint HTTP/1.1" 200 None

for the code below:对于下面的代码:

import logging
from logging.handlers import TimedRotatingFileHandler
_logger = logging.getLogger(__name__)

    def setup_logging(loglevel):
        logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")

        if loglevel is not None:
            if loglevel == 10:
                  http.client.HTTPConnection.debuglevel = 1
            logformat = "%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s"
            logging.basicConfig(level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S")

        fileHandler = logging.handlers.TimedRotatingFileHandler("{0}/{1}.log".format(logPath, logFileName), when="midnight")
        fileHandler.setFormatter(logFormatter)
        _logger.setLevel(logging.DEBUG)
        _logger.addHandler(fileHandler)

When I will call it with logging.DEBUG log file will only contains whatever I will specify in the code as _logger.info or _logger.debug nothing similar like for the console log output.当我使用logging.DEBUG调用它时,日志文件将只包含我将在代码中指定为_logger.info_logger.debug的任何内容,与控制台日志 output 类似。

PS. PS。 Example code how do I call it:示例代码如何调用它:

def main(args):
    args = parse_args(args)
    cfg = config(args.env)
    setup_logging(logging.DEBUG, cfg)
    requests.get("https://stackoverflow.com/a/58769712/100297")

You are adding your handlers and level changes in the wrong place.您在错误的位置添加处理程序和级别更改。

The Python logging module treats logger objects as existing in a hierarchy, based on their name and the presence of . Python 日志记录模块将记录器对象视为存在于层次结构中,基于它们的名称和. delimiters in those names.这些名称中的分隔符。 The name "foo.bar.baz" for a logger is logically placed as child of foo.bar and of foo , should they exist.记录器的名称"foo.bar.baz"在逻辑上被放置为foo.barfoo的子项,如果它们存在的话。 At the base of the hierarchy is the root logger, which has no name.层次结构的基础是根记录器,它没有名称。 You access it with logging.getLogger() (no arguments, although '' and None also would work).您可以使用logging.getLogger()访问它(没有 arguments,尽管''None也可以)。

Now, when logging messages, first the message has to pass the effective level of the logger.现在,在记录消息时,首先消息必须通过记录器的有效级别 If they passed that the messages are passed to the handlers on every logger from the current logger down to the root, provided they clear the level of each handler found.如果他们通过了,则消息将传递给从当前记录器到根的每个记录器上的处理程序,前提是他们清除找到的每个处理程序的级别。

To find the effective level, the hierarchy is traversed to find the nearest logger object with a level set;为了找到有效级别,遍历层次结构以找到最近的具有级别集的记录器 object; if there are none then messages always pass.如果没有,则消息总是通过。 When traversing the hierarchy to find handlers, a log object can block propagation ( propagate is set to False ), at which point traversing stops.当遍历层次结构以查找处理程序时,日志 object 可以阻止传播( propagate设置为False ),此时遍历停止。

When you are trying to handle messages for urllib3.connectionpool() , you need to put a handler on one of three locations: the logger for urllib3.connectionpool , for urllib3 or the root logger.当您尝试处理urllib3.connectionpool()的消息时,您需要将处理程序放在三个位置之一: urllib3.connectionpool的记录器、 urllib3或根记录器。 Your code doesn't do that .您的代码没有这样做

You instead set up your handlers on your own logger with a different name:相反,您可以在自己的记录器上使用不同的名称设置处理程序:

_logger = logging.getLogger(__name__)

That is guaranteed to not match the root logger ( __name__ would need to be empty, that's never the case) nor the urllib3 or urllib3.connectionpool loggers (which would mean your module is also called urllib3 or urllib3.connectionpool) .这保证不匹配根记录器( __name__需要为空,绝不是这种情况)也不urllib3urllib3.connectionpool记录器(这意味着您的模块也称为urllib3urllib3.connectionpool)

Because it is not in the path that urllib3.connectionpool log messages would follow, your handlers are never going to be given those messages.因为它不在urllib3.connectionpool日志消息将遵循的路径中,所以您的处理程序永远不会收到这些消息。

Instead, you want to configure the root logger :相反,您要配置根记录器

fileHandler = logging.handlers.TimedRotatingFileHandler("{0}/{1}.log".format(logPath, logFileName), when="midnight")
fileHandler.setFormatter(logFormatter)
root = logging.getLogger()
root.setLevel(logging.DEBUG)
root.addHandler(fileHandler)

You could just set the log level of each handler to a log level you want to see on that handler, rather than on the root logger (or in addition to the root logger).您可以将每个处理程序的日志级别设置为您希望在该处理程序上看到的日志级别,而不是在根记录器上(或除了根记录器之外)。 Remember that the level set on the root logger is used to determine the effective level of other loggers in the hierarchy that don't have a level set directly and that the effective level works as a 'high water mark' for all messages.请记住,根记录器上设置的级别用于确定层次结构中没有直接设置级别的其他记录器的有效级别,并且有效级别用作所有消息的“高水位标记”。 If the root logger is set to INFO , and no other handlers are configured, then your handlers will never see DEBUG messages.如果根记录器设置为INFO ,并且没有配置其他处理程序,那么您的处理程序将永远不会看到DEBUG消息。 The default level for the root logger is WARNING .根记录器的默认级别是WARNING

You usually only ever want to use named loggers in your modules to produce log messages , and put all your handlers on the root.您通常只想在模块中使用命名记录器来生成日志消息,并将所有处理程序放在根目录上。 Anything else is specialised, 'advanced' use of the logger module (eg a dedicated, separate handler just for the urllib3 log messages, or silencing a whole package by setting their lowest-level logger object to propagate = False ).其他任何东西都是专门的,“高级”使用记录器模块(例如,专用的、单独的处理程序仅用于urllib3日志消息,或者通过将其最低级别的记录器 object 设置为propagate = False来使整个 package 静音)。

Finally, logging.basicConfig() configures the root logger too, but only if there are no handlers on the root logger already.最后, logging.basicConfig()也配置了根记录器,前提是根记录器上已经没有处理程序。 If you use force=True then basicConfig() will remove all existing handlers, then configure the handlers on the root.如果您使用force=True那么basicConfig()将删除所有现有的处理程序,然后在根上配置处理程序。 It'll always create a Formatter() instance and set it on all handlers.它总是会创建一个Formatter()实例并将其设置在所有处理程序上。

You could use basicConfig() for all your root logger needs:您可以使用basicConfig()来满足所有根记录器的需求:

import http.client
import logging
import os.path
import sys
from logging.handlers import TimedRotatingFileHandler

def setup_logging(loglevel):
    # the file handler receives all messages from level DEBUG on up, regardless
    fileHandler = TimedRotatingFileHandler(
        os.path.join(logPath, logFileName + ".log"),
        when="midnight"
    )
    fileHandler.setLevel(logging.DEBUG)
    handlers = [fileHandler]

    if loglevel is not None:
        # if a log level is configured, use that for logging to the console
        stream_handler = logging.StreamHandler(sys.stdout)
        stream_handler.setLevel(loglevel)
        handlers.append(stream_handler)

    if loglevel == logging.DEBUG:
        # when logging at debug level, make http.client extra chatty too
        # http.client *uses `print()` calls*, not logging.
        http.client.HTTPConnection.debuglevel = 1

    # finally, configure the root logger with our choice of handlers
    # the logging level of the root set to DEBUG (defaults to WARNING otherwise).
    logformat = "%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s"
    logging.basicConfig(
        format=logformat, datefmt="%Y-%m-%d %H:%M:%S",
        handlers=handlers, level=logging.DEBUG
    )

Side note: the http.client library does not use logging to output debug messages;旁注: http.client不使用logging到 output 调试消息; it will always use print() to output those.它将始终使用print()到 output 那些。 If you want to see those messages appear in your logs, you'll need to monkey-patch the library, adding in an alternative print() global:如果你想看到这些消息出现在你的日志中,你需要对库进行猴子补丁,添加一个替代的print()全局:

import http.client
import logging

http_client_logger = logging.getLogger("http.client")

def print_to_log(*args):
    http_client_logger.debug(" ".join(args)) 

# monkey-patch a `print` global into the http.client module; all calls to
# print() in that module will then use our print_to_log implementation
http.client.print = print_to_log

With the above http.client trick applied and setup_logging(logging.DEBUG) , I see the following logs appear both on stdout and in the file when I use requests.get("https://stackoverflow.com/a/58769712/100297") :使用上述http.client技巧和setup_logging(logging.DEBUG) ,当我使用requests.get("https://stackoverflow.com/a/58769712/100297") ) 时,我看到以下日志出现在stdout和文件中requests.get("https://stackoverflow.com/a/58769712/100297") :

2019-11-08 16:17:26 [MainThread  ] [DEBUG]  Starting new HTTPS connection (1): stackoverflow.com:443
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  send: b'GET /a/58769712/100297 HTTP/1.1\r\nHost: stackoverflow.com\r\nUser-Agent: python-requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n'
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  reply: 'HTTP/1.1 302 Found\r\n'
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Cache-Control: private
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Type: text/html; charset=utf-8
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Location: /questions/58738195/python-http-request-and-debug-level-logging-to-the-log-file/58769712#58769712
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Frame-Options: SAMEORIGIN
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Request-Guid: 761bd2f8-3e5c-453a-ab46-d01284940541
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Strict-Transport-Security: max-age=15552000
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Feature-Policy: microphone 'none'; speaker 'none'
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Security-Policy: upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Accept-Ranges: bytes
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Age: 0
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Length: 214
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Accept-Ranges: bytes
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Date: Fri, 08 Nov 2019 16:17:27 GMT
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Via: 1.1 varnish
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Age: 0
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Connection: keep-alive
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Served-By: cache-lhr7324-LHR
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Cache: MISS
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Cache-Hits: 0
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Timer: S1573229847.069848,VS0,VE80
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Vary: Fastly-SSL
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-DNS-Prefetch-Control: off
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Set-Cookie: prov=0e92634f-abce-9f8e-1865-0d35ebecc595; domain=.stackoverflow.com; expires=Fri, 01-Jan-2055 00:00:00 GMT; path=/; HttpOnly
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  https://stackoverflow.com:443 "GET /a/58769712/100297 HTTP/1.1" 302 214
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  send: b'GET /questions/58738195/python-http-request-and-debug-level-logging-to-the-log-file/58769712 HTTP/1.1\r\nHost: stackoverflow.com\r\nUser-Agent: python-requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nCookie: prov=0e92634f-abce-9f8e-1865-0d35ebecc595\r\n\r\n'
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  reply: 'HTTP/1.1 200 OK\r\n'
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Cache-Control: private
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Type: text/html; charset=utf-8
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Encoding: gzip
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Last-Modified: Fri, 08 Nov 2019 16:16:07 GMT
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Frame-Options: SAMEORIGIN
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Request-Guid: 5e48399e-a91c-44aa-aad6-00a96014131f
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Strict-Transport-Security: max-age=15552000
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Feature-Policy: microphone 'none'; speaker 'none'
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Security-Policy: upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Accept-Ranges: bytes
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Age: 0
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Content-Length: 42625
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Accept-Ranges: bytes
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Date: Fri, 08 Nov 2019 16:17:27 GMT
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Via: 1.1 varnish
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Age: 0
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Connection: keep-alive
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Served-By: cache-lhr7324-LHR
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Cache: MISS
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Cache-Hits: 0
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-Timer: S1573229847.189349,VS0,VE95
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: Vary: Accept-Encoding,Fastly-SSL
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  header: X-DNS-Prefetch-Control: off
2019-11-08 16:17:27 [MainThread  ] [DEBUG]  https://stackoverflow.com:443 "GET /questions/58738195/python-http-request-and-debug-level-logging-to-the-log-file/58769712 HTTP/1.1" 200 42625

You can reproduce this with a file with:您可以使用以下文件重现此内容:

import requests
import sys

logPath, logFileName = "/tmp", "demo"
level = logging.DEBUG if "-v" in sys.argv else None
setup_logging(level)

requests.get("https://stackoverflow.com/a/58769712/100297")

added in addition to the code above.除了上面的代码之外还添加了。 You can then use python <script.py> -v to set the level to verbose and compare that to python <script.py> with the level not set at all.然后,您可以使用python <script.py> -v将级别设置为详细,并将其与根本未设置级别的python <script.py>进行比较。

Here's a simple solution for underlying urllib3:这是底层 urllib3 的简单解决方案:

https://urllib3.readthedocs.io/en/stable/user-guide.html#logging https://urllib3.readthedocs.io/en/stable/user-guide.html#logging

Example:例子:

logging.getLogger("urllib3").setLevel(logging.WARNING)

Sample output:样品 output:

>>> requests.get('http://www.google.com')
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): www.google.com:80
DEBUG:urllib3.connectionpool:http://www.google.com:80 "GET / HTTP/1.1" 200 6073
<Response [200]>

If you need it redirected to a file as well, use如果您还需要将其重定向到文件,请使用

>>> file_handler = logging.FileHandler('log.txt')
>>> logging.getLogger('urllib3').addHandler(file_handler)
>>> requests.get('http://www.google.com')

Sample output in log.txt: log.txt 中的示例 output:

$ cat log.txt
Starting new HTTP connection (1): www.google.com:80
http://www.google.com:80 "GET / HTTP/1.1" 200 6129

If you need a sophisticated formatting, use a custom formatter.如果您需要复杂的格式化,请使用自定义格式化程序。 https://docs.python.org/3.9/library/logging.html#formatter-objects https://docs.python.org/3.9/library/logging.html#formatter-objects

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM