1use std::fmt;
8use std::fs::{File, OpenOptions};
9use std::io::Write;
10use std::path::Path;
11use std::sync::Mutex;
12
13use rustev_contract::trace::{TraceLine, TraceLineError};
14
15pub const DEFAULT_MAX_BYTES: u64 = 256 * 1024 * 1024;
17
18#[derive(Debug, Clone, PartialEq)]
20pub enum JsonlError {
21 FileFull,
23 Io(String),
25 Line(TraceLineError),
27}
28
29impl fmt::Display for JsonlError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 JsonlError::FileFull => f.write_str("trace file reached its size bound"),
33 JsonlError::Io(e) => write!(f, "trace file: {e}"),
34 JsonlError::Line(e) => write!(f, "{e}"),
35 }
36 }
37}
38
39impl std::error::Error for JsonlError {}
40
41pub fn encode(lines: &[TraceLine]) -> Result<Vec<u8>, TraceLineError> {
43 let mut out = Vec::new();
44 for l in lines {
45 out.extend_from_slice(&l.to_line()?);
46 out.push(b'\n');
47 }
48 Ok(out)
49}
50
51struct State {
52 file: File,
53 written: u64,
54}
55
56pub struct JsonlExporter {
58 state: Mutex<State>,
59 max_bytes: u64,
60}
61
62impl fmt::Debug for JsonlExporter {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.debug_struct("JsonlExporter")
65 .field("max_bytes", &self.max_bytes)
66 .finish_non_exhaustive()
67 }
68}
69
70impl JsonlExporter {
71 pub fn open(path: &Path, max_bytes: u64) -> Result<Self, JsonlError> {
74 let mut o = OpenOptions::new();
75 o.append(true).create(true);
76 #[cfg(unix)]
77 {
78 use std::os::unix::fs::OpenOptionsExt;
79 o.mode(0o600);
80 }
81 let file = o.open(path).map_err(|e| JsonlError::Io(e.to_string()))?;
82 let written = file
83 .metadata()
84 .map_err(|e| JsonlError::Io(e.to_string()))?
85 .len();
86 Ok(JsonlExporter {
87 state: Mutex::new(State { file, written }),
88 max_bytes,
89 })
90 }
91
92 pub fn export(&self, lines: &[TraceLine]) -> Result<u64, JsonlError> {
94 let bytes = encode(lines).map_err(JsonlError::Line)?;
95 let mut s = self.state.lock().unwrap_or_else(|e| e.into_inner());
96 let len = bytes.len() as u64;
97 if s.written.saturating_add(len) > self.max_bytes {
98 return Err(JsonlError::FileFull);
99 }
100 s.file
101 .write_all(&bytes)
102 .and_then(|()| s.file.flush())
103 .map_err(|e| JsonlError::Io(e.to_string()))?;
104 s.written += len;
105 Ok(len)
106 }
107}