Skip to content

Commit

Permalink
feat(configurable-logger): add support for the configurable logger (#58)
Browse files Browse the repository at this point in the history
This commit introduces the configurable logger feature. With this enhancement, the core library now boasts a flexible logging system tailored specifically for logging HTTP request and response information. By addressing the lack of a standardized logging mechanism, developers can now effectively debug and monitor network interactions with ease. The implementation includes a modular logging framework supporting customizable logging formats, default logger, and verbosity levels, empowering users to tailor logging behavior to their specific requirements. Additionally, seamless integration with existing logging libraries. This enhancement marks a significant step towards improving the usability and reliability of the core library, enhancing the development experience for our community.
  • Loading branch information
sufyankhanrao committed May 13, 2024
1 parent 45ce9e1 commit 1966ce7
Show file tree
Hide file tree
Showing 23 changed files with 1,029 additions and 205 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,21 @@ pip install apimatic-core
| [`ApiResponse`](apimatic_core/http/response/api_response.py) | A wrapper class for Api Response |
| [`HttpResponse`](apimatic_core/http/response/http_response.py) | A class which contains information about the HTTP Response |

## Logging Configuration
| Name | Description |
|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------|
| [`ApiLoggingConfiguration`](apimatic_core/logger/configuration/api_logging_configuration.py) | Holds overall logging configuration for logging an API call |
| [`ApiRequestLoggingConfiguration`](apimatic_core/logger/configuration/api_logging_configuration.py) | Holds logging configuration for API request |
| [`ApiResponseLoggingConfiguration`](apimatic_core/logger/configuration/api_logging_configuration.py) | Holds logging configuration for API response |


## Logger
| Name | Description |
|------------------------------------------------------------------|-----------------------------------------------------|
| [`EndpointLogger`](apimatic_core/logger/endpoint_logger.py) | A class to provide logging for an HTTP request |
| Name | Description |
|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`SdkLogger`](apimatic_core/logger/sdk_logger.py) | Responsible for logging the request and response of an API call, it represents the default implementation of ApiLogger when there exist any logging configuration |
| [`NoneSdkLogger`](apimatic_core/logger/sdk_logger.py) | Represents the default implementation for ApiLogger when no logging configuration is provided |
| [`ConsoleLogger`](apimatic_core/logger/default_logger.py) | Represents the default implementation for Logger when no custom implementation is provided |
| [`LoggerFactory`](apimatic_core/logger/sdk_logger.py) | Responsible for providing the ApiLogger implementation (`SdkLogger` \| `NoneSdkLogger`) based on the logging configuration |

## Types
| Name | Description |
Expand Down
3 changes: 2 additions & 1 deletion apimatic_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
'factories',
'types',
'logger',
'exceptions'
'exceptions',
'constants'
]
69 changes: 24 additions & 45 deletions apimatic_core/api_call.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
from apimatic_core.configurations.endpoint_configuration import EndpointConfiguration
from apimatic_core.configurations.global_configuration import GlobalConfiguration
from apimatic_core.logger.endpoint_logger import EndpointLogger
from apimatic_core.logger.sdk_logger import LoggerFactory
from apimatic_core.response_handler import ResponseHandler


class ApiCall:

@property
def new_builder(self):
return ApiCall(self._global_configuration, logger=self._endpoint_logger.logger)
return ApiCall(self._global_configuration)

def __init__(
self,
global_configuration=GlobalConfiguration(),
logger=None
global_configuration=GlobalConfiguration()
):
self._global_configuration = global_configuration
self._request_builder = None
self._response_handler = ResponseHandler()
self._endpoint_configuration = EndpointConfiguration()
self._endpoint_logger = EndpointLogger(logger)
self._endpoint_name_for_logging = None
self._api_logger = LoggerFactory.get_api_logger(self._global_configuration.get_http_client_configuration()
.logging_configuration)

def request(self, request_builder):
self._request_builder = request_builder
Expand All @@ -34,52 +33,32 @@ def endpoint_configuration(self, endpoint_configuration):
self._endpoint_configuration = endpoint_configuration
return self

def endpoint_name_for_logging(self, endpoint_name_for_logging):
self._endpoint_name_for_logging = endpoint_name_for_logging
return self

def execute(self):
try:
_http_client_configuration = self._global_configuration.get_http_client_configuration()

if _http_client_configuration.http_client is None:
raise ValueError("An HTTP client instance is required to execute an Api call.")

_http_request = self._request_builder \
.endpoint_logger(self._endpoint_logger) \
.endpoint_name_for_logging(self._endpoint_name_for_logging) \
.build(self._global_configuration)
_http_client_configuration = self._global_configuration.get_http_client_configuration()

_http_callback = _http_client_configuration.http_callback
if _http_client_configuration.http_client is None:
raise ValueError("An HTTP client instance is required to execute an Api call.")

self.update_http_callback(_http_callback,
_http_callback.on_before_request if _http_callback is not None else None,
_http_request, "Calling the on_before_request method of http_call_back for {}.")
_http_request = self._request_builder.build(self._global_configuration)

self._endpoint_logger.debug("Raw request for {} is: {}".format(
self._endpoint_name_for_logging, vars(_http_request)))
# Logging the request
self._api_logger.log_request(_http_request)

_http_response = _http_client_configuration.http_client.execute(
_http_request, self._endpoint_configuration)
_http_callback = _http_client_configuration.http_callback

self._endpoint_logger.debug("Raw response for {} is: {}".format(
self._endpoint_name_for_logging, vars(_http_response)))
# Applying the on before sending HTTP request callback
if _http_callback is not None:
_http_callback.on_before_request(_http_request)

self.update_http_callback(_http_callback,
_http_callback.on_after_response if _http_callback is not None else None,
_http_response, "Calling on_after_response method of http_call_back for {}.")
# Executing the HTTP call
_http_response = _http_client_configuration.http_client.execute(
_http_request, self._endpoint_configuration)

return self._response_handler.endpoint_logger(self._endpoint_logger) \
.endpoint_name_for_logging(self._endpoint_name_for_logging) \
.handle(_http_response, self._global_configuration.get_global_errors())
except Exception as e:
self._endpoint_logger.error(e)
raise
# Logging the response
self._api_logger.log_response(_http_response)

def update_http_callback(self, http_callback, func, argument, log_message):
if http_callback is None:
return
# Applying the after receiving HTTP response callback
if _http_callback is not None:
_http_callback.on_after_response(_http_response)

self._endpoint_logger.info(log_message.format(
self._endpoint_name_for_logging))
func(argument)
return self._response_handler.handle(_http_response, self._global_configuration.get_global_errors())
3 changes: 3 additions & 0 deletions apimatic_core/constants/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = [
'logger_constants'
]
44 changes: 44 additions & 0 deletions apimatic_core/constants/logger_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

class LoggerConstants:
METHOD = "method"
"""Key representing the method of the HTTP request."""

URL = "url"
"""Key representing the URL of the HTTP request."""

QUERY_PARAMETER = "query_parameter"
"""Key representing the query parameters of the HTTP request."""

HEADERS = "headers"
"""Key representing the headers of the HTTP request or response."""

BODY = "body"
"""Key representing the body of the HTTP request or response."""

STATUS_CODE = "status_code"
"""Key representing the status code of the HTTP response."""

CONTENT_LENGTH = "content_length"
"""Key representing the content length of the HTTP response."""

CONTENT_TYPE = "content_type"
"""Key representing the content type of the HTTP response."""

CONTENT_LENGTH_HEADER = "content-length"
"""Key representing the content length header."""

CONTENT_TYPE_HEADER = "content-type"
"""Key representing the content type header."""

NON_SENSITIVE_HEADERS = tuple(map(lambda x: x.lower(), [
"Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language",
"Access-Control-Allow-Origin", "Cache-Control", "Connection",
"Content-Encoding", "Content-Language", "Content-Length", "Content-Location",
"Content-MD5", "Content-Range", "Content-Type", "Date", "ETag", "Expect",
"Expires", "From", "Host", "If-Match", "If-Modified-Since", "If-None-Match",
"If-Range", "If-Unmodified-Since", "Keep-Alive", "Last-Modified", "Location",
"Max-Forwards", "Pragma", "Range", "Referer", "Retry-After", "Server",
"Trailer", "Transfer-Encoding", "Upgrade", "User-Agent", "Vary", "Via",
"Warning", "X-Forwarded-For", "X-Requested-With", "X-Powered-By"
]))
"""List of sensitive headers that need to be filtered."""
23 changes: 16 additions & 7 deletions apimatic_core/http/configurations/http_client_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,20 @@ def retry_statuses(self):
def retry_methods(self):
return self._retry_methods

def __init__(
self, http_client_instance=None,
override_http_client_configuration=False, http_call_back=None,
timeout=60, max_retries=0, backoff_factor=2,
retry_statuses=[408, 413, 429, 500, 502, 503, 504, 521, 522, 524],
retry_methods=['GET', 'PUT']
):
@property
def logging_configuration(self):
return self._logging_configuration

def __init__(self, http_client_instance=None,
override_http_client_configuration=False, http_call_back=None,
timeout=60, max_retries=0, backoff_factor=2,
retry_statuses=None, retry_methods=None, logging_configuration=None):
if retry_statuses is None:
retry_statuses = [408, 413, 429, 500, 502, 503, 504, 521, 522, 524]

if retry_methods is None:
retry_methods = ['GET', 'PUT']

self._http_response_factory = HttpResponseFactory()

# The Http Client passed from the sdk user for making requests
Expand Down Expand Up @@ -84,5 +91,7 @@ def __init__(
# The Http Client to use for making requests.
self._http_client = None

self._logging_configuration = logging_configuration

def set_http_client(self, http_client):
self._http_client = http_client
6 changes: 4 additions & 2 deletions apimatic_core/logger/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
__all__ = [
'endpoint_logger'
]
'default_logger',
'sdk_logger',
'configuration'
]
3 changes: 3 additions & 0 deletions apimatic_core/logger/configuration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = [
"api_logging_configuration",
]

0 comments on commit 1966ce7

Please sign in to comment.