私はpython リクエストを使用しています。いくつかのOAuth
アクティビティをデバッグする必要があります。そのために、実行中のすべてのリクエストをログに記録します。私はこの情報をngrep
で取得できましたが、残念ながらhttps接続をgrepすることはできません(これはに必要ですOAuth
)
Requests
アクセスしているすべてのURL(+パラメータ)のロギングをアクティブにするにはどうすればよいですか?
私はpython リクエストを使用しています。いくつかのOAuth
アクティビティをデバッグする必要があります。そのために、実行中のすべてのリクエストをログに記録します。私はこの情報をngrep
で取得できましたが、残念ながらhttps接続をgrepすることはできません(これはに必要ですOAuth
)
Requests
アクセスしているすべてのURL(+パラメータ)のロギングをアクティブにするにはどうすればよいですか?
回答:
基になる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
access_token
に、OAuthリクエストにが表示されません。Linkedinは不正なリクエストについて不平を言っています。私が使用しているライブラリ(のrauth
上にrequests
)がリクエストとともにそのトークンを送信しているかどうかを確認したいと思います。私はそれをクエリパラメータとして表示することを期待していましたが、おそらくリクエストヘッダーにありますか?にurllib3
ヘッダーを強制的に表示させるにはどうすればよいですか?そしてリクエストボディ?単純にするために:FULLリクエストはどのように確認できますか?
httplib
ます。そのライブラリがlogging
代わりに使用されることを願っています。デバッグ出力は、選択したログの宛先にリダイレクトするのではなく、stdoutに直接書き込まれます。
あなたはでデバッグを有効にする必要があるhttplib
レベル(requests
→ urllib3
→ httplib
)。
以下に、トグル(..._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に異なるログレベルを設定する方法になると思います。
httplib.HTTPConnection.debuglevel = 2
POST本文の印刷も可能になります。
httplib.HTTPConnection.debuglevel = 1
@ Mandible79 $ curl https://raw.githubusercontent.com/python/cpython/master/Lib/http/client.py |grep debuglevel
で十分ですdebuglevel > 0
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
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
ログファイルへのデバッグurllib3
Python 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.debuglevel
print()ステートメントを有効にする設定した場合 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: ...
この出力はprint
Python logging
システムではなくを使用しているため、従来のlogging
ストリームまたはファイルハンドラーを使用してキャプチャすることはできません(ただし、stdoutをリダイレクトすることで出力をファイルにキャプチャすることは可能です)。
すべての可能なロギングを最大化するには、次のようにしてコンソール/ 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: ...
ネットワークプロトコルデバッグ用のスクリプトまたはアプリケーションのサブシステムさえあれば、有効な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ラウンドトリップを記録する方法です。
上記のロギングが役立つようにするには、ロギングレコードを理解し、補足する特殊なロギングフォーマッタを使用できます。次のようになります。req
res
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
多くのクエリがある場合、シンプルな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 /を開き(ユーザー名に「ロガー」を使用し、基本認証ポップアップに空のパスワードを使用)、「開く」ボタンをクリックすると、次のように表示されます。