Skip to main content

qlog/
lib.rs

1// Copyright (C) 2019, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! The qlog crate is an implementation of the qlog [main logging schema],
28//! [QUIC event definitions], and [HTTP/3 and QPACK event definitions].
29//! The crate provides a qlog data model that can be used for traces with
30//! events. It supports serialization and deserialization but defers logging IO
31//! choices to applications.
32//!
33//! Serialization operates in either a [buffered mode] or a [streaming mode].
34//!
35//! The crate uses Serde for conversion between Rust and JSON.
36//!
37//! [main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema
38//! [QUIC event definitions]:
39//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html
40//! [HTTP/3 and QPACK event definitions]:
41//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html
42//! [buffered mode]: #buffered-traces-with-standard-json
43//! [streaming mode]: #streaming-traces-with-json-seq
44//!
45//! Overview
46//! ---------------
47//! qlog is a hierarchical logging format, with a rough structure of:
48//!
49//! * Log
50//!   * Trace(s)
51//!     * Event(s)
52//!
53//! In practice, a single QUIC connection maps to a single Trace file with one
54//! or more Events. Applications can decide whether to combine Traces from
55//! different connections into the same Log.
56//!
57//! ## Buffered Traces with standard JSON
58//!
59//! A [`Trace`] is a single JSON object. It contains metadata such as the
60//! [`VantagePoint`] of capture and the [`Configuration`], and protocol event
61//! data in the [`Event`] array.
62//!
63//! JSON Traces allow applications to appends events to them before eventually
64//! being serialized as a complete JSON object.
65//!
66//! ### Creating a Trace
67//!
68//! ```
69//! let mut trace = qlog::Trace::new(
70//!     qlog::VantagePoint {
71//!         name: Some("Example client".to_string()),
72//!         ty: qlog::VantagePointType::Client,
73//!         flow: None,
74//!     },
75//!     Some("Example qlog trace".to_string()),
76//!     Some("Example qlog trace description".to_string()),
77//!     Some(qlog::Configuration {
78//!         time_offset: Some(0.0),
79//!         original_uris: None,
80//!     }),
81//!     None,
82//! );
83//! ```
84//!
85//! ### Adding events to a Trace
86//!
87//! Qlog [`Event`] objects are added to [`qlog::Trace.events`].
88//!
89//! The following example demonstrates how to log a qlog QUIC `packet_sent`
90//! event containing a single Crypto frame. It constructs the necessary elements
91//! of the [`Event`], then appends it to the trace with [`push_event()`].
92//!
93//! ```
94//! # let mut trace = qlog::Trace::new (
95//! #     qlog::VantagePoint {
96//! #         name: Some("Example client".to_string()),
97//! #         ty: qlog::VantagePointType::Client,
98//! #         flow: None,
99//! #     },
100//! #     Some("Example qlog trace".to_string()),
101//! #     Some("Example qlog trace description".to_string()),
102//! #     Some(qlog::Configuration {
103//! #         time_offset: Some(0.0),
104//! #         original_uris: None,
105//! #     }),
106//! #     None
107//! # );
108//!
109//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
110//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
111//!
112//! let pkt_hdr = qlog::events::quic::PacketHeader::new(
113//!     qlog::events::quic::PacketType::Initial,
114//!     Some(0),          // packet_number
115//!     None,             // flags
116//!     None,             // token
117//!     None,             // length
118//!     Some(0x00000001), // version
119//!     Some(&scid),
120//!     Some(&dcid),
121//! );
122//!
123//! let frames = vec![qlog::events::quic::QuicFrame::Crypto {
124//!     offset: 0,
125//!     length: 0,
126//! }];
127//!
128//! let raw = qlog::events::RawInfo {
129//!     length: Some(1251),
130//!     payload_length: Some(1224),
131//!     data: None,
132//! };
133//!
134//! let event_data =
135//!     qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
136//!         header: pkt_hdr,
137//!         frames: Some(frames.into()),
138//!         is_coalesced: None,
139//!         retry_token: None,
140//!         stateless_reset_token: None,
141//!         supported_versions: None,
142//!         raw: Some(raw),
143//!         datagram_id: None,
144//!         send_at_time: None,
145//!         trigger: None,
146//!     });
147//!
148//! trace.push_event(qlog::events::Event::with_time(0.0, event_data));
149//! ```
150//!
151//! ### Serializing
152//!
153//! The qlog crate has only been tested with `serde_json`, however
154//! other serializer targets might work.
155//!
156//! For example, serializing the trace created above:
157//!
158//! ```
159//! # let mut trace = qlog::Trace::new (
160//! #     qlog::VantagePoint {
161//! #         name: Some("Example client".to_string()),
162//! #         ty: qlog::VantagePointType::Client,
163//! #         flow: None,
164//! #     },
165//! #     Some("Example qlog trace".to_string()),
166//! #     Some("Example qlog trace description".to_string()),
167//! #     Some(qlog::Configuration {
168//! #         time_offset: Some(0.0),
169//! #         original_uris: None,
170//! #     }),
171//! #     None
172//! # );
173//! serde_json::to_string_pretty(&trace).unwrap();
174//! ```
175//!
176//! which would generate the following:
177//!
178//! ```ignore
179//! {
180//!   "vantage_point": {
181//!     "name": "Example client",
182//!     "type": "client"
183//!   },
184//!   "title": "Example qlog trace",
185//!   "description": "Example qlog trace description",
186//!   "configuration": {
187//!     "time_offset": 0.0
188//!   },
189//!   "events": [
190//!     {
191//!       "time": 0.0,
192//!       "name": "transport:packet_sent",
193//!       "data": {
194//!         "header": {
195//!           "packet_type": "initial",
196//!           "packet_number": 0,
197//!           "version": "1",
198//!           "scil": 8,
199//!           "dcil": 8,
200//!           "scid": "7e37e4dcc6682da8",
201//!           "dcid": "36ce104eee50101c"
202//!         },
203//!         "raw": {
204//!           "length": 1251,
205//!           "payload_length": 1224
206//!         },
207//!         "frames": [
208//!           {
209//!             "frame_type": "crypto",
210//!             "offset": 0,
211//!             "length": 0
212//!           }
213//!         ]
214//!       }
215//!     }
216//!   ]
217//! }
218//! ```
219//!
220//! ## Streaming Traces with JSON-SEQ
221//!
222//! To help support streaming serialization of qlogs,
223//! draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON
224//! Text Sequences (JSON-SEQ). The qlog crate supports this format and provides
225//! utilities that aid streaming.
226//!
227//! A [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and
228//! the [`Configuration`]. However, protocol event data is handled as separate
229//! lines containing a record separator character, a serialized [`Event`], and a
230//! newline.
231//!
232//! ### Creating a TraceSeq
233//!
234//! ```
235//! let mut trace = qlog::TraceSeq::new(
236//!     qlog::VantagePoint {
237//!         name: Some("Example client".to_string()),
238//!         ty: qlog::VantagePointType::Client,
239//!         flow: None,
240//!     },
241//!     Some("Example qlog trace".to_string()),
242//!     Some("Example qlog trace description".to_string()),
243//!     Some(qlog::Configuration {
244//!         time_offset: Some(0.0),
245//!         original_uris: None,
246//!     }),
247//!     None,
248//! );
249//! ```
250//!
251//! Create an object with the [`Write`] trait:
252//!
253//! ```
254//! let mut file = std::fs::File::create("foo.sqlog").unwrap();
255//! ```
256//!
257//! Create a [`QlogStreamer`] and start serialization to foo.sqlog
258//! using [`start_log()`]:
259//!
260//! ```
261//! # let mut trace = qlog::TraceSeq::new(
262//! #    qlog::VantagePoint {
263//! #        name: Some("Example client".to_string()),
264//! #        ty: qlog::VantagePointType::Client,
265//! #        flow: None,
266//! #    },
267//! #    Some("Example qlog trace".to_string()),
268//! #    Some("Example qlog trace description".to_string()),
269//! #    Some(qlog::Configuration {
270//! #        time_offset: Some(0.0),
271//! #        original_uris: None,
272//! #    }),
273//! #    None,
274//! # );
275//! # let mut file = std::fs::File::create("foo.sqlog").unwrap();
276//! let mut streamer = qlog::streamer::QlogStreamer::new(
277//!     qlog::QLOG_VERSION.to_string(),
278//!     Some("Example qlog".to_string()),
279//!     Some("Example qlog description".to_string()),
280//!     None,
281//!     std::time::Instant::now(),
282//!     trace,
283//!     qlog::events::EventImportance::Base,
284//!     Box::new(file),
285//! );
286//!
287//! streamer.start_log().ok();
288//! ```
289//!
290//! ### Adding events
291//!
292//! Once logging has started you can stream events. Events
293//! are written in one step using one of [`add_event()`],
294//! [`add_event_with_instant()`], [`add_event_now()`],
295//! [`add_event_data_with_instant()`], or [`add_event_data_now()`] :
296//!
297//! ```
298//! # let mut trace = qlog::TraceSeq::new(
299//! #    qlog::VantagePoint {
300//! #        name: Some("Example client".to_string()),
301//! #        ty: qlog::VantagePointType::Client,
302//! #        flow: None,
303//! #    },
304//! #    Some("Example qlog trace".to_string()),
305//! #    Some("Example qlog trace description".to_string()),
306//! #    Some(qlog::Configuration {
307//! #        time_offset: Some(0.0),
308//! #        original_uris: None,
309//! #    }),
310//! #    None,
311//! # );
312//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
313//! # let mut streamer = qlog::streamer::QlogStreamer::new(
314//! #     qlog::QLOG_VERSION.to_string(),
315//! #     Some("Example qlog".to_string()),
316//! #     Some("Example qlog description".to_string()),
317//! #     None,
318//! #     std::time::Instant::now(),
319//! #     trace,
320//! #     qlog::events::EventImportance::Base,
321//! #     Box::new(file),
322//! # );
323//!
324//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
325//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
326//!
327//! let pkt_hdr = qlog::events::quic::PacketHeader::with_type(
328//!     qlog::events::quic::PacketType::OneRtt,
329//!     Some(0),
330//!     Some(0x00000001),
331//!     Some(&scid),
332//!     Some(&dcid),
333//! );
334//!
335//! let ping = qlog::events::quic::QuicFrame::Ping {
336//!     length: None,
337//!     payload_length: None,
338//! };
339//! let padding = qlog::events::quic::QuicFrame::Padding {
340//!     length: None,
341//!     payload_length: 1234,
342//! };
343//!
344//! let event_data =
345//!     qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
346//!         header: pkt_hdr,
347//!         frames: Some(vec![ping, padding].into()),
348//!         is_coalesced: None,
349//!         retry_token: None,
350//!         stateless_reset_token: None,
351//!         supported_versions: None,
352//!         raw: None,
353//!         datagram_id: None,
354//!         send_at_time: None,
355//!         trigger: None,
356//!     });
357//!
358//! let event = qlog::events::Event::with_time(0.0, event_data);
359//!
360//! streamer.add_event(event).ok();
361//! ```
362//!
363//! Once all events have been written, the log
364//! can be finalized with [`finish_log()`]:
365//!
366//! ```
367//! # let mut trace = qlog::TraceSeq::new(
368//! #    qlog::VantagePoint {
369//! #        name: Some("Example client".to_string()),
370//! #        ty: qlog::VantagePointType::Client,
371//! #        flow: None,
372//! #    },
373//! #    Some("Example qlog trace".to_string()),
374//! #    Some("Example qlog trace description".to_string()),
375//! #    Some(qlog::Configuration {
376//! #        time_offset: Some(0.0),
377//! #        original_uris: None,
378//! #    }),
379//! #    None,
380//! # );
381//! # let mut file = std::fs::File::create("foo.qlog").unwrap();
382//! # let mut streamer = qlog::streamer::QlogStreamer::new(
383//! #     qlog::QLOG_VERSION.to_string(),
384//! #     Some("Example qlog".to_string()),
385//! #     Some("Example qlog description".to_string()),
386//! #     None,
387//! #     std::time::Instant::now(),
388//! #     trace,
389//! #     qlog::events::EventImportance::Base,
390//! #     Box::new(file),
391//! # );
392//! streamer.finish_log().ok();
393//! ```
394//!
395//! ### Serializing
396//!
397//! Serialization to JSON occurs as methods on the [`QlogStreamer`]
398//! are called. No additional steps are required.
399//!
400//! [`Trace`]: struct.Trace.html
401//! [`TraceSeq`]: struct.TraceSeq.html
402//! [`VantagePoint`]: struct.VantagePoint.html
403//! [`Configuration`]: struct.Configuration.html
404//! [`qlog::Trace.events`]: struct.Trace.html#structfield.events
405//! [`push_event()`]: struct.Trace.html#method.push_event
406//! [`QlogStreamer`]: struct.QlogStreamer.html
407//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
408//! [`start_log()`]: streamer/struct.QlogStreamer.html#method.start_log
409//! [`add_event()`]: streamer/struct.QlogStreamer.html#method.add_event
410//! [`add_event_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_with_instant
411//! [`add_event_now()`]: streamer/struct.QlogStreamer.html#method.add_event_now
412//! [`add_event_data_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_data_with_instant
413//! [`add_event_data_now()`]: streamer/struct.QlogStreamer.html#method.add_event_data_now
414//! [`finish_log()`]: streamer/struct.QlogStreamer.html#method.finish_log
415
416use crate::events::quic::PacketHeader;
417use crate::events::Event;
418
419use serde::Deserialize;
420use serde::Serialize;
421
422/// A quiche qlog error.
423#[derive(Debug)]
424pub enum Error {
425    /// There is no more work to do.
426    Done,
427
428    /// The operation cannot be completed because it was attempted
429    /// in an invalid state.
430    InvalidState,
431
432    // Invalid Qlog format
433    InvalidFormat,
434
435    /// I/O error.
436    IoError(std::io::Error),
437}
438
439impl std::fmt::Display for Error {
440    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
441        write!(f, "{self:?}")
442    }
443}
444
445impl std::error::Error for Error {
446    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
447        None
448    }
449}
450
451impl std::convert::From<std::io::Error> for Error {
452    fn from(err: std::io::Error) -> Self {
453        Error::IoError(err)
454    }
455}
456
457pub const QLOG_VERSION: &str = "0.3";
458
459pub type Bytes = String;
460pub type StatelessResetToken = Bytes;
461
462/// A specialized [`Result`] type for quiche qlog operations.
463///
464/// This type is used throughout the public API for any operation that
465/// can produce an error.
466///
467/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
468pub type Result<T> = std::result::Result<T, Error>;
469
470#[serde_with::skip_serializing_none]
471#[derive(Serialize, Deserialize, Clone)]
472pub struct Qlog {
473    pub qlog_version: String,
474    pub qlog_format: String,
475    pub title: Option<String>,
476    pub description: Option<String>,
477    pub summary: Option<String>,
478
479    pub traces: Vec<Trace>,
480}
481#[serde_with::skip_serializing_none]
482#[derive(Serialize, Deserialize, Clone, Debug)]
483pub struct QlogSeq {
484    pub qlog_version: String,
485    pub qlog_format: String,
486    pub title: Option<String>,
487    pub description: Option<String>,
488    pub summary: Option<String>,
489
490    pub trace: TraceSeq,
491}
492
493#[derive(Clone, Copy)]
494pub enum ImportanceLogLevel {
495    Core  = 0,
496    Base  = 1,
497    Extra = 2,
498}
499
500// We now commence data definitions heavily styled on the QLOG
501// schema definition. Data is serialized using serde.
502#[serde_with::skip_serializing_none]
503#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
504pub struct Trace {
505    pub vantage_point: VantagePoint,
506    pub title: Option<String>,
507    pub description: Option<String>,
508
509    pub configuration: Option<Configuration>,
510
511    pub common_fields: Option<CommonFields>,
512
513    pub events: Vec<Event>,
514}
515
516/// Helper functions for using a qlog [Trace].
517impl Trace {
518    /// Creates a new qlog [Trace]
519    pub fn new(
520        vantage_point: VantagePoint, title: Option<String>,
521        description: Option<String>, configuration: Option<Configuration>,
522        common_fields: Option<CommonFields>,
523    ) -> Self {
524        Trace {
525            vantage_point,
526            title,
527            description,
528            configuration,
529            common_fields,
530            events: Vec::new(),
531        }
532    }
533
534    /// Append an [Event] to a [Trace]
535    pub fn push_event(&mut self, event: Event) {
536        self.events.push(event);
537    }
538}
539
540#[serde_with::skip_serializing_none]
541#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
542pub struct TraceSeq {
543    pub vantage_point: VantagePoint,
544    pub title: Option<String>,
545    pub description: Option<String>,
546
547    pub configuration: Option<Configuration>,
548
549    pub common_fields: Option<CommonFields>,
550}
551
552/// Helper functions for using a qlog [TraceSeq].
553impl TraceSeq {
554    /// Creates a new qlog [TraceSeq]
555    pub fn new(
556        vantage_point: VantagePoint, title: Option<String>,
557        description: Option<String>, configuration: Option<Configuration>,
558        common_fields: Option<CommonFields>,
559    ) -> Self {
560        TraceSeq {
561            vantage_point,
562            title,
563            description,
564            configuration,
565            common_fields,
566        }
567    }
568}
569
570#[serde_with::skip_serializing_none]
571#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
572pub struct VantagePoint {
573    pub name: Option<String>,
574
575    #[serde(rename = "type")]
576    pub ty: VantagePointType,
577
578    pub flow: Option<VantagePointType>,
579}
580
581#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
582#[serde(rename_all = "snake_case")]
583pub enum VantagePointType {
584    Client,
585    Server,
586    Network,
587    Unknown,
588}
589
590#[serde_with::skip_serializing_none]
591#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
592pub struct Configuration {
593    pub time_offset: Option<f64>,
594
595    pub original_uris: Option<Vec<String>>,
596    // TODO: additionalUserSpecifiedProperty
597}
598
599impl Default for Configuration {
600    fn default() -> Self {
601        Configuration {
602            time_offset: Some(0.0),
603            original_uris: None,
604        }
605    }
606}
607
608#[serde_with::skip_serializing_none]
609#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]
610pub struct CommonFields {
611    pub group_id: Option<String>,
612    pub protocol_type: Option<Vec<String>>,
613
614    pub reference_time: Option<f64>,
615    pub time_format: Option<String>,
616    // TODO: additionalUserSpecifiedProperty
617}
618
619#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
620#[serde(rename_all = "snake_case")]
621pub enum TokenType {
622    Retry,
623    Resumption,
624}
625
626#[serde_with::skip_serializing_none]
627#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
628pub struct Token {
629    #[serde(rename(serialize = "type"))]
630    pub ty: Option<TokenType>,
631
632    pub details: Option<String>,
633
634    pub raw: Option<events::RawInfo>,
635}
636
637pub struct HexSlice<'a>(&'a [u8]);
638
639impl<'a> HexSlice<'a> {
640    pub fn new<T>(data: &'a T) -> HexSlice<'a>
641    where
642        T: ?Sized + AsRef<[u8]> + 'a,
643    {
644        HexSlice(data.as_ref())
645    }
646
647    pub fn maybe_string<T>(data: Option<&'a T>) -> Option<String>
648    where
649        T: ?Sized + AsRef<[u8]> + 'a,
650    {
651        data.map(|d| format!("{}", HexSlice::new(d)))
652    }
653}
654
655impl std::fmt::Display for HexSlice<'_> {
656    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
657        for byte in self.0 {
658            write!(f, "{byte:02x}")?;
659        }
660        Ok(())
661    }
662}
663
664pub mod events;
665pub mod reader;
666pub mod streamer;
667#[doc(hidden)]
668pub mod testing;