Skip to content

Commit 06a9c3a

Browse files
[client] Enhance logging on HTTP errors (#121)
1 parent a7a7f50 commit 06a9c3a

File tree

2 files changed

+162
-11
lines changed

2 files changed

+162
-11
lines changed

pyobas/client.py

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
REDIRECT_MSG = (
1010
"pyobas detected a {status_code} ({reason!r}) redirection. You must update "
11-
"your OpenBVAS URL to the correct URL to avoid issues. The redirection was from: "
11+
"your OpenBAS URL to the correct URL to avoid issues. The redirection was from: "
1212
"{source!r} to {target!r}"
1313
)
1414

@@ -202,25 +202,95 @@ def http_request(
202202
if 200 <= result.status_code < 300:
203203
return result.response
204204

205-
error_message = result.content
205+
# Extract a meaningful error message from the server response
206+
error_message: Any = None
207+
208+
# First, try to get the raw text content
206209
try:
207-
error_json = result.json()
208-
for k in ("message", "error"):
209-
if k in error_json:
210-
error_message = error_json[k]
211-
except (KeyError, ValueError, TypeError):
210+
raw_text = result.content.decode("utf-8", errors="ignore").strip()
211+
# If it's a simple text message (not JSON), use it directly
212+
if (
213+
raw_text
214+
and not raw_text.startswith("{")
215+
and not raw_text.startswith("[")
216+
):
217+
error_message = raw_text[:500]
218+
except Exception:
212219
pass
213220

221+
# If we don't have a message yet, try JSON parsing
222+
if not error_message:
223+
try:
224+
error_json = result.json()
225+
# Common fields
226+
if isinstance(error_json, dict):
227+
# First priority: look for a 'message' field (most specific)
228+
if "message" in error_json:
229+
error_message = error_json.get("message")
230+
elif "execution_message" in error_json:
231+
error_message = error_json.get("execution_message")
232+
elif "error" in error_json:
233+
err = error_json.get("error")
234+
if isinstance(err, dict) and "message" in err:
235+
error_message = err.get("message")
236+
elif err and err not in [
237+
"Internal Server Error",
238+
"Bad Request",
239+
"Not Found",
240+
"Unauthorized",
241+
"Forbidden",
242+
]:
243+
# Only use 'error' field if it's not a generic HTTP status
244+
error_message = str(err)
245+
elif "errors" in error_json:
246+
errs = error_json.get("errors")
247+
if isinstance(errs, list) and errs:
248+
# Join any messages in the list
249+
messages = []
250+
for item in errs:
251+
if isinstance(item, dict) and "message" in item:
252+
messages.append(str(item.get("message")))
253+
else:
254+
messages.append(str(item))
255+
error_message = "; ".join(messages)
256+
elif isinstance(error_json, str):
257+
error_message = error_json
258+
# Fallback to serialized json if we still have nothing
259+
if not error_message:
260+
error_message = utils.json_dumps(error_json)[:500]
261+
except Exception:
262+
# If JSON parsing fails, use the raw text we might have
263+
if not error_message:
264+
try:
265+
error_message = result.response.text[:500]
266+
except Exception:
267+
try:
268+
error_message = result.content.decode(errors="ignore")[
269+
:500
270+
]
271+
except Exception:
272+
error_message = str(result.content)[:500]
273+
274+
# If still no message or a generic HTTP status, use status text
275+
if not error_message or error_message == result.response.reason:
276+
error_message = result.response.reason or "Unknown error"
277+
214278
if result.status_code == 401:
215279
raise exceptions.OpenBASAuthenticationError(
216280
response_code=result.status_code,
217-
error_message=error_message,
281+
error_message=error_message or "Authentication failed",
218282
response_body=result.content,
219283
)
220284

285+
# Use the extracted error message, not the HTTP reason
286+
final_error_message = error_message
287+
if not final_error_message or final_error_message == result.response.reason:
288+
# Only use HTTP reason as last resort
289+
final_error_message = result.response.reason or "Unknown error"
290+
221291
raise exceptions.OpenBASHttpError(
222292
response_code=result.status_code,
223-
error_message=error_message,
293+
error_message=final_error_message,
224294
response_body=result.content,
225295
)
226296

pyobas/exceptions.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,90 @@ def __init__(
2626
self.error_message = error_message
2727

2828
def __str__(self) -> str:
29+
# Start with the provided error message
30+
message = self.error_message
31+
32+
# List of generic HTTP status messages that indicate we should look deeper
33+
generic_messages = (
34+
"Internal Server Error",
35+
"Bad Request",
36+
"Not Found",
37+
"Unauthorized",
38+
"Forbidden",
39+
"Service Unavailable",
40+
"Gateway Timeout",
41+
"Unknown error",
42+
)
43+
44+
# Only try to extract from response body if message is truly generic
45+
# Don't override if we already have a specific error message
46+
if (
47+
not message or (message in generic_messages and len(message) < 30)
48+
) and self.response_body is not None:
49+
try:
50+
import json
51+
52+
body = self.response_body.decode(errors="ignore")
53+
data = json.loads(body)
54+
extracted_msg = None
55+
56+
if isinstance(data, dict):
57+
# Try various common error fields
58+
if "error" in data:
59+
err = data.get("error")
60+
if isinstance(err, dict) and "message" in err:
61+
extracted_msg = err.get("message")
62+
elif isinstance(err, str):
63+
extracted_msg = err
64+
elif "message" in data:
65+
extracted_msg = data.get("message")
66+
elif "execution_message" in data:
67+
extracted_msg = data.get("execution_message")
68+
elif "detail" in data:
69+
extracted_msg = data.get("detail")
70+
elif "errors" in data:
71+
errs = data.get("errors")
72+
if isinstance(errs, list) and errs:
73+
# Join any messages in the list
74+
parts = []
75+
for item in errs:
76+
if isinstance(item, dict) and "message" in item:
77+
parts.append(str(item.get("message")))
78+
else:
79+
parts.append(str(item))
80+
extracted_msg = "; ".join(parts)
81+
elif isinstance(errs, str):
82+
extracted_msg = errs
83+
84+
# Use extracted message if it's better than what we have
85+
if extracted_msg and extracted_msg not in generic_messages:
86+
message = str(extracted_msg)
87+
elif not message:
88+
# Last resort: use the raw body
89+
message = body[:500]
90+
91+
except json.JSONDecodeError:
92+
# Not JSON, use raw text if we don't have a good message
93+
if not message or message in generic_messages:
94+
try:
95+
decoded = self.response_body.decode(errors="ignore")[:500]
96+
if decoded and decoded not in generic_messages:
97+
message = decoded
98+
except Exception:
99+
pass
100+
except Exception:
101+
pass
102+
103+
# Final fallback
104+
if not message:
105+
message = "Unknown error"
106+
107+
# Clean up the message - remove extra whitespace and newlines
108+
message = " ".join(message.split())
109+
29110
if self.response_code is not None:
30-
return f"{self.response_code}: {self.error_message}"
31-
return f"{self.error_message}"
111+
return f"{self.response_code}: {message}"
112+
return f"{message}"
32113

33114

34115
class OpenBASAuthenticationError(OpenBASError):

0 commit comments

Comments
 (0)