Skip to content

Commit 372476e

Browse files
authored
fix: logging API roles (#1244)
1 parent a4bdb4a commit 372476e

File tree

9 files changed

+67
-60
lines changed

9 files changed

+67
-60
lines changed

api/src/feeds/impl/feeds_api_impl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
InternalHTTPException,
3636
gbfs_feed_not_found,
3737
)
38-
from shared.common.logging_utils import Logger
3938
from shared.database.database import Database, with_db_session
4039
from shared.database_gen.sqlacodegen_models import (
4140
Feed as FeedOrm,
@@ -50,6 +49,7 @@
5049
from shared.feed_filters.gtfs_feed_filter import LocationFilter
5150
from shared.feed_filters.gtfs_rt_feed_filter import GtfsRtFeedFilter, EntityTypeFilter
5251
from utils.date_utils import valid_iso_date
52+
from utils.logger import get_logger
5353

5454
T = TypeVar("T", bound="Feed")
5555

@@ -64,7 +64,7 @@ class FeedsApiImpl(BaseFeedsApi):
6464
APIFeedType = Union[FeedOrm, GtfsFeed, GtfsRTFeed]
6565

6666
def __init__(self) -> None:
67-
self.logger = Logger("FeedsApiImpl").get_logger()
67+
self.logger = get_logger("FeedsApiImpl")
6868

6969
@with_db_session
7070
def get_feed(self, id: str, db_session: Session) -> Feed:

api/src/feeds/impl/models/validation_report_impl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from shared.database_gen.sqlacodegen_models import Validationreport
22
from feeds_gen.models.validation_report import ValidationReport
3-
from shared.common.logging_utils import Logger
3+
from utils.logger import get_logger
44

55
DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
66

@@ -18,7 +18,7 @@ class Config:
1818

1919
@classmethod
2020
def _get_logger(cls):
21-
return Logger(ValidationReportImpl.__class__.__module__).get_logger()
21+
return get_logger(ValidationReportImpl.__class__.__module__)
2222

2323
@classmethod
2424
def from_orm(cls, validation_report: Validationreport | None) -> ValidationReport | None:

api/src/middleware/request_context.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import requests
55
from google.auth import jwt
6-
from requests.exceptions import RequestException
76
from starlette.datastructures import Headers
87
from starlette.types import Scope
98

@@ -33,8 +32,10 @@ def resolve_google_public_keys(self):
3332
)
3433
response.raise_for_status()
3534
self.google_public_keys = response.json()
36-
except RequestException as e:
37-
print(f"Error fetching Google's public keys: {e}")
35+
if not self.google_public_keys:
36+
logging.error("Fetched Google's public keys, but the result is empty or invalid.")
37+
except requests.exceptions.RequestException as e:
38+
logging.error(f"Failed to fetch Google's public keys: {e}")
3839

3940
def decode_jwt(self, token: str):
4041
"""
@@ -47,10 +48,11 @@ def decode_jwt(self, token: str):
4748
token = token.replace("Bearer ", "")
4849
self.resolve_google_public_keys()
4950
if not self.google_public_keys:
51+
logging.error("Cannot decode JWT: Google's public keys are not available.")
5052
return None
5153
return jwt.decode(token, self.google_public_keys, audience=get_config(PROJECT_ID))
5254
except Exception as e:
53-
logging.warning("Error decoding JWT: %s", e)
55+
logging.error("Error decoding JWT: %s", e)
5456
return None
5557

5658
def _extract_from_headers(self, headers: dict, scope: Scope) -> None:

api/src/scripts/load_dataset_on_create.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
from google.cloud.pubsub_v1.futures import Future
1111

1212
from shared.database_gen.sqlacodegen_models import Feed
13-
from shared.common.logging_utils import Logger
13+
from utils.logger import get_logger
1414

1515
# Lazy create so we won't try to connect to google cloud when the file is imported.
1616
pubsub_client = None
1717

1818
lock = threading.Lock()
19-
logger = Logger("load_dataset_on_create").get_logger()
19+
logger = get_logger("load_dataset_on_create")
2020

2121

2222
def get_pubsub_client():

api/src/scripts/populate_db.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
from dotenv import load_dotenv
1010
from sqlalchemy import text
1111

12-
from shared.common.logging_utils import Logger
1312
from shared.database.database import Database
1413
from shared.database.database import configure_polymorphic_mappers
1514
from shared.database_gen.sqlacodegen_models import Feed, Gtfsrealtimefeed, Gtfsfeed, Gbfsfeed
1615
from shared.database_gen.sqlacodegen_models import (
1716
t_feedsearch,
1817
)
18+
from utils.logger import get_logger
1919

2020
if TYPE_CHECKING:
2121
from sqlalchemy.orm import Session
@@ -50,7 +50,7 @@ def __init__(self, filepaths):
5050
Specify a list of files to load the csv data from.
5151
Can also be a single string with a file name.
5252
"""
53-
self.logger = Logger(self.__class__.__name__).get_logger()
53+
self.logger = get_logger(self.__class__.__name__)
5454
self.logger.setLevel(logging.INFO)
5555
self.db = Database(echo_sql=False)
5656
self.df = pandas.DataFrame()

api/src/scripts/populate_db_test_data.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
Gbfsfeed,
2121
)
2222
from scripts.populate_db import set_up_configs, DatabasePopulateHelper
23-
from shared.common.logging_utils import Logger
2423
from typing import TYPE_CHECKING
2524

25+
from utils.logger import get_logger
26+
2627
if TYPE_CHECKING:
2728
from sqlalchemy.orm import Session
2829

@@ -38,7 +39,7 @@ def __init__(self, filepaths):
3839
Specify a list of files to load the json data from.
3940
Can also be a single string with a file name.
4041
"""
41-
self.logger = Logger(self.__class__.__module__).get_logger()
42+
self.logger = get_logger(self.__class__.__module__)
4243

4344
if not isinstance(filepaths, list):
4445
self.filepaths = [filepaths]

api/src/shared/common/logging_utils.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,3 @@ def get_env_logging_level():
77
Get the logging level from the environment via OS variable LOGGING_LEVEL. Returns INFO if not set.
88
"""
99
return logging.getLevelName(os.getenv("LOGGING_LEVEL", "INFO"))
10-
11-
12-
class Logger:
13-
"""
14-
Util class for logging information, errors or warnings
15-
"""
16-
17-
def __init__(self, name):
18-
"""
19-
Initialize the logger
20-
"""
21-
formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
22-
23-
console_handler = logging.StreamHandler()
24-
console_handler.setFormatter(formatter)
25-
26-
self.logger = logging.getLogger(name)
27-
self.logger.addHandler(console_handler)
28-
self.logger.setLevel(get_env_logging_level())
29-
30-
def get_logger(self):
31-
"""
32-
Get the logger instance
33-
:return: the logger instance
34-
"""
35-
return self.logger

api/src/utils/logger.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,26 @@ class GoogleCloudLogFilter(CloudLoggingFilter):
6262
"""
6363

6464
def filter(self, record: logging.LogRecord) -> bool:
65-
request_context = get_request_context()
66-
http_request = get_http_request(record)
67-
if http_request:
68-
record.http_request = asdict(http_request)
69-
span_id = request_context.get("span_id")
70-
trace = get_trace(request_context)
71-
record.trace = trace
72-
record.span_id = span_id
73-
74-
record._log_fields = {
75-
"logging.googleapis.com/trace": trace,
76-
"logging.googleapis.com/spanId": span_id,
77-
"logging.googleapis.com/httpRequest": asdict(http_request) if http_request else None,
78-
"logging.googleapis.com/trace_sampled": True,
79-
}
80-
super().filter(record)
81-
65+
try:
66+
request_context = get_request_context()
67+
http_request = get_http_request(record)
68+
if http_request:
69+
record.http_request = asdict(http_request)
70+
span_id = request_context.get("span_id")
71+
trace = get_trace(request_context)
72+
record.trace = trace
73+
record.span_id = span_id
74+
75+
record._log_fields = {
76+
"logging.googleapis.com/trace": trace,
77+
"logging.googleapis.com/spanId": span_id,
78+
"logging.googleapis.com/httpRequest": asdict(http_request) if http_request else None,
79+
"logging.googleapis.com/trace_sampled": True,
80+
}
81+
super().filter(record)
82+
except Exception as e:
83+
# Using print to avoid a recursive call the log filter
84+
print(f"Error in GoogleCloudLogFilter: {e}")
8285
return True
8386

8487

@@ -116,10 +119,12 @@ def is_local_env():
116119

117120

118121
def global_logging_setup():
122+
logging.debug("Starting cloud up logging")
119123
if is_local_env():
124+
logging.debug("Setting local up logging")
120125
logging.basicConfig(level=get_env_logging_level())
121126
return
122-
127+
logging.debug("Setting cloud up logging")
123128
# Send warnings through logging
124129
logging.captureWarnings(True)
125130
# Replace sys.stderr
@@ -152,4 +157,4 @@ def global_logging_setup():
152157
]:
153158
get_logger(name)
154159

155-
logging.info("Setting cloud up logging completed")
160+
logging.debug("Setting cloud up logging completed")

infra/feed-api/main.tf

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ locals {
3232
# DEV and QA use the vpc connector
3333
vpc_connector_name = lower(var.environment) == "dev" ? "vpc-connector-qa" : "vpc-connector-${lower(var.environment)}"
3434
vpc_connector_project = lower(var.environment) == "dev" ? "mobility-feeds-qa" : var.project_id
35+
service_account_roles = [
36+
# Cloud Logging: allows writing logs to GCP
37+
"roles/logging.logWriter",
38+
# Cloud Trace: allows writing trace and span data
39+
"roles/cloudtrace.agent",
40+
# Cloud Monitoring: allows publishing custom metrics
41+
"roles/monitoring.metricWriter",
42+
# Serverless VPC Access: required to use a VPC connector
43+
"roles/vpcaccess.user"
44+
]
45+
service_account_role_bindings = {
46+
for role in local.service_account_roles :
47+
"${role}" => {
48+
role = role
49+
project = var.project_id
50+
}
51+
}
3552
}
3653

3754
data "google_vpc_access_connector" "vpc_connector" {
@@ -56,7 +73,7 @@ resource "google_cloud_run_v2_service" "mobility-feed-api" {
5673
service_account = google_service_account.containers_service_account.email
5774
vpc_access {
5875
connector = data.google_vpc_access_connector.vpc_connector.id
59-
egress = "PRIVATE_RANGES_ONLY"
76+
egress = "ALL_TRAFFIC"
6077
}
6178
containers {
6279
image = "${var.gcp_region}-docker.pkg.dev/${var.project_id}/${var.docker_repository_name}/${var.feed_api_service}:${var.feed_api_image_version}"
@@ -108,6 +125,14 @@ resource "google_secret_manager_secret_iam_member" "policy" {
108125
member = "serviceAccount:${google_service_account.containers_service_account.email}"
109126
}
110127

128+
resource "google_project_iam_member" "containers_service_account_roles" {
129+
for_each = local.service_account_role_bindings
130+
131+
project = each.value.project
132+
role = each.value.role
133+
member = "serviceAccount:${google_service_account.containers_service_account.email}"
134+
}
135+
111136
output "feed_api_uri" {
112137
value = google_cloud_run_v2_service.mobility-feed-api.uri
113138
description = "Main URI of the Feed API"

0 commit comments

Comments
 (0)