Skip to content

Add easier debug logging for users - #277

Merged
sethmlarson merged 9 commits into
encode:masterfrom
sethmlarson:logging
Aug 27, 2019
Merged

sethmlarson merged 9 commits into
encode:masterfrom
sethmlarson:logging

Conversation

@sethmlarson

@sethmlarson sethmlarson commented Aug 26, 2019 •

Copy link
Copy Markdown
Contributor

This builds on #240 and adds it to all request-making dispatchers. It can be added to ASGI and WSGI dispatchers as well but I'm less familiar with what information would be useful for debugging those use-cases.

This also adds a new environment variable HTTPX_DEBUG which can be turned on to automatically display these debug logs.

A new section Environment Variables has been added to the documentation with the intent of future expansion for all environment variables that HTTPX will react to.

Closes #221.

@florimondmanca

Copy link
Copy Markdown
Contributor

This is a great idea. I'll take a closer look later, but do you know about structlog? Your key=value logging style here made me think it could be worth considering it (or another structured logging library). :-)

@florimondmanca
florimondmanca self-requested a review August 26, 2019 12:11
@sethmlarson

Copy link
Copy Markdown
Contributor Author

I initially thought to use structlog, mostly didn't want to add a dependency just so we can do debug logging on a client library. Are there other structured logging libraries out there?

@florimondmanca

florimondmanca commented Aug 26, 2019 •

Copy link
Copy Markdown
Contributor

I don't know of other structured logging libraries. But I think the real question here is, as you said: is adding another dependency for debug logs worth it? I'm not sure it is either. :-)

It's probably possible to build our own formatting helper, starting with something really simple.

There's a structured logging section in the Logging cookbook, which could helper us build a helper to be used like this:

from httpx.logging import structured
logger.debug(structured("log message", key="value"))

The cookbook recipe dumps the output as JSON, but we can take inspiration from structlog's rendering implementation here too to achieve the style you've got here.

@florimondmanca

Copy link
Copy Markdown
Contributor

I think all of this can be figured out later as a refactoring step for this PR, though — just jotting down ideas here, but it's not a blocker at all. :-)

@florimondmanca florimondmanca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really excited about how helpful this will be for debugging requests! I left a couple of comments on details.

Comment thread docs/environment_variables.md Outdated
Comment thread docs/environment_variables.md Outdated
Here are a list of environment variables that HTTPX recognizes
and what function they serve:

HTTPX_DEBUG

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
HTTPX_DEBUG
`HTTPX_DEBUG`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't make any display difference using mkdocs-material (I tried it) but we'll go with it anyways :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or really? I looked at the "Developer Interface" page and e.g. Client is rendered in a monospace font. Perhaps it's the use of === / --- instead of # and ## for headers?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked, and regardless of the header markup style (which I think we could/should convert to using shebangs since that's what we use everywhere else?) I do get monospace when using code fences:

Screenshot 2019-08-26 at 23 43 38

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @tomchristie potential mkdocs-material improvement? :)

Comment thread docs/environment_variables.md Outdated
Comment thread httpx/utils.py
Comment thread docs/environment_variables.md Outdated
@florimondmanca

Copy link
Copy Markdown
Contributor

Also, will this fix #33?

sethmlarson and others added 3 commits August 26, 2019 16:02
Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>
Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>
Co-Authored-By: Florimond Manca <florimond.manca@gmail.com>
@sethmlarson

Copy link
Copy Markdown
Contributor Author

Not quite, that issue is way too broad for its own good. Maybe we should close it and open issues with the specific tasks it requires.

@florimondmanca florimondmanca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple more polish suggestions!

Comment thread httpx/utils.py Outdated
Comment thread docs/environment_variables.md Outdated
@sethmlarson

Copy link
Copy Markdown
Contributor Author

@florimondmanca Incorporated your suggestions!

@florimondmanca florimondmanca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last suggestion I didn’t see earlier and we’ll be good to merge 🚀

Comment thread tests/dispatch/utils.py
Comment thread httpx/dispatch/http11.py Outdated
Comment thread httpx/dispatch/http11.py
f"send_request method={request.method!r} "
f"target={request.url.full_path!r} "
f"headers={request.headers!r}"
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably also want to log the sending of h11.Data and h11.EndOfMessage events, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can add that!

@lovelydinosaur

Copy link
Copy Markdown
Member

Fantastic, I'm loving this!

Couple of comments worth taking a look at.

@lovelydinosaur

Copy link
Copy Markdown
Member

Possible later additions here:

  • INFO level - log responses in an access log like style - method, URL, status code. We'd probably want to include all redirect requests there too.
  • WARNING level - Dropped connections, invalid responses, timeouts etc. Basically anything where a request was started but no response was available to log.
INFO httpx.client GET https://example.com/redirect-to-home 304 <Location: https://example.com/>
INFO httpx.client GET https://example.com/ 200
WARN http.client GET https://example.com/ Failed with ReadTimeout(...)

@sethmlarson

Copy link
Copy Markdown
Contributor Author

With the latest change I tried to normalize the two sets of events being logged for HTTP/1.1 and HTTP/2 so they're similar to look at and debug.

HTTP/1.1

07:02:42.128 - httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='google.com' port=443)
07:02:42.128 - httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='google.com' port=443))
07:02:42.133 - httpx.dispatch.connection - start_connect host='google.com' port=443 timeout=TimeoutConfig(timeout=5.0)
07:02:42.195 - httpx.dispatch.connection - connected http_version='HTTP/1.1'
07:02:42.195 - httpx.dispatch.http11 - send_headers method='GET' target='/' headers=Headers({'host': 'google.com', 'user-agent': 'python-httpx/0.7.1', 'accept': '*/*', 'accept-encoding': 'gzip, deflate, br', 'connection': 'keep-alive'})
07:02:42.195 - httpx.dispatch.http11 - receive_event event=NEED_DATA
07:02:42.239 - httpx.dispatch.http11 - receive_event event=Response(status_code=301, headers=[(b'location', b'https://www.google.com/'), (b'content-type', b'text/html; charset=UTF-8'), (b'date', b'Tue, 27 Aug 2019 12:02:42 GMT'), (b'expires', b'Thu, 26 Sep 2019 12:02:42 GMT'), (b'cache-control', b'public, max-age=2592000'), (b'server', b'gws'), (b'content-length', b'220'), (b'x-xss-protection', b'0'), (b'x-frame-options', b'SAMEORIGIN'), (b'alt-svc', b'quic=":443"; ma=2592000; v="46,43,39"')], http_version=b'1.1', reason=b'Moved Permanently')
07:02:42.241 - httpx.dispatch.http11 - response_closed our_state=DONE their_state=SEND_BODY
07:02:42.242 - httpx.dispatch.http11 - send_event event=ConnectionClosed()
07:02:42.242 - httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='google.com' port=443))
07:02:42.242 - httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='www.google.com' port=443)
07:02:42.243 - httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.google.com' port=443))
07:02:42.249 - httpx.dispatch.connection - start_connect host='www.google.com' port=443 timeout=TimeoutConfig(timeout=5.0)
07:02:42.309 - httpx.dispatch.connection - connected http_version='HTTP/1.1'
07:02:42.310 - httpx.dispatch.http11 - send_headers method='GET' target='/' headers=Headers({'host': 'www.google.com', 'user-agent': 'python-httpx/0.7.1', 'accept': '*/*', 'accept-encoding': 'gzip, deflate, br', 'connection': 'keep-alive'})
07:02:42.311 - httpx.dispatch.http11 - receive_event event=NEED_DATA
07:02:42.381 - httpx.dispatch.http11 - receive_event event=Response(status_code=200, headers=[(b'date', b'Tue, 27 Aug 2019 12:02:42 GMT'), (b'expires', b'-1'), (b'cache-control', b'private, max-age=0'), (b'content-type', b'text/html; charset=ISO-8859-1'), (b'p3p', b'CP="This is not a P3P policy! See g.co/p3phelp for more info."'), (b'content-encoding', b'gzip'), (b'server', b'gws'), (b'x-xss-protection', b'0'), (b'x-frame-options', b'SAMEORIGIN'), (b'set-cookie', b'1P_JAR=2019-08-27-12; expires=Thu, 26-Sep-2019 12:02:42 GMT; path=/; domain=.google.com; SameSite=none'), (b'set-cookie', b'NID=188=Gp8DK6pOzIS9u9wQ_WWtdgqgIYonZLumM5T_UnZ1GrYTwI_c6SJKlX2Ob8RTQMRTUpzinmnoCy5m0kA0gP6ISENfG0LIZ3-ZVBovXWA5kT_R_NubidkP5_haPb-qDGwTG_qrlLBA-kLsIpZNWKpD6t3_E2uOpJFC8jZ2m5I-iJ4; expires=Wed, 26-Feb-2020 12:02:42 GMT; path=/; domain=.google.com; HttpOnly'), (b'alt-svc', b'quic=":443"; ma=2592000; v="46,43,39"'), (b'transfer-encoding', b'chunked')], http_version=b'1.1', reason=b'OK')
07:02:42.383 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.384 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.384 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.385 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.385 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.386 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.387 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.387 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.387 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.388 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.388 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.389 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.389 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.390 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.390 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.391 - httpx.dispatch.http11 - receive_event event=Data(<1 bytes>)
07:02:42.391 - httpx.dispatch.http11 - receive_event event=Data(<2581 bytes>)
07:02:42.392 - httpx.dispatch.http11 - receive_event event=NEED_DATA
07:02:42.392 - httpx.dispatch.http11 - receive_event event=Data(<2439 bytes>)
07:02:42.393 - httpx.dispatch.http11 - receive_event event=EndOfMessage(headers=[])
07:02:42.393 - httpx.dispatch.http11 - response_closed our_state=DONE their_state=DONE
07:02:42.393 - httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.google.com' port=443))

HTTP/2

07:04:41.083 - httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='google.com' port=443)
07:04:41.083 - httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='google.com' port=443))
07:04:41.088 - httpx.dispatch.connection - start_connect host='google.com' port=443 timeout=TimeoutConfig(timeout=5.0)
07:04:41.166 - httpx.dispatch.connection - connected http_version='HTTP/2'
07:04:41.166 - httpx.dispatch.http2 - send_headers stream_id=1 method='GET' target='/' headers=[(b':method', b'GET'), (b':authority', b'google.com'), (b':scheme', b'https'), (b':path', b'/'), (b'user-agent', b'python-httpx/0.7.1'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate, br'), (b'connection', b'keep-alive')]
07:04:41.167 - httpx.dispatch.http2 - end_stream stream_id=1
07:04:41.191 - httpx.dispatch.http2 - receive_event stream_id=0 event=<RemoteSettingsChanged changed_settings:{ChangedSetting(setting=SettingCodes.MAX_CONCURRENT_STREAMS, original_value=None, new_value=100), ChangedSetting(setting=SettingCodes.INITIAL_WINDOW_SIZE, original_value=65535, new_value=1048576), ChangedSetting(setting=SettingCodes.MAX_HEADER_LIST_SIZE, original_value=None, new_value=16384)}>
07:04:41.192 - httpx.dispatch.http2 - receive_event stream_id=0 event=<WindowUpdated stream_id:0, delta:983041>
07:04:41.193 - httpx.dispatch.http2 - receive_event stream_id=0 event=<SettingsAcknowledged changed_settings:{}>
07:04:41.213 - httpx.dispatch.http2 - receive_event stream_id=1 event=<ResponseReceived stream_id:1, headers:[(b':status', b'301'), (b'location', b'https://www.google.com/'), (b'content-type', b'text/html; charset=UTF-8'), (b'date', b'Tue, 27 Aug 2019 12:04:41 GMT'), (b'expires', b'Thu, 26 Sep 2019 12:04:41 GMT'), (b'cache-control', b'public, max-age=2592000'), (b'server', b'gws'), (b'content-length', b'220'), (b'x-xss-protection', b'0'), (b'x-frame-options', b'SAMEORIGIN'), (b'alt-svc', b'quic=":443"; ma=2592000; v="46,43,39"')]>
07:04:41.213 - httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:220, data:3c48544d4c3e3c484541443e3c6d657461206874>
07:04:41.213 - httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:0, data:>
07:04:41.214 - httpx.dispatch.http2 - receive_event stream_id=1 event=<StreamEnded stream_id:1>
07:04:41.214 - httpx.dispatch.http2 - receive_event stream_id=0 event=<PingReceived ping_data:0000000000000000>
07:04:41.217 - httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='google.com' port=443))
07:04:41.217 - httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='www.google.com' port=443)
07:04:41.218 - httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.google.com' port=443))
07:04:41.238 - httpx.dispatch.connection - start_connect host='www.google.com' port=443 timeout=TimeoutConfig(timeout=5.0)
07:04:41.314 - httpx.dispatch.connection - connected http_version='HTTP/2'
07:04:41.315 - httpx.dispatch.http2 - send_headers stream_id=1 method='GET' target='/' headers=[(b':method', b'GET'), (b':authority', b'www.google.com'), (b':scheme', b'https'), (b':path', b'/'), (b'user-agent', b'python-httpx/0.7.1'), (b'accept', b'*/*'), (b'accept-encoding', b'gzip, deflate, br'), (b'connection', b'keep-alive')]
07:04:41.317 - httpx.dispatch.http2 - end_stream stream_id=1
07:04:41.338 - httpx.dispatch.http2 - receive_event stream_id=0 event=<RemoteSettingsChanged changed_settings:{ChangedSetting(setting=SettingCodes.MAX_CONCURRENT_STREAMS, original_value=None, new_value=100), ChangedSetting(setting=SettingCodes.INITIAL_WINDOW_SIZE, original_value=65535, new_value=1048576), ChangedSetting(setting=SettingCodes.MAX_HEADER_LIST_SIZE, original_value=None, new_value=16384)}>
07:04:41.339 - httpx.dispatch.http2 - receive_event stream_id=0 event=<WindowUpdated stream_id:0, delta:983041>
07:04:41.339 - httpx.dispatch.http2 - receive_event stream_id=0 event=<SettingsAcknowledged changed_settings:{}>
07:04:41.392 - httpx.dispatch.http2 - receive_event stream_id=1 event=<ResponseReceived stream_id:1, headers:[(b':status', b'200'), (b'date', b'Tue, 27 Aug 2019 12:04:41 GMT'), (b'expires', b'-1'), (b'cache-control', b'private, max-age=0'), (b'content-type', b'text/html; charset=ISO-8859-1'), (b'p3p', b'CP="This is not a P3P policy! See g.co/p3phelp for more info."'), (b'content-encoding', b'gzip'), (b'server', b'gws'), (b'content-length', b'5048'), (b'x-xss-protection', b'0'), (b'x-frame-options', b'SAMEORIGIN'), (b'set-cookie', b'1P_JAR=2019-08-27-12; expires=Thu, 26-Sep-2019 12:04:41 GMT; path=/; domain=.google.com; SameSite=none'), (b'set-cookie', b'NID=188=prjg5iYy-bcIfsom2sUIFPTHtxxpHicUGQvEJwW2Zs49Sswu69FtIq_A35GRTus9xTLmFUJ66wgENfVEnePNlrx09nadujyCHzyMxS24ucqSkVee6YvAJJHLA6ih-MQMGw-HckU_6hVw4nUOlqIWiN5yqogYs2cG7b96qF4oFTY; expires=Wed, 26-Feb-2020 12:04:41 GMT; path=/; domain=.google.com; HttpOnly'), (b'alt-svc', b'quic=":443"; ma=2592000; v="46,43,39"')]>
07:04:41.395 - httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:5157, data:1f8b08000000000002ffc55ae976dbb6b6fedfa7>
07:04:41.395 - httpx.dispatch.http2 - receive_event stream_id=1 event=<DataReceived stream_id:1, flow_controlled_length:196, data:>
07:04:41.395 - httpx.dispatch.http2 - receive_event stream_id=1 event=<StreamEnded stream_id:1>
07:04:41.395 - httpx.dispatch.http2 - receive_event stream_id=0 event=<PingReceived ping_data:0000000000000000>
07:04:41.397 - httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.google.com' port=443))

Comment thread httpx/utils.py Outdated

@florimondmanca florimondmanca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👏

@sethmlarson
sethmlarson merged commit 6f4f186 into encode:master Aug 27, 2019
@sethmlarson
sethmlarson deleted the logging branch August 27, 2019 16:43
@sethmlarson

Copy link
Copy Markdown
Contributor Author

Thanks for the review @florimondmanca and @tomchristie!

Kludex referenced this pull request in pydantic/httpx2 May 11, 2026
* Ensured AnyIO 3.x compatibility

* Fixed linting errors

* Improved the comment

* Fixed deprecation warnings

* Fixed mypy errors and upgraded AnyIO
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Add DEBUG logging to Dispatchers and Client for easier debugging

3 participants