1use crate::events::EventData;
28use crate::events::EventImportance;
29use crate::events::EventType;
30use crate::events::Eventable;
31use crate::events::ExData;
32
33pub enum EventTimePrecision {
39 MilliSeconds,
42 MicroSeconds,
44 NanoSeconds,
46}
47
48fn duration_to_millis(
51 dur: std::time::Duration, precision: &EventTimePrecision,
52) -> f64 {
53 match precision {
54 EventTimePrecision::MilliSeconds => dur.as_millis() as f64,
55 EventTimePrecision::MicroSeconds => dur.as_micros() as f64 / 1_000.0,
56 EventTimePrecision::NanoSeconds => dur.as_nanos() as f64 / 1_000_000.0,
57 }
58}
59
60fn elapsed_millis(
63 start: std::time::Instant, now: std::time::Instant,
64 precision: &EventTimePrecision,
65) -> f64 {
66 if cfg!(test) {
67 return 0.0;
68 }
69
70 let dur = now.saturating_duration_since(start);
71 duration_to_millis(dur, precision)
72}
73
74use super::*;
85
86#[derive(PartialEq, Eq, Debug)]
87pub enum StreamerState {
88 Initial,
89 Ready,
90 Finished,
91}
92
93pub struct QlogStreamer {
94 start_time: std::time::Instant,
95 writer: Box<dyn std::io::Write + Send + Sync>,
96 qlog: QlogSeq,
97 state: StreamerState,
98 log_level: EventImportance,
99 time_precision: EventTimePrecision,
100}
101
102impl QlogStreamer {
103 #[allow(clippy::too_many_arguments)]
113 pub fn new(
114 title: Option<String>, description: Option<String>,
115 start_time: std::time::Instant, trace: TraceSeq,
116 log_level: EventImportance, time_precision: EventTimePrecision,
117 writer: Box<dyn std::io::Write + Send + Sync>,
118 ) -> Self {
119 let qlog = QlogSeq {
120 file_schema: QLOGFILESEQ_URI.to_string(),
121 serialization_format: "JSON-SEQ".to_string(),
122 title,
123 description,
124 trace,
125 };
126
127 QlogStreamer {
128 start_time,
129 writer,
130 qlog,
131 state: StreamerState::Initial,
132 log_level,
133 time_precision,
134 }
135 }
136
137 pub fn start_log(&mut self) -> Result<()> {
150 if self.state != StreamerState::Initial {
151 return Err(Error::Done);
152 }
153
154 self.writer.as_mut().write_all(b"")?;
155 serde_json::to_writer(self.writer.as_mut(), &self.qlog)
156 .map_err(|_| Error::Done)?;
157 self.writer.as_mut().write_all(b"\n")?;
158
159 self.state = StreamerState::Ready;
160
161 Ok(())
162 }
163
164 pub fn finish_log(&mut self) -> Result<()> {
168 if self.state == StreamerState::Initial ||
169 self.state == StreamerState::Finished
170 {
171 return Err(Error::InvalidState);
172 }
173
174 self.state = StreamerState::Finished;
175
176 self.writer.as_mut().flush()?;
177
178 Ok(())
179 }
180
181 pub fn add_event_now<E: Serialize + Eventable>(
184 &mut self, event: E,
185 ) -> Result<()> {
186 let now = std::time::Instant::now();
187
188 self.add_event_with_instant(event, now)
189 }
190
191 pub fn add_event_now_pretty<E: Serialize + Eventable>(
194 &mut self, event: E,
195 ) -> Result<()> {
196 let now = std::time::Instant::now();
197
198 self.add_event_with_instant_pretty(event, now)
199 }
200
201 pub fn add_event_with_instant<E: Serialize + Eventable>(
204 &mut self, event: E, now: std::time::Instant,
205 ) -> Result<()> {
206 self.event_with_instant(event, now, false)
207 }
208
209 pub fn add_event_with_instant_pretty<E: Serialize + Eventable>(
212 &mut self, event: E, now: std::time::Instant,
213 ) -> Result<()> {
214 self.event_with_instant(event, now, true)
215 }
216
217 fn event_with_instant<E: Serialize + Eventable>(
218 &mut self, mut event: E, now: std::time::Instant, pretty: bool,
219 ) -> Result<()> {
220 if self.state != StreamerState::Ready {
221 return Err(Error::InvalidState);
222 }
223
224 if !event.importance().is_contained_in(&self.log_level) {
225 return Err(Error::Done);
226 }
227
228 event.set_time(elapsed_millis(
229 self.start_time,
230 now,
231 &self.time_precision,
232 ));
233
234 if pretty {
235 self.add_event_pretty(event)
236 } else {
237 self.add_event(event)
238 }
239 }
240
241 pub fn add_event_data_now(&mut self, event_data: EventData) -> Result<()> {
244 self.add_event_data_ex_now(event_data, Default::default())
245 }
246
247 pub fn add_event_data_now_pretty(
250 &mut self, event_data: EventData,
251 ) -> Result<()> {
252 self.add_event_data_ex_now_pretty(event_data, Default::default())
253 }
254
255 pub fn add_event_data_ex_now(
258 &mut self, event_data: EventData, ex_data: ExData,
259 ) -> Result<()> {
260 let now = std::time::Instant::now();
261
262 self.add_event_data_ex_with_instant(event_data, ex_data, now)
263 }
264
265 pub fn add_event_data_ex_now_pretty(
268 &mut self, event_data: EventData, ex_data: ExData,
269 ) -> Result<()> {
270 let now = std::time::Instant::now();
271
272 self.add_event_data_ex_with_instant_pretty(event_data, ex_data, now)
273 }
274
275 pub fn add_event_data_with_instant(
278 &mut self, event_data: EventData, now: std::time::Instant,
279 ) -> Result<()> {
280 self.add_event_data_ex_with_instant(event_data, Default::default(), now)
281 }
282
283 pub fn add_event_data_with_instant_pretty(
286 &mut self, event_data: EventData, now: std::time::Instant,
287 ) -> Result<()> {
288 self.add_event_data_ex_with_instant_pretty(
289 event_data,
290 Default::default(),
291 now,
292 )
293 }
294
295 pub fn add_event_data_ex_with_instant(
298 &mut self, event_data: EventData, ex_data: ExData,
299 now: std::time::Instant,
300 ) -> Result<()> {
301 self.event_data_ex_with_instant(event_data, ex_data, now, false)
302 }
303
304 pub fn add_event_data_ex_with_instant_pretty(
307 &mut self, event_data: EventData, ex_data: ExData,
308 now: std::time::Instant,
309 ) -> Result<()> {
310 self.event_data_ex_with_instant(event_data, ex_data, now, true)
311 }
312
313 fn event_data_ex_with_instant(
314 &mut self, event_data: EventData, ex_data: ExData,
315 now: std::time::Instant, pretty: bool,
316 ) -> Result<()> {
317 if self.state != StreamerState::Ready {
318 return Err(Error::InvalidState);
319 }
320
321 let ty = EventType::from(&event_data);
322 if !EventImportance::from(ty).is_contained_in(&self.log_level) {
323 return Err(Error::Done);
324 }
325
326 let event = Event::with_time_ex(
327 elapsed_millis(self.start_time, now, &self.time_precision),
328 event_data,
329 ex_data,
330 );
331
332 if pretty {
333 self.add_event_pretty(event)
334 } else {
335 self.add_event(event)
336 }
337 }
338
339 pub fn add_event<E: Serialize + Eventable>(
341 &mut self, event: E,
342 ) -> Result<()> {
343 self.write_event(event, false)
344 }
345
346 pub fn add_event_pretty<E: Serialize + Eventable>(
349 &mut self, event: E,
350 ) -> Result<()> {
351 self.write_event(event, true)
352 }
353
354 fn write_event<E: Serialize + Eventable>(
356 &mut self, event: E, pretty: bool,
357 ) -> Result<()> {
358 if self.state != StreamerState::Ready {
359 return Err(Error::InvalidState);
360 }
361
362 if !event.importance().is_contained_in(&self.log_level) {
363 return Err(Error::Done);
364 }
365
366 self.writer.as_mut().write_all(b"")?;
367 if pretty {
368 serde_json::to_writer_pretty(self.writer.as_mut(), &event)
369 .map_err(|_| Error::Done)?;
370 } else {
371 serde_json::to_writer(self.writer.as_mut(), &event)
372 .map_err(|_| Error::Done)?;
373 }
374 self.writer.as_mut().write_all(b"\n")?;
375
376 Ok(())
377 }
378
379 #[allow(clippy::borrowed_box)]
381 pub fn writer(&self) -> &Box<dyn std::io::Write + Send + Sync> {
382 &self.writer
383 }
384
385 pub fn start_time(&self) -> std::time::Instant {
386 self.start_time
387 }
388}
389
390impl Drop for QlogStreamer {
391 fn drop(&mut self) {
392 let _ = self.finish_log();
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use std::collections::BTreeMap;
399
400 use super::*;
401 use crate::events::quic;
402 use crate::events::quic::QuicFrame;
403 use crate::events::RawInfo;
404 use testing::*;
405
406 use serde_json::json;
407
408 #[test]
409 fn serialization_states() {
410 let v: Vec<u8> = Vec::new();
411 let buff = std::io::Cursor::new(v);
412 let writer = Box::new(buff);
413
414 let trace = make_trace_seq();
415 let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);
416 let raw = Some(RawInfo {
417 length: Some(1251),
418 payload_length: Some(1224),
419 data: None,
420 });
421
422 let frame1 = QuicFrame::Stream {
423 stream_id: 40,
424 offset: Some(40),
425 raw: Some(Box::new(RawInfo {
426 length: None,
427 payload_length: Some(400),
428 data: None,
429 })),
430 fin: Some(true),
431 };
432
433 let event_data1 = EventData::QuicPacketSent(quic::PacketSent {
434 header: pkt_hdr.clone(),
435 frames: Some(vec![frame1]),
436 raw: raw.clone(),
437 ..Default::default()
438 });
439
440 let ev1 = Event::with_time(0.0, event_data1);
441
442 let frame2 = QuicFrame::Stream {
443 stream_id: 0,
444 offset: Some(0),
445 raw: Some(Box::new(RawInfo {
446 length: None,
447 payload_length: Some(100),
448 data: None,
449 })),
450 fin: Some(true),
451 };
452
453 let frame3 = QuicFrame::Stream {
454 stream_id: 0,
455 offset: Some(0),
456 raw: Some(Box::new(RawInfo {
457 length: None,
458 payload_length: Some(100),
459 data: None,
460 })),
461 fin: Some(true),
462 };
463
464 let event_data2 = EventData::QuicPacketSent(quic::PacketSent {
465 header: pkt_hdr.clone(),
466 frames: Some(vec![frame2]),
467 raw: raw.clone(),
468 ..Default::default()
469 });
470
471 let ev2 = Event::with_time(0.0, event_data2);
472
473 let event_data3 = EventData::QuicPacketSent(quic::PacketSent {
474 header: pkt_hdr,
475 frames: Some(vec![frame3]),
476 stateless_reset_token: Some(Box::new("reset_token".to_string())),
477 raw,
478 ..Default::default()
479 });
480
481 let ev3 = Event::with_time(0.0, event_data3);
482
483 let mut s = streamer::QlogStreamer::new(
484 Some("title".to_string()),
485 Some("description".to_string()),
486 std::time::Instant::now(),
487 trace,
488 EventImportance::Base,
489 EventTimePrecision::NanoSeconds,
490 writer,
491 );
492
493 assert!(matches!(s.add_event(ev2.clone()), Err(Error::InvalidState)));
495 assert!(matches!(s.finish_log(), Err(Error::InvalidState)));
496
497 assert!(matches!(s.start_log(), Ok(())));
499 assert!(matches!(s.add_event(ev1), Ok(())));
500
501 assert!(matches!(s.add_event(ev2), Ok(())));
503 assert!(matches!(s.add_event(ev3.clone()), Ok(())));
504
505 let now = std::time::Instant::now();
509
510 assert!(matches!(s.add_event_with_instant(ev3, now), Ok(())));
511
512 assert!(matches!(s.finish_log(), Ok(())));
513
514 let r = s.writer();
515 #[allow(clippy::borrowed_box)]
516 let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
517
518 let log_string = r#"{"file_schema":"urn:ietf:params:qlog:file:sequential","serialization_format":"JSON-SEQ","title":"title","description":"description","trace":{"title":"Quiche qlog trace","description":"Quiche qlog trace description","vantage_point":{"type":"server"},"event_schemas":[]}}
519{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"fin":true,"raw":{"payload_length":400}}]}}
520{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
521{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"stateless_reset_token":"reset_token","raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
522{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"stateless_reset_token":"reset_token","raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
523"#;
524
525 let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
526
527 pretty_assertions::assert_eq!(log_string, written_string);
528 }
529
530 #[test]
531 fn stream_json_event() {
532 let data = json!({"foo": "Bar", "hello": 123});
533 let ev = events::JsonEvent {
534 time: 0.0,
535 importance: events::EventImportance::Core,
536 name: "jsonevent:sample".into(),
537 data,
538 };
539
540 let v: Vec<u8> = Vec::new();
541 let buff = std::io::Cursor::new(v);
542 let writer = Box::new(buff);
543
544 let trace = make_trace_seq();
545
546 let mut s = streamer::QlogStreamer::new(
547 Some("title".to_string()),
548 Some("description".to_string()),
549 std::time::Instant::now(),
550 trace,
551 EventImportance::Base,
552 EventTimePrecision::NanoSeconds,
553 writer,
554 );
555
556 assert!(matches!(s.start_log(), Ok(())));
557 assert!(matches!(s.add_event(ev), Ok(())));
558 assert!(matches!(s.finish_log(), Ok(())));
559
560 let r = s.writer();
561 #[allow(clippy::borrowed_box)]
562 let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
563
564 let log_string = r#"{"file_schema":"urn:ietf:params:qlog:file:sequential","serialization_format":"JSON-SEQ","title":"title","description":"description","trace":{"title":"Quiche qlog trace","description":"Quiche qlog trace description","vantage_point":{"type":"server"},"event_schemas":[]}}
565{"time":0.0,"name":"jsonevent:sample","data":{"foo":"Bar","hello":123}}
566"#;
567
568 let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
569
570 pretty_assertions::assert_eq!(log_string, written_string);
571 }
572
573 #[test]
574 fn stream_data_ex() {
575 let v: Vec<u8> = Vec::new();
576 let buff = std::io::Cursor::new(v);
577 let writer = Box::new(buff);
578
579 let trace = make_trace_seq();
580 let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);
581 let raw = Some(RawInfo {
582 length: Some(1251),
583 payload_length: Some(1224),
584 data: None,
585 });
586
587 let frame1 = QuicFrame::Stream {
588 stream_id: 40,
589 offset: Some(40),
590 raw: Some(Box::new(RawInfo {
591 length: None,
592 payload_length: Some(400),
593 data: None,
594 })),
595 fin: Some(true),
596 };
597
598 let event_data1 = EventData::QuicPacketSent(quic::PacketSent {
599 header: pkt_hdr.clone(),
600 frames: Some(vec![frame1]),
601 raw: raw.clone(),
602 ..Default::default()
603 });
604 let j1 = json!({"foo": "Bar", "hello": 123});
605 let j2 = json!({"baz": [1,2,3,4]});
606 let mut ex_data = BTreeMap::new();
607 ex_data.insert("first".to_string(), j1);
608 ex_data.insert("second".to_string(), j2);
609
610 let ev1 = Event::with_time_ex(0.0, event_data1, ex_data);
611
612 let frame2 = QuicFrame::Stream {
613 stream_id: 1,
614 offset: Some(0),
615 raw: Some(Box::new(RawInfo {
616 length: None,
617 payload_length: Some(100),
618 data: None,
619 })),
620 fin: Some(true),
621 };
622
623 let event_data2 = EventData::QuicPacketSent(quic::PacketSent {
624 header: pkt_hdr.clone(),
625 frames: Some(vec![frame2]),
626 raw: raw.clone(),
627 ..Default::default()
628 });
629
630 let ev2 = Event::with_time(0.0, event_data2);
631
632 let mut s = streamer::QlogStreamer::new(
633 Some("title".to_string()),
634 Some("description".to_string()),
635 std::time::Instant::now(),
636 trace,
637 EventImportance::Base,
638 EventTimePrecision::NanoSeconds,
639 writer,
640 );
641
642 assert!(matches!(s.start_log(), Ok(())));
643 assert!(matches!(s.add_event(ev1), Ok(())));
644 assert!(matches!(s.add_event(ev2), Ok(())));
645 assert!(matches!(s.finish_log(), Ok(())));
646
647 let r = s.writer();
648 #[allow(clippy::borrowed_box)]
649 let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
650
651 let log_string = r#"{"file_schema":"urn:ietf:params:qlog:file:sequential","serialization_format":"JSON-SEQ","title":"title","description":"description","trace":{"title":"Quiche qlog trace","description":"Quiche qlog trace description","vantage_point":{"type":"server"},"event_schemas":[]}}
652{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"fin":true,"raw":{"payload_length":400}}]},"first":{"foo":"Bar","hello":123},"second":{"baz":[1,2,3,4]}}
653{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":1,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
654"#;
655
656 let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
657
658 pretty_assertions::assert_eq!(log_string, written_string);
659 }
660
661 #[test]
662 fn elapsed_millis_precision() {
663 let dur = std::time::Duration::from_nanos(1_234_567);
664 assert_eq!(
665 duration_to_millis(dur, &EventTimePrecision::MilliSeconds),
666 1.0
667 );
668 assert_eq!(
669 duration_to_millis(dur, &EventTimePrecision::MicroSeconds),
670 1.234000
671 );
672 assert_eq!(
673 duration_to_millis(dur, &EventTimePrecision::NanoSeconds),
674 1.234567
675 );
676 }
677
678 #[test]
679 fn elapsed_millis_zero_duration_all_precisions() {
680 let dur = std::time::Duration::from_secs(0);
681 assert_eq!(
682 duration_to_millis(dur, &EventTimePrecision::MilliSeconds),
683 0.0
684 );
685 assert_eq!(
686 duration_to_millis(dur, &EventTimePrecision::MicroSeconds),
687 0.0
688 );
689 assert_eq!(
690 duration_to_millis(dur, &EventTimePrecision::NanoSeconds),
691 0.0
692 );
693 }
694}