Skip to content

Commit 690a9a6

Browse files
mladedavhawkw
andauthored
tracing: allow &[u8] to be recorded as event/span field (#2954)
## Motivation Users may want to pass data to `Subscribe`s which is not valid UTF-8. Currently, it would have to be encoded into `&str` to be passed as a field value. ## Solution This branch adds a `record_bytes` method to `Visit`. It has an implementation falling back to `record_debug` so it is not be a breaking change. `JsonVisitor` got an overridden implementation as it should not use `Debug` output and encode it as a string, but rather store the bytes as an array. `PrettyVisitor` go an overridden implementation to make the output more pretty. Co-authored-by: Eliza Weisman <[email protected]>
1 parent 2c5a1f0 commit 690a9a6

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

tracing-core/src/field.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
use crate::callsite;
3939
use core::{
4040
borrow::Borrow,
41-
fmt,
41+
fmt::{self, Write},
4242
hash::{Hash, Hasher},
4343
num,
4444
ops::Range,
@@ -224,6 +224,11 @@ pub trait Visit {
224224
self.record_debug(field, &value)
225225
}
226226

227+
/// Visit a byte slice.
228+
fn record_bytes(&mut self, field: &Field, value: &[u8]) {
229+
self.record_debug(field, &HexBytes(value))
230+
}
231+
227232
/// Records a type implementing `Error`.
228233
///
229234
/// <div class="example-wrap" style="display:inline-block">
@@ -283,6 +288,26 @@ where
283288
DebugValue(t)
284289
}
285290

291+
struct HexBytes<'a>(&'a [u8]);
292+
293+
impl<'a> fmt::Debug for HexBytes<'a> {
294+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295+
f.write_char('[')?;
296+
297+
let mut bytes = self.0.iter();
298+
299+
if let Some(byte) = bytes.next() {
300+
f.write_fmt(format_args!("{byte:02x}"))?;
301+
}
302+
303+
for byte in bytes {
304+
f.write_fmt(format_args!(" {byte:02x}"))?;
305+
}
306+
307+
f.write_char(']')
308+
}
309+
}
310+
286311
// ===== impl Visit =====
287312

288313
impl<'a, 'b> Visit for fmt::DebugStruct<'a, 'b> {
@@ -443,6 +468,14 @@ impl Value for str {
443468
}
444469
}
445470

471+
impl crate::sealed::Sealed for [u8] {}
472+
473+
impl Value for [u8] {
474+
fn record(&self, key: &Field, visitor: &mut dyn Visit) {
475+
visitor.record_bytes(key, self)
476+
}
477+
}
478+
446479
#[cfg(feature = "std")]
447480
impl crate::sealed::Sealed for dyn std::error::Error + 'static {}
448481

@@ -1131,4 +1164,23 @@ mod test {
11311164
});
11321165
assert_eq!(result, format!("{}", err));
11331166
}
1167+
1168+
#[test]
1169+
fn record_bytes() {
1170+
let fields = TEST_META_1.fields();
1171+
let first = &b"abc"[..];
1172+
let second: &[u8] = &[192, 255, 238];
1173+
let values = &[
1174+
(&fields.field("foo").unwrap(), Some(&first as &dyn Value)),
1175+
(&fields.field("bar").unwrap(), Some(&" " as &dyn Value)),
1176+
(&fields.field("baz").unwrap(), Some(&second as &dyn Value)),
1177+
];
1178+
let valueset = fields.value_set(values);
1179+
let mut result = String::new();
1180+
valueset.record(&mut |_: &Field, value: &dyn fmt::Debug| {
1181+
use core::fmt::Write;
1182+
write!(&mut result, "{:?}", value).unwrap();
1183+
});
1184+
assert_eq!(result, format!("{}", r#"[61 62 63]" "[c0 ff ee]"#));
1185+
}
11341186
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,11 @@ impl<'a> field::Visit for JsonVisitor<'a> {
488488
.insert(field.name(), serde_json::Value::from(value));
489489
}
490490

491+
fn record_bytes(&mut self, field: &Field, value: &[u8]) {
492+
self.values
493+
.insert(field.name(), serde_json::Value::from(value));
494+
}
495+
491496
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
492497
match field.name() {
493498
// Skip fields that are actually log metadata that have already been handled
@@ -528,13 +533,19 @@ mod test {
528533
#[test]
529534
fn json() {
530535
let expected =
531-
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
536+
"{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3,\"slice\":[97,98,99]},\"spans\":[{\"answer\":42,\"name\":\"json_span\",\"number\":3,\"slice\":[97,98,99]}],\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n";
532537
let collector = collector()
533538
.flatten_event(false)
534539
.with_current_span(true)
535540
.with_span_list(true);
536541
test_json(expected, collector, || {
537-
let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
542+
let span = tracing::span!(
543+
tracing::Level::INFO,
544+
"json_span",
545+
answer = 42,
546+
number = 3,
547+
slice = &b"abc"[..]
548+
);
538549
let _guard = span.enter();
539550
tracing::info!("some json test");
540551
});

0 commit comments

Comments
 (0)