Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: patch
changes:
added:
- Implemented structured logging in policyengine_api/utils/logger.py to produce GCP-compatible JSON logs.
4 changes: 2 additions & 2 deletions policyengine_api/libs/simulation_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import time
from typing import Any, Literal, Annotated
from dotenv import load_dotenv
from policyengine_api.gcp_logging import logger
from policyengine_api.utils.python_logging import logger
from google.cloud.workflows import executions_v1


Expand All @@ -21,7 +21,7 @@ class SimulationAPI:

def __init__(self):
if os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") is None:
logger.log_text(
logger.log_struct(
"GOOGLE_APPLICATION_CREDENTIALS not set; unable to run simulation API.",
severity="ERROR",
)
Expand Down
2 changes: 1 addition & 1 deletion policyengine_api/services/economy_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
ReformImpactsService,
)
from policyengine_api.constants import COUNTRY_PACKAGE_VERSIONS
from policyengine_api.gcp_logging import logger
from policyengine_api.utils.python_logging import logger
from policyengine_api.libs.simulation_api import SimulationAPI
from policyengine_api.data.model_setup import get_dataset_version
from policyengine.simulation import SimulationOptions
Expand Down
49 changes: 49 additions & 0 deletions policyengine_api/utils/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import logging
import json
import sys
from datetime import datetime, timezone


class Logger:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question, blocking: How did you test this?

My concern here is that we're looking to emit JSON to GCP, if possible. I'm not sure this code does that.

def __init__(self, name="policyengine-api", level=logging.INFO):
self.logger = logging.getLogger(name)
self.logger.setLevel(level)

if not self.logger.handlers:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(message)s"))
self.logger.addHandler(handler)

def log_struct(
self, payload: dict, severity: str = "INFO", message: str = None
):
log_entry = {
"severity": severity.upper(),
"timestamp": datetime.now(timezone.utc)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
}

if message:
log_entry["message"] = message

if isinstance(payload, dict):
log_entry.update(payload)

json_log = json.dumps(log_entry, ensure_ascii=False)
self._emit(severity, json_log)

def _emit(self, severity: str, msg: str):
level = severity.upper()
if level == "DEBUG":
self.logger.debug(msg)
elif level == "INFO":
self.logger.info(msg)
elif level == "WARNING":
self.logger.warning(msg)
elif level == "ERROR":
self.logger.error(msg)
elif level == "CRITICAL":
self.logger.critical(msg)
else:
self.logger.info(msg)
3 changes: 3 additions & 0 deletions policyengine_api/utils/python_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from policyengine_api.utils.logger import Logger

logger = Logger("policyengine-api")
14 changes: 14 additions & 0 deletions tests/unit/test_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from policyengine_api.utils.logger import Logger


def test_log_struct(capsys):
logger = Logger("test-logger")
logger.log_struct(
{"event": "test_event", "user_id": "abc123"},
severity="INFO",
message="Logging from test",
)
captured = capsys.readouterr()
assert '"severity": "INFO"' in captured.out
assert '"event": "test_event"' in captured.out
assert '"message": "Logging from test"' in captured.out
Loading