diff --git a/tracing-core/src/lib.rs b/tracing-core/src/lib.rs index b78cfcecdf..340556e297 100644 --- a/tracing-core/src/lib.rs +++ b/tracing-core/src/lib.rs @@ -170,7 +170,7 @@ pub mod __macro_support { // Re-export the `core` functions that are used in macros. This allows // a crate to be named `core` and avoid name clashes. // See here: https://github.com/tokio-rs/tracing/issues/2761 - pub use core::{file, line, module_path, option::Option}; + pub use core::{column, file, line, module_path, option::Option}; } /// Statically constructs an [`Identifier`] for the provided [`Callsite`]. @@ -272,11 +272,22 @@ macro_rules! metadata { $name, $target, $level, + $crate::location!(), + $crate::field::FieldSet::new($fields, $crate::identify_callsite!($callsite)), + $kind, + ) + }; +} + +/// Statically constructs a new source code location. +#[macro_export] +macro_rules! location { + () => { + $crate::metadata::Location::new( $crate::__macro_support::Option::Some($crate::__macro_support::file!()), $crate::__macro_support::Option::Some($crate::__macro_support::line!()), + $crate::__macro_support::Option::Some($crate::__macro_support::column!()), $crate::__macro_support::Option::Some($crate::__macro_support::module_path!()), - $crate::field::FieldSet::new($fields, $crate::identify_callsite!($callsite)), - $kind, ) }; } @@ -311,7 +322,7 @@ pub use self::{ dispatch::Dispatch, event::Event, field::Field, - metadata::{Level, LevelFilter, Metadata}, + metadata::{Level, LevelFilter, Location, Metadata}, }; pub use self::{collect::Interest, metadata::Kind}; diff --git a/tracing-core/src/metadata.rs b/tracing-core/src/metadata.rs index 219f299967..9575fb8560 100644 --- a/tracing-core/src/metadata.rs +++ b/tracing-core/src/metadata.rs @@ -67,17 +67,8 @@ pub struct Metadata<'a> { /// The level of verbosity of the described span. level: Level, - /// The name of the Rust module where the span occurred, or `None` if this - /// could not be determined. - module_path: Option<&'a str>, - - /// The name of the source code file where the span occurred, or `None` if - /// this could not be determined. - file: Option<&'a str>, - - /// The line number in the source code file where the span occurred, or - /// `None` if this could not be determined. - line: Option, + /// The position in the source code. + location: Location<'a>, /// The names of the key-value fields attached to the described span or /// event. @@ -87,6 +78,15 @@ pub struct Metadata<'a> { kind: Kind, } +/// Describes a source code location. +#[derive(Eq, PartialEq, Clone, Default)] +pub struct Location<'a> { + file: Option<&'a str>, + line: Option, + column: Option, + module_path: Option<&'a str>, +} + /// Indicates whether the callsite is a span or event. #[derive(Clone, Eq, PartialEq)] pub struct Kind(u8); @@ -256,9 +256,7 @@ impl<'a> Metadata<'a> { name: &'static str, target: &'a str, level: Level, - file: Option<&'a str>, - line: Option, - module_path: Option<&'a str>, + location: Location<'a>, fields: field::FieldSet, kind: Kind, ) -> Self { @@ -266,9 +264,7 @@ impl<'a> Metadata<'a> { name, target, level, - module_path, - file, - line, + location, fields, kind, } @@ -299,25 +295,31 @@ impl<'a> Metadata<'a> { self.target } - /// Returns the path to the Rust module where the span occurred, or - /// `None` if the module path is unknown. - pub fn module_path(&self) -> Option<&'a str> { - self.module_path + /// Returns the name of the source code file where this `Metadata` + /// originated from, or [`None`] if the file is unknown. + pub fn file(&self) -> Option<&'a str> { + self.location.file() } - /// Returns the name of the source code file where the span - /// occurred, or `None` if the file is unknown - pub fn file(&self) -> Option<&'a str> { - self.file + /// Returns the path to the Rust module where this `Metadata` + /// originated from, or [`None`] if the module path is unknown. + pub fn module_path(&self) -> Option<&'a str> { + self.location.module_path() } - /// Returns the line number in the source code file where the span - /// occurred, or `None` if the line number is unknown. + /// Returns the line number in the source code file where this `Metadata` + /// originated from, or [`None`] if the line number is unknown. pub fn line(&self) -> Option { - self.line + self.location.line() + } + + /// Returns the column in the source code file where this `Metadata` + /// originated from, or [`None`] if the line number is unknown. + pub fn column(&self) -> Option { + self.location.column() } - /// Returns an opaque `Identifier` that uniquely identifies the callsite + /// Returns an opaque [`Identifier`](callsite::Identifier) that uniquely identifies the callsite /// this `Metadata` originated from. #[inline] pub fn callsite(&self) -> callsite::Identifier { @@ -340,31 +342,99 @@ impl fmt::Debug for Metadata<'_> { let mut meta = f.debug_struct("Metadata"); meta.field("name", &self.name) .field("target", &self.target) - .field("level", &self.level); + .field("level", &self.level) + .field("location", &self.location) + .field("fields", &format_args!("{}", self.fields)) + .field("callsite", &self.callsite()) + .field("kind", &self.kind) + .finish() + } +} + +// ===== impl Location ===== +impl<'a> Location<'a> { + /// Constructs a new source code location where the [`Metadata`] originated from. + pub const fn new( + file: Option<&'a str>, + line: Option, + column: Option, + module_path: Option<&'a str>, + ) -> Self { + Self { + file, + line, + column, + module_path, + } + } + + /// Returns the name of the source code file where this `Metadata` + /// originated from, or [`None`] if the file is unknown. + pub fn column(&self) -> Option { + self.column + } + + /// Returns the path to the Rust module where this + /// `Metadata` originated from, or `None` if the module path is unknown. + pub fn module_path(&self) -> Option<&'a str> { + self.module_path + } + + /// Returns the path to the Rust module where this `Metadata` + /// originated from, or [`None`] if the module path is unknown. + pub fn file(&self) -> Option<&'a str> { + self.file + } + + /// Returns the column in the source code file where this `Metadata` + /// originated from, or [`None`] if the line number is unknown. + pub fn line(&self) -> Option { + self.line + } +} + +impl<'a> fmt::Debug for Location<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut loc = f.debug_struct("Location"); if let Some(path) = self.module_path() { - meta.field("module_path", &path); + loc.field("module_path", &path); + } + + if let Some(column) = self.column() { + loc.field("column", &column); } - match (self.file(), self.line()) { - (Some(file), Some(line)) => { - meta.field("location", &format_args!("{}:{}", file, line)); + match (self.file(), self.line(), self.column()) { + (Some(file), Some(line), Some(column)) => { + loc.field("location", &format_args!("{}:{}:{}", file, line, column)); + } + (Some(file), Some(line), None) => { + loc.field("file", &format_args!("{}:{}", file, line)); } - (Some(file), None) => { - meta.field("file", &format_args!("{}", file)); + (Some(file), None, None) => { + loc.field("file", &format_args!("{}", file)); + } + (None, None, Some(column)) => { + loc.field("column", &column); } // Note: a line num with no file is a kind of weird case that _probably_ never occurs... - (None, Some(line)) => { - meta.field("line", &line); + (None, Some(line), None) => { + loc.field("line", &line); + } + (None, Some(line), Some(column)) => { + loc.field("column", &column); + loc.field("line", &line); } - (None, None) => {} + (Some(file), None, Some(column)) => { + loc.field("column", &column); + loc.field("file", &file); + } + (None, None, None) => {} }; - meta.field("fields", &format_args!("{}", self.fields)) - .field("callsite", &self.callsite()) - .field("kind", &self.kind) - .finish() + loc.finish() } } @@ -465,9 +535,7 @@ impl PartialEq for Metadata<'_> { name: lhs_name, target: lhs_target, level: lhs_level, - module_path: lhs_module_path, - file: lhs_file, - line: lhs_line, + location: lhs_location, fields: lhs_fields, kind: lhs_kind, } = self; @@ -476,9 +544,7 @@ impl PartialEq for Metadata<'_> { name: rhs_name, target: rhs_target, level: rhs_level, - module_path: rhs_module_path, - file: rhs_file, - line: rhs_line, + location: rhs_location, fields: rhs_fields, kind: rhs_kind, } = &other; @@ -490,9 +556,7 @@ impl PartialEq for Metadata<'_> { && lhs_name == rhs_name && lhs_target == rhs_target && lhs_level == rhs_level - && lhs_module_path == rhs_module_path - && lhs_file == rhs_file - && lhs_line == rhs_line + && lhs_location == rhs_location && lhs_fields == rhs_fields && lhs_kind == rhs_kind } diff --git a/tracing-log/src/lib.rs b/tracing-log/src/lib.rs index 400d8ca1da..996d99bfa0 100644 --- a/tracing-log/src/lib.rs +++ b/tracing-log/src/lib.rs @@ -133,7 +133,7 @@ use tracing_core::{ collect, dispatch, field::{self, Field, Visit}, identify_callsite, - metadata::{Kind, Level}, + metadata::{Kind, Level, Location}, Event, Metadata, }; @@ -230,9 +230,7 @@ impl<'a> AsTrace for log::Metadata<'a> { "log record", self.target(), self.level().as_trace(), - None, - None, - None, + Location::new(None, None, None, None), field::FieldSet::new(FIELD_NAMES, cs_id), Kind::EVENT, ) @@ -245,6 +243,7 @@ struct Fields { module: field::Field, file: field::Field, line: field::Field, + column: field::Field, } static FIELD_NAMES: &[&str] = &[ @@ -253,6 +252,7 @@ static FIELD_NAMES: &[&str] = &[ "log.module_path", "log.file", "log.line", + "log.column", ]; impl Fields { @@ -263,12 +263,14 @@ impl Fields { let module = fieldset.field("log.module_path").unwrap(); let file = fieldset.field("log.file").unwrap(); let line = fieldset.field("log.line").unwrap(); + let column = fieldset.field("log.column").unwrap(); Fields { message, target, module, file, line, + column, } } } @@ -281,9 +283,7 @@ macro_rules! log_cs { "log event", "log", $level, - ::core::option::Option::None, - ::core::option::Option::None, - ::core::option::Option::None, + Location::new(None, None, None, None), field::FieldSet::new(FIELD_NAMES, identify_callsite!(&$cs)), Kind::EVENT, ); @@ -360,9 +360,7 @@ impl<'a> AsTrace for log::Record<'a> { "log record", self.target(), self.level().as_trace(), - self.file(), - self.line(), - self.module_path(), + Location::new(self.file(), self.line(), None, self.module_path()), field::FieldSet::new(FIELD_NAMES, cs_id), Kind::EVENT, ) @@ -474,9 +472,12 @@ impl<'a> NormalizeEvent<'a> for Event<'a> { "log event", fields.target.unwrap_or("log"), *original.level(), - fields.file, - fields.line.map(|l| l as u32), - fields.module_path, + Location::new( + fields.file, + fields.line.map(|l| l as u32), + fields.column.map(|l| l as u32), + fields.module_path, + ), field::FieldSet::new(&["message"], original.callsite()), Kind::EVENT, )) @@ -495,6 +496,7 @@ struct LogVisitor<'a> { module_path: Option<&'a str>, file: Option<&'a str>, line: Option, + column: Option, fields: &'static Fields, } @@ -508,6 +510,7 @@ impl<'a> LogVisitor<'a> { module_path: None, file: None, line: None, + column: None, fields, } } @@ -519,6 +522,8 @@ impl Visit for LogVisitor<'_> { fn record_u64(&mut self, field: &Field, value: u64) { if field == &self.fields.line { self.line = Some(value); + } else if field == &self.fields.column { + self.column = Some(value); } } diff --git a/tracing-log/tests/log_tracer.rs b/tracing-log/tests/log_tracer.rs index 79d3f45cac..aba7a56629 100644 --- a/tracing-log/tests/log_tracer.rs +++ b/tracing-log/tests/log_tracer.rs @@ -16,6 +16,7 @@ struct OwnedMetadata { module_path: Option, file: Option, line: Option, + column: Option, } struct TestSubscriber(Arc); @@ -49,6 +50,7 @@ impl Collect for TestSubscriber { module_path: normalized.module_path().map(String::from), file: normalized.file().map(String::from), line: normalized.line(), + column: normalized.column(), }), ) } @@ -88,6 +90,7 @@ fn normalized_metadata() { module_path: None, file: None, line: None, + column: None, }), ); @@ -110,6 +113,7 @@ fn normalized_metadata() { module_path: Some("log_tracer".to_string()), file: Some("server.rs".to_string()), line: Some(144), + column: None, }), ); diff --git a/tracing-serde/src/lib.rs b/tracing-serde/src/lib.rs index 7411a307fc..1835d1db76 100644 --- a/tracing-serde/src/lib.rs +++ b/tracing-serde/src/lib.rs @@ -274,6 +274,7 @@ impl Serialize for SerializeMetadata<'_> { state.serialize_field("module_path", &self.0.module_path())?; state.serialize_field("file", &self.0.file())?; state.serialize_field("line", &self.0.line())?; + state.serialize_field("column", &self.0.column())?; state.serialize_field("fields", &SerializeFieldSet(self.0.fields()))?; state.serialize_field("is_span", &self.0.is_span())?; state.serialize_field("is_event", &self.0.is_event())?; diff --git a/tracing-subscriber/src/filter/env/mod.rs b/tracing-subscriber/src/filter/env/mod.rs index 89dfe780ef..df67f83df9 100644 --- a/tracing-subscriber/src/filter/env/mod.rs +++ b/tracing-subscriber/src/filter/env/mod.rs @@ -856,9 +856,7 @@ mod tests { "mySpan", "app", Level::TRACE, - None, - None, - None, + Location::new(None, None, None, None), FieldSet::new(&[], identify_callsite!(&Cs)), Kind::SPAN, ); @@ -874,9 +872,7 @@ mod tests { "mySpan", "app", Level::ERROR, - None, - None, - None, + Location::new(None, None, None, None), FieldSet::new(&[], identify_callsite!(&Cs)), Kind::SPAN, ); @@ -892,9 +888,7 @@ mod tests { "mySpan", "app", Level::TRACE, - None, - None, - None, + Location::new(None, None, None, None), FieldSet::new(&[], identify_callsite!(&Cs)), Kind::SPAN, ); @@ -911,9 +905,7 @@ mod tests { "mySpan", "app", Level::TRACE, - None, - None, - None, + Location::new(None, None, None, None), FieldSet::new(&["field"], identify_callsite!(&Cs)), Kind::SPAN, ); @@ -930,9 +922,7 @@ mod tests { "mySpan", "app", Level::TRACE, - None, - None, - None, + Location::new(None, None, None, None), FieldSet::new(&["field"], identify_callsite!(&Cs)), Kind::SPAN, ); diff --git a/tracing/src/lib.rs b/tracing/src/lib.rs index 4fe13ab701..ac5faf3bb9 100644 --- a/tracing/src/lib.rs +++ b/tracing/src/lib.rs @@ -1014,7 +1014,9 @@ pub mod __macro_support { // Re-export the `core` functions that are used in macros. This allows // a crate to be named `core` and avoid name clashes. // See here: https://github.com/tokio-rs/tracing/issues/2761 - pub use core::{concat, file, format_args, iter::Iterator, line, option::Option, stringify}; + pub use core::{ + column, concat, file, format_args, iter::Iterator, line, option::Option, stringify, + }; /// Callsite implementation used by macro-generated code. /// diff --git a/tracing/src/span.rs b/tracing/src/span.rs index cff4d21ba5..743291177e 100644 --- a/tracing/src/span.rs +++ b/tracing/src/span.rs @@ -328,6 +328,7 @@ pub trait AsId: crate::sealed::Sealed { /// span will silently do nothing. Thus, the handle can be used in the same /// manner regardless of whether or not the trace is currently being collected. #[derive(Clone)] +#[must_use = "Once a span has been created, it should be entered."] pub struct Span { /// A handle used to enter the span when it is not executing. /// @@ -1410,6 +1411,10 @@ impl fmt::Debug for Span { span.field("line", &line); } + if let Some(ref column) = meta.column() { + span.field("column", &column); + } + if let Some(ref file) = meta.file() { span.field("file", &file); }