Skip to content

Commit 8eaa90d

Browse files
authored
Improve Logging for API HTTP requests (#715)
* improve: unify logging for all HTTP requests and responses * convert requests.HTTPError to readable errors * skip logging if verbose is off * truncate long json params
1 parent 9290573 commit 8eaa90d

File tree

6 files changed

+161
-91
lines changed

6 files changed

+161
-91
lines changed

mapillary_tools/api_v4.py

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import ssl
44
import typing as T
5+
from json import dumps
56

67
import requests
78
from requests.adapters import HTTPAdapter
@@ -46,6 +47,106 @@ def cert_verify(self, *args, **kwargs):
4647
conn.ca_certs = None
4748

4849

50+
@T.overload
51+
def _truncate(s: bytes, limit: int = 512) -> bytes: ...
52+
53+
54+
@T.overload
55+
def _truncate(s: str, limit: int = 512) -> str: ...
56+
57+
58+
def _truncate(s, limit=512):
59+
if limit < len(s):
60+
remaining = len(s) - limit
61+
if isinstance(s, bytes):
62+
return (
63+
s[:limit]
64+
+ b"..."
65+
+ f"({remaining} more bytes truncated)".encode("utf-8")
66+
)
67+
else:
68+
return str(s[:limit]) + f"...({remaining} more chars truncated)"
69+
else:
70+
return s
71+
72+
73+
def _sanitize(headers: T.Dict):
74+
new_headers = {}
75+
76+
for k, v in headers.items():
77+
if k.lower() in [
78+
"authorization",
79+
"cookie",
80+
"x-fb-access-token",
81+
"access-token",
82+
"access_token",
83+
"password",
84+
]:
85+
new_headers[k] = "[REDACTED]"
86+
else:
87+
new_headers[k] = _truncate(v)
88+
89+
return new_headers
90+
91+
92+
def _log_debug_request(
93+
method: str,
94+
url: str,
95+
json: T.Optional[T.Dict] = None,
96+
params: T.Optional[T.Dict] = None,
97+
headers: T.Optional[T.Dict] = None,
98+
timeout: T.Any = None,
99+
):
100+
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
101+
return
102+
103+
msg = f"HTTP {method} {url}"
104+
105+
if USE_SYSTEM_CERTS:
106+
msg += " (w/sys_certs)"
107+
108+
if json:
109+
t = _truncate(dumps(_sanitize(json)))
110+
msg += f" JSON={t}"
111+
112+
if params:
113+
msg += f" PARAMS={_sanitize(params)}"
114+
115+
if headers:
116+
msg += f" HEADERS={_sanitize(headers)}"
117+
118+
if timeout is not None:
119+
msg += f" TIMEOUT={timeout}"
120+
121+
LOG.debug(msg)
122+
123+
124+
def _log_debug_response(resp: requests.Response):
125+
if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
126+
return
127+
128+
data: T.Union[str, bytes]
129+
try:
130+
data = _truncate(dumps(_sanitize(resp.json())))
131+
except Exception:
132+
data = _truncate(resp.content)
133+
134+
LOG.debug(f"HTTP {resp.status_code} ({resp.reason}): %s", data)
135+
136+
137+
def readable_http_error(ex: requests.HTTPError) -> str:
138+
req = ex.request
139+
resp = ex.response
140+
141+
data: T.Union[str, bytes]
142+
try:
143+
data = _truncate(dumps(_sanitize(resp.json())))
144+
except Exception:
145+
data = _truncate(resp.content)
146+
147+
return f"{req.method} {resp.url} => {resp.status_code} ({resp.reason}): {str(data)}"
148+
149+
49150
def request_post(
50151
url: str,
51152
data: T.Optional[T.Any] = None,
@@ -54,14 +155,23 @@ def request_post(
54155
) -> requests.Response:
55156
global USE_SYSTEM_CERTS
56157

158+
_log_debug_request(
159+
"POST",
160+
url,
161+
json=json,
162+
params=kwargs.get("params"),
163+
headers=kwargs.get("headers"),
164+
timeout=kwargs.get("timeout"),
165+
)
166+
57167
if USE_SYSTEM_CERTS:
58168
with requests.Session() as session:
59169
session.mount("https://", HTTPSystemCertsAdapter())
60-
return session.post(url, data=data, json=json, **kwargs)
170+
resp = session.post(url, data=data, json=json, **kwargs)
61171

62172
else:
63173
try:
64-
return requests.post(url, data=data, json=json, **kwargs)
174+
resp = requests.post(url, data=data, json=json, **kwargs)
65175
except requests.exceptions.SSLError as ex:
66176
if "SSLCertVerificationError" not in str(ex):
67177
raise ex
@@ -70,9 +180,11 @@ def request_post(
70180
LOG.warning(
71181
"SSL error occurred, falling back to system SSL certificates: %s", ex
72182
)
73-
with requests.Session() as session:
74-
session.mount("https://", HTTPSystemCertsAdapter())
75-
return session.post(url, data=data, json=json, **kwargs)
183+
return request_post(url, data=data, json=json, **kwargs)
184+
185+
_log_debug_response(resp)
186+
187+
return resp
76188

77189

78190
def request_get(
@@ -82,13 +194,21 @@ def request_get(
82194
) -> requests.Response:
83195
global USE_SYSTEM_CERTS
84196

197+
_log_debug_request(
198+
"GET",
199+
url,
200+
params=kwargs.get("params"),
201+
headers=kwargs.get("headers"),
202+
timeout=kwargs.get("timeout"),
203+
)
204+
85205
if USE_SYSTEM_CERTS:
86206
with requests.Session() as session:
87207
session.mount("https://", HTTPSystemCertsAdapter())
88-
return session.get(url, params=params, **kwargs)
208+
resp = session.get(url, params=params, **kwargs)
89209
else:
90210
try:
91-
return requests.get(url, params=params, **kwargs)
211+
resp = requests.get(url, params=params, **kwargs)
92212
except requests.exceptions.SSLError as ex:
93213
if "SSLCertVerificationError" not in str(ex):
94214
raise ex
@@ -97,15 +217,17 @@ def request_get(
97217
LOG.warning(
98218
"SSL error occurred, falling back to system SSL certificates: %s", ex
99219
)
100-
with requests.Session() as session:
101-
session.mount("https://", HTTPSystemCertsAdapter())
102-
return session.get(url, params=params, **kwargs)
220+
resp = request_get(url, params=params, **kwargs)
221+
222+
_log_debug_response(resp)
223+
224+
return resp
103225

104226

105227
def get_upload_token(email: str, password: str) -> requests.Response:
106228
resp = request_post(
107229
f"{MAPILLARY_GRAPH_API_ENDPOINT}/login",
108-
params={"access_token": MAPILLARY_CLIENT_TOKEN},
230+
headers={"Authorization": f"OAuth {MAPILLARY_CLIENT_TOKEN}"},
109231
json={"email": email, "password": password, "locale": "en_US"},
110232
timeout=REQUESTS_TIMEOUT,
111233
)

mapillary_tools/commands/__main__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import typing as T
66
from pathlib import Path
77

8-
from .. import constants, exceptions, VERSION
8+
import requests
9+
10+
from .. import api_v4, constants, exceptions, VERSION
911
from . import (
1012
authenticate,
1113
process,
@@ -160,11 +162,14 @@ def main():
160162

161163
try:
162164
args.func(argvars)
163-
except exceptions.MapillaryUserError as exc:
165+
except requests.HTTPError as ex:
166+
LOG.error("%s: %s", ex.__class__.__name__, api_v4.readable_http_error(ex))
167+
168+
except exceptions.MapillaryUserError as ex:
164169
LOG.error(
165-
"%s: %s", exc.__class__.__name__, exc, exc_info=log_level == logging.DEBUG
170+
"%s: %s", ex.__class__.__name__, ex, exc_info=log_level == logging.DEBUG
166171
)
167-
sys.exit(exc.exit_code)
172+
sys.exit(ex.exit_code)
168173

169174

170175
if __name__ == "__main__":

mapillary_tools/commands/process_and_upload.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ def run(self, args: dict):
1515
# \x00 is a special path similiar to /dev/null
1616
# it tells process command do not write anything
1717
args["desc_path"] = "\x00"
18+
1819
ProcessCommand().run(args)
1920
UploadCommand().run(args)

mapillary_tools/upload.py

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,6 @@ def __init__(self, inner_ex) -> None:
4747
super().__init__(str(inner_ex))
4848

4949

50-
class UploadHTTPError(Exception):
51-
pass
52-
53-
54-
def wrap_http_exception(ex: requests.HTTPError):
55-
req = ex.request
56-
resp = ex.response
57-
if isinstance(resp, requests.Response) and isinstance(req, requests.Request):
58-
lines = [
59-
f"{req.method} {resp.url}",
60-
f"> HTTP Status: {resp.status_code}",
61-
str(resp.content),
62-
]
63-
else:
64-
lines = []
65-
66-
return UploadHTTPError("\n".join(lines))
67-
68-
6950
def _load_validate_metadatas_from_desc_path(
7051
desc_path: T.Optional[str], import_paths: T.Sequence[Path]
7152
) -> T.List[types.Metadata]:
@@ -175,18 +156,12 @@ def fetch_user_items(
175156
"Found multiple Mapillary accounts. Please specify one with --user_name"
176157
)
177158
else:
178-
try:
179-
user_items = authenticate.authenticate_user(user_name)
180-
except requests.HTTPError as exc:
181-
raise wrap_http_exception(exc) from exc
159+
user_items = authenticate.authenticate_user(user_name)
182160

183161
if organization_key is not None:
184-
try:
185-
resp = api_v4.fetch_organization(
186-
user_items["user_upload_token"], organization_key
187-
)
188-
except requests.HTTPError as ex:
189-
raise wrap_http_exception(ex) from ex
162+
resp = api_v4.fetch_organization(
163+
user_items["user_upload_token"], organization_key
164+
)
190165
org = resp.json()
191166
LOG.info("Uploading to organization: %s", json.dumps(org))
192167
user_items = T.cast(
@@ -430,15 +405,12 @@ def _api_logging_finished(summary: T.Dict):
430405
action: api_v4.ActionType = "upload_finished_upload"
431406
LOG.debug("API Logging for action %s: %s", action, summary)
432407
try:
433-
api_v4.log_event(
434-
action,
435-
summary,
436-
)
408+
api_v4.log_event(action, summary)
437409
except requests.HTTPError as exc:
438410
LOG.warning(
439-
"Error from API Logging for action %s",
411+
"HTTPError from API Logging for action %s: %s",
440412
action,
441-
exc_info=wrap_http_exception(exc),
413+
api_v4.readable_http_error(exc),
442414
)
443415
except Exception:
444416
LOG.warning("Error from API Logging for action %s", action, exc_info=True)
@@ -452,16 +424,12 @@ def _api_logging_failed(payload: T.Dict, exc: Exception):
452424
action: api_v4.ActionType = "upload_failed_upload"
453425
LOG.debug("API Logging for action %s: %s", action, payload)
454426
try:
455-
api_v4.log_event(
456-
action,
457-
payload_with_reason,
458-
)
427+
api_v4.log_event(action, payload_with_reason)
459428
except requests.HTTPError as exc:
460-
wrapped_exc = wrap_http_exception(exc)
461429
LOG.warning(
462-
"Error from API Logging for action %s",
430+
"HTTPError from API Logging for action %s: %s",
463431
action,
464-
exc_info=wrapped_exc,
432+
api_v4.readable_http_error(exc),
465433
)
466434
except Exception:
467435
LOG.warning("Error from API Logging for action %s", action, exc_info=True)
@@ -678,7 +646,7 @@ def upload(
678646
raise exceptions.MapillaryUploadUnauthorizedError(
679647
debug_info.get("message")
680648
) from inner_ex
681-
raise wrap_http_exception(inner_ex) from inner_ex
649+
raise inner_ex
682650

683651
raise inner_ex
684652

0 commit comments

Comments
 (0)