Skip to main content

journal/
export.rs

1use super::Entry;
2
3pub fn export_entry_bytes(entry: &Entry) -> Vec<u8> {
4    let mut out = Vec::new();
5    write_export_field(&mut out, "__CURSOR", entry.cursor.as_bytes());
6    write_export_field(
7        &mut out,
8        "__REALTIME_TIMESTAMP",
9        entry.realtime.to_string().as_bytes(),
10    );
11    write_export_field(
12        &mut out,
13        "__MONOTONIC_TIMESTAMP",
14        entry.monotonic.to_string().as_bytes(),
15    );
16    write_export_field(&mut out, "_BOOT_ID", hex::encode(entry.boot_id).as_bytes());
17
18    let mut keys: Vec<_> = entry.field_values.keys().collect();
19    keys.sort();
20    for key in keys {
21        if key == "_BOOT_ID" {
22            continue;
23        }
24        if let Some(values) = entry.field_values.get(key) {
25            for value in values {
26                write_export_field(&mut out, key, value);
27            }
28        }
29    }
30    let mut byte_name_fields: Vec<_> = entry
31        .raw_fields()
32        .filter(|field| std::str::from_utf8(field.name).is_err() && field.name != b"_BOOT_ID")
33        .collect();
34    byte_name_fields.sort_by(|left, right| {
35        left.name
36            .cmp(right.name)
37            .then_with(|| left.value.cmp(right.value))
38    });
39    for field in byte_name_fields {
40        write_export_field_bytes(&mut out, field.name, field.value);
41    }
42    out.push(b'\n');
43    out
44}
45
46pub fn export_entry(entry: &Entry) -> String {
47    String::from_utf8_lossy(&export_entry_bytes(entry)).into_owned()
48}
49
50fn write_export_field(out: &mut Vec<u8>, name: &str, value: &[u8]) {
51    write_export_field_bytes(out, name.as_bytes(), value);
52}
53
54fn write_export_field_bytes(out: &mut Vec<u8>, name: &[u8], value: &[u8]) {
55    if value
56        .iter()
57        .all(|byte| *byte == b'\t' || (0x20..0x7f).contains(byte))
58    {
59        out.extend_from_slice(name);
60        out.push(b'=');
61        out.extend_from_slice(value);
62        out.push(b'\n');
63    } else {
64        out.extend_from_slice(name);
65        out.push(b'\n');
66        out.extend_from_slice(&(value.len() as u64).to_le_bytes());
67        out.extend_from_slice(value);
68        out.push(b'\n');
69    }
70}
71
72pub fn json_entry(entry: &Entry) -> serde_json::Value {
73    let mut map = serde_json::Map::new();
74    map.insert(
75        "__CURSOR".to_string(),
76        serde_json::Value::String(entry.cursor.clone()),
77    );
78    map.insert(
79        "__REALTIME_TIMESTAMP".to_string(),
80        serde_json::Value::String(entry.realtime.to_string()),
81    );
82    map.insert(
83        "__MONOTONIC_TIMESTAMP".to_string(),
84        serde_json::Value::String(entry.monotonic.to_string()),
85    );
86    map.insert(
87        "_BOOT_ID".to_string(),
88        serde_json::Value::String(hex::encode(entry.boot_id)),
89    );
90
91    let mut keys: Vec<_> = entry.field_values.keys().collect();
92    keys.sort();
93    for key in keys {
94        if key == "_BOOT_ID" {
95            continue;
96        }
97        let values = &entry.field_values[key];
98        let json_values: Vec<_> = values
99            .iter()
100            .map(|value| json_value_for_bytes(value))
101            .collect();
102        let value = if json_values.len() == 1 {
103            json_values.into_iter().next().unwrap()
104        } else {
105            serde_json::Value::Array(json_values)
106        };
107        map.insert(key.clone(), value);
108    }
109
110    serde_json::Value::Object(map)
111}
112
113fn json_value_for_bytes(value: &[u8]) -> serde_json::Value {
114    if json_bytes_printable(value) {
115        serde_json::Value::String(String::from_utf8_lossy(value).into_owned())
116    } else {
117        serde_json::Value::Array(
118            value
119                .iter()
120                .map(|byte| serde_json::Value::Number((*byte).into()))
121                .collect(),
122        )
123    }
124}
125
126fn json_bytes_printable(value: &[u8]) -> bool {
127    let Ok(text) = std::str::from_utf8(value) else {
128        return false;
129    };
130    for ch in text.chars() {
131        let cp = ch as u32;
132        if cp < 0x20 && ch != '\t' && ch != '\n' {
133            return false;
134        }
135        if (0x7f..=0x9f).contains(&cp) {
136            return false;
137        }
138    }
139    true
140}
141
142pub fn format_entry_text(entry: &Entry) -> Vec<u8> {
143    let mut out = Vec::new();
144    if let Some(message) = entry.get("MESSAGE") {
145        out.extend_from_slice(message);
146    }
147    out.push(b'\n');
148    out
149}