-
Years ago, before One crucial thing I really like about slog is that For example, I have a debug!(&self.log, "Read bytes: ...") and in the log output I automatically see something like Going one level up, I have a debug!(&self.log, "Read device ident: {}", ident) and get The device typically uses a binary transport as a means of communication. It is created as following: impl Device {
pub fn new(log: &Logger, ...) -> Self {
let log = log.new(o!("device" => "FooDevice")),
Device {
transport: BinaryTransport::rs232(&log, ...),
log,
...
}
}
}
impl BinaryTransport {
pub fn rs232(log: &Logger, ...) -> Rs232 {
let log = log.new(o!("port" => port)),
Rs232 {
log,
...
}
}
} The best part of such an approach is that the Thus, I see all the structural context in each log message. I can configure filtering based on communication peer, or on the device, or some combination of both. The setup of the logging configuration is probably slightly more complicated than usual, but the great advantage is that the hierarchy of loggers respects the structural configuration, not the call-stack configuration. Also, it is very clear in the code what particular logger each component uses. I wonder if this is something |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
Thanks for asking this question and providing examples showing what you're looking for. impl Device {
pub fn new(...) -> Self {
let _span = span!(Level::INFO, "device", name = "FooDevice").entered();
Device {
transport: BinaryTransport::rs232(...),
...
}
}
}
impl BinaryTransport {
pub fn rs232(...) -> Rs232 {
let _span = span!(Level::INFO, "transport", name = "rs232", port = %port).entered();
Rs232 {
...
}
}
} This will result in any logging statements ( |
Beta Was this translation helpful? Give feedback.
-
It's certainly possible to replicate impl Device {
pub fn new(span: &tracing::Span, ...) -> Self {
let span = tracing::info_span!(parent: &span, "device", name = "FooDevice");
Device {
transport: BinaryTransport::rs232(&span, ...),
span,
...
}
}
}
impl BinaryTransport {
pub fn rs232(span: &tracing::Span, ...) -> Rs232 {
let span = tracing::debug_span!(parent: &span, "transport", name = "rs232", ?port);
Rs232 {
span,
...
}
}
} In functions where you want to log events, you could either explicitly pass the struct's span to the event macros, like this: impl BinaryTransport {
fn read_bytes(&self, buf: &mut [u8]) {
let bytes_read = // ...
// log an event with `self.span` as the parent scope.
tracing::debug!(parent: &self.span, bytes_read);
}
} or by entering those spans on the stack in the scope of functions on your types: impl BinaryTransport {
fn read_bytes(&self, buf: &mut [u8]) {
// enter `self.span` for the duration of this function call.
let _enter = self.span.enter();
let bytes_read = // ...
// log an event (implicitly in the scope of `self.span`, due to the `enter` call)
tracing::debug!(bytes_read);
}
} Regarding the question about what's considered idiomatic,
In general, the As to why implicit context passing is generally preferred, there are a few reasons:
Of course, using Hope that's helpful! |
Beta Was this translation helpful? Give feedback.
It's certainly possible to replicate
slog
-style explicit context passing via a struct field or parameter. You can do this by passing aroundtracing::Span
instances, and passing them as the parent of your spans or events explicitly. For example, to replicate your example intracing
, you could write: