The qlog crate is an implementation of the qlog main logging schema, QUIC event definitions, and HTTP/3 and QPACK event definitions. The crate provides a qlog data model that can be used for traces with events. It supports serialization and deserialization but defers logging IO choices to applications.
The crate uses Serde for conversion between Rust and JSON.
qlog is a hierarchical logging format, with a rough structure of:
- Log
- Trace(s)
- Event(s)
- Trace(s)
In practice, a single QUIC connection maps to a single Trace file with one or more Events. Applications can decide whether to combine Traces from different connections into the same Log.
A Trace
is a single JSON object. It contains metadata such as the
VantagePoint
of capture and the Configuration
, and protocol event
data in the [Event
] array.
JSON Traces allow applications to appends events to them before eventually being serialized as a complete JSON object.
let mut trace = qlog::Trace::new(
qlog::VantagePoint {
name: Some("Example client".to_string()),
ty: qlog::VantagePointType::Client,
flow: None,
},
Some("Example qlog trace".to_string()),
Some("Example qlog trace description".to_string()),
Some(qlog::Configuration {
time_offset: Some(0.0),
original_uris: None,
}),
None,
);
Qlog Event
objects are added to qlog::Trace.events
.
The following example demonstrates how to log a qlog QUIC packet_sent
event
containing a single Crypto frame. It constructs the necessary elements of the
[Event
], then appends it to the trace with push_event()
.
let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
let pkt_hdr = qlog::events::quic::PacketHeader::new(
qlog::events::quic::PacketType::Initial,
0, // packet_number
None, // flags
None, // token
None, // length
Some(0x00000001), // version
Some(&scid),
Some(&dcid),
);
let frames = vec![qlog::events::quic::QuicFrame::Crypto {
offset: 0,
length: 0,
}];
let raw = qlog::events::RawInfo {
length: Some(1251),
payload_length: Some(1224),
data: None,
};
let event_data =
qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
header: pkt_hdr,
frames: Some(frames.into()),
is_coalesced: None,
retry_token: None,
stateless_reset_token: None,
supported_versions: None,
raw: Some(raw),
datagram_id: None,
});
trace.push_event(qlog::events::Event::with_time(0.0, event_data));
The qlog crate has only been tested with serde_json
, however other serializer
targets might work.
For example, serializing the trace created above:
serde_json::to_string_pretty(&trace).unwrap();
would generate the following:
{
"vantage_point": {
"name": "Example client",
"type": "client"
},
"title": "Example qlog trace",
"description": "Example qlog trace description",
"configuration": {
"time_offset": 0.0
},
"events": [
{
"time": 0.0,
"name": "transport:packet_sent",
"data": {
"header": {
"packet_type": "initial",
"packet_number": 0,
"version": "1",
"scil": 8,
"dcil": 8,
"scid": "7e37e4dcc6682da8",
"dcid": "36ce104eee50101c"
},
"raw": {
"length": 1251,
"payload_length": 1224
},
"frames": [
{
"frame_type": "crypto",
"offset": 0,
"length": 0
}
]
}
}
]
}
To help support streaming serialization of qlogs, draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON Text Sequences (JSON-SEQ). The qlog crate supports this format and provides utilities that aid streaming.
A TraceSeq
contains metadata such as the VantagePoint
of capture and
the Configuration
. However, protocol event data is handled as separate
lines containing a record separator character, a serialized [Event
], and a
newline.
let mut trace = qlog::TraceSeq::new(
qlog::VantagePoint {
name: Some("Example client".to_string()),
ty: qlog::VantagePointType::Client,
flow: None,
},
Some("Example qlog trace".to_string()),
Some("Example qlog trace description".to_string()),
Some(qlog::Configuration {
time_offset: Some(0.0),
original_uris: None,
}),
None,
);
Create an object with the Write
trait:
let mut file = std::fs::File::create("foo.sqlog").unwrap();
Create a QlogStreamer
and start serialization to foo.sqlog
using start_log()
:
let mut streamer = qlog::QlogStreamer::new(
qlog::QLOG_VERSION.to_string(),
Some("Example qlog".to_string()),
Some("Example qlog description".to_string()),
None,
std::time::Instant::now(),
trace,
qlog::EventImportance::Base,
Box::new(file),
);
streamer.start_log().ok();
Once logging has started you can stream events. Simple events can be written in
one step using add_event()
:
let event_data = qlog::events::EventData::MetricsUpdated(
qlog::events::quic::MetricsUpdated {
min_rtt: Some(1.0),
smoothed_rtt: Some(1.0),
latest_rtt: Some(1.0),
rtt_variance: Some(1.0),
pto_count: Some(1),
congestion_window: Some(1234),
bytes_in_flight: Some(5678),
ssthresh: None,
packets_in_flight: None,
pacing_rate: None,
},
);
let event = qlog::events::Event::with_time(0.0, event_data);
streamer.add_event(event).ok();
Some events contain optional arrays of QUIC frames. If the event has
Some(Vec<QuicFrame>)
, even if it is empty, the streamer enters a frame
serializing mode that must be finalized before other events can be logged.
In this example, a PacketSent
event is created with an empty frame array and
frames are written out later:
let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];
let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];
let pkt_hdr = qlog::events::quic::PacketHeader::with_type(
qlog::events::quic::PacketType::OneRtt,
0,
Some(0x00000001),
Some(&scid),
Some(&dcid),
);
let event_data =
qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {
header: pkt_hdr,
frames: Some(vec![]),
is_coalesced: None,
retry_token: None,
stateless_reset_token: None,
supported_versions: None,
raw: None,
datagram_id: None,
};
let event = qlog::events::Event::with_time(0.0, event_data);
streamer.add_event(event).ok();
In this example, the frames contained in the QUIC packet
are PING and PADDING. Each frame is written using the
add_frame()
method. Frame writing is concluded with
finish_frames()
.
let ping = qlog::events::quic::QuicFrame::Ping;
let padding = qlog::events::quic::QuicFrame::Padding;
streamer.add_frame(ping, false).ok();
streamer.add_frame(padding, false).ok();
streamer.finish_frames().ok();
Once all events have been written, the log
can be finalized with finish_log()
:
streamer.finish_log().ok();
Serialization to JSON occurs as methods on the QlogStreamer
are called. No additional steps are required.