From 9dc58a233dfe3c8664b77c681865aae40817f1f7 Mon Sep 17 00:00:00 2001 From: Lucas Pedroza Date: Thu, 10 Oct 2024 14:20:45 +0200 Subject: [PATCH] feat: add debug logging --- README.md | 17 ++++++++++++++ fauna/client/client.py | 6 ++++- fauna/http/httpx_client.py | 46 +++++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 7 deletions(-) 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..d1834e0b 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,8 @@ 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..2ca06593 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,25 @@ 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 +100,35 @@ 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: