diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index 0b63f6f6..8cdc05e6 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -515,9 +515,18 @@ def handle() -> None: request.get_method = lambda: method # type: ignore for k, v in headers: request.add_header(k, v) - resp = build_opener(base_handler).open(request, timeout=timeout) + try: + resp = build_opener(base_handler).open(request, timeout=timeout) + except HTTPError as e: + # The pushgateway reports what was wrong with the request in the + # response body, so include it in the error message. + body = e.read().decode('utf-8', 'replace').strip() + if not body: + raise + raise HTTPError(e.url, e.code, f'{e.reason} ({body})', e.headers, None) from e if resp.code >= 400: - raise OSError(f"error talking to pushgateway: {resp.code} {resp.msg}") + body = resp.read().decode('utf-8', 'replace').strip() + raise OSError(f"error talking to pushgateway: {resp.code} {resp.msg} ({body})") return handle diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 1885480f..f19940bd 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -231,7 +231,12 @@ def setUp(self): class TestHandler(BaseHTTPRequestHandler): def do_PUT(self): - if 'with_basic_auth' in self.requestline and self.headers['authorization'] != 'Basic Zm9vOmJhcg==': + error_body = None + if 'error' in self.requestline: + error_body = b'text format parsing error in line 1' + self.send_response(500) + self.send_header('Content-Length', str(len(error_body))) + elif 'with_basic_auth' in self.requestline and self.headers['authorization'] != 'Basic Zm9vOmJhcg==': self.send_response(401) elif 'redirect' in self.requestline and redirect_flag not in self.requestline: # checks for an initial test request with 'redirect' but without the redirect_flag, @@ -243,6 +248,8 @@ def do_PUT(self): length = int(self.headers['content-length']) requests.append((self, self.rfile.read(length))) self.end_headers() + if error_body: + self.wfile.write(error_body) do_POST = do_PUT do_DELETE = do_PUT @@ -324,6 +331,12 @@ def test_push_with_complex_job(self): self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_PLAIN_0_0_4) self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n') + def test_push_error_includes_response_body(self): + with self.assertRaises(urllib.error.HTTPError) as cm: + push_to_gateway(self.address, "error_job", self.registry) + self.assertEqual(cm.exception.code, 500) + self.assertIn('text format parsing error in line 1', str(cm.exception)) + def test_pushadd(self): pushadd_to_gateway(self.address, "my_job", self.registry) self.assertEqual(self.requests[0][0].command, 'POST')