use crate::event::{flatten_dynamic, Event, FlattenStyle};
use crate::pipeline;
use rhai::Dynamic;
pub(crate) fn escape_logfmt_string(input: &str) -> String {
let mut output = String::with_capacity(input.len() + 10);
for ch in input.chars() {
match ch {
'"' => output.push_str("\\\""),
'\\' => output.push_str("\\\\"),
'\n' => output.push_str("\\n"),
'\t' => output.push_str("\\t"),
'\r' => output.push_str("\\r"),
_ => output.push(ch),
}
}
output
}
pub(crate) fn needs_logfmt_quoting(value: &str) -> bool {
value.is_empty()
|| value.contains(' ')
|| value.contains('\t')
|| value.contains('\n')
|| value.contains('\r')
|| value.contains('"')
|| value.contains('=')
}
fn format_quoted_logfmt_value(value: &str, output: &mut String) {
if needs_logfmt_quoting(value) {
output.push('"');
output.push_str(&escape_logfmt_string(value));
output.push('"');
} else {
output.push_str(value);
}
}
pub(crate) fn sanitize_logfmt_key(key: &str) -> String {
key.chars()
.map(|c| match c {
' ' | '\t' | '\n' | '\r' | '=' => '_',
c => c,
})
.collect()
}
pub struct LogfmtFormatter;
impl LogfmtFormatter {
pub fn new() -> Self {
Self
}
fn format_dynamic_value_into(&self, value: &Dynamic, output: &mut String) {
let string_val = self.format_logfmt_value(value);
let is_string = value.is_string();
if is_string {
format_quoted_logfmt_value(&string_val, output);
} else {
output.push_str(&string_val);
}
}
fn format_logfmt_value(&self, value: &Dynamic) -> String {
if value.clone().try_cast::<rhai::Map>().is_some()
|| value.clone().try_cast::<rhai::Array>().is_some()
{
let flattened = flatten_dynamic(value, FlattenStyle::Underscore, 0);
if flattened.len() == 1 {
flattened.values().next().unwrap().to_string()
} else if flattened.is_empty() {
String::new()
} else {
flattened
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(",")
}
} else {
value.to_string()
}
}
}
impl pipeline::Formatter for LogfmtFormatter {
fn format(&self, event: &Event) -> String {
if event.fields.is_empty() {
return String::new();
}
let estimated_capacity = event.fields.len() * 32;
let mut output = String::with_capacity(estimated_capacity);
let mut first = true;
for (key, value) in crate::event::ordered_fields(event) {
if !first {
output.push(' ');
}
first = false;
let sanitized_key = sanitize_logfmt_key(key);
output.push_str(&sanitized_key);
output.push('=');
self.format_dynamic_value_into(value, &mut output);
}
output
}
}