|
1 | | -use tracing_subscriber::fmt::format::FmtSpan; |
| 1 | +use std::{ |
| 2 | + io::Write, |
| 3 | + sync::{Arc, Mutex}, |
| 4 | +}; |
| 5 | + |
| 6 | +use tracing_subscriber::fmt::{MakeWriter, format::FmtSpan}; |
2 | 7 |
|
3 | 8 | use crate::{ |
4 | | - TraceWriter, |
| 9 | + TraceEvent, |
5 | 10 | tracer::{Layered, Tracer}, |
6 | 11 | }; |
7 | 12 |
|
8 | | -pub struct StdoutTracer; |
| 13 | +// A custom MakeWriter that wraps a shared Arc<Mutex<>> writer |
| 14 | +#[derive(Clone)] |
| 15 | +struct SharedWriterMaker { |
| 16 | + writer: Arc<Mutex<dyn Write + Send>>, |
| 17 | +} |
| 18 | + |
| 19 | +// Wrapper to implement Write trait for the MakeWriter |
| 20 | +struct SharedWriter { |
| 21 | + writer: Arc<Mutex<dyn Write + Send>>, |
| 22 | +} |
| 23 | + |
| 24 | +impl Write for SharedWriter { |
| 25 | + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { |
| 26 | + self |
| 27 | + .writer |
| 28 | + .lock() |
| 29 | + .expect("Failed to lock writer") |
| 30 | + .write(buf) |
| 31 | + } |
| 32 | + |
| 33 | + fn flush(&mut self) -> std::io::Result<()> { |
| 34 | + self.writer.lock().expect("Failed to lock writer").flush() |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +impl<'a> MakeWriter<'a> for SharedWriterMaker { |
| 39 | + type Writer = SharedWriter; |
| 40 | + |
| 41 | + fn make_writer(&'a self) -> Self::Writer { |
| 42 | + SharedWriter { |
| 43 | + writer: self.writer.clone(), |
| 44 | + } |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +/// Converts a microsecond timestamp to ISO 8601 format with microsecond precision |
| 49 | +/// Example: 1704708707916028 -> "2026-01-08T11:31:47.916028Z" |
| 50 | +fn format_timestamp_iso8601(micros: u64) -> String { |
| 51 | + use chrono::{DateTime, Utc}; |
| 52 | + |
| 53 | + let secs = (micros / 1_000_000) as i64; |
| 54 | + let subsec_micros = (micros % 1_000_000) as u32; |
| 55 | + let nanos = subsec_micros * 1000; |
| 56 | + |
| 57 | + DateTime::<Utc>::from_timestamp(secs, nanos) |
| 58 | + .map(|dt| dt.to_rfc3339_opts(chrono::SecondsFormat::Micros, true)) |
| 59 | + .unwrap_or_else(|| "Invalid timestamp".to_string()) |
| 60 | +} |
| 61 | + |
| 62 | +#[derive(Default)] |
| 63 | +pub struct StdoutTracer { |
| 64 | + begin_ts: u64, |
| 65 | + writer: Option<Arc<Mutex<dyn Write + Send>>>, |
| 66 | +} |
9 | 67 |
|
10 | 68 | impl Tracer for StdoutTracer { |
11 | 69 | fn setup(&mut self, output: &str) -> Option<Layered> { |
12 | 70 | use tracing_subscriber::{fmt, prelude::*}; |
13 | | - let trace_writer = TraceWriter::from(output.to_owned()); |
| 71 | + |
| 72 | + // Record the start time in microseconds since UNIX epoch |
| 73 | + self.begin_ts = std::time::SystemTime::now() |
| 74 | + .duration_since(std::time::UNIX_EPOCH) |
| 75 | + .expect("System time before UNIX epoch") |
| 76 | + .as_micros() as u64; |
| 77 | + |
| 78 | + // Create the shared writer wrapped in Arc<Mutex<>> |
| 79 | + let writer: Arc<Mutex<Box<dyn Write + Send>>> = match output { |
| 80 | + "stdout" => Arc::new(Mutex::new(Box::new(std::io::stdout()))), |
| 81 | + "stderr" => Arc::new(Mutex::new(Box::new(std::io::stderr()))), |
| 82 | + path => { |
| 83 | + let file = std::fs::File::create(path) |
| 84 | + .unwrap_or_else(|e| panic!("Failed to create trace file: {path} due to {e}")); |
| 85 | + Arc::new(Mutex::new(Box::new(file))) |
| 86 | + } |
| 87 | + }; |
| 88 | + |
| 89 | + // Store the shared writer for sync_trace |
| 90 | + self.writer = Some(writer.clone()); |
| 91 | + |
| 92 | + // Create a custom MakeWriter that uses the same shared writer |
| 93 | + let make_writer = SharedWriterMaker { writer }; |
| 94 | + |
14 | 95 | Some( |
15 | 96 | fmt::layer() |
16 | 97 | .json() // Use JSON format for structured logging for easier parsing and debugging |
17 | 98 | .with_file(false) |
18 | 99 | // To keep track of the closing point of spans |
19 | 100 | .with_span_events(FmtSpan::CLOSE) |
20 | | - .with_writer(trace_writer.make_writer()) |
| 101 | + .with_writer(make_writer) |
21 | 102 | .boxed(), |
22 | 103 | ) |
23 | 104 | } |
24 | 105 |
|
| 106 | + fn sync_trace(&mut self, events: Vec<TraceEvent>) { |
| 107 | + if let Some(writer) = &self.writer { |
| 108 | + use std::collections::HashMap; |
| 109 | + |
| 110 | + // Track begin events by uuid to match with end events |
| 111 | + let mut pending_events: HashMap<u32, TraceEvent> = HashMap::new(); |
| 112 | + |
| 113 | + for event in events { |
| 114 | + match event.ph.as_str() { |
| 115 | + "b" => { |
| 116 | + // Store begin event |
| 117 | + pending_events.insert(event.uuid, event); |
| 118 | + } |
| 119 | + "e" => { |
| 120 | + // Find matching begin event and calculate duration |
| 121 | + if let Some(begin_event) = pending_events.remove(&event.uuid) { |
| 122 | + let duration_ns = event.ts.saturating_sub(begin_event.ts); |
| 123 | + let duration_ms = duration_ns as f64 / 1_000_000.0; |
| 124 | + |
| 125 | + // Build fields object |
| 126 | + let mut fields = serde_json::Map::new(); |
| 127 | + fields.insert("message".to_string(), serde_json::json!("close")); |
| 128 | + fields.insert( |
| 129 | + "time.busy".to_string(), |
| 130 | + serde_json::json!(format!("{:.2}ms", duration_ms)), |
| 131 | + ); |
| 132 | + |
| 133 | + // Add any args from the event |
| 134 | + if let Some(args) = begin_event.args { |
| 135 | + for (key, value) in args { |
| 136 | + fields.insert(key, serde_json::json!(value)); |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + // Build span object if we have track_name |
| 141 | + let span_obj = begin_event.track_name.map(|track_name| { |
| 142 | + serde_json::json!({ |
| 143 | + "name": track_name, |
| 144 | + }) |
| 145 | + }); |
| 146 | + |
| 147 | + // Convert relative microsecond timestamp to absolute ISO 8601 format |
| 148 | + let absolute_ts_micros = self.begin_ts + begin_event.ts; |
| 149 | + let timestamp_iso = format_timestamp_iso8601(absolute_ts_micros); |
| 150 | + |
| 151 | + // Build JSON in Rust trace format |
| 152 | + let json_value = serde_json::json!({ |
| 153 | + "timestamp": timestamp_iso, |
| 154 | + "level": "DEBUG", |
| 155 | + "fields": fields, |
| 156 | + "target": "javascript", |
| 157 | + "span": span_obj, |
| 158 | + }); |
| 159 | + |
| 160 | + if let Ok(json_str) = serde_json::to_string(&json_value) { |
| 161 | + // Lock the mutex to access the writer |
| 162 | + let _ = writeln!( |
| 163 | + writer.lock().expect("Failed to lock writer"), |
| 164 | + "{}", |
| 165 | + json_str |
| 166 | + ); |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + _ => {} |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + // Flush to ensure events are written immediately |
| 175 | + let _ = writer.lock().expect("Failed to lock writer").flush(); |
| 176 | + } |
| 177 | + } |
| 178 | + |
25 | 179 | fn teardown(&mut self) { |
26 | | - // noop |
| 180 | + // Flush any remaining data |
| 181 | + if let Some(writer) = &self.writer { |
| 182 | + let _ = writer.lock().expect("Failed to lock writer").flush(); |
| 183 | + } |
27 | 184 | } |
28 | 185 | } |
0 commit comments