python-requestsモジュールからのすべてのリクエストをログに記録します


95

私はpython リクエストを使用しています。いくつかのOAuthアクティビティをデバッグする必要があります。そのために、実行中のすべてのリクエストをログに記録します。私はこの情報をngrepで取得できましたが、残念ながらhttps接続をgrepすることはできません(これはに必要ですOAuth

RequestsアクセスしているすべてのURL(+パラメータ)のロギングをアクティブにするにはどうすればよいですか?


@yohannによる応答は、送信しているヘッダーを含め、さらに多くのログ出力を取得する方法を示しています。Martijnの回答ではなく、受け入れられた回答である必要があります。これは、結局、wireshark経由で取得し、リクエストを手動でカスタマイズしたヘッダーを表示しないためです。
nealmcb

回答:


91

基になるurllib3ライブラリは、loggingモジュールではなくすべての新しい接続とURLを記録しますがPOST本体は記録しません。以下のためGETの要求これは十分なはずです。

import logging

logging.basicConfig(level=logging.DEBUG)

これにより、最も詳細なログオプションが提供されます。ログレベルと宛先の構成方法の詳細については、logging HOWTOを参照してください。

短いデモ:

>>> import requests
>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> r = requests.get('http://httpbin.org/get?foo=bar&baz=python')
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): httpbin.org:80
DEBUG:urllib3.connectionpool:http://httpbin.org:80 "GET /get?foo=bar&baz=python HTTP/1.1" 200 366

urllib3の正確なバージョンに応じて、次のメッセージがログに記録されます。

  • INFO:リダイレクト
  • WARN:接続プールがいっぱいです(これが発生すると、接続プールのサイズが大きくなることがよくあります)
  • WARN:ヘッダーの解析に失敗しました(無効な形式の応答ヘッダー)
  • WARN:接続を再試行します
  • WARN:証明書が予期されたホスト名と一致しませんでした
  • WARN:チャンクされた応答を処理するときに、Content-LengthとTransfer-Encodingの両方で応答を受信しました
  • DEBUG:新しい接続(HTTPまたはHTTPS)
  • DEBUG:ドロップされた接続
  • DEBUG:接続の詳細:メソッド、パス、HTTPバージョン、ステータスコード、応答の長さ
  • DEBUG:再試行カウントの増分

これには、ヘッダーや本文は含まれません。urllib3使用していますhttp.client.HTTPConnection作男-仕事をするために、クラスを、しかし、そのクラスはロギングをサポートしていない、正常にだけするように構成することができる印刷 stdoutに。ただし、printそのモジュールに代替名を導入することで、すべてのデバッグ情報をロギングに送信するようにリグすることができます。

import logging
import http.client

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

def httpclient_logging_patch(level=logging.DEBUG):
    """Enable HTTPConnection debug logging to the logging framework"""

    def httpclient_log(*args):
        httpclient_logger.log(level, " ".join(args))

    # mask the print() built-in in the http.client module to use
    # logging instead
    http.client.print = httpclient_log
    # enable debugging
    http.client.HTTPConnection.debuglevel = 1

呼び出しhttpclient_logging_patch()により、http.client接続はすべてのデバッグ情報を標準ロガーに出力するため、以下によってピックアップされlogging.basicConfig()ます。

>>> httpclient_logging_patch()
>>> r = requests.get('http://httpbin.org/get?foo=bar&baz=python')
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): httpbin.org:80
DEBUG:http.client:send: b'GET /get?foo=bar&baz=python HTTP/1.1\r\nHost: httpbin.org\r\nUser-Agent: python-requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n'
DEBUG:http.client:reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:http.client:header: Date: Tue, 04 Feb 2020 13:36:53 GMT
DEBUG:http.client:header: Content-Type: application/json
DEBUG:http.client:header: Content-Length: 366
DEBUG:http.client:header: Connection: keep-alive
DEBUG:http.client:header: Server: gunicorn/19.9.0
DEBUG:http.client:header: Access-Control-Allow-Origin: *
DEBUG:http.client:header: Access-Control-Allow-Credentials: true
DEBUG:urllib3.connectionpool:http://httpbin.org:80 "GET /get?foo=bar&baz=python HTTP/1.1" 200 366

2
不思議なことaccess_tokenに、OAuthリクエストにが表示されません。Linkedinは不正なリクエストについて不平を言っています。私が使用しているライブラリ(のrauth上にrequests)がリクエストとともにそのトークンを送信しているかどうかを確認したいと思います。私はそれをクエリパラメータとして表示することを期待していましたが、おそらくリクエストヘッダーにありますか?にurllib3ヘッダーを強制的に表示させるにはどうすればよいですか?そしてリクエストボディ?単純にするために:FULLリクエストはどのように確認できますか?
blueFast 2013年

パッチを当てずにそれを行うことはできません、私は恐れています。このような問題を診断する最も一般的な方法は、プロキシまたはパケットロガーを使用することです(私は自分自身でWiresharkを使用して、完全な要求と応答をキャプチャしています)。でも、あなたはこの問題について新しい質問をしたと思います。
Martijn Pieters

1
確かに、私はWiresharkを使用して現在デバッグしていますが、問題があります。httpを実行すると、完全なパケットの内容が表示されますが、Linkedinはhttpsを使用するよう指示しているため、期待どおりの401を返します。しかし、httpsでも機能しません。wiresharkでTLSレイヤーを検査できないため、デバッグできません。
blueFast 2013年

1
@nealmcb:はい、グローバルクラス属性を設定すると、実際にでデバッグが有効になりhttplibます。そのライブラリがlogging代わりに使用されることを願っています。デバッグ出力は、選択したログの宛先にリダイレクトするのではなく、stdoutに直接書き込まれます。
Martijn Pieters


111

あなたはでデバッグを有効にする必要があるhttplibレベル(requestsurllib3httplib)。

以下に、トグル(..._on()および..._off())または一時的にオンにする機能をいくつか示します。

import logging
import contextlib
try:
    from http.client import HTTPConnection # py3
except ImportError:
    from httplib import HTTPConnection # py2

def debug_requests_on():
    '''Switches on logging of the requests module.'''
    HTTPConnection.debuglevel = 1

    logging.basicConfig()
    logging.getLogger().setLevel(logging.DEBUG)
    requests_log = logging.getLogger("requests.packages.urllib3")
    requests_log.setLevel(logging.DEBUG)
    requests_log.propagate = True

def debug_requests_off():
    '''Switches off logging of the requests module, might be some side-effects'''
    HTTPConnection.debuglevel = 0

    root_logger = logging.getLogger()
    root_logger.setLevel(logging.WARNING)
    root_logger.handlers = []
    requests_log = logging.getLogger("requests.packages.urllib3")
    requests_log.setLevel(logging.WARNING)
    requests_log.propagate = False

@contextlib.contextmanager
def debug_requests():
    '''Use with 'with'!'''
    debug_requests_on()
    yield
    debug_requests_off()

デモ使用:

>>> requests.get('http://httpbin.org/')
<Response [200]>

>>> debug_requests_on()
>>> requests.get('http://httpbin.org/')
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): httpbin.org
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 12150
send: 'GET / HTTP/1.1\r\nHost: httpbin.org\r\nConnection: keep-alive\r\nAccept-
Encoding: gzip, deflate\r\nAccept: */*\r\nUser-Agent: python-requests/2.11.1\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Server: nginx
...
<Response [200]>

>>> debug_requests_off()
>>> requests.get('http://httpbin.org/')
<Response [200]>

>>> with debug_requests():
...     requests.get('http://httpbin.org/')
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): httpbin.org
...
<Response [200]>

HEADERSとDATAを含むREQUESTと、HEADERSはあるがDATAはないRESPONSEが表示されます。不足しているのは、ログに記録されないresponse.bodyだけです。

ソース


を使用httplib.HTTPConnection.debuglevel = 1してヘッダーを取得することについての洞察をありがとう-素晴らしい!しかしlogging.basicConfig(level=logging.DEBUG)、他の5行の代わりに使用しても同じ結果が得られると思います。何か不足していますか?必要に応じて、ルートとurllib3に異なるログレベルを設定する方法になると思います。
nealmcb 2015

ソリューションのヘッダーがありません。
ヨハン2015

7
httplib.HTTPConnection.debuglevel = 2POST本文の印刷も可能になります。
Mandible79 2015年

1
httplib.HTTPConnection.debuglevel = 1@ Mandible79 $ curl https://raw.githubusercontent.com/python/cpython/master/Lib/http/client.py |grep debuglevelで十分ですdebuglevel > 0
Yohann

3
ログに記録されたコンテンツが標準出力に送信されないようにするにはどうすればよいですか?
ユーサー

45

Python 3以降を使用している方

import requests
import logging
import http.client

http.client.HTTPConnection.debuglevel = 1

logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

ログファイルを使用するにはどうすればよいですか?だけで動作するようですstdout。ここでの問題の例:stackoverflow.com/q/58738195/1090360
JackTheKnife

15

Pythonロギングシステム(import logging)に低レベルのデバッグログメッセージを出力させようとすると、次のようなことに気が付きました。

requests --> urllib3 --> http.client.HTTPConnection

urllib3実際にはPython loggingシステムのみを使用します。

  • requests 番号
  • http.client.HTTPConnection 番号
  • urllib3 はい

もちろん、次のようにHTTPConnection設定することで、デバッグメッセージを抽出できます。

HTTPConnection.debuglevel = 1

しかし、これらの出力は単にprintステートメントを介して出力されます。これを証明するには、Python 3.7 client.pyソースコードをgrepして、printステートメントを自分で表示します(@Yohannに感謝します)。

curl https://raw.githubusercontent.com/python/cpython/3.7/Lib/http/client.py |grep -A1 debuglevel` 

おそらく何らかの方法でstdoutをリダイレクトすると、ログシステムにstdoutをホーンし、ログファイルなどにキャプチャする可能性があります。

「」urllib3ではなく「requests.packages.urllib3」ロガーを選択してください

インターネットでの多くのアドバイスに反しurllib3て、Python 3 loggingシステムを介してデバッグ情報をキャプチャするには、@ MikeSmithが指摘するように、運を妨害することはあまりありません。

log = logging.getLogger('requests.packages.urllib3')

代わりに:

log = logging.getLogger('urllib3')

urllib3ログファイルへのデバッグ

urllib3Python loggingシステムを使用してログファイルに動作を記録するコードを次に示します。

import requests
import logging
from http.client import HTTPConnection  # py3

# log = logging.getLogger('requests.packages.urllib3')  # useless
log = logging.getLogger('urllib3')  # works

log.setLevel(logging.DEBUG)  # needed
fh = logging.FileHandler("requests.log")
log.addHandler(fh)

requests.get('http://httpbin.org/')

結果:

Starting new HTTP connection (1): httpbin.org:80
http://httpbin.org:80 "GET / HTTP/1.1" 200 3168

HTTPConnection.debuglevelprint()ステートメントを有効にする

設定した場合 HTTPConnection.debuglevel = 1

from http.client import HTTPConnection  # py3
HTTPConnection.debuglevel = 1
requests.get('http://httpbin.org/')

あなたは追加のジューシーな低レベル情報のprintステートメント出力を取得します

send: b'GET / HTTP/1.1\r\nHost: httpbin.org\r\nUser-Agent: python- 
requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Access-Control-Allow-Credentials header: Access-Control-Allow-Origin 
header: Content-Encoding header: Content-Type header: Date header: ...

この出力はprintPython loggingシステムではなくを使用しているため、従来のloggingストリームまたはファイルハンドラーを使用してキャプチャすることはできません(ただし、stdoutをリダイレクトすることで出力をファイルにキャプチャすることは可能です)

上記の2つを組み合わせる-コンソールへの可能なすべてのロギングを最大化

すべての可能なロギングを最大化するには、次のようにしてコンソール/ stdout出力を解決する必要があります。

import requests
import logging
from http.client import HTTPConnection  # py3

log = logging.getLogger('urllib3')
log.setLevel(logging.DEBUG)

# logging from urllib3 to console
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
log.addHandler(ch)

# print statements from `http.client.HTTPConnection` to console/stdout
HTTPConnection.debuglevel = 1

requests.get('http://httpbin.org/')

出力の全範囲を与える:

Starting new HTTP connection (1): httpbin.org:80
send: b'GET / HTTP/1.1\r\nHost: httpbin.org\r\nUser-Agent: python-requests/2.22.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
http://httpbin.org:80 "GET / HTTP/1.1" 200 3168
header: Access-Control-Allow-Credentials header: Access-Control-Allow-Origin 
header: Content-Encoding header: ...

3
そして、印刷の詳細をロガーにリダイレクトするのはどうですか?
yucer

ロガーに印刷の詳細を取得することに成功しましたか?
Erika Dsouza

3

私はpython 3.4を使用していますが、2.19.1を要求します。

'urllib3'は今取得するロガーです(もはや 'requests.packages.urllib3'ではありません)。基本的なロギングは、http.client.HTTPConnection.debuglevelを設定しなくても発生します


さらに説明するともっと良いでしょう
ジェイミーリンジー

2

ネットワークプロトコルデバッグ用のスクリプトまたはアプリケーションのサブシステムさえあれば、有効なURL、ヘッダー、ペイロード、ステータスなど、要求と応答のペアが正確に何であるかを確認する必要があります。また、個々のリクエストをあちこちにインストルメント化することは、一般的に非現実的です。同時に、単一の(またはいくつかの特殊化された)を使用することを提案するパフォーマンスの考慮事項があるrequests.Sessionため、以下では、提案に従うことを前提としてます。

requestsいわゆるイベントフックをサポートします(2.23以降、実際にはresponseフックのみです)。これは基本的にイベントリスナーであり、イベントはからコントロールを返す前に発行されrequests.requestます。この時点では、要求と応答の両方が完全に定義されているため、ログに記録できます。

import logging

import requests


logger = logging.getLogger('httplogger')

def logRoundtrip(response, *args, **kwargs):
    extra = {'req': response.request, 'res': response}
    logger.debug('HTTP roundtrip', extra=extra)

session = requests.Session()
session.hooks['response'].append(logRoundtrip)

これが基本的に、セッションのすべてのHTTPラウンドトリップを記録する方法です。

HTTPラウンドトリップログレコードのフォーマット

上記のロギングが役立つようにするには、ロギングレコードを理解し、補足する特殊なロギングフォーマッタを使用できます。次のようになります。reqres

import textwrap

class HttpFormatter(logging.Formatter):   

    def _formatHeaders(self, d):
        return '\n'.join(f'{k}: {v}' for k, v in d.items())

    def formatMessage(self, record):
        result = super().formatMessage(record)
        if record.name == 'httplogger':
            result += textwrap.dedent('''
                ---------------- request ----------------
                {req.method} {req.url}
                {reqhdrs}

                {req.body}
                ---------------- response ----------------
                {res.status_code} {res.reason} {res.url}
                {reshdrs}

                {res.text}
            ''').format(
                req=record.req,
                res=record.res,
                reqhdrs=self._formatHeaders(record.req.headers),
                reshdrs=self._formatHeaders(record.res.headers),
            )

        return result

formatter = HttpFormatter('{asctime} {levelname} {name} {message}', style='{')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logging.basicConfig(level=logging.DEBUG, handlers=[handler])

ここで、次のようにを使用していくつかのリクエストを行う場合session

session.get('https://httpbin.org/user-agent')
session.get('https://httpbin.org/status/200')

への出力stderrは次のようになります。

2020-05-14 22:10:13,224 DEBUG urllib3.connectionpool Starting new HTTPS connection (1): httpbin.org:443
2020-05-14 22:10:13,695 DEBUG urllib3.connectionpool https://httpbin.org:443 "GET /user-agent HTTP/1.1" 200 45
2020-05-14 22:10:13,698 DEBUG httplogger HTTP roundtrip
---------------- request ----------------
GET https://httpbin.org/user-agent
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

None
---------------- response ----------------
200 OK https://httpbin.org/user-agent
Date: Thu, 14 May 2020 20:10:13 GMT
Content-Type: application/json
Content-Length: 45
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
  "user-agent": "python-requests/2.23.0"
}


2020-05-14 22:10:13,814 DEBUG urllib3.connectionpool https://httpbin.org:443 "GET /status/200 HTTP/1.1" 200 0
2020-05-14 22:10:13,818 DEBUG httplogger HTTP roundtrip
---------------- request ----------------
GET https://httpbin.org/status/200
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

None
---------------- response ----------------
200 OK https://httpbin.org/status/200
Date: Thu, 14 May 2020 20:10:13 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

GUIの方法

多くのクエリがある場合、シンプルなUIとレコードをフィルターする方法があると便利です。そのために、Chronologerを使用する方法を示します(私はその作成者です)。

まず、フックは、loggingネットワーク経由で送信するときにシリアル化できるレコードを生成するように書き換えられました。次のようになります。

def logRoundtrip(response, *args, **kwargs): 
    extra = {
        'req': {
            'method': response.request.method,
            'url': response.request.url,
            'headers': response.request.headers,
            'body': response.request.body,
        }, 
        'res': {
            'code': response.status_code,
            'reason': response.reason,
            'url': response.url,
            'headers': response.headers,
            'body': response.text
        },
    }
    logger.debug('HTTP roundtrip', extra=extra)

session = requests.Session()
session.hooks['response'].append(logRoundtrip)

次に、ロギング構成を使用するように調整する必要がありますlogging.handlers.HTTPHandler(これはChronologerが理解しています)。

import logging.handlers

chrono = logging.handlers.HTTPHandler(
  'localhost:8080', '/api/v1/record', 'POST', credentials=('logger', ''))
handlers = [logging.StreamHandler(), chrono]
logging.basicConfig(level=logging.DEBUG, handlers=handlers)

最後に、Chronologerインスタンスを実行します。例:Dockerの使用:

docker run --rm -it -p 8080:8080 -v /tmp/db \
    -e CHRONOLOGER_STORAGE_DSN=sqlite:////tmp/db/chrono.sqlite \
    -e CHRONOLOGER_SECRET=example \
    -e CHRONOLOGER_ROLES="basic-reader query-reader writer" \
    saaj/chronologer \
    python -m chronologer -e production serve -u www-data -g www-data -m

そして再度リクエストを実行します:

session.get('https://httpbin.org/user-agent')
session.get('https://httpbin.org/status/200')

ストリームハンドラーは以下を生成します。

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): httpbin.org:443
DEBUG:urllib3.connectionpool:https://httpbin.org:443 "GET /user-agent HTTP/1.1" 200 45
DEBUG:httplogger:HTTP roundtrip
DEBUG:urllib3.connectionpool:https://httpbin.org:443 "GET /status/200 HTTP/1.1" 200 0
DEBUG:httplogger:HTTP roundtrip

http:// localhost:8080 /を開き(ユーザー名に「ロガー」を使用し、基本認証ポップアップに空のパスワードを使用)、「開く」ボタンをクリックすると、次のように表示されます。

Chronologerのスクリーンショット

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.