Имея сценарий или даже подсистему приложения для отладки сетевого протокола, желательно видеть, какие именно пары запрос-ответ, включая эффективные 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
Для того, чтобы приведенное выше ведение журнала было полезным, может быть специализированное средство форматирования журнала, которое понимает 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
GUI способ
Когда у вас много запросов, вам пригодится простой пользовательский интерфейс и способ фильтрации записей. Я покажу, как использовать для этого 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 / (используйте logger для имени пользователя и пустой пароль для основного всплывающего окна авторизации) и нажмете кнопку «Открыть», вы должны увидеть что-то вроде:
