1use alloc::vec::Vec;
2use smallvec::SmallVec;
3use tracing_core::{Level, Metadata};
4
5use crate::record::{FieldValue, OwnedField, OwnedRecord, SpanSnapshot, Timestamp};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum OutputFormat {
10 Ndjson,
12 Binary,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct EncodeConfig {
19 pub include_current_span: bool,
21 pub include_span_list: bool,
23}
24
25impl Default for EncodeConfig {
26 fn default() -> Self {
27 Self {
28 include_current_span: true,
29 include_span_list: false,
30 }
31 }
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum CallsiteKind {
37 Event,
38 Span,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct CallsiteMetadata {
44 pub id: u32,
45 pub name: &'static str,
46 pub target: &'static str,
47 pub level: Level,
48 pub file: Option<&'static str>,
49 pub line: Option<u32>,
50 pub module_path: Option<&'static str>,
51 pub fields: SmallVec<[&'static str; 8]>,
52 pub kind: CallsiteKind,
53}
54
55impl CallsiteMetadata {
56 pub fn from_metadata(id: u32, metadata: &'static Metadata<'static>) -> Self {
58 let mut fields = SmallVec::new();
59 for field in metadata.fields().iter() {
60 fields.push(field.name());
61 }
62
63 Self {
64 id,
65 name: metadata.name(),
66 target: metadata.target(),
67 level: *metadata.level(),
68 file: metadata.file(),
69 line: metadata.line(),
70 module_path: metadata.module_path(),
71 fields,
72 kind: if metadata.is_span() {
73 CallsiteKind::Span
74 } else {
75 CallsiteKind::Event
76 },
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum BinaryFrameKind {
84 Metadata = 1,
85 Event = 2,
86}
87
88const KEY_TIMESTAMP_UNIX_NS: &[u8] = br#""timestamp_unix_ns":"#;
89const KEY_LEVEL: &[u8] = br#""level":"#;
90const KEY_TARGET: &[u8] = br#""target":"#;
91const KEY_NAME: &[u8] = br#""name":"#;
92const KEY_METADATA_ID: &[u8] = br#""metadata_id":"#;
93const KEY_FIELDS: &[u8] = br#""fields":"#;
94const KEY_SPAN: &[u8] = br#""span":"#;
95const KEY_SPANS: &[u8] = br#""spans":"#;
96const KEY_ID: &[u8] = br#""id":"#;
97
98pub fn encode_ndjson_record(config: EncodeConfig, record: &OwnedRecord, out: &mut Vec<u8>) {
100 out.clear();
101 out.reserve(estimate_ndjson_record_len(config, record));
102 out.push(b'{');
103 out.extend_from_slice(KEY_TIMESTAMP_UNIX_NS);
104 push_timestamp_unix_nanos(out, record.timestamp);
105 out.push(b',');
106 out.extend_from_slice(KEY_LEVEL);
107 push_json_string(out, level_str(record.level));
108 out.push(b',');
109 out.extend_from_slice(KEY_TARGET);
110 push_json_string(out, record.target);
111 out.push(b',');
112 out.extend_from_slice(KEY_NAME);
113 push_json_string(out, record.name);
114 out.push(b',');
115 out.extend_from_slice(KEY_METADATA_ID);
116 push_json_u32(out, record.metadata_id);
117 out.push(b',');
118 out.extend_from_slice(KEY_FIELDS);
119 push_fields_object(out, &record.fields);
120
121 if config.include_current_span
122 && let Some(span) = &record.current_span
123 {
124 out.push(b',');
125 out.extend_from_slice(KEY_SPAN);
126 push_span(out, span);
127 }
128
129 if config.include_span_list {
130 out.push(b',');
131 out.extend_from_slice(KEY_SPANS);
132 push_spans(out, &record.spans);
133 }
134
135 out.extend_from_slice(b"}\n");
136}
137
138pub fn encode_binary_metadata(metadata: &CallsiteMetadata, out: &mut Vec<u8>) {
140 out.clear();
141 out.push(BinaryFrameKind::Metadata as u8);
142 let len_offset = reserve_length(out);
143 push_u32_le(out, metadata.id);
144 out.push(match metadata.kind {
145 CallsiteKind::Event => 1,
146 CallsiteKind::Span => 2,
147 });
148 out.push(level_code(metadata.level));
149 push_optional_string(out, metadata.file);
150 push_optional_u32(out, metadata.line);
151 push_optional_string(out, metadata.module_path);
152 push_string(out, metadata.name);
153 push_string(out, metadata.target);
154 push_u16_le(out, metadata.fields.len() as u16);
155 for field in &metadata.fields {
156 push_string(out, field);
157 }
158 write_length(out, len_offset);
159}
160
161pub fn encode_binary_record(config: EncodeConfig, record: &OwnedRecord, out: &mut Vec<u8>) {
163 out.clear();
164 out.push(BinaryFrameKind::Event as u8);
165 let len_offset = reserve_length(out);
166 push_u64_le(out, record.timestamp.unix_seconds);
167 push_u32_le(out, record.timestamp.subsec_nanos);
168 push_u32_le(out, record.metadata_id);
169 push_u64_le(
170 out,
171 record
172 .current_span
173 .as_ref()
174 .map(|span| span.id)
175 .unwrap_or_default(),
176 );
177 push_fields(out, &record.fields);
178
179 if config.include_current_span {
180 match &record.current_span {
181 Some(span) => {
182 out.push(1);
183 push_span_binary(out, span);
184 }
185 None => out.push(0),
186 }
187 } else {
188 out.push(0);
189 }
190
191 if config.include_span_list {
192 push_u16_le(out, record.spans.len() as u16);
193 for span in &record.spans {
194 push_span_binary(out, span);
195 }
196 } else {
197 push_u16_le(out, 0);
198 }
199
200 write_length(out, len_offset);
201}
202
203fn push_span(out: &mut Vec<u8>, span: &SpanSnapshot) {
204 out.push(b'{');
205 out.extend_from_slice(KEY_ID);
206 push_json_u64(out, span.id);
207 out.push(b',');
208 out.extend_from_slice(KEY_METADATA_ID);
209 push_json_u32(out, span.metadata_id);
210 out.push(b',');
211 out.extend_from_slice(KEY_NAME);
212 push_json_string(out, span.name);
213 out.push(b',');
214 out.extend_from_slice(KEY_TARGET);
215 push_json_string(out, span.target);
216 out.push(b',');
217 out.extend_from_slice(KEY_LEVEL);
218 push_json_string(out, level_str(span.level));
219 out.push(b',');
220 out.extend_from_slice(KEY_FIELDS);
221 push_fields_object(out, &span.fields);
222 out.push(b'}');
223}
224
225fn push_spans(out: &mut Vec<u8>, spans: &[SpanSnapshot]) {
226 out.push(b'[');
227 for (index, span) in spans.iter().enumerate() {
228 if index > 0 {
229 out.push(b',');
230 }
231 push_span(out, span);
232 }
233 out.push(b']');
234}
235
236fn push_fields_object(out: &mut Vec<u8>, fields: &[OwnedField]) {
237 out.push(b'{');
238 for (index, field) in fields.iter().enumerate() {
239 if index > 0 {
240 out.push(b',');
241 }
242 push_json_key(out, field.name);
243 push_field_value(out, &field.value);
244 }
245 out.push(b'}');
246}
247
248fn push_field_value(out: &mut Vec<u8>, value: &FieldValue) {
249 match value {
250 FieldValue::Bool(value) => push_bool(out, *value),
251 FieldValue::I64(value) => push_i64(out, *value),
252 FieldValue::U64(value) => push_json_u64(out, *value),
253 FieldValue::I128(value) => push_i128(out, *value),
254 FieldValue::U128(value) => push_u128(out, *value),
255 FieldValue::F64(value) => push_f64(out, *value),
256 FieldValue::Str(value) | FieldValue::Debug(value) => push_json_string(out, value.as_str()),
257 FieldValue::Bytes(value) => push_json_hex_bytes(out, value),
258 }
259}
260
261fn push_json_key(out: &mut Vec<u8>, key: &str) {
262 push_json_string(out, key);
263 out.push(b':');
264}
265
266fn push_json_string(out: &mut Vec<u8>, value: &str) {
267 out.push(b'"');
268 let bytes = value.as_bytes();
269 if bytes.iter().all(|byte| !needs_json_escape(*byte)) {
270 out.extend_from_slice(bytes);
271 out.push(b'"');
272 return;
273 }
274
275 let mut start = 0;
276 for (index, byte) in bytes.iter().copied().enumerate() {
277 if !needs_json_escape(byte) {
278 continue;
279 }
280
281 if start < index {
282 out.extend_from_slice(&bytes[start..index]);
283 }
284
285 match byte {
286 b'"' => out.extend_from_slice(br#"\""#),
287 b'\\' => out.extend_from_slice(br#"\\"#),
288 b'\n' => out.extend_from_slice(br#"\n"#),
289 b'\r' => out.extend_from_slice(br#"\r"#),
290 b'\t' => out.extend_from_slice(br#"\t"#),
291 0x00..=0x1f => {
292 out.extend_from_slice(br#"\u00"#);
293 let high = byte >> 4;
294 let low = byte & 0x0f;
295 out.push(hex_digit(high));
296 out.push(hex_digit(low));
297 }
298 _ => out.push(byte),
299 }
300 start = index + 1;
301 }
302 if start < bytes.len() {
303 out.extend_from_slice(&bytes[start..]);
304 }
305 out.push(b'"');
306}
307
308fn push_json_hex_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
309 out.push(b'"');
310 for byte in bytes {
311 out.push(hex_digit(byte >> 4));
312 out.push(hex_digit(byte & 0x0f));
313 }
314 out.push(b'"');
315}
316
317fn hex_digit(value: u8) -> u8 {
318 match value {
319 0..=9 => b'0' + value,
320 _ => b'a' + (value - 10),
321 }
322}
323
324fn push_bool(out: &mut Vec<u8>, value: bool) {
325 out.extend_from_slice(if value { b"true" } else { b"false" });
326}
327
328fn push_i64(out: &mut Vec<u8>, value: i64) {
329 let mut buffer = itoa::Buffer::new();
330 out.extend_from_slice(buffer.format(value).as_bytes());
331}
332
333fn push_i128(out: &mut Vec<u8>, value: i128) {
334 let mut buffer = itoa::Buffer::new();
335 out.extend_from_slice(buffer.format(value).as_bytes());
336}
337
338fn push_json_u32(out: &mut Vec<u8>, value: u32) {
339 let mut buffer = itoa::Buffer::new();
340 out.extend_from_slice(buffer.format(value).as_bytes());
341}
342
343fn push_json_u64(out: &mut Vec<u8>, value: u64) {
344 let mut buffer = itoa::Buffer::new();
345 out.extend_from_slice(buffer.format(value).as_bytes());
346}
347
348fn push_u128(out: &mut Vec<u8>, value: u128) {
349 let mut buffer = itoa::Buffer::new();
350 out.extend_from_slice(buffer.format(value).as_bytes());
351}
352
353fn push_f64(out: &mut Vec<u8>, value: f64) {
354 let mut buffer = ryu::Buffer::new();
355 out.extend_from_slice(buffer.format(value).as_bytes());
356}
357
358fn push_timestamp_unix_nanos(out: &mut Vec<u8>, timestamp: Timestamp) {
359 push_json_u64(out, timestamp.unix_seconds);
360
361 let mut nanos = timestamp.subsec_nanos;
362 let mut digits = [b'0'; 9];
363 for index in (0..9).rev() {
364 digits[index] = b'0' + (nanos % 10) as u8;
365 nanos /= 10;
366 }
367 out.extend_from_slice(&digits);
368}
369
370fn level_str(level: Level) -> &'static str {
371 match level {
372 Level::ERROR => "ERROR",
373 Level::WARN => "WARN",
374 Level::INFO => "INFO",
375 Level::DEBUG => "DEBUG",
376 Level::TRACE => "TRACE",
377 }
378}
379
380fn level_code(level: Level) -> u8 {
381 match level {
382 Level::ERROR => 1,
383 Level::WARN => 2,
384 Level::INFO => 3,
385 Level::DEBUG => 4,
386 Level::TRACE => 5,
387 }
388}
389
390fn reserve_length(out: &mut Vec<u8>) -> usize {
391 let offset = out.len();
392 out.extend_from_slice(&0u32.to_le_bytes());
393 offset
394}
395
396fn write_length(out: &mut [u8], len_offset: usize) {
397 let len = (out.len() - len_offset - 4) as u32;
398 out[len_offset..len_offset + 4].copy_from_slice(&len.to_le_bytes());
399}
400
401fn push_optional_string(out: &mut Vec<u8>, value: Option<&str>) {
402 match value {
403 Some(value) => {
404 out.push(1);
405 push_string(out, value);
406 }
407 None => out.push(0),
408 }
409}
410
411fn push_optional_u32(out: &mut Vec<u8>, value: Option<u32>) {
412 match value {
413 Some(value) => {
414 out.push(1);
415 push_u32_le(out, value);
416 }
417 None => out.push(0),
418 }
419}
420
421fn push_string(out: &mut Vec<u8>, value: &str) {
422 push_u32_le(out, value.len() as u32);
423 out.extend_from_slice(value.as_bytes());
424}
425
426fn push_fields(out: &mut Vec<u8>, fields: &[OwnedField]) {
427 push_u16_le(out, fields.len() as u16);
428 for field in fields {
429 push_string(out, field.name);
430 push_field_binary(out, &field.value);
431 }
432}
433
434fn push_span_binary(out: &mut Vec<u8>, span: &SpanSnapshot) {
435 push_u64_le(out, span.id);
436 push_u32_le(out, span.metadata_id);
437 out.push(level_code(span.level));
438 push_string(out, span.name);
439 push_string(out, span.target);
440 push_fields(out, &span.fields);
441}
442
443fn push_field_binary(out: &mut Vec<u8>, value: &FieldValue) {
444 match value {
445 FieldValue::Bool(false) => out.push(1),
446 FieldValue::Bool(true) => out.push(2),
447 FieldValue::I64(value) => {
448 out.push(3);
449 out.extend_from_slice(&value.to_le_bytes());
450 }
451 FieldValue::U64(value) => {
452 out.push(4);
453 out.extend_from_slice(&value.to_le_bytes());
454 }
455 FieldValue::I128(value) => {
456 out.push(5);
457 out.extend_from_slice(&value.to_le_bytes());
458 }
459 FieldValue::U128(value) => {
460 out.push(6);
461 out.extend_from_slice(&value.to_le_bytes());
462 }
463 FieldValue::F64(value) => {
464 out.push(7);
465 out.extend_from_slice(&value.to_le_bytes());
466 }
467 FieldValue::Str(value) => {
468 out.push(8);
469 push_string(out, value.as_str());
470 }
471 FieldValue::Debug(value) => {
472 out.push(9);
473 push_string(out, value.as_str());
474 }
475 FieldValue::Bytes(value) => {
476 out.push(10);
477 push_u32_le(out, value.len() as u32);
478 out.extend_from_slice(value);
479 }
480 }
481}
482
483fn push_u16_le(out: &mut Vec<u8>, value: u16) {
484 out.extend_from_slice(&value.to_le_bytes());
485}
486
487fn push_u32_le(out: &mut Vec<u8>, value: u32) {
488 out.extend_from_slice(&value.to_le_bytes());
489}
490
491fn push_u64_le(out: &mut Vec<u8>, value: u64) {
492 out.extend_from_slice(&value.to_le_bytes());
493}
494
495fn estimate_ndjson_record_len(config: EncodeConfig, record: &OwnedRecord) -> usize {
496 let mut estimate = 96 + record.target.len() + record.name.len();
497 estimate += estimate_fields_len(&record.fields);
498
499 if config.include_current_span
500 && let Some(span) = &record.current_span
501 {
502 estimate += 64 + span.target.len() + span.name.len();
503 estimate += estimate_fields_len(&span.fields);
504 }
505
506 if config.include_span_list {
507 for span in &record.spans {
508 estimate += 64 + span.target.len() + span.name.len();
509 estimate += estimate_fields_len(&span.fields);
510 }
511 }
512
513 estimate
514}
515
516fn estimate_fields_len(fields: &[OwnedField]) -> usize {
517 fields
518 .iter()
519 .map(|field| field.name.len() + estimate_field_value_len(&field.value) + 8)
520 .sum()
521}
522
523fn estimate_field_value_len(value: &FieldValue) -> usize {
524 match value {
525 FieldValue::Bool(_) => 5,
526 FieldValue::I64(_) | FieldValue::U64(_) => 24,
527 FieldValue::I128(_) | FieldValue::U128(_) => 40,
528 FieldValue::F64(_) => 32,
529 FieldValue::Str(value) | FieldValue::Debug(value) => value.as_str().len() + 2,
530 FieldValue::Bytes(value) => value.len() * 2 + 2,
531 }
532}
533
534fn needs_json_escape(byte: u8) -> bool {
535 matches!(byte, b'"' | b'\\' | b'\n' | b'\r' | b'\t' | 0x00..=0x1f)
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use crate::record::{FieldValue, OwnedField, OwnedRecord, Timestamp};
542 use alloc::string::String;
543 use smallvec::smallvec;
544
545 #[test]
546 fn ndjson_escapes_control_characters() {
547 let record = OwnedRecord {
548 timestamp: Timestamp::new(1, 2),
549 metadata_id: 7,
550 name: "event",
551 target: "app",
552 level: Level::INFO,
553 fields: smallvec![OwnedField {
554 name: "message",
555 value: FieldValue::Str("line\nbreak".into()),
556 }],
557 current_span: None,
558 spans: SmallVec::new(),
559 };
560
561 let mut out = Vec::new();
562 encode_ndjson_record(EncodeConfig::default(), &record, &mut out);
563
564 let rendered = String::from_utf8(out).unwrap();
565 assert!(rendered.contains(r#""message":"line\nbreak""#));
566 assert!(rendered.ends_with('\n'));
567 }
568
569 #[test]
570 fn binary_frame_prefixes_length() {
571 let mut fields = SmallVec::new();
572 fields.push("message");
573 let metadata = CallsiteMetadata {
574 id: 1,
575 name: "event",
576 target: "app",
577 level: Level::INFO,
578 file: None,
579 line: None,
580 module_path: None,
581 fields,
582 kind: CallsiteKind::Event,
583 };
584
585 let mut out = Vec::new();
586 encode_binary_metadata(&metadata, &mut out);
587 assert_eq!(out[0], BinaryFrameKind::Metadata as u8);
588 let length = u32::from_le_bytes([out[1], out[2], out[3], out[4]]) as usize;
589 assert_eq!(length + 5, out.len());
590 }
591}