From ba788ab904f82eceee4db6ee96de654ada4424c3 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Thu, 31 Jul 2025 12:40:18 +0000 Subject: [PATCH 01/14] Committed changes including added logs to the Household Service and Metadata Service. Further investigating the cause of the 502 errors. --- .gitignore | 2 + policyengine_api/endpoints/household.py | 131 +++++++++++++++++- policyengine_api/services/metadata_service.py | 41 +++++- 3 files changed, 161 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 91af8c3c1..1d7d72a29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .venv +/venv +__pycache__ **/__pycache__ *.egg-info .pytest_cache diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index b841c5e10..4b28e37c2 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -11,6 +11,7 @@ import json import logging from datetime import date +from policyengine_api.gcp_logging import logger from policyengine_api.utils.payload_validators import validate_country @@ -85,17 +86,63 @@ def get_household_under_policy( household_id (str): The household ID. policy_id (str): The policy ID. """ - + api_version = COUNTRY_PACKAGE_VERSIONS.get(country_id) - # Look in computed_households to see if already computed + # Log start of request + logger.log_struct( + { + "event": "get_household_under_policy_start", + "input": { + "country_id": country_id, + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, + "request_path": request.path, + }, + "message": "Started processing household under policy request." + }, + severity="INFO", + ) - row = local_database.query( - f"SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?", - (household_id, policy_id, api_version), - ).fetchone() + # Look in computed_household cache table + try: + row = local_database.query( + "SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?", + (household_id, policy_id, api_version), + ).fetchone() + except Exception as e: + logger.log_struct( + { + "event": "computed_household_query_failed", + "input": { + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, + }, + "message": f"Database query failed: {e}", + }, + severity="ERROR", + ) + + return Response(json.dumps({ + "status": "error", + "message": "Internal server error while querying computed_household." + }), status=500, mimetype="application/json") if row is not None: + logger.log_struct( + { + "event": "cached_computed_household_found", + "input": { + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, + }, + "message": "Found precomputed household result in cache.", + }, + severity="INFO", + ) result = dict( policy_id=row["policy_id"], household_id=row["household_id"], @@ -122,7 +169,24 @@ def get_household_under_policy( if row is not None: household = dict(row) household["household_json"] = json.loads(household["household_json"]) + logger.log_struct( + { + "event": "household_data_loaded", + "input": {"household_id": household_id,"country_id": country_id}, + "message": "Loaded household data from DB.", + }, + severity="INFO", + ) else: + logger.log_struct( + { + "event": "household_not_found", + "input": {"household_id": household_id,"country_id": country_id}, + "message": f"Household #{household_id} not found.", + }, + severity="WARNING", + ) + response_body = dict( status="error", message=f"Household #{household_id} not found.", @@ -168,7 +232,24 @@ def get_household_under_policy( household_id, policy_id, ) + + logger.log_struct( + { + "event": "calculation_success", + "input": {"household_id": household_id, "policy_id": policy_id}, + "message": "Household calculation succeeded.", + }, + severity="INFO", + ) except Exception as e: + logger.log_struct( + { + "event": "calculation_failed", + "input": {"household_id": household_id, "policy_id": policy_id}, + "message": f"Calculation failed: {e}", + }, + severity="ERROR", + ) logging.exception(e) response_body = dict( status="error", @@ -193,7 +274,23 @@ def get_household_under_policy( api_version, ), ) - except Exception: + logger.log_struct( + { + "event": "computed_household_inserted", + "input": {"household_id": household_id, "policy_id": policy_id}, + "message": "Inserted new computed_household record.", + }, + severity="INFO", + ) + except Exception as e: + logger.log_struct( + { + "event": "computed_household_insert_failed_updating", + "input": {"household_id": household_id, "policy_id": policy_id}, + "message": f"Insert failed; updated existing record instead. Error: {e}", + }, + severity="ERROR", + ) # Update the result if it already exists local_database.query( f"UPDATE computed_household SET computed_household_json = ? WHERE country_id = ? AND household_id = ? AND policy_id = ?", @@ -227,7 +324,27 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: try: result = country.calculate(household_json, policy_json) + logger.log_struct( + { + "event": "calculation_success", + "input": { + "country_id": country_id, + }, + "message": "Calculation completed successfully.", + }, + severity="INFO", + ) except Exception as e: + logger.log_struct( + { + "event": "calculation_failed", + "input": { + "country_id": country_id, + }, + "message": f"Error calculating household under policy: {e}", + }, + severity="ERROR", + ) logging.exception(e) response_body = dict( status="error", diff --git a/policyengine_api/services/metadata_service.py b/policyengine_api/services/metadata_service.py index b6c7d4e3b..c93d72369 100644 --- a/policyengine_api/services/metadata_service.py +++ b/policyengine_api/services/metadata_service.py @@ -1,12 +1,41 @@ from policyengine_api.country import COUNTRIES +from policyengine_api.gcp_logging import logger class MetadataService: def get_metadata(self, country_id: str) -> dict: - country = COUNTRIES.get(country_id) - if country == None: - raise RuntimeError( - f"Attempted to get metadata for a nonexistant country: '{country_id}'" - ) - return country.metadata + # Log the metadata retrieval attempt + logger.log_struct({ + "event": "MetadataService.get_metadata_called", + "country_id": country_id, + }, severity="INFO") + + try: + country = COUNTRIES.get(country_id) + if country is None: + error_msg = f"Attempted to get metadata for a nonexistant country: '{country_id}'" + logger.log_struct({ + "event": "MetadataService.get_metadata_failed", + "country_id": country_id, + "message": f"Metadata successfully retrieved for country_id '{country_id}'", + "error": error_msg, + }, severity="ERROR") + raise RuntimeError(error_msg) + + metadata = country.metadata + + logger.log_struct({ + "event": "MetadataService.get_metadata_success", + "country_id": country_id, + }, severity="INFO") + + return metadata + + except Exception as e: + logger.log_struct({ + "event": "MetadataService.get_metadata_exception", + "country_id": country_id, + "error": str(e), + }, severity="ERROR") + raise RuntimeError(f"Unexpected error retrieving metadata for country_id '{country_id}': {e}") from e \ No newline at end of file From 155edb94bb4992e3335484bd92091429c9d035bb Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Thu, 31 Jul 2025 13:17:22 +0000 Subject: [PATCH 02/14] added the changelog --- changelog.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.yaml b/changelog.yaml index f8ec11ff2..34d1eade0 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -5053,3 +5053,7 @@ fixed: - UK package updated. date: 2025-07-16 11:54:01 +- bump: patch + changes: + added: + - Added GCP logs in Household and Metadata services to assist further investigation of the 502 errors. From 0f95cb388dc54ed3a4d680264f3189f46acb9e24 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Thu, 31 Jul 2025 18:15:09 +0000 Subject: [PATCH 03/14] changelog entry added --- changelog.yaml | 4 ---- changelog_entry.yaml | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.yaml b/changelog.yaml index 34d1eade0..f8ec11ff2 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -5053,7 +5053,3 @@ fixed: - UK package updated. date: 2025-07-16 11:54:01 -- bump: patch - changes: - added: - - Added GCP logs in Household and Metadata services to assist further investigation of the 502 errors. diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..cd2312dc8 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: patch + changes: + added: + - Added GCP logs in Household and Metadata services to assist further investigation of the 502 errors. \ No newline at end of file From 214e951b875a85a5f5fa8e8799758baf8f4070c5 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Thu, 31 Jul 2025 18:56:04 +0000 Subject: [PATCH 04/14] Reformatted code with Black for metadata and household services --- policyengine_api/endpoints/household.py | 34 ++++++------ policyengine_api/services/metadata_service.py | 54 ++++++++++++------- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 4b28e37c2..163041267 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -42,11 +42,7 @@ def add_yearly_variables(household, country_id): if variables[variable]["isInputVariable"]: household[entity_plural][entity][ variables[variable]["name"] - ] = { - household_year: variables[variable][ - "defaultValue" - ] - } + ] = {household_year: variables[variable]["defaultValue"]} else: household[entity_plural][entity][ variables[variable]["name"] @@ -76,9 +72,7 @@ def get_household_year(household): @validate_country -def get_household_under_policy( - country_id: str, household_id: str, policy_id: str -): +def get_household_under_policy(country_id: str, household_id: str, policy_id: str): """Get a household's output data under a given policy. Args: @@ -86,7 +80,7 @@ def get_household_under_policy( household_id (str): The household ID. policy_id (str): The policy ID. """ - + api_version = COUNTRY_PACKAGE_VERSIONS.get(country_id) # Log start of request @@ -100,12 +94,12 @@ def get_household_under_policy( "api_version": api_version, "request_path": request.path, }, - "message": "Started processing household under policy request." + "message": "Started processing household under policy request.", }, severity="INFO", ) - # Look in computed_household cache table + # Look in computed_household cache table try: row = local_database.query( "SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?", @@ -125,10 +119,16 @@ def get_household_under_policy( severity="ERROR", ) - return Response(json.dumps({ - "status": "error", - "message": "Internal server error while querying computed_household." - }), status=500, mimetype="application/json") + return Response( + json.dumps( + { + "status": "error", + "message": "Internal server error while querying computed_household.", + } + ), + status=500, + mimetype="application/json", + ) if row is not None: logger.log_struct( @@ -172,7 +172,7 @@ def get_household_under_policy( logger.log_struct( { "event": "household_data_loaded", - "input": {"household_id": household_id,"country_id": country_id}, + "input": {"household_id": household_id, "country_id": country_id}, "message": "Loaded household data from DB.", }, severity="INFO", @@ -181,7 +181,7 @@ def get_household_under_policy( logger.log_struct( { "event": "household_not_found", - "input": {"household_id": household_id,"country_id": country_id}, + "input": {"household_id": household_id, "country_id": country_id}, "message": f"Household #{household_id} not found.", }, severity="WARNING", diff --git a/policyengine_api/services/metadata_service.py b/policyengine_api/services/metadata_service.py index c93d72369..dfcaa9dcc 100644 --- a/policyengine_api/services/metadata_service.py +++ b/policyengine_api/services/metadata_service.py @@ -6,36 +6,50 @@ class MetadataService: def get_metadata(self, country_id: str) -> dict: # Log the metadata retrieval attempt - logger.log_struct({ - "event": "MetadataService.get_metadata_called", - "country_id": country_id, - }, severity="INFO") + logger.log_struct( + { + "event": "MetadataService.get_metadata_called", + "country_id": country_id, + }, + severity="INFO", + ) try: country = COUNTRIES.get(country_id) if country is None: error_msg = f"Attempted to get metadata for a nonexistant country: '{country_id}'" - logger.log_struct({ - "event": "MetadataService.get_metadata_failed", - "country_id": country_id, - "message": f"Metadata successfully retrieved for country_id '{country_id}'", - "error": error_msg, - }, severity="ERROR") + logger.log_struct( + { + "event": "MetadataService.get_metadata_failed", + "country_id": country_id, + "message": f"Metadata successfully retrieved for country_id '{country_id}'", + "error": error_msg, + }, + severity="ERROR", + ) raise RuntimeError(error_msg) metadata = country.metadata - logger.log_struct({ - "event": "MetadataService.get_metadata_success", - "country_id": country_id, - }, severity="INFO") + logger.log_struct( + { + "event": "MetadataService.get_metadata_success", + "country_id": country_id, + }, + severity="INFO", + ) return metadata except Exception as e: - logger.log_struct({ - "event": "MetadataService.get_metadata_exception", - "country_id": country_id, - "error": str(e), - }, severity="ERROR") - raise RuntimeError(f"Unexpected error retrieving metadata for country_id '{country_id}': {e}") from e \ No newline at end of file + logger.log_struct( + { + "event": "MetadataService.get_metadata_exception", + "country_id": country_id, + "error": str(e), + }, + severity="ERROR", + ) + raise RuntimeError( + f"Unexpected error retrieving metadata for country_id '{country_id}': {e}" + ) from e From 217b48f3d6fa69973dd971e576a1b06ce7e82639 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 1 Aug 2025 04:09:08 +0000 Subject: [PATCH 05/14] Fix formatting with Black --- policyengine_api/endpoints/household.py | 40 ++++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 163041267..ac0a8d191 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -42,7 +42,11 @@ def add_yearly_variables(household, country_id): if variables[variable]["isInputVariable"]: household[entity_plural][entity][ variables[variable]["name"] - ] = {household_year: variables[variable]["defaultValue"]} + ] = { + household_year: variables[variable][ + "defaultValue" + ] + } else: household[entity_plural][entity][ variables[variable]["name"] @@ -72,7 +76,9 @@ def get_household_year(household): @validate_country -def get_household_under_policy(country_id: str, household_id: str, policy_id: str): +def get_household_under_policy( + country_id: str, household_id: str, policy_id: str +): """Get a household's output data under a given policy. Args: @@ -172,7 +178,10 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st logger.log_struct( { "event": "household_data_loaded", - "input": {"household_id": household_id, "country_id": country_id}, + "input": { + "household_id": household_id, + "country_id": country_id, + }, "message": "Loaded household data from DB.", }, severity="INFO", @@ -181,7 +190,10 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st logger.log_struct( { "event": "household_not_found", - "input": {"household_id": household_id, "country_id": country_id}, + "input": { + "household_id": household_id, + "country_id": country_id, + }, "message": f"Household #{household_id} not found.", }, severity="WARNING", @@ -236,7 +248,10 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st logger.log_struct( { "event": "calculation_success", - "input": {"household_id": household_id, "policy_id": policy_id}, + "input": { + "household_id": household_id, + "policy_id": policy_id, + }, "message": "Household calculation succeeded.", }, severity="INFO", @@ -245,7 +260,10 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st logger.log_struct( { "event": "calculation_failed", - "input": {"household_id": household_id, "policy_id": policy_id}, + "input": { + "household_id": household_id, + "policy_id": policy_id, + }, "message": f"Calculation failed: {e}", }, severity="ERROR", @@ -277,7 +295,10 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st logger.log_struct( { "event": "computed_household_inserted", - "input": {"household_id": household_id, "policy_id": policy_id}, + "input": { + "household_id": household_id, + "policy_id": policy_id, + }, "message": "Inserted new computed_household record.", }, severity="INFO", @@ -286,7 +307,10 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st logger.log_struct( { "event": "computed_household_insert_failed_updating", - "input": {"household_id": household_id, "policy_id": policy_id}, + "input": { + "household_id": household_id, + "policy_id": policy_id, + }, "message": f"Insert failed; updated existing record instead. Error: {e}", }, severity="ERROR", From a131e0f3a7f59cdf2f6859da65da42527005950f Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 8 Aug 2025 08:36:31 +0000 Subject: [PATCH 06/14] Agnostic Logging Implemented --- policyengine_api/endpoints/household.py | 160 ++++++++---------- policyengine_api/services/metadata_service.py | 37 ++-- policyengine_api/structured_logger.py | 58 +++++++ 3 files changed, 152 insertions(+), 103 deletions(-) create mode 100644 policyengine_api/structured_logger.py diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index ac0a8d191..239f196fb 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -11,7 +11,9 @@ import json import logging from datetime import date -from policyengine_api.gcp_logging import logger +from policyengine_api.structured_logger import get_logger, log_struct + +logger = get_logger() from policyengine_api.utils.payload_validators import validate_country @@ -42,11 +44,7 @@ def add_yearly_variables(household, country_id): if variables[variable]["isInputVariable"]: household[entity_plural][entity][ variables[variable]["name"] - ] = { - household_year: variables[variable][ - "defaultValue" - ] - } + ] = {household_year: variables[variable]["defaultValue"]} else: household[entity_plural][entity][ variables[variable]["name"] @@ -76,9 +74,7 @@ def get_household_year(household): @validate_country -def get_household_under_policy( - country_id: str, household_id: str, policy_id: str -): +def get_household_under_policy(country_id: str, household_id: str, policy_id: str): """Get a household's output data under a given policy. Args: @@ -90,41 +86,37 @@ def get_household_under_policy( api_version = COUNTRY_PACKAGE_VERSIONS.get(country_id) # Log start of request - logger.log_struct( - { - "event": "get_household_under_policy_start", - "input": { - "country_id": country_id, - "household_id": household_id, - "policy_id": policy_id, - "api_version": api_version, - "request_path": request.path, - }, - "message": "Started processing household under policy request.", + log_struct( + event="get_household_under_policy_start", + input_data={ + "country_id": country_id, + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, + "request_path": request.path, }, + message="Started processing household under policy request.", severity="INFO", + logger=logger, # optional if you've already called get_logger() ) # Look in computed_household cache table try: row = local_database.query( - "SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?", + f"SELECT * FROM computed_household WHERE household_id = ? AND policy_id = ? AND api_version = ?", (household_id, policy_id, api_version), ).fetchone() except Exception as e: - logger.log_struct( - { - "event": "computed_household_query_failed", - "input": { - "household_id": household_id, - "policy_id": policy_id, - "api_version": api_version, - }, - "message": f"Database query failed: {e}", + log_struct( + event="computed_household_query_failed", + input_data={ + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, }, + message=f"Database query failed: {e}", severity="ERROR", ) - return Response( json.dumps( { @@ -137,18 +129,17 @@ def get_household_under_policy( ) if row is not None: - logger.log_struct( - { - "event": "cached_computed_household_found", - "input": { - "household_id": household_id, - "policy_id": policy_id, - "api_version": api_version, - }, - "message": "Found precomputed household result in cache.", + log_struct( + event="cached_computed_household_found", + input_data={ + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, }, + message="Found precomputed household result in cache.", severity="INFO", ) + result = dict( policy_id=row["policy_id"], household_id=row["household_id"], @@ -175,27 +166,24 @@ def get_household_under_policy( if row is not None: household = dict(row) household["household_json"] = json.loads(household["household_json"]) - logger.log_struct( - { - "event": "household_data_loaded", - "input": { - "household_id": household_id, - "country_id": country_id, - }, - "message": "Loaded household data from DB.", + log_struct( + event="household_data_loaded", + input_data={ + "household_id": household_id, + "country_id": country_id, }, + message="Loaded household data from DB.", severity="INFO", ) + else: - logger.log_struct( - { - "event": "household_not_found", - "input": { - "household_id": household_id, - "country_id": country_id, - }, - "message": f"Household #{household_id} not found.", + log_struct( + event="household_not_found", + input_data={ + "household_id": household_id, + "country_id": country_id, }, + message=f"Household #{household_id} not found.", severity="WARNING", ) @@ -245,29 +233,27 @@ def get_household_under_policy( policy_id, ) - logger.log_struct( - { - "event": "calculation_success", - "input": { - "household_id": household_id, - "policy_id": policy_id, - }, - "message": "Household calculation succeeded.", + log_struct( + event="calculation_success", + input_data={ + "household_id": household_id, + "policy_id": policy_id, }, + message="Household calculation succeeded.", severity="INFO", ) + except Exception as e: - logger.log_struct( - { - "event": "calculation_failed", - "input": { - "household_id": household_id, - "policy_id": policy_id, - }, - "message": f"Calculation failed: {e}", + log_struct( + event="calculation_failed", + input_data={ + "household_id": household_id, + "policy_id": policy_id, }, + message=f"Calculation failed: {e}", severity="ERROR", ) + logging.exception(e) response_body = dict( status="error", @@ -292,29 +278,27 @@ def get_household_under_policy( api_version, ), ) - logger.log_struct( - { - "event": "computed_household_inserted", - "input": { - "household_id": household_id, - "policy_id": policy_id, - }, - "message": "Inserted new computed_household record.", + log_struct( + event="computed_household_inserted", + input_data={ + "household_id": household_id, + "policy_id": policy_id, }, + message="Inserted new computed_household record.", severity="INFO", ) + except Exception as e: - logger.log_struct( - { - "event": "computed_household_insert_failed_updating", - "input": { - "household_id": household_id, - "policy_id": policy_id, - }, - "message": f"Insert failed; updated existing record instead. Error: {e}", + log_struct( + event="computed_household_insert_failed_updating", + input_data={ + "household_id": household_id, + "policy_id": policy_id, }, + message=f"Insert failed; updated existing record instead. Error: {e}", severity="ERROR", ) + # Update the result if it already exists local_database.query( f"UPDATE computed_household SET computed_household_json = ? WHERE country_id = ? AND household_id = ? AND policy_id = ?", diff --git a/policyengine_api/services/metadata_service.py b/policyengine_api/services/metadata_service.py index dfcaa9dcc..59ebf9a2d 100644 --- a/policyengine_api/services/metadata_service.py +++ b/policyengine_api/services/metadata_service.py @@ -1,55 +1,62 @@ from policyengine_api.country import COUNTRIES -from policyengine_api.gcp_logging import logger +from policyengine_api.structured_logger import get_logger, log_struct + +logger = get_logger() class MetadataService: def get_metadata(self, country_id: str) -> dict: # Log the metadata retrieval attempt - logger.log_struct( - { - "event": "MetadataService.get_metadata_called", + log_struct( + event="MetadataService.get_metadata_called", + input_data={ "country_id": country_id, }, + message="Metadata retrieval called.", severity="INFO", ) try: country = COUNTRIES.get(country_id) - if country is None: + if country == None: error_msg = f"Attempted to get metadata for a nonexistant country: '{country_id}'" - logger.log_struct( - { - "event": "MetadataService.get_metadata_failed", + log_struct( + event="MetadataService.get_metadata_failed", + input_data={ "country_id": country_id, - "message": f"Metadata successfully retrieved for country_id '{country_id}'", "error": error_msg, }, + message=f"Metadata successfully retrieved for country_id '{country_id}'", severity="ERROR", ) + raise RuntimeError(error_msg) metadata = country.metadata - logger.log_struct( - { - "event": "MetadataService.get_metadata_success", + log_struct( + event="MetadataService.get_metadata_success", + input_data={ "country_id": country_id, }, + message="Metadata successfully retrieved.", severity="INFO", ) return metadata except Exception as e: - logger.log_struct( - { - "event": "MetadataService.get_metadata_exception", + log_struct( + event="MetadataService.get_metadata_exception", + input_data={ "country_id": country_id, "error": str(e), }, + message="Exception occurred while retrieving metadata.", severity="ERROR", ) + raise RuntimeError( f"Unexpected error retrieving metadata for country_id '{country_id}': {e}" ) from e diff --git a/policyengine_api/structured_logger.py b/policyengine_api/structured_logger.py new file mode 100644 index 000000000..a25169b99 --- /dev/null +++ b/policyengine_api/structured_logger.py @@ -0,0 +1,58 @@ +import logging +import json +import sys + +# Only import if using GCP logging +try: + from google.cloud import logging as gcp_logging + from google.cloud.logging.handlers import CloudLoggingHandler +except ImportError: + gcp_logging = None + CloudLoggingHandler = None + + +class JsonFormatter(logging.Formatter): + """Formatter that outputs logs as structured JSON.""" + + def format(self, record): + log_record = { + "severity": record.levelname, + "event": getattr(record, "event", None), + "input": getattr(record, "input", None), + "message": record.getMessage(), + } + if record.exc_info: + log_record["exception"] = self.formatException(record.exc_info) + return json.dumps(log_record) + + +def get_logger(name="policyengine-api", level=logging.INFO): + logger = logging.getLogger(name) + logger.setLevel(level) + + # If no handlers are set, add a StreamHandler with JSON formatting + if not logger.handlers: + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(JsonFormatter()) + logger.addHandler(handler) + + # If using GCP logging, add a CloudLoggingHandler + # For more advanced GCP integration, consider enabling CloudLoggingHandler. + # if gcp_logging and CloudLoggingHandler: + # client = gcp_logging.Client() + # gcp_handler = CloudLoggingHandler(client, name=name) + # gcp_handler.setFormatter(JsonFormatter()) # Optional + # logger.addHandler(gcp_handler) + + return logger + + +def log_struct(event, input_data, message, severity="INFO", logger=None): + """ + Implementation-agnostic structured logger. + """ + if logger is None: + logger = get_logger() + + log_func = getattr(logger, severity.lower(), logger.info) + log_func(message, extra={"event": event, "input": input_data}) From 8a629f8b40dd43e0797ce6715d3a232e9fd053c9 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 8 Aug 2025 09:07:22 +0000 Subject: [PATCH 07/14] Format household.py with Black (line length 105) --- policyengine_api/endpoints/household.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 239f196fb..abece4e1b 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -37,18 +37,15 @@ def add_yearly_variables(household, country_id): if entity_plural in household: possible_entities = household[entity_plural].keys() for entity in possible_entities: - if ( - not variables[variable]["name"] - in household[entity_plural][entity] - ): + if not variables[variable]["name"] in household[entity_plural][entity]: if variables[variable]["isInputVariable"]: - household[entity_plural][entity][ - variables[variable]["name"] - ] = {household_year: variables[variable]["defaultValue"]} + household[entity_plural][entity][variables[variable]["name"]] = { + household_year: variables[variable]["defaultValue"] + } else: - household[entity_plural][entity][ - variables[variable]["name"] - ] = {household_year: None} + household[entity_plural][entity][variables[variable]["name"]] = { + household_year: None + } return household @@ -63,9 +60,7 @@ def get_household_year(household): household_year = date.today().year # Determine if "age" variable present within household and return list of values at it - household_age_list = list( - household.get("people", {}).get("you", {}).get("age", {}).keys() - ) + household_age_list = list(household.get("people", {}).get("you", {}).get("age", {}).keys()) # If it is, overwrite household_year with the value present if len(household_age_list) > 0: household_year = household_age_list[0] @@ -198,9 +193,7 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st ) # Add in any missing yearly variables - household["household_json"] = add_yearly_variables( - household["household_json"], country_id - ) + household["household_json"] = add_yearly_variables(household["household_json"], country_id) # Retrieve from the policy table From 899ad20c931c18ed5dc8367e32a0f085cebe3ade Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 8 Aug 2025 09:22:23 +0000 Subject: [PATCH 08/14] Fix formatting: household.py --- policyengine_api/endpoints/household.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index abece4e1b..239f196fb 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -37,15 +37,18 @@ def add_yearly_variables(household, country_id): if entity_plural in household: possible_entities = household[entity_plural].keys() for entity in possible_entities: - if not variables[variable]["name"] in household[entity_plural][entity]: + if ( + not variables[variable]["name"] + in household[entity_plural][entity] + ): if variables[variable]["isInputVariable"]: - household[entity_plural][entity][variables[variable]["name"]] = { - household_year: variables[variable]["defaultValue"] - } + household[entity_plural][entity][ + variables[variable]["name"] + ] = {household_year: variables[variable]["defaultValue"]} else: - household[entity_plural][entity][variables[variable]["name"]] = { - household_year: None - } + household[entity_plural][entity][ + variables[variable]["name"] + ] = {household_year: None} return household @@ -60,7 +63,9 @@ def get_household_year(household): household_year = date.today().year # Determine if "age" variable present within household and return list of values at it - household_age_list = list(household.get("people", {}).get("you", {}).get("age", {}).keys()) + household_age_list = list( + household.get("people", {}).get("you", {}).get("age", {}).keys() + ) # If it is, overwrite household_year with the value present if len(household_age_list) > 0: household_year = household_age_list[0] @@ -193,7 +198,9 @@ def get_household_under_policy(country_id: str, household_id: str, policy_id: st ) # Add in any missing yearly variables - household["household_json"] = add_yearly_variables(household["household_json"], country_id) + household["household_json"] = add_yearly_variables( + household["household_json"], country_id + ) # Retrieve from the policy table From 846f38ffb90a0d62e630d51e2a10d446991a7d4e Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 8 Aug 2025 09:34:29 +0000 Subject: [PATCH 09/14] Fix formatting: household.py --- policyengine_api/endpoints/household.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 239f196fb..7a1c8f375 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -44,7 +44,11 @@ def add_yearly_variables(household, country_id): if variables[variable]["isInputVariable"]: household[entity_plural][entity][ variables[variable]["name"] - ] = {household_year: variables[variable]["defaultValue"]} + ] = { + household_year: variables[variable][ + "defaultValue" + ] + } else: household[entity_plural][entity][ variables[variable]["name"] @@ -74,7 +78,9 @@ def get_household_year(household): @validate_country -def get_household_under_policy(country_id: str, household_id: str, policy_id: str): +def get_household_under_policy( + country_id: str, household_id: str, policy_id: str +): """Get a household's output data under a given policy. Args: From 03a69bd7ac70cc05827ffa293ef80970c6693da6 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 8 Aug 2025 09:56:18 +0000 Subject: [PATCH 10/14] added logger for get calculate method --- policyengine_api/endpoints/household.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 7a1c8f375..88a303e95 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -338,7 +338,7 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: try: result = country.calculate(household_json, policy_json) - logger.log_struct( + log_struct( { "event": "calculation_success", "input": { @@ -349,7 +349,7 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: severity="INFO", ) except Exception as e: - logger.log_struct( + log_struct( { "event": "calculation_failed", "input": { From cf7b86f521845cab3b5830cae724be18b60a5c3e Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 8 Aug 2025 10:49:57 +0000 Subject: [PATCH 11/14] added logger for get calculate method --- policyengine_api/endpoints/household.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 88a303e95..4cab24212 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -339,26 +339,24 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: try: result = country.calculate(household_json, policy_json) log_struct( - { - "event": "calculation_success", - "input": { - "country_id": country_id, - }, - "message": "Calculation completed successfully.", + event="calculation_success", + input_data={ + "country_id": country_id, }, + message="Calculation completed successfully.", severity="INFO", ) + except Exception as e: log_struct( - { - "event": "calculation_failed", - "input": { - "country_id": country_id, - }, - "message": f"Error calculating household under policy: {e}", + event="calculation_failed", + input_data={ + "country_id": country_id, }, + message=f"Error calculating household under policy: {e}", severity="ERROR", ) + logging.exception(e) response_body = dict( status="error", From 721a6e14c312974e5ccdd140a56f8669f5c703f7 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Mon, 18 Aug 2025 12:16:37 +0000 Subject: [PATCH 12/14] changes related PR comments --- policyengine_api/endpoints/household.py | 101 ++++++++---------- policyengine_api/routes/metadata_routes.py | 9 +- policyengine_api/services/metadata_service.py | 73 ++++++++----- policyengine_api/structured_logger.py | 17 --- 4 files changed, 93 insertions(+), 107 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 4cab24212..8592b042f 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -3,6 +3,7 @@ ) from policyengine_api.data import database, local_database import json +import uuid from flask import Response, request from policyengine_api.utils import hash_object from policyengine_api.constants import COUNTRY_PACKAGE_VERSIONS @@ -91,19 +92,26 @@ def get_household_under_policy( api_version = COUNTRY_PACKAGE_VERSIONS.get(country_id) + # Generate a unique request ID for tracing + request_id = uuid.uuid4().hex + + # Common context for all logs + log_context = { + "request_id": request_id, + "country_id": country_id, + "household_id": household_id, + "policy_id": policy_id, + "api_version": api_version, + "request_path": request.path, + } + # Log start of request log_struct( event="get_household_under_policy_start", - input_data={ - "country_id": country_id, - "household_id": household_id, - "policy_id": policy_id, - "api_version": api_version, - "request_path": request.path, - }, + input_data=log_context, message="Started processing household under policy request.", severity="INFO", - logger=logger, # optional if you've already called get_logger() + logger=logger, ) # Look in computed_household cache table @@ -115,11 +123,7 @@ def get_household_under_policy( except Exception as e: log_struct( event="computed_household_query_failed", - input_data={ - "household_id": household_id, - "policy_id": policy_id, - "api_version": api_version, - }, + input_data=log_context, message=f"Database query failed: {e}", severity="ERROR", ) @@ -137,15 +141,10 @@ def get_household_under_policy( if row is not None: log_struct( event="cached_computed_household_found", - input_data={ - "household_id": household_id, - "policy_id": policy_id, - "api_version": api_version, - }, + input_data=log_context, message="Found precomputed household result in cache.", severity="INFO", ) - result = dict( policy_id=row["policy_id"], household_id=row["household_id"], @@ -174,10 +173,7 @@ def get_household_under_policy( household["household_json"] = json.loads(household["household_json"]) log_struct( event="household_data_loaded", - input_data={ - "household_id": household_id, - "country_id": country_id, - }, + input_data=log_context, message="Loaded household data from DB.", severity="INFO", ) @@ -185,10 +181,7 @@ def get_household_under_policy( else: log_struct( event="household_not_found", - input_data={ - "household_id": household_id, - "country_id": country_id, - }, + input_data=log_context, message=f"Household #{household_id} not found.", severity="WARNING", ) @@ -240,22 +233,16 @@ def get_household_under_policy( ) log_struct( - event="calculation_success", - input_data={ - "household_id": household_id, - "policy_id": policy_id, - }, + event="household_calculation_db_success", + input_data=log_context, message="Household calculation succeeded.", severity="INFO", ) except Exception as e: log_struct( - event="calculation_failed", - input_data={ - "household_id": household_id, - "policy_id": policy_id, - }, + event="household_calculation_db_failed", + input_data=log_context, message=f"Calculation failed: {e}", severity="ERROR", ) @@ -286,22 +273,16 @@ def get_household_under_policy( ) log_struct( event="computed_household_inserted", - input_data={ - "household_id": household_id, - "policy_id": policy_id, - }, + input_data=log_context, message="Inserted new computed_household record.", severity="INFO", ) except Exception as e: log_struct( - event="computed_household_insert_failed_updating", - input_data={ - "household_id": household_id, - "policy_id": policy_id, - }, - message=f"Insert failed; updated existing record instead. Error: {e}", + event="computed_household_insert_failed", + input_data=log_context, + message=f"Insert operation failed. Error: {e}", severity="ERROR", ) @@ -326,6 +307,16 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: country_id (str): The country ID. """ + # Generate a unique request ID for tracing + request_id = uuid.uuid4().hex + + # Log context shared across all logs + log_context = { + "request_id": request_id, + "country_id": country_id, + "request_path": request.path, + } + payload = request.json household_json = payload.get("household", {}) policy_json = payload.get("policy", {}) @@ -339,21 +330,17 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: try: result = country.calculate(household_json, policy_json) log_struct( - event="calculation_success", - input_data={ - "country_id": country_id, - }, - message="Calculation completed successfully.", + event="calculation_success_lightweight", + input_data=log_context, + message="Calculation completed successfully without DB storage.", severity="INFO", ) except Exception as e: log_struct( - event="calculation_failed", - input_data={ - "country_id": country_id, - }, - message=f"Error calculating household under policy: {e}", + event="calculation_failed_lightweight", + input_data=log_context, + message=f"Error calculating household under policy without DB storage: {e}", severity="ERROR", ) diff --git a/policyengine_api/routes/metadata_routes.py b/policyengine_api/routes/metadata_routes.py index 496d9556d..8945f652d 100644 --- a/policyengine_api/routes/metadata_routes.py +++ b/policyengine_api/routes/metadata_routes.py @@ -18,11 +18,4 @@ def get_metadata(country_id: str) -> Response: """ # Retrieve country metadata and add status and message to the response - country_metadata = metadata_service.get_metadata(country_id) - return Response( - json.dumps( - {"status": "ok", "message": None, "result": country_metadata} - ), - status=200, - mimetype="application/json", - ) + return metadata_service.get_metadata(country_id) diff --git a/policyengine_api/services/metadata_service.py b/policyengine_api/services/metadata_service.py index 59ebf9a2d..61f13cae4 100644 --- a/policyengine_api/services/metadata_service.py +++ b/policyengine_api/services/metadata_service.py @@ -1,5 +1,7 @@ from policyengine_api.country import COUNTRIES +from flask import Response, json, request from policyengine_api.structured_logger import get_logger, log_struct +import uuid logger = get_logger() @@ -7,12 +9,21 @@ class MetadataService: def get_metadata(self, country_id: str) -> dict: + # Generate a unique request ID for tracing + request_id = uuid.uuid4().hex + + # Common log context + log_context = { + "request_id": request_id, + "endpoint": "MetadataService.get_metadata", + "country_id": country_id, + "request_path": request.path, + } + # Log the metadata retrieval attempt log_struct( - event="MetadataService.get_metadata_called", - input_data={ - "country_id": country_id, - }, + event="metadata_retrieval_called", + input_data=log_context, message="Metadata retrieval called.", severity="INFO", ) @@ -22,41 +33,53 @@ def get_metadata(self, country_id: str) -> dict: if country == None: error_msg = f"Attempted to get metadata for a nonexistant country: '{country_id}'" log_struct( - event="MetadataService.get_metadata_failed", - input_data={ - "country_id": country_id, - "error": error_msg, - }, - message=f"Metadata successfully retrieved for country_id '{country_id}'", - severity="ERROR", + event="metadata_retrieval_failed", + input_data=log_context, + message=error_msg, + severity="WARNING", ) - raise RuntimeError(error_msg) + # Return structured 404 response + response_body = dict(status="error", message=error_msg) + return Response( + json.dumps(response_body), + status=404, + mimetype="application/json", + ) + # Retrieve metadata from the country object metadata = country.metadata + # Success log log_struct( - event="MetadataService.get_metadata_success", - input_data={ - "country_id": country_id, - }, + event="metadata_retrieval_success", + input_data=log_context, message="Metadata successfully retrieved.", severity="INFO", ) - return metadata + return Response( + json.dumps( + {"status": "ok", "message": None, "result": metadata} + ), + status=200, + mimetype="application/json", + ) except Exception as e: log_struct( - event="MetadataService.get_metadata_exception", - input_data={ - "country_id": country_id, - "error": str(e), - }, + event="metadata_retrieval_exception", + input_data=log_context, message="Exception occurred while retrieving metadata.", severity="ERROR", ) - raise RuntimeError( - f"Unexpected error retrieving metadata for country_id '{country_id}': {e}" - ) from e + response_body = dict( + status="error", + message=f"Unexpected error encountered while retrieving metadata for country_id '{country_id}': {e}", + ) + return Response( + json.dumps(response_body), + status=500, + mimetype="application/json", + ) diff --git a/policyengine_api/structured_logger.py b/policyengine_api/structured_logger.py index a25169b99..9fb9c9d98 100644 --- a/policyengine_api/structured_logger.py +++ b/policyengine_api/structured_logger.py @@ -2,14 +2,6 @@ import json import sys -# Only import if using GCP logging -try: - from google.cloud import logging as gcp_logging - from google.cloud.logging.handlers import CloudLoggingHandler -except ImportError: - gcp_logging = None - CloudLoggingHandler = None - class JsonFormatter(logging.Formatter): """Formatter that outputs logs as structured JSON.""" @@ -35,15 +27,6 @@ def get_logger(name="policyengine-api", level=logging.INFO): handler = logging.StreamHandler(sys.stdout) handler.setFormatter(JsonFormatter()) logger.addHandler(handler) - - # If using GCP logging, add a CloudLoggingHandler - # For more advanced GCP integration, consider enabling CloudLoggingHandler. - # if gcp_logging and CloudLoggingHandler: - # client = gcp_logging.Client() - # gcp_handler = CloudLoggingHandler(client, name=name) - # gcp_handler.setFormatter(JsonFormatter()) # Optional - # logger.addHandler(gcp_handler) - return logger From e86d3fa9db8ac289af0730e6c79e945f8f456555 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Mon, 18 Aug 2025 13:21:42 +0000 Subject: [PATCH 13/14] changes related to metadata --- policyengine_api/routes/metadata_routes.py | 9 +- policyengine_api/services/metadata_service.py | 83 ++----------------- 2 files changed, 13 insertions(+), 79 deletions(-) diff --git a/policyengine_api/routes/metadata_routes.py b/policyengine_api/routes/metadata_routes.py index 8945f652d..496d9556d 100644 --- a/policyengine_api/routes/metadata_routes.py +++ b/policyengine_api/routes/metadata_routes.py @@ -18,4 +18,11 @@ def get_metadata(country_id: str) -> Response: """ # Retrieve country metadata and add status and message to the response - return metadata_service.get_metadata(country_id) + country_metadata = metadata_service.get_metadata(country_id) + return Response( + json.dumps( + {"status": "ok", "message": None, "result": country_metadata} + ), + status=200, + mimetype="application/json", + ) diff --git a/policyengine_api/services/metadata_service.py b/policyengine_api/services/metadata_service.py index 61f13cae4..b6c7d4e3b 100644 --- a/policyengine_api/services/metadata_service.py +++ b/policyengine_api/services/metadata_service.py @@ -1,85 +1,12 @@ from policyengine_api.country import COUNTRIES -from flask import Response, json, request -from policyengine_api.structured_logger import get_logger, log_struct -import uuid - -logger = get_logger() class MetadataService: def get_metadata(self, country_id: str) -> dict: - - # Generate a unique request ID for tracing - request_id = uuid.uuid4().hex - - # Common log context - log_context = { - "request_id": request_id, - "endpoint": "MetadataService.get_metadata", - "country_id": country_id, - "request_path": request.path, - } - - # Log the metadata retrieval attempt - log_struct( - event="metadata_retrieval_called", - input_data=log_context, - message="Metadata retrieval called.", - severity="INFO", - ) - - try: - country = COUNTRIES.get(country_id) - if country == None: - error_msg = f"Attempted to get metadata for a nonexistant country: '{country_id}'" - log_struct( - event="metadata_retrieval_failed", - input_data=log_context, - message=error_msg, - severity="WARNING", - ) - - # Return structured 404 response - response_body = dict(status="error", message=error_msg) - return Response( - json.dumps(response_body), - status=404, - mimetype="application/json", - ) - - # Retrieve metadata from the country object - metadata = country.metadata - - # Success log - log_struct( - event="metadata_retrieval_success", - input_data=log_context, - message="Metadata successfully retrieved.", - severity="INFO", - ) - - return Response( - json.dumps( - {"status": "ok", "message": None, "result": metadata} - ), - status=200, - mimetype="application/json", + country = COUNTRIES.get(country_id) + if country == None: + raise RuntimeError( + f"Attempted to get metadata for a nonexistant country: '{country_id}'" ) - except Exception as e: - log_struct( - event="metadata_retrieval_exception", - input_data=log_context, - message="Exception occurred while retrieving metadata.", - severity="ERROR", - ) - - response_body = dict( - status="error", - message=f"Unexpected error encountered while retrieving metadata for country_id '{country_id}': {e}", - ) - return Response( - json.dumps(response_body), - status=500, - mimetype="application/json", - ) + return country.metadata From 047ad3a3cdf03c96f4d53264856021654b907299 Mon Sep 17 00:00:00 2001 From: Naresh Thotakuri Date: Fri, 22 Aug 2025 06:12:57 +0000 Subject: [PATCH 14/14] Fixed the PR comments --- policyengine_api/endpoints/household.py | 10 ++++++---- policyengine_api/structured_logger.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/policyengine_api/endpoints/household.py b/policyengine_api/endpoints/household.py index 8592b042f..d2e1dd09a 100644 --- a/policyengine_api/endpoints/household.py +++ b/policyengine_api/endpoints/household.py @@ -310,17 +310,19 @@ def get_calculate(country_id: str, add_missing: bool = False) -> dict: # Generate a unique request ID for tracing request_id = uuid.uuid4().hex + payload = request.json + household_json = payload.get("household", {}) + policy_json = payload.get("policy", {}) + # Log context shared across all logs log_context = { "request_id": request_id, "country_id": country_id, "request_path": request.path, + "household_json": household_json, + "policy_json": policy_json, } - payload = request.json - household_json = payload.get("household", {}) - policy_json = payload.get("policy", {}) - if add_missing: # Add in any missing yearly variables to household_json household_json = add_yearly_variables(household_json, country_id) diff --git a/policyengine_api/structured_logger.py b/policyengine_api/structured_logger.py index 9fb9c9d98..a52d1fe4d 100644 --- a/policyengine_api/structured_logger.py +++ b/policyengine_api/structured_logger.py @@ -15,7 +15,7 @@ def format(self, record): } if record.exc_info: log_record["exception"] = self.formatException(record.exc_info) - return json.dumps(log_record) + return json.dumps(log_record, indent=2) def get_logger(name="policyengine-api", level=logging.INFO):