diff --git a/tracing-core/src/field.rs b/tracing-core/src/field.rs
index 663a98b49..fbaa50dec 100644
--- a/tracing-core/src/field.rs
+++ b/tracing-core/src/field.rs
@@ -38,7 +38,7 @@
use crate::callsite;
use core::{
borrow::Borrow,
- fmt,
+ fmt::{self, Write},
hash::{Hash, Hasher},
num,
ops::Range,
@@ -224,6 +224,11 @@ pub trait Visit {
self.record_debug(field, &value)
}
+ /// Visit a byte slice.
+ fn record_bytes(&mut self, field: &Field, value: &[u8]) {
+ self.record_debug(field, &HexBytes(value))
+ }
+
/// Records a type implementing `Error`.
///
///
@@ -283,6 +288,26 @@ where
DebugValue(t)
}
+struct HexBytes<'a>(&'a [u8]);
+
+impl<'a> fmt::Debug for HexBytes<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_char('[')?;
+
+ let mut bytes = self.0.iter();
+
+ if let Some(byte) = bytes.next() {
+ f.write_fmt(format_args!("{byte:02x}"))?;
+ }
+
+ for byte in bytes {
+ f.write_fmt(format_args!(" {byte:02x}"))?;
+ }
+
+ f.write_char(']')
+ }
+}
+
// ===== impl Visit =====
impl<'a, 'b> Visit for fmt::DebugStruct<'a, 'b> {
@@ -443,6 +468,14 @@ impl Value for str {
}
}
+impl crate::sealed::Sealed for [u8] {}
+
+impl Value for [u8] {
+ fn record(&self, key: &Field, visitor: &mut dyn Visit) {
+ visitor.record_bytes(key, self)
+ }
+}
+
#[cfg(feature = "std")]
impl crate::sealed::Sealed for dyn std::error::Error + 'static {}
@@ -1131,4 +1164,23 @@ mod test {
});
assert_eq!(result, format!("{}", err));
}
+
+ #[test]
+ fn record_bytes() {
+ let fields = TEST_META_1.fields();
+ let first = &b"abc"[..];
+ let second: &[u8] = &[192, 255, 238];
+ let values = &[
+ (&fields.field("foo").unwrap(), Some(&first as &dyn Value)),
+ (&fields.field("bar").unwrap(), Some(&" " as &dyn Value)),
+ (&fields.field("baz").unwrap(), Some(&second as &dyn Value)),
+ ];
+ let valueset = fields.value_set(values);
+ let mut result = String::new();
+ valueset.record(&mut |_: &Field, value: &dyn fmt::Debug| {
+ use core::fmt::Write;
+ write!(&mut result, "{:?}", value).unwrap();
+ });
+ assert_eq!(result, format!("{}", r#"[61 62 63]" "[c0 ff ee]"#));
+ }
}
diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs
index f4e61fb12..1f045d93b 100644
--- a/tracing-subscriber/src/fmt/format/json.rs
+++ b/tracing-subscriber/src/fmt/format/json.rs
@@ -488,6 +488,11 @@ impl<'a> field::Visit for JsonVisitor<'a> {
.insert(field.name(), serde_json::Value::from(value));
}
+ fn record_bytes(&mut self, field: &Field, value: &[u8]) {
+ self.values
+ .insert(field.name(), serde_json::Value::from(value));
+ }
+
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
match field.name() {
// Skip fields that are actually log metadata that have already been handled
@@ -528,13 +533,19 @@ mod test {
#[test]
fn json() {
let expected =
- "{\"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";
+ "{\"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";
let collector = collector()
.flatten_event(false)
.with_current_span(true)
.with_span_list(true);
test_json(expected, collector, || {
- let span = tracing::span!(tracing::Level::INFO, "json_span", answer = 42, number = 3);
+ let span = tracing::span!(
+ tracing::Level::INFO,
+ "json_span",
+ answer = 42,
+ number = 3,
+ slice = &b"abc"[..]
+ );
let _guard = span.enter();
tracing::info!("some json test");
});