{"id":19422213,"url":"https://github.com/u-ways/logging-http-client","last_synced_at":"2025-04-24T15:32:11.016Z","repository":{"id":253706391,"uuid":"844131476","full_name":"u-ways/logging-http-client","owner":"u-ways","description":"A logging library built on top of the requests library to provide a familiar interface for sending HTTP requests with observability features out-of-the-box.","archived":false,"fork":false,"pushed_at":"2024-10-30T20:16:36.000Z","size":64,"stargazers_count":0,"open_issues_count":4,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-30T21:21:21.226Z","etag":null,"topics":["client","hacktoberfest","http","library","logging","observability","python"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/logging-http-client","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/u-ways.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-18T13:34:16.000Z","updated_at":"2024-10-30T20:34:41.000Z","dependencies_parsed_at":"2024-08-23T20:55:14.691Z","dependency_job_id":"bbdcafce-f18f-487f-a637-b7a581e976c9","html_url":"https://github.com/u-ways/logging-http-client","commit_stats":null,"previous_names":["u-ways/logging-http-client"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/u-ways%2Flogging-http-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/u-ways%2Flogging-http-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/u-ways%2Flogging-http-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/u-ways%2Flogging-http-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/u-ways","download_url":"https://codeload.github.com/u-ways/logging-http-client/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223958288,"owners_count":17231764,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","hacktoberfest","http","library","logging","observability","python"],"created_at":"2024-11-10T13:32:39.477Z","updated_at":"2025-04-24T15:32:11.009Z","avatar_url":"https://github.com/u-ways.png","language":"Python","readme":"# Logging HTTP Client\n\n[![CICD](https://github.com/u-ways/logging-http-client/actions/workflows/CICD.yml/badge.svg)](https://github.com/u-ways/logging-http-client/actions/workflows/CICD.yml)\n[![Python: 3.12](https://img.shields.io/badge/Python-3.12-008be1.svg)](https://www.python.org/downloads/release/python-3110/)\n[![Build: Poetry](https://img.shields.io/badge/Build-Poetry-008be1.svg)](https://python-poetry.org/)\n[![Linter: Flake8](https://img.shields.io/badge/Linter-Flake8-008be1.svg)](https://flake8.pycqa.org/en/latest/)\n[![Style: Black](https://img.shields.io/badge/Style-Black-008be1.svg)](https://github.com/psf/black)\n[![PyPI - Version](https://img.shields.io/pypi/v/logging-http-client?color=ffd343)](https://pypi.org/project/logging-http-client/)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/logging-http-client?color=ffd343)](https://pypistats.org/packages/logging-http-client)\n[![Docs](https://img.shields.io/badge/ReadTheDocs-6e21d5.svg)](https://logging-http-client.readthedocs.io/en/latest/)\n\nA logging library built on top of the [requests](https://pypi.org/project/requests/) library to provide a familiar\ninterface for sending HTTP requests with observability features out-of-the-box.\n\n## Table of Contents\n\n- [Logging HTTP Client](#logging-http-client)\n  - [Table of Contents](#table-of-contents)\n  - [Background](#background)\n  - [Usage](#usage)\n    - [1. Drop-in Replacement for requests](#1-drop-in-replacement-for-requests)\n    - [2. Using the HTTP Client with reusable Sessions](#2-using-the-http-client-with-reusable-sessions)\n      - [i. Disabling Reusable Sessions For The HTTP Client](#i-disabling-reusable-sessions-for-the-http-client)\n      - [ii. Adding Shared Headers to the HTTP Client](#ii-adding-shared-headers-to-the-http-client)\n      - [iii. Setting the client's `x-source`](#iii-setting-the-clients-x-source)\n      - [iii. `x-request-id` is automatically set](#iii-x-request-id-is-automatically-set)\n      - [iv. `x-correlation-id` can be automatically set](#iv-x-correlation-id-can-be-automatically-set)\n    - [3. Custom Logging Hooks](#3-custom-logging-hooks)\n      - [i. Request Logging Hook](#i-request-logging-hook)\n      - [ii. Response Logging Hook](#ii-response-logging-hook)\n    - [4. Default Logging Configurations](#4-default-logging-configurations)\n      - [i. Disabling Request or Response Logging](#i-disabling-request-or-response-logging)\n      - [ii. Enabling Request or Response Body Logging](#ii-enabling-request-or-response-body-logging)\n      - [iii. Customizing the logging level](#iii-customizing-the-logging-level)\n    - [5. Obscuring Sensitive Data](#5-obscuring-sensitive-data)\n      - [i. Request Log Record Obscurer](#i-request-log-record-obscurer)\n      - [ii. Response Log Record Obscurer](#ii-response-log-record-obscurer)\n      - [iii. Activating Obscurers In Your Own Logging Hooks](#iii-activating-obscurers-in-your-own-logging-hooks)\n  - [HTTP Log Record Structure](#http-log-record-structure)\n  - [Contributing](#contributing)\n    - [Prerequisites](#prerequisites)\n    - [Environment Setup](#environment-setup)\n    - [Code Quality](#code-quality)\n    - [Versioning Strategy](#versioning-strategy)\n\n## Background\n\nThe [requests](https://pypi.org/project/requests/) library is a popular library for sending HTTP requests in Python. \n\nHowever, it does not provide adequate observability features out of the box such as tracing and logging. This means \ndevelopers might be inclined to write their own custom traceability wrappers or logging utilities when writing code that \ndeals with requests. As such, this library serves as a drop-in replacement to the requests library that takes an \nopinionated approach to provide you with common observability features out-of-the-box.\n\nFor example, by simply using this library for your requests, the following will be logged:\n\n```python\nimport logging\nimport logging_http_client as requests\n\n# Basic logging configuration for demonstration purposes \nlogging.basicConfig(\n    level=logging.INFO,\n    format='%(message)s - %(http)s'\n)\n\nrequests.get(\n    url=\"https://www.python.org\",\n    headers={\"x-foo\": \"bar\"},\n)\n\n# =\u003e Log records will include:\n#    message: REQUEST, \n#    http: { \n#       request_id: \"6a09ec23-b318-43d2-81a1-8c1fcaf77d05\", \n#       request_method: \"GET\", \n#       request_url: \"https://www.python.org\", \n#       request_headers: { \"x-foo\": \"bar\", \"x-request-id\": \"6a09ec23-b318-43d2-81a1-8c1fcaf77d05\", ... } \n#   }\n#\n#    message: RESPONSE,\n#    http: {\n#       request_id: \"6a09ec23-b318-43d2-81a1-8c1fcaf77d05\",\n#       response_status: 200,\n#       response_headers: { \"content-type\": \"text/html\", ... },\n#       response_duration_ms: 30\n#    }\n```\n\nYou have full control over the logging behaviour, and you can customise it to suit your needs. The library provides\nhooks for custom logging, and you can disable or enable request or response logging as needed. You can also obscure\nsensitive data in the log records, and set shared headers for the client instances that relies on reusable sessions\nfor better performance by default.\n\n## Usage\n\nThe quickest way to get started is to install the package from PyPI:\n\n```shell\npip install logging-http-client\n```\n\nFor poetry users:\n\n```shell\npoetry add logging-http-client\n```\n\n### 1. Drop-in Replacement for requests\n\nThe library is designed to decorate requests library existing API.\nHence, you can use it in the same way you would use the [requests](https://pypi.org/project/requests/) library:\n\n```python\nimport logging_http_client\n\nresponse = logging_http_client.get('https://www.python.org')\nprint(response.status_code)\n# =\u003e 200\n```\n\nGiven it's built as a wrapper around the requests library, you can alias the\nimport to `requests` and use it as drop-in replacement for the requests' library.\n\n```python\nimport logging_http_client as requests\n\nresponse = requests.get('https://www.python.org')\nprint(response.status_code)\n# =\u003e 200\n```\n\nThe other HTTP methods are supported - see `requests.api`.\nFull documentation is at: https://requests.readthedocs.io\n\n### 2. Using the HTTP Client with reusable Sessions\n\nThe library provides a `LoggingHttpClient` class which is essentially a wrapper around the core component of the\nrequests library, the `Session` object, with additional features such as enabling reusable sessions or not.\n\n```python\nimport logging_http_client\n\nclient = logging_http_client.create()\n\nresponse = client.get('https://www.python.org')\nprint(response.status_code)\n# =\u003e 200\n```\n\n#### i. Disabling Reusable Sessions For The HTTP Client\n\nBy default, the `LoggingHttpClient` class is created with a reusable session. If you want to disable this behaviour, you\ncan pass the `reusable_session=False` argument to the `create` method.\n\n```python\nimport logging_http_client\n\nclient = logging_http_client.create(reusable_session=False)\n\nresponse = client.get('https://www.python.org')\nprint(response.status_code)\n# =\u003e 200\n```\n\n#### ii. Adding Shared Headers to the HTTP Client\n\nYou also have access to the session object headers within the `LoggingHttpClient` class, so you can add shared headers to the\nsession object [just like you would with the requests library](https://requests.readthedocs.io/en/latest/user/advanced/#session-objects).\n\n```python\nimport logging_http_client\n\nclient = logging_http_client.create()\n\nclient.shared_headers = {\"Authorization\": \"Bearer \u003ctoken\u003e\"}\n\n# To clear the headers, you can set it to None\nclient.shared_headers = None\n# or delete the attribute\ndel client.shared_headers\n```\n\n#### iii. Setting the client's `x-source`\n\nIt's common to set a `x-source` header to identify the source of the request.\nYou can set this header on the client by passing the `source` argument to the\n`create` method.\n\n```python\nimport logging\n\nimport logging_http_client\n\nroot_logger = logging.getLogger()\nroot_logger.setLevel(level=logging.INFO)\n\nclient = logging_http_client.create(source=\"my-system-name\", logger=root_logger)\n\nresponse = client.get('https://www.python.org')\n# =\u003e Log record will include: \n#    { http { request_source: \"my-system-name\", ... } }\n```\n\n#### iii. `x-request-id` is automatically set\n\nThe library automatically sets a `x-request-id` header on the request, and is logged within the response as well. The\n`x-request-id` is a UUID that is generated for each request, and it's attached on both the request and the response\nlogs.\n\n```python\nimport logging\n\nimport logging_http_client\n\nroot_logger = logging.getLogger()\nroot_logger.setLevel(level=logging.INFO)\n\nclient = logging_http_client.create(source=\"my-system-name\", logger=root_logger)\n\nresponse = client.get('https://www.python.org')\n# =\u003e The client will append the `x-request-id` header to the request\n#\n# =\u003e Both request and response log records will include: \n#    { http { request_id: \"\u003cuuid\u003e\", ... } }\n# =\u003e The reqeust log record will also attach it as a header: \n#    { http { request_headers: { \"x-request-id\": \"\u003cuuid\u003e\", ... }, ... } }\n```\n\n#### iv. `x-correlation-id` can be automatically set\n\nIt's common to set a `x-correlation-id` header to identify the correlation of the request within a distributed system.\nInstead of having to set this header manually every single request you make, you can pass a correlation ID generator\nfunction to the client, and it will automatically set the `x-correlation-id` header for each request.\n\n\u003e [!WARNING]\n\u003e Be aware that `x-request-id` is not the same as `x-correlation-id`.\n\u003e \n\u003e The `x-request-id` is unique to each request, while the `x-correlation-id` is used to correlate requests within a\n\u003e chain of events that can span multiple services, this is common in a microservice architecture. Please ensure you\n\u003e understand the difference between the two whilst using them with this library.\n\n```python\nimport uuid\nimport logging_http_client \n\ndef correlation_id_provider() -\u003e str:\n    return str(uuid.uuid4())\n\nlogging_http_client.set_correlation_id_provider(correlation_id_provider)\n\nlogging_http_client.create().get('https://www.python.org')\n# =\u003e The client will append the `x-correlation-id` header to the request \n#\n# =\u003e The request log records will include:\n#    { http { request_headers: { \"x-correlation-id\": \"\u003cuuid\u003e\", ... }, ... } }\n```\n\nDo note we do NOT set the `x-correlation-id` header on the response, it's the responsibility of the server to set it\nback on the response, if they don't, then you need to rely on your logging setup to append the `correlation_id` as an \nextra log record attribute on the client side by other means.\n\n### 3. Custom Logging Hooks\n\nThe library provides a way to attach custom logging hooks at the global level. They're intended to REPLACE the\ndefault logging behaviour with your own logging logic. Here is how you can apply:\n\n#### i. Request Logging Hook\n\nThe request logging hook is called **before** the request is sent. It gives you access to the client logger, and\nthe [prepared request](https://requests.readthedocs.io/en/latest/user/advanced/#prepared-requests) object. You can use \nthis hook to log the request before it's sent.\n\n```python\nimport logging\n\nfrom requests import PreparedRequest\n\nimport logging_http_client\n\n\ndef custom_request_logging_hook(logger: logging.Logger, request: PreparedRequest):\n  logger.debug(\"Custom request logging for %s\", request.url)\n\n\nlogging_http_client.set_request_logging_hooks([custom_request_logging_hook])\n\nlogging_http_client.create().get('https://www.python.org')\n\n# =\u003e Log record will include:\n#    { message { \"Custom request logging for https://www.python.org\" } }\n```\n\nIt's worth noting that setting the logging hooks list applies a replace operation. As such, if you want to keep the \ndefault logging hooks you will need to append them in your list, i.e.\n\n```python\nimport logging\n\nfrom requests import PreparedRequest\n\nimport logging_http_client\nfrom logging_http_client import default_request_logging_hook\n\n\ndef custom_request_logging_hook(logger: logging.Logger, request: PreparedRequest):\n  logger.debug(\"Custom request logging for %s\", request.url)\n\n\nlogging_http_client.set_request_logging_hooks(\n  [default_request_logging_hook, custom_request_logging_hook]\n)\n```\n\nThe hooks will be called in the order they are provided. Each hook will receive an IMMUTABLE request object.\n\n#### ii. Response Logging Hook\n\nThe response logging hook is called **after** the response is received. It gives you access to the client logger, and\nthe [response object](https://requests.readthedocs.io/en/latest/api/#requests.Response). You can use this hook to log \nthe response after it's received.\n\n```python\nimport logging\n\nfrom requests import Response\n\nimport logging_http_client\n\n\ndef custom_response_logging_hook(logger: logging.Logger, response: Response):\n  logger.debug(\"Custom response logging for %s\", response.url)\n\n\ndef custom_response_logging_hook_2(logger: logging.Logger, response: Response):\n  logger.debug(\"Another custom response logging for %s\", response.url)\n\n\nlogging_http_client.set_response_logging_hooks(\n  [custom_response_logging_hook, custom_response_logging_hook_2]\n)\n\nlogging_http_client.create().get('https://www.python.org')\n\n# =\u003e Log records will include:\n#    { message { \"Custom response logging for https://www.python.org\" } }\n#    { message { \"Another custom response logging for https://www.python.org\" } }\n```\n\nThe hooks will be called in the order they are provided. Each hook will receive an IMMUTABLE request object.\n\n### 4. Default Logging Configurations\n\nThe default logging comes with a set of configurations that can be customised to suit your needs.\n\n#### i. Disabling Request or Response Logging\n\nYou can disable request or response logging by calling the `disable_request_logging` or `disable_response_logging`\nmethods respectively. This will prevent the library from generating log records for requests or responses UNLESS you\nhave custom logging hooks set.\n\n```python\nimport logging_http_client\n\nlogging_http_client.disable_request_logging()\nlogging_http_client.disable_response_logging()\n\nlogging_http_client.create().get('https://www.python.org')\n# =\u003e No request log record will be generated\n# =\u003e No response log record will be generated\n```\n\n#### ii. Enabling Request or Response Body Logging\n\nBy default, the library does not log the request or response body. You can enable this by calling the `enable_request_body_logging`\nor `enable_response_body_logging` methods respectively. This will log the request or response body in the log record.\n\n```python\nimport logging_http_client\n\nlogging_http_client.enable_request_body_logging()\nlogging_http_client.enable_response_body_logging()\n\nlogging_http_client.create().get('https://www.python.org')\n# =\u003e Log record will include the request or response body (if present)\n```\n\n#### iii. Customizing the logging level\n\nBy default, the library provided logging hooks will log at the `INFO` level. To adjust this, \nyou can specify the desired level as an integer per Python log level setting conventions:\n\n```python\nimport logging\n\nimport logging_http_client\n\n\"\"\"\nCRITICAL = 50\nERROR    = 40\nWARNING  = 30\nINFO     = 20\nDEBUG    = 10\nNOTSET   = 0\n\"\"\"\nlogging_http_client.set_default_hooks_logging_level(logging.DEBUG)\n\nlogging_http_client.create().get('https://www.python.org')\n# =\u003e Logs will be recorded at the DEBUG level now.\n```\n\n### 5. Obscuring Sensitive Data\n\nThe library provides a way to obscure sensitive data in the request or response log records. This is useful when you\nwant to log the request or response body but want to obscure sensitive data such as passwords, tokens, etc.\n\n#### i. Request Log Record Obscurer\n\nYou can set a request log record obscurer by calling the `set_request_log_record_obscurers` method. The obscurer\nfunction should take a `HttpLogRecord` object and expects to return a modified `HttpLogRecord` object. The obscurer\nfunction will be called JUST BEFORE the request is logged.\n\n```python\nimport logging_http_client\nfrom logging_http_client import HttpLogRecord\n\n\ndef request_log_record_obscurer(record: HttpLogRecord) -\u003e HttpLogRecord:\n  record.request_method = \"REDACTED\"\n  if record.request_headers.get(\"Authorization\") is not None:\n    record.request_headers[\"Authorization\"] = \"****\"\n  return record\n\n\nlogging_http_client.set_request_log_record_obscurers([request_log_record_obscurer])\n\nlogging_http_client.create().get(\n  url='https://www.python.org',\n  headers={\"Authorization\": \"Bearer SOME-SECRET-TOKEN\"}\n)\n\n# =\u003e Log record will include:\n#    { http { request_headers: { \"Authorization \": \"****\", ... }, ... } }\n```\n\n#### ii. Response Log Record Obscurer\n\nLikewise, you can set a response log record obscurer by calling the `set_response_log_record_obscurers` method.\nThe obscurer function should take a `HttpLogRecord` object and expects to return a modified `HttpLogRecord` object.\n\n```python\nimport logging_http_client\nfrom logging_http_client import HttpLogRecord\n\n\ndef response_log_record_obscurer(record: HttpLogRecord) -\u003e HttpLogRecord:\n  record.response_status = 999\n  if record.response_body is not None:\n    record.response_body = record.response_body.replace(\"SENSITIVE\", \"****\")\n  return record\n\n\nlogging_http_client.set_response_log_record_obscurers([response_log_record_obscurer])\nlogging_http_client.enable_response_body_logging()\n\nlogging_http_client.create().get('https://www.python.org')\n# Assume the response body contains \"some response body with SENSITIVE information\" \n\n# =\u003e Log record will include:\n#    { http { response_status: 999, response_body: \"some response body with **** information\", ... } }\n```\n\n#### iii. Activating Obscurers In Your Own Logging Hooks\n\nIt's important to know that obscurers are applicable to hooks that utilize the `http_log_record.HttpLogRecord`\ndata structure, AND which call the `HttpLogRecord.from_request` method (or `HttpLogRecord.from_response` for \nresponse obscurers) to generate the log record within the logging hook. \n\nAlso, much like the logging hooks, you can provide multiple obscurers. They will run in the order they are provided, \nand they're accumulative. This means that the output of the first obscurer will be passed to the next obscurer, \nand so on.\n\nHere is a comprehensive example of a custom logging hook that correctly uses `HttpLogRecord.from_request` to apply \nobscurers without having to manually run them yourself:\n\n```python\nimport logging\nimport logging_http_client\n\nfrom requests import PreparedRequest\nfrom logging_http_client import HttpLogRecord\n\n\ndef custom_request_logging_hook(logger: logging.Logger, request: PreparedRequest):\n  logger.debug(\n    \"Custom request logging for %s\",\n    request.url,\n    # IMPORTANT: Usage of this static method will automatically apply the obscurers for you.\n    extra=HttpLogRecord.from_request(request)\n  )\n\n\ndef first_request_obscurer(record: HttpLogRecord) -\u003e HttpLogRecord:\n  if record.request_headers.get(\"Authorization\"):\n    record.request_headers[\"Authorization\"] = \"Bearer ****\"\n  return record\n\n\ndef second_request_obscurer(record: HttpLogRecord) -\u003e HttpLogRecord:\n  if record.request_body:\n    record.request_body = \"OBSCURED_BODY\"\n  return record\n\n\nlogging_http_client.enable_request_body_logging()\nlogging_http_client.set_request_logging_hooks([custom_request_logging_hook])\nlogging_http_client.set_request_log_record_obscurers([first_request_obscurer, second_request_obscurer])\n\nroot_logger = logging.getLogger()\nroot_logger.setLevel(level=logging.DEBUG)\n\nclient = logging_http_client.create(logger=root_logger)\nclient.post(\n  url=\"https://www.python.org\",\n  headers={\"accept\": \"application/json\", \"Authorization\": \"Bearer secret\"},\n  json={\"sensitive\": \"data\"},\n)\n# =\u003e Log record will include:\n#    { http { 'request_headers': { 'Authorization': 'Bearer ****', ... }, 'request_body': 'OBSCURED_BODY', ... }\n```\n\n## HTTP Log Record Structure\n\nThe library logs HTTP requests and responses as structured log records. The log records are structured as JSON\nobject passed to the logger's `extra` keyword argument. The log records are structured as follows:\n\n```json\n{\n  \"http\": {\n    \"request_id\": \"\u003cuuid\u003e\",\n    \"request_source\": \"\u003csource\u003e\",\n    \"request_method\": \"\u003cmethod\u003e\",\n    \"request_url\": \"\u003curl\u003e\",\n    \"request_query_params\": \"\u003cquery_params\u003e\",\n    \"request_headers\": \"\u003cheaders\u003e\",\n    \"request_body\": \"\u003cbody\u003e\",\n    \"response_source\": \"\u003csource\u003e\",\n    \"response_status\": \"\u003cstatus\u003e\",\n    \"response_headers\": \"\u003cheaders\u003e\",\n    \"response_duration_ms\": \"\u003cduration\u003e\",\n    \"response_body\": \"\u003cbody\u003e\"\n  }\n}\n```\n\nThe `request_id` is a UUID generated for each request, and it's attached to both the request and the response log \nrecords. The `request_source` is collected form the request's `x-source` header, if it's not set, it will be `UNKNOWN`.\nLikewise, the `response_source` is collected from the response's `x-source` header, but if it's not set, it will be \ncollected from the `request_url` host (and port) values instead.\n\nIf any of those top-level fields are `None`, `{}`, `[]`, `\"\"`, `0`, or `0.0`,\nthey will be omitted from the log record for brevity purposes.\n\nThe actual data class used to represent the log record is `HttpLogRecord` and is available in the `logging_http_client`.\n\n## Contributing\n\nIf you have any suggestions or improvements, feel free to open a PR or an issue. The build and development process has\nbeen made to be as seamless as possible, so you can easily run and test your changes locally before submitting a PR.\n\n### Prerequisites\n\n- [Python](https://www.python.org/downloads/): The project is built with Python 3.12.\n- [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer): The dependency management tool of\n  choice for this project.\n- [Docker](https://docs.docker.com/engine/install/): For containerisation support, so it can be completely built and run\n  in an isolated environment.\n- [Make](https://www.gnu.org/software/make/): For running common tasks such as installing dependencies, building the\n  project, running tests, etc.\n\n### Environment Setup\n\nBefore opening the project in your IDE, I highly recommend running the following recipe:\n\n```shell\nmake setup\n```\n\nThis will create your Poetry's virtual environment, install the project's dependencies, set up the code quality\npre-commit hook, and configure your IDE (VSCode and PyCharm) as appropriate.\n\n### Code Quality\n\nWe ask for adequate test coverage and adherence to the project's code quality standards. This includes running the\ntests, formatter, and linter before submitting a PR. You can run the following command to ensure your changes are in\nline with the project standards:\n\n```bash\nmake check-code-quality\n```\n\n### Versioning Strategy\n\nSince this project is tightly coupled with the requests library, we will follow the versioning strategy of the requests'\nlibrary. This means that the major, minor, and patch versions of this library will be the same as the requests' library\nversion it currently decorates. On top of that, an extra versioning suffix will be added to the end of the version to \nindicate the iteration of this library.\n\nSo for example, if the requests library is at version `1.2.3`, then this library will be at version `1.2.3.X`, where `X`\nis the iteration of this library, which will be numerical increments starting from `0`. \n\nWe have no intention to follow [Semantic Versioning](https://semver.org/) strategy to version this library, as I've made\na design decision to keep the features of this library's as small as possible, i.e. \n\n_\"Do few things, but do them well...\"_\n\nSo for the most part, the maintenance of this library will be keeping it up-to-date with newer versions of the\nthe [requests](https://pypi.org/project/requests/) library, whilist ensuring **everything** still works as expeceted.\nTherefore, maintaining our high test coverage is crucial for long-term useability.\n\n___\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fu-ways%2Flogging-http-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fu-ways%2Flogging-http-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fu-ways%2Flogging-http-client/lists"}