1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
|
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.
[main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema
[QUIC event definitions]:
https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html
[HTTP/3 and QPACK event definitions]:
https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html
Overview
--------
qlog is a hierarchical logging format, with a rough structure of:
* Log
* Trace(s)
* Event(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.
## Buffered Traces with standard JSON
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.
### Creating a Trace
```rust
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,
);
```
### Adding events to a Trace
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()`].
```rust
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));
```
### Serializing
The qlog crate has only been tested with `serde_json`, however other serializer
targets might work.
For example, serializing the trace created above:
```rust
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
}
]
}
}
]
}
```
## Streaming Traces JSON Text Sequences (JSON-SEQ)
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.
### Creating a TraceSeq
``` rust
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()`]:
```rust
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();
```
### Adding simple events
Once logging has started you can stream events. Simple events can be written in
one step using [`add_event()`]:
```rust
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();
```
### Adding events with frames
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:
```rust
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()`].
```rust
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()`]:
```rust
streamer.finish_log().ok();
```
### Serializing
Serialization to JSON occurs as methods on the [`QlogStreamer`]
are called. No additional steps are required.
[`Trace`]: struct.Trace.html
[`TraceSeq`]: struct.TraceSeq.html
[`VantagePoint`]: struct.VantagePoint.html
[`Configuration`]: struct.Configuration.html
[`qlog::Trace.events`]: struct.Trace.html#structfield.events
[`push_event()`]: struct.Trace.html#method.push_event
[`packet_sent_min()`]: event/struct.Event.html#method.packet_sent_min
[`QuicFrame::crypto()`]: enum.QuicFrame.html#variant.Crypto
[`QlogStreamer`]: struct.QlogStreamer.html
[`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
[`start_log()`]: struct.QlogStreamer.html#method.start_log
[`add_event()`]: struct.QlogStreamer.html#method.add_event
[`add_frame()`]: struct.QlogStreamer.html#method.add_frame
[`finish_frames()`]: struct.QlogStreamer.html#method.finish_frames
[`finish_log()`]: struct.QlogStreamer.html#method.finish_log
|