Маючи сценарій або навіть підсистему програми для налагодження мережевого протоколу, бажано побачити, якими саме є пари запит-відповідь, включаючи ефективні 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
Графічний інтерфейс
Коли у вас багато запитів, просто користувальницький інтерфейс та спосіб фільтрування записів стане в нагоді. Я покажу використовувати 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 / (використовуйте "реєстратор" для імені користувача та порожній пароль для основного спливаючого вікна auth) і натискаєте кнопку "Відкрити", ви повинні побачити щось на зразок: