Я использую запросы python . Мне нужно отладить некоторые OAuth
действия, и для этого я хотел бы, чтобы он регистрировал все выполняемые запросы. Я мог бы получить эту информацию ngrep
, но, к сожалению, невозможно использовать grep https-соединения (которые необходимы для OAuth
)
Как я могу активировать ведение журнала всех URL-адресов (+ параметров), Requests
к которым осуществляется доступ?
python
logging
python-requests
blueFast
источник
источник
Ответы:
Базовая
urllib3
библиотека регистрирует все новые соединения и URL-адреса сlogging
модулем , но неPOST
тела. ДляGET
запросов этого должно хватить:import logging logging.basicConfig(level=logging.DEBUG)
что дает вам наиболее подробный вариант ведения журнала; см. 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
класс для выполнения основной работы, но этот класс не поддерживает ведение журнала, обычно его можно настроить только для печати на стандартный вывод. Однако вы можете настроить его для отправки всей отладочной информации в журнал, вместо этого введя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
показывать и заголовки? А тело запроса? Чтобы было проще: как я могу увидеть ПОЛНЫЙ запрос?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]>
Вы увидите ЗАПРОС, включая ЗАГОЛОВКИ и ДАННЫЕ, и ОТВЕТ с ЗАГОЛОВКАМИ, но без ДАННЫХ. Единственное, чего не хватает, это 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
) выдавать сообщения журнала отладки низкого уровня, я с удивлением обнаружил, что это:который
urllib3
фактически использует толькоlogging
систему Python :requests
нетhttp.client.HTTPConnection
нетurllib3
даКонечно, вы можете извлекать сообщения отладки
HTTPConnection
, установив:HTTPConnection.debuglevel = 1
но эти выходные данные просто передаются через
print
оператор. Чтобы доказать это, просто выполните grep сclient.py
исходным кодом Python 3.7 и просмотрите операторы печати самостоятельно (спасибо @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 3logging
, вопреки многим советам в Интернете, и, как указывает @MikeSmith, вам не удастся перехватить:log = logging.getLogger('requests.packages.urllib3')
вместо этого вам необходимо:
log = logging.getLogger('urllib3')
Отладка
urllib3
в файл журналаВот код, который записывает
urllib3
работу в файл журнала с помощьюlogging
системы Python :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/')
вы получите вывод оператора печати с дополнительной сочной информацией низкого уровня:
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
а не систему Pythonlogging
, и, следовательно, не может быть захвачен с помощью традиционногоlogging
потока или обработчика файлов (хотя может быть возможно захватить вывод в файл, перенаправив stdout) .Объедините два вышеупомянутых - максимизируйте все возможные журналы на консоли
Чтобы максимизировать все возможные журналы, вы должны согласиться на вывод console / 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-циклов сеанса.
Форматирование записей журнала приема-передачи 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 для имени пользователя и пустой пароль для основного всплывающего окна авторизации) и нажмете кнопку «Открыть», вы должны увидеть что-то вроде:
источник
Я использую python 3.4, запросы 2.19.1:
"urllib3" - это средство ведения журнала, которое нужно получить сейчас (больше не "requests.packages.urllib3"). Базовое ведение журнала по-прежнему будет происходить без настройки http.client.HTTPConnection.debuglevel
источник
Я использую
logger_config.yaml
файл, чтобы настроить ведение журнала, и чтобы эти журналы отображались, все, что мне нужно было сделать, это добавить вdisable_existing_loggers: False
его конец.Моя настройка ведения журнала довольно обширна и запутана, поэтому я даже не знаю, как это объяснить, но если кто-то также использует файл YAML для настройки ведения журнала, это может помочь.
https://docs.python.org/3/howto/logging.html#configuring-logging
источник
Просто улучшаю этот ответ
У меня это сработало так:
import logging import sys import requests import textwrap root = logging.getLogger('httplogger') def logRoundtrip(response, *args, **kwargs): extra = {'req': response.request, 'res': response} root.debug('HTTP roundtrip', extra=extra) 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(sys.stdout) handler.setFormatter(formatter) root.addHandler(handler) root.setLevel(logging.DEBUG) session = requests.Session() session.hooks['response'].append(logRoundtrip) session.get('http://httpbin.org')
источник