Skip to content

Commit 1bd3952

Browse files
committed
exposition: include pushgateway response body in HTTP error
The pushgateway returns a descriptive message in the response body when it rejects a payload (e.g. a malformed metric). urllib raises HTTPError before the existing resp.code check runs, and its default message omits the body, so the useful detail was lost. Catch HTTPError and surface the body. Fixes #96 Signed-off-by: pritam360 <pritamchavan1212@gmail.com>
1 parent 2098346 commit 1bd3952

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

prometheus_client/exposition.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,17 @@ def handle() -> None:
515515
request.get_method = lambda: method # type: ignore
516516
for k, v in headers:
517517
request.add_header(k, v)
518-
resp = build_opener(base_handler).open(request, timeout=timeout)
518+
try:
519+
resp = build_opener(base_handler).open(request, timeout=timeout)
520+
except HTTPError as e:
521+
# The pushgateway returns a helpful message in the response body
522+
# (e.g. which metric was malformed). urllib raises HTTPError before
523+
# the resp.code check below can run, and its default message drops
524+
# the body, so read it here and surface it in the error.
525+
body = e.read().decode('utf-8', 'replace').strip()
526+
raise OSError(
527+
f"error talking to pushgateway: {e.code} {e.reason}: {body}"
528+
) from e
519529
if resp.code >= 400:
520530
raise OSError(f"error talking to pushgateway: {resp.code} {resp.msg}")
521531

tests/test_exposition.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,17 @@ def do_PUT(self):
238238
# and simulates a redirect to a url with the redirect_flag (which will produce a 201)
239239
self.send_response(301)
240240
self.send_header('Location', getattr(self, 'redirect_address', None))
241+
elif 'error500' in self.requestline:
242+
# simulate a pushgateway rejecting the payload with a 500 and
243+
# a descriptive body, to exercise error-body surfacing.
244+
length = int(self.headers['content-length'])
245+
self.rfile.read(length)
246+
body = b'text format parsing error: duplicate metric'
247+
self.send_response(500)
248+
self.send_header('Content-Length', str(len(body)))
249+
self.end_headers()
250+
self.wfile.write(body)
251+
return
241252
else:
242253
self.send_response(201)
243254
length = int(self.headers['content-length'])
@@ -282,6 +293,13 @@ def test_push(self):
282293
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_PLAIN_0_0_4)
283294
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
284295

296+
def test_push_500_error_includes_response_body(self):
297+
with self.assertRaises(OSError) as cm:
298+
push_to_gateway(self.address, "my_job_error500", self.registry)
299+
message = str(cm.exception)
300+
self.assertIn('500', message)
301+
self.assertIn('duplicate metric', message)
302+
285303
def test_push_schemeless_url(self):
286304
push_to_gateway(self.address.replace('http://', ''), "my_job", self.registry)
287305
self.assertEqual(self.requests[0][0].command, 'PUT')

0 commit comments

Comments
 (0)