diff --git a/README.md b/README.md index 9f848615..44f19f50 100644 --- a/README.md +++ b/README.md @@ -568,6 +568,23 @@ options = ChangeFeedOptions( client.change_feed(fql('Product.all().toStream()'), options) ``` +## Logging + +Debug logging is handled by the standard logging package in under the `fauna` namespace. We will log the request with body, excluding the Authorization header, as well as the full response. + +In your application, you can enable debug logging with the following. Given this is a standard convention in Python, you may want more fine-grained control over which libraries log. See Python's how-to at https://docs.python.org/3/howto/logging.html. +```python +import logging +from fauna.client import Client +from fauna import fql + +logging.basicConfig( + level=logging.DEBUG +) +c = Client() +c.query(fql("42")) +``` + ## Setup ```bash diff --git a/fauna/client/client.py b/fauna/client/client.py index d1cac5c8..6b03ad6d 100644 --- a/fauna/client/client.py +++ b/fauna/client/client.py @@ -1,3 +1,4 @@ +import logging from dataclasses import dataclass from datetime import timedelta from typing import Any, Dict, Iterator, Mapping, Optional, Union, List @@ -14,6 +15,8 @@ from fauna.query import Query, Page, fql from fauna.query.models import StreamToken +logger = logging.getLogger("fauna") + DefaultHttpConnectTimeout = timedelta(seconds=5) DefaultHttpReadTimeout: Optional[timedelta] = None DefaultHttpWriteTimeout = timedelta(seconds=5) @@ -217,7 +220,7 @@ def __init__( max_keepalive_connections=DefaultMaxIdleConnections, keepalive_expiry=idle_timeout_s, ), - )) + ), logger) fauna.global_http_client = c self._session = fauna.global_http_client diff --git a/fauna/http/httpx_client.py b/fauna/http/httpx_client.py index c317d7fa..189405ab 100644 --- a/fauna/http/httpx_client.py +++ b/fauna/http/httpx_client.py @@ -1,4 +1,5 @@ import json +import logging from contextlib import contextmanager from json import JSONDecodeError from typing import Mapping, Any, Optional, Iterator @@ -50,9 +51,10 @@ def close(self) -> None: class HTTPXClient(HTTPClient): - def __init__(self, client: httpx.Client): + def __init__(self, client: httpx.Client, logger: logging.Logger): super(HTTPXClient, self).__init__() self._c = client + self._logger = logger def request( self, @@ -69,14 +71,29 @@ def request( json=data, headers=headers, ) + + if self._logger.isEnabledFor(logging.DEBUG): + headers_to_log = request.headers.copy() + headers_to_log.pop("Authorization") + self._logger.debug( + f"query.request method={request.method} url={request.url} headers={headers_to_log} data={data}" + ) + except httpx.InvalidURL as e: raise ClientError("Invalid URL Format") from e try: - return HTTPXResponse(self._c.send( + response = self._c.send( request, stream=False, - )) + ) + + if self._logger.isEnabledFor(logging.DEBUG): + self._logger.debug( + f"query.response status_code={response.status_code} headers={response.headers} data={response.text}" + ) + + return HTTPXResponse(response) except (httpx.HTTPError, httpx.InvalidURL) as e: raise NetworkError("Exception re-raised from HTTP request") from e @@ -87,14 +104,37 @@ def stream( headers: Mapping[str, str], data: Mapping[str, Any], ) -> Iterator[Any]: - with self._c.stream( - "POST", url=url, headers=headers, json=data) as response: + request = self._c.build_request( + method="POST", + url=url, + headers=headers, + json=data, + ) + + if self._logger.isEnabledFor(logging.DEBUG): + headers_to_log = request.headers.copy() + headers_to_log.pop("Authorization") + self._logger.debug( + f"stream.request method={request.method} url={request.url} headers={headers_to_log} data={data}" + ) + + response = self._c.send( + request=request, + stream=True, + ) + + try: yield self._transform(response) + finally: + response.close() def _transform(self, response): try: for line in response.iter_lines(): - yield json.loads(line) + loaded = json.loads(line) + if self._logger.isEnabledFor(logging.DEBUG): + self._logger.debug(f"stream.data data={loaded}") + yield loaded except httpx.ReadTimeout as e: raise NetworkError("Stream timeout") from e except (httpx.HTTPError, httpx.InvalidURL) as e: