Ghi nhật ký tất cả các yêu cầu từ mô-đun python-request


95

Tôi đang sử dụng Yêu cầu python . Tôi cần gỡ lỗi một số OAuthhoạt động và tôi muốn nó ghi lại tất cả các yêu cầu đang được thực hiện. Tôi có thể lấy thông tin này bằng ngrep, nhưng rất tiếc là không thể grep các kết nối https (cần thiết cho OAuth)

Làm cách nào để kích hoạt ghi nhật ký tất cả các URL (+ tham số) Requestsđang truy cập?


Phản hồi của @yohann cho thấy cách nhận được nhiều kết quả ghi nhật ký hơn, bao gồm cả các tiêu đề bạn đang gửi. Đó phải là câu trả lời được chấp nhận chứ không phải của Martijn, không hiển thị các tiêu đề mà bạn đã nhận được thông qua wirehark và thay vào đó tùy chỉnh thủ công một yêu cầu.
nealmcb

Câu trả lời:


91

urllib3Thư viện bên dưới ghi lại tất cả các kết nối và URL mới với loggingmô-đun , nhưng không ghi lại nội dung POST. Đối với GETcác yêu cầu, điều này là đủ:

import logging

logging.basicConfig(level=logging.DEBUG)

cung cấp cho bạn tùy chọn ghi nhật ký dài dòng nhất; xem HOWTO ghi nhật ký để biết thêm chi tiết về cách định cấu hình các cấp và đích ghi nhật ký.

Bản demo ngắn:

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

Tùy thuộc vào phiên bản chính xác của urllib3, các thông báo sau được ghi lại:

  • INFO: Chuyển hướng
  • WARN: Nhóm kết nối đầy (nếu điều này xảy ra thường xuyên sẽ tăng kích thước nhóm kết nối)
  • WARN: Không thể phân tích cú pháp các tiêu đề (tiêu đề phản hồi có định dạng không hợp lệ)
  • WARN: Thử lại kết nối
  • WARN: Chứng chỉ không khớp với tên máy chủ dự kiến
  • WARN: Đã nhận được phản hồi với cả Độ dài nội dung và Mã hóa truyền, khi xử lý một phản hồi phân đoạn
  • DEBUG: Kết nối mới (HTTP hoặc HTTPS)
  • DEBUG: Kết nối bị ngắt
  • DEBUG: Chi tiết kết nối: phương thức, đường dẫn, phiên bản HTTP, mã trạng thái và độ dài phản hồi
  • DEBUG: Thử lại số gia tăng

Điều này không bao gồm tiêu đề hoặc nội dung. urllib3sử dụng http.client.HTTPConnectionlớp để thực hiện grunt-work, nhưng lớp đó không hỗ trợ ghi nhật ký, nó thường chỉ có thể được cấu hình để in ra stdout. Tuy nhiên, bạn có thể cài đặt nó để gửi tất cả thông tin gỡ lỗi vào nhật ký thay thế bằng cách giới thiệu một printtên thay thế vào mô-đun đó:

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

Việc gọi httpclient_logging_patch()khiến các http.clientkết nối xuất ra tất cả thông tin gỡ lỗi tới một trình ghi nhật ký chuẩn và do đó, được chọn bởi 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
Thật kỳ lạ, tôi không thấy access_tokenyêu cầu OAuth. Linkedin đang phàn nàn về yêu cầu trái phép và tôi muốn xác minh xem liệu thư viện mà tôi đang sử dụng ( rauthở trên cùng requests) có đang gửi mã thông báo đó cùng với yêu cầu hay không. Tôi đã mong đợi xem đó là một tham số truy vấn, nhưng có thể nó nằm trong tiêu đề yêu cầu? Làm cách nào tôi có thể buộc urllib3hiển thị cả tiêu đề? Và cơ quan yêu cầu? Chỉ để làm cho nó đơn giản: làm thế nào tôi có thể xem yêu cầu ĐẦY ĐỦ ?
blueFast

Bạn không thể làm điều đó mà không vá lỗi, tôi sợ. Cách phổ biến nhất để chẩn đoán những sự cố như vậy là sử dụng proxy hoặc trình ghi gói (tôi sử dụng Wirehark để tự mình nắm bắt các yêu cầu và phản hồi đầy đủ). Tôi thấy bạn đã hỏi một câu hỏi mới về chủ đề này.
Martijn Pieters

1
Chắc chắn, tôi đang gỡ lỗi ngay bây giờ với Wirehark, nhưng tôi gặp sự cố: nếu tôi sử dụng http, tôi thấy nội dung gói đầy đủ, nhưng Linkedin trả về 401, điều này được mong đợi, vì Linkedin yêu cầu sử dụng https. Nhưng với https, nó cũng không hoạt động và tôi không thể gỡ lỗi nó vì tôi không thể kiểm tra lớp TLS bằng wirehark.
blueFast

1
@nealmcb: gah, vâng, việc đặt thuộc tính lớp toàn cục sẽ thực sự cho phép gỡ lỗi trong httplib. Tôi ước rằng thư viện được sử dụng loggingthay thế; đầu ra gỡ lỗi được ghi trực tiếp vào stdout thay vì cho phép bạn chuyển hướng nó đến đích nhật ký mà bạn chọn.
Martijn Pieters


111

Bạn cần bật gỡ lỗi ở httplibcấp độ ( requestsurllib3httplib).

Dưới đây là một số chức năng để bật tắt cả ( ..._on()..._off()) hoặc tạm thời:

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()

Sử dụng demo:

>>> 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]>

Bạn sẽ thấy YÊU CẦU, bao gồm HEADERS và DỮ LIỆU, và PHẢN ỨNG với HEADER nhưng không có DỮ LIỆU. Điều duy nhất còn thiếu sẽ là response.body chưa được ghi lại.

Nguồn


Cảm ơn bạn đã hiểu rõ về cách sử dụng httplib.HTTPConnection.debuglevel = 1để lấy tiêu đề - tuyệt vời! Nhưng tôi nghĩ rằng tôi nhận được kết quả tương tự khi chỉ sử dụng logging.basicConfig(level=logging.DEBUG)5 dòng khác của bạn. Tui bỏ lỡ điều gì vậy? Tôi đoán nó có thể là một cách để thiết lập các cấp độ ghi nhật ký khác nhau cho root so với urllib3, nếu muốn.
nealmcb

Bạn không có tiêu đề với giải pháp của bạn.
Yohann

7
httplib.HTTPConnection.debuglevel = 2cũng sẽ cho phép in nội dung POST.
79

1
httplib.HTTPConnection.debuglevel = 1là đủ @ Mandible79 $ curl https://raw.githubusercontent.com/python/cpython/master/Lib/http/client.py |grep debuglevelluôn luôndebuglevel > 0
Yohann

3
Bằng cách nào đó để ngăn nội dung đã ghi được gửi đến đầu ra chuẩn?
yucer

45

Đối với những người sử dụng 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

Làm cách nào để tôi có thể làm cho nó hoạt động với tệp nhật ký? Nó dường như chỉ hoạt động cho stdout. Ví dụ sự cố ở đây: stackoverflow.com/q/58738195/1090360
JackTheKnife,

15

Khi cố gắng để hệ thống ghi nhật ký Python ( import logging) phát ra thông báo nhật ký gỡ lỗi cấp thấp, tôi ngạc nhiên khi phát hiện ra rằng:

requests --> urllib3 --> http.client.HTTPConnection

mà chỉ urllib3thực sự sử dụng logginghệ thống Python :

  • requests Không
  • http.client.HTTPConnection Không
  • urllib3 Đúng

Chắc chắn, bạn có thể trích xuất thông báo gỡ lỗi từ HTTPConnectioncài đặt:

HTTPConnection.debuglevel = 1

nhưng những đầu ra này chỉ được phát ra thông qua printcâu lệnh. Để chứng minh điều này, chỉ cần ghi client.pymã nguồn Python 3.7 và tự xem các câu lệnh in (cảm ơn @Yohann):

curl https://raw.githubusercontent.com/python/cpython/3.7/Lib/http/client.py |grep -A1 debuglevel` 

Có lẽ việc chuyển hướng stdout theo một cách nào đó có thể có tác dụng đưa stdout vào hệ thống ghi nhật ký và có khả năng chiếm được ví dụ như một tệp nhật ký.

Chọn ' urllib3' không ghi nhật ký ' requests.packages.urllib3'

Để nắm bắt urllib3thông tin gỡ lỗi thông qua logginghệ thống Python 3 , trái với nhiều lời khuyên trên internet và như @MikeSmith đã chỉ ra, bạn sẽ không gặp nhiều may mắn khi chặn được:

log = logging.getLogger('requests.packages.urllib3')

thay vào đó bạn cần:

log = logging.getLogger('urllib3')

Gỡ lỗi urllib3cho tệp nhật ký

Đây là một số mã ghi nhật ký urllib3hoạt động với tệp nhật ký bằng logginghệ thống 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/')

kết quả:

Starting new HTTP connection (1): httpbin.org:80
http://httpbin.org:80 "GET / HTTP/1.1" 200 3168

Bật câu HTTPConnection.debuglevellệnh print ()

Nếu bạn đặt HTTPConnection.debuglevel = 1

from http.client import HTTPConnection  # py3
HTTPConnection.debuglevel = 1
requests.get('http://httpbin.org/')

bạn sẽ nhận được bản in báo cáo đầu ra của thông tin cấp thấp bổ sung:

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: ...

Hãy nhớ rằng đầu ra này sử dụng printchứ không phải logginghệ thống Python , và do đó không thể được ghi lại bằng cách sử dụng loggingluồng hoặc trình xử lý tệp truyền thống (mặc dù có thể ghi đầu ra vào một tệp bằng cách chuyển hướng stdout) .

Kết hợp cả hai điều trên - tối đa hóa tất cả ghi nhật ký có thể vào bảng điều khiển

Để tối đa hóa tất cả ghi nhật ký có thể, bạn phải giải quyết đầu ra console / stdout với điều này:

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/')

cung cấp đầy đủ các đầu ra:

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
Và điều gì về chuyển hướng các chi tiết in đến bộ ghi nhật ký?
yucer

bạn có thành công trong việc đưa các chi tiết in vào bộ ghi nhật ký không?
Erika Dsouza

3

Tôi đang sử dụng python 3.4, yêu cầu 2.19.1:

'urllib3' là trình ghi nhật ký để có ngay bây giờ (không còn là 'request.packages.urllib3'). Việc ghi nhật ký cơ bản sẽ vẫn diễn ra mà không cần đặt http.client.HTTPConnection.debuglevel


Sẽ tốt hơn nhiều nếu bạn giải thích thêm
Jamie Lindsey

2

Có một tập lệnh hoặc thậm chí là một hệ thống con của ứng dụng để gỡ lỗi giao thức mạng, bạn nên xem chính xác cặp yêu cầu-phản hồi nào, bao gồm URL hiệu quả, tiêu đề, tải trọng và trạng thái. Và nó thường không thực tế đối với các yêu cầu cá nhân cụ thể ở khắp nơi. Đồng thời, có những cân nhắc về hiệu suất đề xuất sử dụng đơn lẻ (hoặc ít chuyên biệt) requests.Session, vì vậy điều sau giả định rằng đề xuất được tuân theo.

requestshỗ trợ cái gọi là móc sự kiện (kể từ 2.23 thực sự chỉ có responsemóc). Về cơ bản, nó là một trình lắng nghe sự kiện và sự kiện được phát ra trước khi trả lại quyền kiểm soát từ đó requests.request. Tại thời điểm này, cả yêu cầu và phản hồi đều được xác định đầy đủ, do đó có thể được ghi lại.

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)

Về cơ bản, đó là cách ghi lại tất cả các chu trình HTTP của một phiên.

Định dạng bản ghi nhật ký khứ hồi HTTP

Để việc ghi nhật ký ở trên trở nên hữu ích, có thể có trình định dạng ghi nhật ký chuyên dụng có thể hiểu reqresbổ sung cho các bản ghi ghi nhật ký. Nó có thể trông như thế này:

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])

Bây giờ nếu bạn thực hiện một số yêu cầu bằng cách sử dụng session, như:

session.get('https://httpbin.org/user-agent')
session.get('https://httpbin.org/status/200')

Kết quả đầu ra stderrsẽ như sau.

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

Một cách GUI

Khi bạn có nhiều truy vấn, việc có một giao diện người dùng đơn giản và một cách lọc các bản ghi sẽ rất hữu ích. Tôi sẽ sử dụng Chronologer cho điều đó (mà tôi là tác giả của nó).

Đầu tiên, hook đã được viết lại để tạo ra các bản ghi loggingcó thể tuần tự hóa khi gửi qua dây. Nó có thể trông như thế này:

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)

Thứ hai, cấu hình ghi nhật ký phải được điều chỉnh để sử dụng logging.handlers.HTTPHandler(mà Chronologer hiểu được).

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)

Cuối cùng, chạy phiên bản Chronologer. ví dụ: sử dụng 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

Và chạy lại các yêu cầu:

session.get('https://httpbin.org/user-agent')
session.get('https://httpbin.org/status/200')

Trình xử lý luồng sẽ tạo ra:

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

Bây giờ nếu bạn mở http: // localhost: 8080 / (sử dụng "logger" cho tên người dùng và mật khẩu trống cho cửa sổ xác thực cơ bản) và nhấp vào nút "Open", bạn sẽ thấy một cái gì đó như:

Ảnh chụp màn hình Chronologer

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.