Skip to content

Commit c109197

Browse files
authored
subscriber: allow thread name and ID to be displayed in traces (#818)
## Motivation Fixes #809 ## Solution This PR adds two new methods i.e `with_thread_ids` and `with_thread_names` to the fmt Layer which passed till the pre-configured Format type, where the check is performed and these the above values are formatted based on the flags. The formatting is done by creating to new Types that impl Display for Id and Name. For Thread Name, we keep track of the max length of the thread name that occured till now using a thread safe static type and pad accordingly. For Thread Id, we pad directly with 2 as in most cases we would see a double digit number of threads. Signed-off-by: Tarun Pothulapati <[email protected]>
1 parent 56b841d commit c109197

File tree

5 files changed

+247
-0
lines changed

5 files changed

+247
-0
lines changed

examples/examples/thread-info.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![deny(rust_2018_idioms)]
2+
/// This is a example showing how thread info can be displayed when
3+
/// formatting events with `tracing_subscriber::fmt`. This is useful
4+
/// as `tracing` spans can be entered by multicple threads concurrently,
5+
/// or move across threads freely.
6+
///
7+
/// You can run this example by running the following command in a terminal
8+
///
9+
/// ```
10+
/// cargo run --example thread-info
11+
/// ```
12+
///
13+
/// Example output:
14+
///
15+
/// ```not_rust
16+
/// Jul 17 00:38:07.177 INFO ThreadId(02) thread_info: i=9
17+
/// Jul 17 00:38:07.177 INFO thread 1 ThreadId(03) thread_info: i=9
18+
/// Jul 17 00:38:07.177 INFO large name thread 2 ThreadId(04) thread_info: i=9
19+
/// ```
20+
use std::thread;
21+
use std::time::Duration;
22+
use tracing::info;
23+
24+
fn main() {
25+
tracing_subscriber::fmt()
26+
.with_max_level(tracing::Level::DEBUG)
27+
// enable thread id to be emitted
28+
.with_thread_ids(true)
29+
// enabled thread name to be emitted
30+
.with_thread_names(true)
31+
.init();
32+
33+
let do_work = || {
34+
for i in 1..10 {
35+
info!(i);
36+
thread::sleep(Duration::from_millis(1));
37+
}
38+
};
39+
40+
let thread_with_no_name = thread::spawn(do_work);
41+
let thread_one = thread::Builder::new()
42+
.name("thread 1".to_string())
43+
.spawn(do_work)
44+
.expect("could not spawn a new thread");
45+
let thread_two = thread::Builder::new()
46+
.name("large name thread 2".to_string())
47+
.spawn(do_work)
48+
.expect("could not spawn a new thread");
49+
50+
thread_with_no_name
51+
.join()
52+
.expect("could not wait for a thread");
53+
thread_one.join().expect("could not wait for a thread");
54+
thread_two.join().expect("could not wait for a thread");
55+
}

tracing-subscriber/src/fmt/fmt_layer.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,37 @@ where
290290
}
291291
}
292292

293+
/// Sets whether or not the [thread ID] of the current thread is displayed
294+
/// when formatting events
295+
///
296+
/// [thread ID]: https://doc.rust-lang.org/stable/std/thread/struct.ThreadId.html
297+
pub fn with_thread_ids(self, display_thread_ids: bool) -> Layer<S, N, format::Format<L, T>, W> {
298+
Layer {
299+
fmt_event: self.fmt_event.with_thread_ids(display_thread_ids),
300+
fmt_fields: self.fmt_fields,
301+
fmt_span: self.fmt_span,
302+
make_writer: self.make_writer,
303+
_inner: self._inner,
304+
}
305+
}
306+
307+
/// Sets whether or not the [name] of the current thread is displayed
308+
/// when formatting events
309+
///
310+
/// [name]: https://doc.rust-lang.org/stable/std/thread/index.html#naming-threads
311+
pub fn with_thread_names(
312+
self,
313+
display_thread_names: bool,
314+
) -> Layer<S, N, format::Format<L, T>, W> {
315+
Layer {
316+
fmt_event: self.fmt_event.with_thread_names(display_thread_names),
317+
fmt_fields: self.fmt_fields,
318+
fmt_span: self.fmt_span,
319+
make_writer: self.make_writer,
320+
_inner: self._inner,
321+
}
322+
}
323+
293324
/// Sets the layer being built to use a [less verbose formatter](../fmt/format/struct.Compact.html).
294325
pub fn compact(self) -> Layer<S, N, format::Format<format::Compact, T>, W>
295326
where

tracing-subscriber/src/fmt/format/json.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,26 @@ where
236236
serializer.serialize_entry("target", meta.target())?;
237237
}
238238

239+
if self.display_thread_name {
240+
let current_thread = std::thread::current();
241+
match current_thread.name() {
242+
Some(name) => {
243+
serializer.serialize_entry("threadName", name)?;
244+
}
245+
// fall-back to thread id when name is absent and ids are not enabled
246+
None if !self.display_thread_id => {
247+
serializer
248+
.serialize_entry("threadName", &format!("{:?}", current_thread.id()))?;
249+
}
250+
_ => {}
251+
}
252+
}
253+
254+
if self.display_thread_id {
255+
serializer
256+
.serialize_entry("threadId", &format!("{:?}", std::thread::current().id()))?;
257+
}
258+
239259
if !self.format.flatten_event {
240260
use tracing_serde::fields::AsMap;
241261
serializer.serialize_entry("fields", &event.field_map())?;

tracing-subscriber/src/fmt/format/mod.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ pub struct Format<F = Full, T = SystemTime> {
179179
pub(crate) ansi: bool,
180180
pub(crate) display_target: bool,
181181
pub(crate) display_level: bool,
182+
pub(crate) display_thread_id: bool,
183+
pub(crate) display_thread_name: bool,
182184
}
183185

184186
impl Default for Format<Full, SystemTime> {
@@ -189,6 +191,8 @@ impl Default for Format<Full, SystemTime> {
189191
ansi: true,
190192
display_target: true,
191193
display_level: true,
194+
display_thread_id: false,
195+
display_thread_name: false,
192196
}
193197
}
194198
}
@@ -204,6 +208,8 @@ impl<F, T> Format<F, T> {
204208
ansi: self.ansi,
205209
display_target: self.display_target,
206210
display_level: self.display_level,
211+
display_thread_id: self.display_thread_id,
212+
display_thread_name: self.display_thread_name,
207213
}
208214
}
209215

@@ -232,6 +238,8 @@ impl<F, T> Format<F, T> {
232238
ansi: self.ansi,
233239
display_target: self.display_target,
234240
display_level: self.display_level,
241+
display_thread_id: self.display_thread_id,
242+
display_thread_name: self.display_thread_name,
235243
}
236244
}
237245

@@ -253,6 +261,8 @@ impl<F, T> Format<F, T> {
253261
ansi: self.ansi,
254262
display_target: self.display_target,
255263
display_level: self.display_level,
264+
display_thread_id: self.display_thread_id,
265+
display_thread_name: self.display_thread_name,
256266
}
257267
}
258268

@@ -264,6 +274,8 @@ impl<F, T> Format<F, T> {
264274
ansi: self.ansi,
265275
display_target: self.display_target,
266276
display_level: self.display_level,
277+
display_thread_id: self.display_thread_id,
278+
display_thread_name: self.display_thread_name,
267279
}
268280
}
269281

@@ -287,6 +299,28 @@ impl<F, T> Format<F, T> {
287299
..self
288300
}
289301
}
302+
303+
/// Sets whether or not the [thread ID] of the current thread is displayed
304+
/// when formatting events
305+
///
306+
/// [thread ID]: https://doc.rust-lang.org/stable/std/thread/struct.ThreadId.html
307+
pub fn with_thread_ids(self, display_thread_id: bool) -> Format<F, T> {
308+
Format {
309+
display_thread_id,
310+
..self
311+
}
312+
}
313+
314+
/// Sets whether or not the [name] of the current thread is displayed
315+
/// when formatting events
316+
///
317+
/// [name]: https://doc.rust-lang.org/stable/std/thread/index.html#naming-threads
318+
pub fn with_thread_names(self, display_thread_name: bool) -> Format<F, T> {
319+
Format {
320+
display_thread_name,
321+
..self
322+
}
323+
}
290324
}
291325

292326
#[cfg(feature = "json")]
@@ -367,6 +401,24 @@ where
367401
write!(writer, "{} ", fmt_level)?;
368402
}
369403

404+
if self.display_thread_name {
405+
let current_thread = std::thread::current();
406+
match current_thread.name() {
407+
Some(name) => {
408+
write!(writer, "{} ", FmtThreadName::new(name))?;
409+
}
410+
// fall-back to thread id when name is absent and ids are not enabled
411+
None if !self.display_thread_id => {
412+
write!(writer, "{:0>2?} ", current_thread.id())?;
413+
}
414+
_ => {}
415+
}
416+
}
417+
418+
if self.display_thread_id {
419+
write!(writer, "{:0>2?} ", std::thread::current().id())?;
420+
}
421+
370422
let full_ctx = {
371423
#[cfg(feature = "ansi")]
372424
{
@@ -424,6 +476,24 @@ where
424476
write!(writer, "{} ", fmt_level)?;
425477
}
426478

479+
if self.display_thread_name {
480+
let current_thread = std::thread::current();
481+
match current_thread.name() {
482+
Some(name) => {
483+
write!(writer, "{} ", FmtThreadName::new(name))?;
484+
}
485+
// fall-back to thread id when name is absent and ids are not enabled
486+
None if !self.display_thread_id => {
487+
write!(writer, "{:0>2?} ", current_thread.id())?;
488+
}
489+
_ => {}
490+
}
491+
}
492+
493+
if self.display_thread_id {
494+
write!(writer, "{:0>2?} ", std::thread::current().id())?;
495+
}
496+
427497
let fmt_ctx = {
428498
#[cfg(feature = "ansi")]
429499
{
@@ -761,6 +831,49 @@ impl Style {
761831
}
762832
}
763833

834+
struct FmtThreadName<'a> {
835+
name: &'a str,
836+
}
837+
838+
impl<'a> FmtThreadName<'a> {
839+
pub(crate) fn new(name: &'a str) -> Self {
840+
Self { name }
841+
}
842+
}
843+
844+
impl<'a> fmt::Display for FmtThreadName<'a> {
845+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
846+
use std::sync::atomic::{
847+
AtomicUsize,
848+
Ordering::{AcqRel, Acquire, Relaxed},
849+
};
850+
851+
// Track the longest thread name length we've seen so far in an atomic,
852+
// so that it can be updated by any thread.
853+
static MAX_LEN: AtomicUsize = AtomicUsize::new(0);
854+
let len = self.name.len();
855+
// Snapshot the current max thread name length.
856+
let mut max_len = MAX_LEN.load(Relaxed);
857+
858+
while len > max_len {
859+
// Try to set a new max length, if it is still the value we took a
860+
// snapshot of.
861+
match MAX_LEN.compare_exchange(max_len, len, AcqRel, Acquire) {
862+
// We successfully set the new max value
863+
Ok(_) => break,
864+
// Another thread set a new max value since we last observed
865+
// it! It's possible that the new length is actually longer than
866+
// ours, so we'll loop again and check whether our length is
867+
// still the longest. If not, we'll just use the newer value.
868+
Err(actual) => max_len = actual,
869+
}
870+
}
871+
872+
// pad thread name using `max_len`
873+
write!(f, "{:>width$}", self.name, width = max_len)
874+
}
875+
}
876+
764877
struct FmtLevel<'a> {
765878
level: &'a Level,
766879
#[cfg(feature = "ansi")]

tracing-subscriber/src/fmt/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,34 @@ where
542542
}
543543
}
544544

545+
/// Sets whether or not the [name] of the current thread is displayed
546+
/// when formatting events
547+
///
548+
/// [name]: https://doc.rust-lang.org/stable/std/thread/index.html#naming-threads
549+
pub fn with_thread_names(
550+
self,
551+
display_thread_names: bool,
552+
) -> SubscriberBuilder<N, format::Format<L, T>, F, W> {
553+
SubscriberBuilder {
554+
filter: self.filter,
555+
inner: self.inner.with_thread_names(display_thread_names),
556+
}
557+
}
558+
559+
/// Sets whether or not the [thread ID] of the current thread is displayed
560+
/// when formatting events
561+
///
562+
/// [thread ID]: https://doc.rust-lang.org/stable/std/thread/struct.ThreadId.html
563+
pub fn with_thread_ids(
564+
self,
565+
display_thread_ids: bool,
566+
) -> SubscriberBuilder<N, format::Format<L, T>, F, W> {
567+
SubscriberBuilder {
568+
filter: self.filter,
569+
inner: self.inner.with_thread_ids(display_thread_ids),
570+
}
571+
}
572+
545573
/// Sets the subscriber being built to use a less verbose formatter.
546574
///
547575
/// See [`format::Compact`](../fmt/format/struct.Compact.html).

0 commit comments

Comments
 (0)