[python] python-requests 모듈의 모든 요청을 기록합니다.

파이썬 요청을 사용하고 있습니다. 일부 OAuth활동 을 디버그해야하며 이를 위해 수행중인 모든 요청을 기록하고 싶습니다. 나는이 정보를 얻을 수 ngrep있지만, (에 필요한 그렙 HTTPS 연결 할 수 없습니다 불행히도 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.HTTPConnectiongrunt-work를 수행하지만 해당 클래스는 로깅을 지원하지 않으며 일반적으로 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


답변

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를 사용한 RESPONSE (데이터 제외)가 표시됩니다. 빠진 유일한 것은 기록되지 않은 response.body입니다.

출처


답변

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


답변

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‘ 로거를 선택하십시오.

인터넷에 대한 많은 조언과는 달리 urllib3Python 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스트림 또는 파일 핸들러를 사용하여 캡처 할 수 없습니다. (표준 출력을 리디렉션하여 파일로 출력을 캡처 할 수 있음) .

위의 두 가지를 결합하여 가능한 모든 로깅을 콘솔에 최대화하십시오.

가능한 모든 로깅을 최대화하려면 다음과 같이 콘솔 / 표준 출력을 설정해야합니다.

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.4를 사용하고 있으며 2.19.1을 요청합니다.

‘urllib3’은 지금 가져올 로거입니다 (더 이상 ‘requests.packages.urllib3’이 아님). http.client.HTTPConnection.debuglevel을 설정하지 않아도 기본 로깅이 계속 발생합니다.


답변

네트워크 프로토콜 디버깅을위한 스크립트 또는 애플리케이션의 하위 시스템이있는 경우 효과적인 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 / (사용자 이름에는 “logger”사용, 기본 인증 팝업에는 빈 암호 사용)을 열고 “열기”버튼을 클릭하면 다음과 같은 내용이 표시됩니다.

Chronologer의 스크린 샷


답변