mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
ch-remote: also pretty-print remote server errors
Remote server errors are transferred as raw HTTP body. This way,
we lose the nested structured error information.
This is an attempt to retrieve the errors from the HTTP response
and to align the output with the normal error output.
For example, this produces the following chain of errors. Note
that everything after level 0 was retrieved from the HTTP server
response:
```
Error: ch-remote exited with the following chain of errors:
0: http client error
1: Server responded with InternalServerError
2: Error from API
3: The disk could not be added to the VM
4: Failed to validate config
5: Identifier disk1 is not unique
Debug Info: HttpApiClient(ServerResponse(InternalServerError, Some("Error from API<br>The disk could not be added to the VM<br>Failed to validate config<br>Identifier disk1 is not unique")))
```
In case the JSON can't be parsed properly, ch-remote will print:
```
Error: ch-remote exited with the following chain of errors:
0: http client error
X: Can't get remote's error messages from JSON response: EOF while parsing a value at line 1 column 0: body=''
Debug Info: HttpApiClient(ServerResponse(InternalServerError, Some("")))
```
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
This commit is contained in:
committed by
Rob Bradford
parent
6ea132708c
commit
190a11f212
@@ -1132,7 +1132,70 @@ fn main() {
|
||||
};
|
||||
|
||||
if let Err(top_error) = target_api.do_command(&matches) {
|
||||
cloud_hypervisor::cli_print_error_chain(&top_error, "ch-remote");
|
||||
// Helper to join strings with a newline.
|
||||
fn join_strs(mut acc: String, next: String) -> String {
|
||||
if !acc.is_empty() {
|
||||
acc.push('\n');
|
||||
}
|
||||
acc.push_str(&next);
|
||||
acc
|
||||
}
|
||||
|
||||
// This function helps to modify the Display representation of remote
|
||||
// API failures so that it aligns with the regular output of error
|
||||
// messages. As we transfer a deep/rich chain of errors as String via
|
||||
// the HTTP API, the nested error chain is lost. We retrieve it from
|
||||
// the error response.
|
||||
//
|
||||
// In case the repose itself is broken, the error is printed directly
|
||||
// by using the `X` level.
|
||||
fn server_api_error_display_modifier(
|
||||
level: usize,
|
||||
indention: usize,
|
||||
error: &(dyn std::error::Error + 'static),
|
||||
) -> Option<String> {
|
||||
if let Some(api_client::Error::ServerResponse(status_code, body)) =
|
||||
error.downcast_ref::<api_client::Error>()
|
||||
{
|
||||
let body = body.as_ref().map(|body| body.as_str()).unwrap_or("");
|
||||
|
||||
// Retrieve the list of error messages back.
|
||||
let lines: Vec<&str> = match serde_json::from_str(body) {
|
||||
Ok(json) => json,
|
||||
Err(e) => {
|
||||
return Some(format!(
|
||||
"{idention}X: Can't get remote's error messages from JSON response: {e}: body='{body}'",
|
||||
idention = " ".repeat(indention)
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let error_status = format!("Server responded with {status_code:?}");
|
||||
// Prepend the error status line to the lines iter.
|
||||
let lines = std::iter::once(error_status.as_str()).chain(lines);
|
||||
let error_msg_multiline = lines
|
||||
.enumerate()
|
||||
.map(|(index, error_msg)| (index + level, error_msg))
|
||||
.map(|(level, error_msg)| {
|
||||
format!(
|
||||
"{idention}{level}: {error_msg}",
|
||||
idention = " ".repeat(indention)
|
||||
)
|
||||
})
|
||||
.fold(String::new(), join_strs);
|
||||
|
||||
return Some(error_msg_multiline);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
let top_error: &dyn std::error::Error = &top_error;
|
||||
cloud_hypervisor::cli_print_error_chain(
|
||||
top_error,
|
||||
"ch-remote",
|
||||
server_api_error_display_modifier,
|
||||
);
|
||||
process::exit(1)
|
||||
};
|
||||
}
|
||||
|
||||
18
src/lib.rs
18
src/lib.rs
@@ -7,7 +7,16 @@ use std::error::Error;
|
||||
/// Prints a chain of errors to the user in a consistent manner.
|
||||
/// The user will see a clear chain of errors, followed by debug output
|
||||
/// for opening issues.
|
||||
pub fn cli_print_error_chain(top_error: &dyn Error, component: &str) {
|
||||
pub fn cli_print_error_chain<'a>(
|
||||
top_error: &'a (dyn Error + 'static),
|
||||
component: &str,
|
||||
// Function optionally returning the display representation of an error.
|
||||
display_modifier: impl Fn(
|
||||
/* level */ usize,
|
||||
/*indention */ usize,
|
||||
&'a (dyn Error + 'static),
|
||||
) -> Option<String>,
|
||||
) {
|
||||
eprint!("Error: {component} exited with the following ");
|
||||
if top_error.source().is_none() {
|
||||
eprintln!("error:");
|
||||
@@ -21,7 +30,12 @@ pub fn cli_print_error_chain(top_error: &dyn Error, component: &str) {
|
||||
})
|
||||
.enumerate()
|
||||
.for_each(|(level, error)| {
|
||||
eprintln!(" {level}: {error}",);
|
||||
// Special case: handling of HTTP Server responses in ch-remote
|
||||
if let Some(message) = display_modifier(level, 2, error) {
|
||||
eprintln!("{message}");
|
||||
} else {
|
||||
eprintln!(" {level}: {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -884,7 +884,7 @@ fn main() {
|
||||
0
|
||||
}
|
||||
Err(top_error) => {
|
||||
cloud_hypervisor::cli_print_error_chain(&top_error, "Cloud Hypervisor");
|
||||
cloud_hypervisor::cli_print_error_chain(&top_error, "Cloud Hypervisor", |_, _, _| None);
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user