use std::fmt;
use tracing_core::field::{Field, Visit};
pub(crate) struct FieldWriter<'a> {
out: &'a mut String,
first: bool,
}
impl<'a> FieldWriter<'a> {
pub(crate) fn new(out: &'a mut String) -> Self {
Self { out, first: true }
}
pub(crate) fn appending(out: &'a mut String) -> Self {
let first = out.is_empty();
Self { out, first }
}
fn key(&mut self, field: &Field) {
if !self.first {
self.out.push(',');
}
self.first = false;
write_string(self.out, field.name());
self.out.push(':');
}
}
impl Visit for FieldWriter<'_> {
fn record_str(&mut self, field: &Field, value: &str) {
self.key(field);
write_string(self.out, value);
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.key(field);
self.out.push_str(if value { "true" } else { "false" });
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.key(field);
self.out.push_str(itoa(value as i128).as_str());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.key(field);
self.out.push_str(itoa(value as i128).as_str());
}
fn record_i128(&mut self, field: &Field, value: i128) {
self.key(field);
self.out.push_str(itoa(value).as_str());
}
fn record_u128(&mut self, field: &Field, value: u128) {
self.key(field);
self.out.push_str(&value.to_string());
}
fn record_f64(&mut self, field: &Field, value: f64) {
self.key(field);
if value.is_finite() {
let mut buffer = ryu_like(value);
self.out.push_str(buffer.as_mut_str());
} else {
self.out.push_str("null");
}
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
let mut rendered = value.to_string();
let mut source = value.source();
while let Some(error) = source {
rendered.push_str(": ");
rendered.push_str(&error.to_string());
source = error.source();
}
self.key(field);
write_string(self.out, &rendered);
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.key(field);
write_string(self.out, &format!("{value:?}"));
}
}
fn itoa(value: i128) -> String {
value.to_string()
}
fn ryu_like(value: f64) -> String {
if value == value.trunc() && value.abs() < 1e16 {
format!("{value:.1}")
} else {
format!("{value}")
}
}
pub(crate) fn write_string(out: &mut String, value: &str) {
out.push('"');
let mut start = 0;
for (index, byte) in value.bytes().enumerate() {
let escape = match byte {
b'"' => "\\\"",
b'\\' => "\\\\",
0x08 => "\\b",
0x09 => "\\t",
0x0A => "\\n",
0x0C => "\\f",
0x0D => "\\r",
0x00..=0x1F => {
out.push_str(&value[start..index]);
const HEX: &[u8; 16] = b"0123456789abcdef";
out.push_str("\\u00");
out.push(HEX[usize::from(byte >> 4)] as char);
out.push(HEX[usize::from(byte & 0xF)] as char);
start = index + 1;
continue;
}
_ => continue,
};
out.push_str(&value[start..index]);
out.push_str(escape);
start = index + 1;
}
out.push_str(&value[start..]);
out.push('"');
}
#[cfg(test)]
mod tests {
use super::*;
fn escaped(value: &str) -> String {
let mut out = String::new();
write_string(&mut out, value);
out
}
#[test]
fn strings_escape_exactly_as_serde_json_does() {
for case in [
"",
"plain",
"quote\" and back\\slash",
"tab\tnewline\ncr\rbs\u{8}ff\u{c}",
"\u{1}\u{1f} control and \u{7f} del",
"unicode Γ©δΈπ kept",
"slash / not escaped",
] {
assert_eq!(
escaped(case),
serde_json::to_string(case).unwrap(),
"{case:?}"
);
}
}
#[test]
fn floats_stay_floats() {
assert_eq!(ryu_like(1.0), "1.0");
assert_eq!(ryu_like(166.6), "166.6");
assert_eq!(ryu_like(-0.5), "-0.5");
}
}