Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: send structured fields on the wire #26

Merged
merged 5 commits into from
May 25, 2021

Conversation

zaharidichev
Copy link
Collaborator

This PR adds structured fields to the wire format.

Fix: #6

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall, this looks great!

i commented on a couple minor style nits you may want to address, and on potential follow-up work we can do once this is merged. also, it would be nice to see comments for the additions to the protobuf...but that's also not really a blocker as i haven't been consistent about documenting the protos either. :)

// NOTE: eventually, it would be nice to support structured fields in tasks;
// we can deprecate this when we add that.
string string_fields = 4;
repeated common.Field fields = 4;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it would be nice to have a comment here

Comment on lines 28 to 37
oneof name {
string str_name = 1;
uint64 name_idx = 2;
}
oneof value {
string debug_val = 3;
string str_val = 4;
uint64 u64_val = 5;
sint64 i64_val = 6;
bool bool_val = 7;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it would be nice to have some comments here explaining the different potential fields here. in particular, it would be nice to explain how names work, since that's potentially not clear?

string debug = 1;
oneof name {
string str_name = 1;
uint64 name_idx = 2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in order to actually use this, we'll also want to include the list of field names in the metadata message, so that these indices are meaningful. but we don't have to do that in this PR, that can be added in a follow-up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, just a thought: it might be worth having this message also include the metadata ID that the index refers to, so that a Field message with a name index can always be hydrated? but, since the Field message is only ever sent in the Span and Task messages, which also include metadata IDs, this isn't strictly necessary...

@@ -304,3 +287,37 @@ impl proto::tasks::tasks_server::Tasks for Server {
Ok(tonic::Response::new(stream))
}
}

impl Visit for FieldsCollector {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, take it or leave it: I might call this FieldVisitor or something --- in tracing 0.2, the name "Collector" will refer to what are currently called "Subscribers", so this could be a little confusing in the future.

name: Some(field.name().into()),
value: Some(value.into()),
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that &dyn std::error::Error is also a potential primitive tracing field value.

this implementation will just record Errors with fmt::Debug, which is fine for now, but it might be nice to also allow recording Errors in a more structured way as a follow-up? we could have an Error message which includes the Error type's fmt::Display output and an optional source field, or maybe the entire chain of sources as a protobuf list?

right now this wouldn't be particularly useful, since we only look at the tokio::task spans which never record Error fields. but in the future, we could potentially support nice error reporting in the console, so it's something that could be worth thinking about as a follow-up change...

Comment on lines 119 to 133
let fields = task
.fields
.iter()
.fold(String::new(), |mut res, f| {
write!(&mut res, "{} ", f).unwrap();
res
})
.trim_end()
.into();
let id = task.id?.id;
let stats = stats_update.remove(&id)?.into();
let task = Task {
id,
id_hex: format!("{:x}", id),
fields: task.string_fields,
fields,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for now, since the purpose of this change was just to add structured fields to the wire format.

a good follow-up would be to change the console TUI to store task fields as structured values as well (maybe a hashmap of names -> values for starters?). that would enable some additional things we could do, such as sorting the task list by the values of various fields, filtering which fields are displayed, and nicer formatting (e.g. bolding field names or displaying them in a different color).

Comment on lines 298 to 322

fn record_i64(&mut self, field: &tracing_core::Field, value: i64) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicky, but: let's prefer to always put a newline between these methods:

Suggested change
fn record_i64(&mut self, field: &tracing_core::Field, value: i64) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_i64(&mut self, field: &tracing_core::Field, value: i64) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_u64(&mut self, field: &tracing_core::Field, value: u64) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}
fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
self.fields.push(proto::Field {
name: Some(field.name().into()),
value: Some(value.into()),
});
}

Comment on lines 67 to 71
field::Value::BoolVal(v) => write!(f, "{}", v)?,
field::Value::StrVal(v) => write!(f, "{}", v)?,
field::Value::U64Val(v) => write!(f, "{}", v)?,
field::Value::DebugVal(v) => write!(f, "{}", v)?,
field::Value::I64Val(v) => write!(f, "{}", v)?,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it might be slightly simpler to just call the inner value's fmt::Display impl directly, rather than using the write! macro...i think there may be a very small amount of additional overhead introduced by going through a format_args!, and also i think this will ensure that any format parameters (e.g. padding/width/etc) are passed through to the inner value

Suggested change
field::Value::BoolVal(v) => write!(f, "{}", v)?,
field::Value::StrVal(v) => write!(f, "{}", v)?,
field::Value::U64Val(v) => write!(f, "{}", v)?,
field::Value::DebugVal(v) => write!(f, "{}", v)?,
field::Value::I64Val(v) => write!(f, "{}", v)?,
field::Value::BoolVal(v) => fmt::Display::fmt(v, f)?,
field::Value::StrVal(v) => fmt::Display::fmt(v, f)?,
field::Value::U64Val(v) => fmt::Display::fmt(v, f)?,
field::Value::DebugVal(v) => fmt::Display::fmt(v, f)?,
field::Value::I64Val(v) => fmt::Display::fmt(v, f)?,

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
@zaharidichev zaharidichev requested a review from hawkw May 25, 2021 18:28
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks great, thank you! i'll open issues for some of the follow-up changes.

Comment on lines +69 to +71
// The names of the key-value fields attached to the
// span or event this metadata is associated with.
repeated string field_names = 7;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a follow-up, we should actually populate this field...

@hawkw hawkw changed the title Structured fields feat: send structured fields on the wire May 25, 2021
@hawkw hawkw merged commit 38adbd9 into tokio-rs:main May 25, 2021
hawkw pushed a commit that referenced this pull request May 27, 2021
In #26 `field_names` field was introduced in the `Metadata`
message but was never populated. This PR populates it
with the names of the fields

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

subscriber: structured span fields
2 participants