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


95

파이썬 요청을 사용하고 있습니다. 일부 OAuth활동 을 디버그해야하며 이를 위해 수행중인 모든 요청을 기록하고 싶습니다. 나는이 정보를 얻을 수 ngrep있지만, (에 필요한 그렙 HTTPS 연결 할 수 없습니다 불행히도 OAuth)

Requests액세스 하는 모든 URL (+ 매개 변수)의 로깅을 활성화하려면 어떻게해야 합니까?


@yohann의 응답은 보내는 헤더를 포함하여 더 많은 로깅 출력을 얻는 방법을 보여줍니다. 그것은 당신이 wireshark를 통해 얻은 헤더를 보여주지 않고 대신 요청을 직접 커스터마이징하지 않는 Martijn보다는 받아 들여진 대답이어야합니다.
nealmcb

답변:


91

기본 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

2
이상하게도 access_tokenOAuth 요청에이 표시되지 않습니다 . Linkedin이 무단 요청에 대해 불만을 제기하고 있으며 사용중인 라이브러리 ( rauth위에있는 requests)가 해당 토큰을 요청과 함께 보내는 지 확인하고 싶습니다 . 나는 그것을 쿼리 매개 변수로 볼 것으로 예상했지만 요청 헤더에있을 수 있습니까? urllib3에서 헤더도 표시 하도록하려면 어떻게 해야합니까? 그리고 요청 본문? 간단히 말해서 FULL 요청을 어떻게 볼 수 있습니까?
blueFast 2013 년

패치 없이는 그렇게 할 수 없습니다. 이러한 문제를 진단하는 가장 일반적인 방법은 프록시 또는 패킷 로거를 사용하는 것입니다 (저는 wireshark를 사용하여 전체 요청과 응답을 직접 캡처합니다). 나는 당신이 주제에 대해 새로운 질문을 한 것을 봅니다.
Martijn Pieters

1
물론, 나는 지금 wireshark로 디버깅하고 있지만 문제가 있습니다 .http를 수행하면 전체 패킷 내용이 표시되지만 Linkedin이 https를 사용하도록 지시하기 때문에 Linkedin이 예상되는 401을 반환합니다. 그러나 https를 사용하면 작동하지 않으며 wireshark로 TLS 계층을 검사 할 수 없기 때문에 디버깅 할 수 없습니다.
blueFast

1
@nealmcb : gah, 예, 전역 클래스 속성을 설정하면 실제로 httplib. logging대신 그 라이브러리를 사용하고 싶습니다 . 디버그 출력은 선택한 로그 대상으로 리디렉션하지 않고 stdout에 직접 기록됩니다.
Martijn Pieters


111

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입니다.

출처


httplib.HTTPConnection.debuglevel = 1헤더를 가져 오는 데 사용하는 방법에 대한 통찰력에 감사드립니다. 훌륭합니다! 그러나 나는 logging.basicConfig(level=logging.DEBUG)다른 5 줄 대신에 동일한 결과를 얻는 것 같습니다 . 내가 뭔가를 놓치고 있습니까? 원한다면 루트 대 urllib3에 대해 다른 로깅 수준을 설정하는 방법이 될 수 있다고 생각합니다.
nealmcb

솔루션에 헤더가 없습니다.
Yohann

7
httplib.HTTPConnection.debuglevel = 2POST 본문도 인쇄 할 수 있습니다.
Mandible79 2015 년

1
httplib.HTTPConnection.debuglevel = 1Mandible79 @ 충분 $ curl https://raw.githubusercontent.com/python/cpython/master/Lib/http/client.py |grep debuglevel그것은 항상debuglevel > 0
요한 디

3
로깅 된 콘텐츠가 표준 출력으로 전송되는 것을 방지하려면 어떻게해야합니까?
yucer

45

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
JackTheKnife

15

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
그리고 인쇄 세부 사항을 로거로 리디렉션하는 것은 어떻습니까?
yucer

로거에 인쇄 세부 정보를 가져 오는 데 성공 했습니까?
에리카 Dsouza

3

파이썬 3.4를 사용하고 있으며 2.19.1을 요청합니다.

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


이 더 설명한다면 훨씬 더 좋을 것이다
제이미 린지을

2

네트워크 프로토콜 디버깅을위한 스크립트 또는 애플리케이션의 하위 시스템이있는 경우 효과적인 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의 스크린 샷

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.