Log HTTP requests made with Python Requests

Use urllib3 diagnostics or Requests response hooks, understand which failures and payloads they omit, and avoid exposing credentials in HTTP logs.

Best practices
Python programming and application logging

For temporary connection diagnostics, enable the urllib3 logger. For application logs, a Requests response hook gives you more control over the fields you retain. Neither method automatically produces a complete, safe record of every HTTP exchange.

Enable connection-level diagnostics

import logging
import requests

logging.basicConfig(level=logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG)
response = requests.get("https://httpbin.org/get", timeout=10)

This can show connection activity and response summaries. It is not a complete request-and-response body recorder. The example makes a real external request; use an endpoint you control when testing application data.

For lower-level debugging, http.client can print additional protocol details directly to stdout:

from http.client import HTTPConnection

previous_debuglevel = HTTPConnection.debuglevel
try:
    HTTPConnection.debuglevel = 1
    response = requests.get("https://httpbin.org/get", timeout=10)
finally:
    HTTPConnection.debuglevel = previous_debuglevel

This can expose outgoing headers and body bytes, status lines, and response headers. It does not generally dump response bodies. It bypasses logging handlers, so a logging filter will not redact that stdout output. Changing this class setting also affects other relevant connections in the process.

Record selected fields with a response hook

import logging
from urllib.parse import urlsplit
import requests

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("outbound_http")

def log_response(response, *args, **kwargs):
    url = urlsplit(response.url)
    logger.info(
        "%s host=%s status=%s",
        response.request.method,
        url.hostname,
        response.status_code,
    )

with requests.Session() as session:
    session.hooks["response"].append(log_response)
    response = session.get("https://httpbin.org/get", timeout=10)

This deliberately omits credentials, query strings, URL paths, and bodies. If you need a path or more context, add an allowlisted, redacted field appropriate to your application's data.

Hooks run when a response is received, including intermediate redirect responses. They do not cover connection failures that never produce a response, and they do not provide one entry for every lower-level retry attempt. Catch requests.RequestException around the call when you also need a failure record. Avoid logging the full exception string without checking whether it contains a sensitive URL.

Choose diagnostic detail deliberately

Low-level debug output can contain tokens, cookies, or private payloads. Keep it scoped to a controlled reproduction and turn it off afterward. For sustained production visibility, use selected application fields and supported HTTP tracing instrumentation rather than retaining raw protocol dumps. Check instrumentation coverage and redaction for your client version.

FAQ

Can I log just the URL and status? Use a response hook, but sanitize the URL first. Query parameters and paths can contain secrets or personal data.

Can debuglevel output go to a file? Redirecting stdout captures it, but also captures other stdout output and does not route it through logging. A response hook is easier to format, filter, and send to a chosen handler.

Which logger does Requests use? Its connection diagnostics primarily come from urllib3. Setting a logger named requests to DEBUG does not by itself produce full exchange details.

References

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting