Improve the error message of Tracing extension.

This commit is contained in:
Sunli 2020-09-26 12:35:28 +08:00
parent 44a1869112
commit 5c293ffdc2
7 changed files with 160 additions and 46 deletions

View File

@ -1,5 +1,6 @@
use crate::{Pos, QueryPathNode, Value};
use std::fmt::{Debug, Display};
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ops::Deref;
use thiserror::Error;
/// An error in the format of an input value.
@ -382,6 +383,27 @@ pub struct RuleError {
pub message: String,
}
impl Display for RuleError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
for (idx, loc) in self.locations.iter().enumerate() {
if idx == 0 {
write!(f, "[")?;
} else {
write!(f, ", ")?;
}
write!(f, "{}:{}", loc.line, loc.column)?;
if idx == self.locations.len() - 1 {
write!(f, "] ")?;
}
}
write!(f, "{}", self.message)?;
Ok(())
}
}
/// An error serving a GraphQL query.
#[derive(Debug, Error, PartialEq)]
pub enum Error {
@ -403,9 +425,45 @@ pub enum Error {
},
/// The query statement verification failed.
#[error("Rule error")]
#[error("Rule error:\n{errors}")]
Rule {
/// List of errors.
errors: Vec<RuleError>,
errors: RuleErrors,
},
}
/// A collection of RuleError.
#[derive(Debug, PartialEq)]
pub struct RuleErrors(Vec<RuleError>);
impl From<Vec<RuleError>> for RuleErrors {
fn from(errors: Vec<RuleError>) -> Self {
Self(errors)
}
}
impl Deref for RuleErrors {
type Target = Vec<RuleError>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl IntoIterator for RuleErrors {
type Item = RuleError;
type IntoIter = std::vec::IntoIter<RuleError>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Display for RuleErrors {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
for err in &self.0 {
writeln!(f, " {}", err)?;
}
Ok(())
}
}

View File

@ -94,7 +94,7 @@ impl Extension for Logger {
}
}
Error::Rule { errors } => {
for error in errors {
for error in errors.iter() {
let locations = error
.locations
.iter()

View File

@ -46,69 +46,69 @@ impl Extension for Tracing {
fn parse_end(&mut self, _document: &ExecutableDocument) {
self.parse
.take()
.unwrap()
.with_subscriber(|(id, d)| d.exit(id));
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
}
fn validation_start(&mut self) {
let parent = self.root.as_ref().unwrap();
let validation_span = span!(
target: "async_graphql::graphql",
parent: parent,
Level::INFO,
"validation"
);
validation_span.with_subscriber(|(id, d)| d.enter(id));
self.validation.replace(validation_span);
if let Some(parent) = &self.root {
let validation_span = span!(
target: "async_graphql::graphql",
parent: parent,
Level::INFO,
"validation"
);
validation_span.with_subscriber(|(id, d)| d.enter(id));
self.validation.replace(validation_span);
}
}
fn validation_end(&mut self) {
self.validation
.take()
.unwrap()
.with_subscriber(|(id, d)| d.exit(id));
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
}
fn execution_start(&mut self) {
let parent = self.root.as_ref().unwrap();
let execute_span = span!(
target: "async_graphql::graphql",
parent: parent,
Level::INFO,
"execute"
);
execute_span.with_subscriber(|(id, d)| d.enter(id));
self.execute.replace(execute_span);
if let Some(parent) = &self.root {
let execute_span = span!(
target: "async_graphql::graphql",
parent: parent,
Level::INFO,
"execute"
);
execute_span.with_subscriber(|(id, d)| d.enter(id));
self.execute.replace(execute_span);
}
}
fn execution_end(&mut self) {
self.execute
.take()
.unwrap()
.with_subscriber(|(id, d)| d.exit(id));
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
self.root
.take()
.unwrap()
.with_subscriber(|(id, d)| d.exit(id));
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
}
fn resolve_start(&mut self, info: &ResolveInfo<'_>) {
let parent_span = match info.resolve_id.parent {
Some(parent_id) if parent_id > 0 => self.fields.get(&parent_id).unwrap(),
_ => self.execute.as_ref().unwrap(),
Some(parent_id) if parent_id > 0 => self.fields.get(&parent_id),
_ => self.execute.as_ref(),
};
let span = span!(
target: "async_graphql::graphql",
parent: parent_span,
Level::INFO,
"field",
id = %info.resolve_id.current,
path = %info.path_node
);
span.with_subscriber(|(id, d)| d.enter(id));
self.fields.insert(info.resolve_id.current, span);
if let Some(parent_span) = parent_span {
let span = span!(
target: "async_graphql::graphql",
parent: parent_span,
Level::INFO,
"field",
id = %info.resolve_id.current,
path = %info.path_node
);
span.with_subscriber(|(id, d)| d.enter(id));
self.fields.insert(info.resolve_id.current, span);
}
}
fn resolve_end(&mut self, info: &ResolveInfo<'_>) {
@ -118,6 +118,24 @@ impl Extension for Tracing {
}
fn error(&mut self, err: &Error) {
tracing::error!(target: "async_graphql::graphql", error = %err);
tracing::error!(target: "async_graphql::graphql", error = %err.to_string());
for span in self.fields.values() {
span.with_subscriber(|(id, d)| d.exit(id));
}
self.fields.clear();
self.execute
.take()
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
self.validation
.take()
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
self.parse
.take()
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
self.root
.take()
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
}
}

View File

@ -72,7 +72,7 @@ impl Serialize for Error {
}
Error::Rule { errors } => {
let mut seq = serializer.serialize_seq(Some(errors.len()))?;
for error in errors {
for error in errors.iter() {
seq.serialize_element(&serde_json::json!({
"message": error.message,
"locations": error.locations.iter().map(|pos| serde_json::json!({"line": pos.line, "column": pos.column})).collect_vec(),

View File

@ -89,7 +89,9 @@ pub fn check_rules(
}
if !ctx.errors.is_empty() {
return Err(Error::Rule { errors: ctx.errors });
return Err(Error::Rule {
errors: ctx.errors.into(),
});
}
Ok(CheckResult {
cache_control,

View File

@ -355,7 +355,9 @@ where
let mut visitor = factory();
visit(&mut visitor, &mut ctx, doc);
if !ctx.errors.is_empty() {
return Err(Error::Rule { errors: ctx.errors });
return Err(Error::Rule {
errors: ctx.errors.into(),
});
}
Ok(())
}

View File

@ -73,6 +73,7 @@ pub async fn test_input_validator_string_min_length() {
}),
message: field_error_msg
})
.into()
}
);
@ -90,6 +91,7 @@ pub async fn test_input_validator_string_min_length() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -183,6 +185,7 @@ pub async fn test_input_validator_string_max_length() {
}),
message: field_error_msg
})
.into()
}
);
@ -200,6 +203,7 @@ pub async fn test_input_validator_string_max_length() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -320,6 +324,7 @@ pub async fn test_input_validator_string_email() {
}),
message: field_error_msg
})
.into()
}
);
@ -338,6 +343,7 @@ pub async fn test_input_validator_string_email() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -468,6 +474,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: field_error_msg.clone()
})
.into()
}
);
@ -486,6 +493,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: object_error_msg.clone()
})
.into()
}
);
@ -503,6 +511,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: field_error_msg
})
.into()
}
);
@ -521,6 +530,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: object_error_msg
})
.into()
}
);
}
@ -578,6 +588,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: field_error_msg
})
.into()
}
);
@ -596,6 +607,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -637,6 +649,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: field_error_msg
})
.into()
}
);
@ -655,6 +668,7 @@ pub async fn test_input_validator_string_mac() {
}),
message: object_error_msg
})
.into()
}
);
}
@ -715,6 +729,7 @@ pub async fn test_input_validator_int_range() {
}),
message: field_error_msg
})
.into()
}
);
@ -732,6 +747,7 @@ pub async fn test_input_validator_int_range() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -820,6 +836,7 @@ pub async fn test_input_validator_int_less_than() {
}),
message: field_error_msg
})
.into()
}
);
@ -837,6 +854,7 @@ pub async fn test_input_validator_int_less_than() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -927,6 +945,7 @@ pub async fn test_input_validator_int_greater_than() {
}),
message: field_error_msg
})
.into()
}
);
@ -944,6 +963,7 @@ pub async fn test_input_validator_int_greater_than() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1027,6 +1047,7 @@ pub async fn test_input_validator_int_nonzero() {
}),
message: field_error_msg
})
.into()
}
);
@ -1044,6 +1065,7 @@ pub async fn test_input_validator_int_nonzero() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1128,6 +1150,7 @@ pub async fn test_input_validator_int_equal() {
}),
message: field_error_msg
})
.into()
}
);
@ -1145,6 +1168,7 @@ pub async fn test_input_validator_int_equal() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1244,6 +1268,7 @@ pub async fn test_input_validator_list_max_length() {
}),
message: field_error_msg
})
.into()
}
);
@ -1261,6 +1286,7 @@ pub async fn test_input_validator_list_max_length() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1360,6 +1386,7 @@ pub async fn test_input_validator_list_min_length() {
}),
message: field_error_msg
})
.into()
}
);
@ -1377,6 +1404,7 @@ pub async fn test_input_validator_list_min_length() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1484,6 +1512,7 @@ pub async fn test_input_validator_operator_or() {
}),
message: field_error_msg
})
.into()
}
);
@ -1501,6 +1530,7 @@ pub async fn test_input_validator_operator_or() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1601,6 +1631,7 @@ pub async fn test_input_validator_operator_and() {
}),
message: field_error_msg
})
.into()
}
);
@ -1618,6 +1649,7 @@ pub async fn test_input_validator_operator_and() {
}),
message: object_error_msg
})
.into()
}
);
} else {
@ -1724,6 +1756,7 @@ pub async fn test_input_validator_variable() {
}),
message: field_error_msg
})
.into()
}
);
@ -1741,6 +1774,7 @@ pub async fn test_input_validator_variable() {
}),
message: object_error_msg
})
.into()
}
);
} else {