netlog/lib.rs
1// Copyright (C) 2025, 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 netlog crate is a reverse-engineered deserializer for the Chrome
28//! [netlog] format. It supports QUIC and HTTP(/2 and /3) events.
29//!
30//! # Overview
31//!
32//! Chromium-based browsers allow users to enable detailed logging, netlog,
33//! which is useful for debugging interoperability or performance issues. A
34//! netlog file uses a kind of line-delimited JSON format. The first line
35//! contains "constants", which are specific to the version of the software used
36//! to generate the log. These constants are used for a form of compressed
37//! encoding for the netlog events that appear on each subsequent newline.
38//!
39//! This crate supports parsing a netlog file and converting a subset of netlog
40//! events into Rust structures, via Serde.
41//!
42//! # Example usage
43//!
44//! Assuming a netlog file name of `chrome-net-export-log-error.json`, the first
45//! task is to create a `BufReader` for the file and initialize the netlog
46//! constants.
47//!
48//! ```no_run
49//! use netlog::read_netlog_constants;
50//! use std::fs::File;
51//! use std::io::BufReader;
52//!
53//! let mut reader =
54//! BufReader::new(File::open("chrome-net-export-log-error.json").unwrap());
55//!
56//! let constants = read_netlog_constants(&mut reader).unwrap();
57//! ```
58//!
59//! Then move on to parsing the netlog file until the end.
60//!
61//! ```no_run
62//! # use std::io::BufReader;
63//! # use std::fs::File;
64//! # use netlog::read_netlog_constants;
65//! use netlog::read_netlog_record;
66//! use netlog::EventHeader;
67//! use netlog::h2::Http2SessionEvent;
68//! use netlog::quic::QuicSessionEvent;
69//! # let mut reader =
70//! # BufReader::new(File::open("chrome-net-export-log-error.json").unwrap());
71//! # let constants = read_netlog_constants(&mut reader).unwrap();
72//! // The second line of a netlog is `"events" [`, which can be skipped over.
73//! read_netlog_record(&mut reader);
74//!
75//! while let Some(record) = read_netlog_record(&mut reader) {
76//! let res: Result<EventHeader, serde_json::Error> =
77//! serde_json::from_slice(&record);
78//!
79//! match res {
80//! Ok(mut event_hdr) => {
81//! event_hdr.populate_strings(&constants);
82//! event_hdr.time_num = event_hdr.time.parse::<u64>().unwrap();
83//!
84//! // Netlogs can hold many different sessions.
85//! // Application might want to track these separately
86//! if event_hdr.phase_string == "PHASE_BEGIN" {
87//! match event_hdr.ty_string.as_str() {
88//! "HTTP2_SESSION" => {
89//! let ev: Http2SessionEvent =
90//! serde_json::from_slice(&record).unwrap();
91//! // Handle new session event ...
92//! },
93//! "QUIC_SESSION" => {
94//! let ev: QuicSessionEvent =
95//! serde_json::from_slice(&record).unwrap();
96//! // Handle new session event ...
97//! },
98//!
99//! // Ignore others
100//! _ => (),
101//! }
102//! }
103//!
104//! // Try to parse other events.
105//! if let Some(ev) = netlog::parse_event(&event_hdr, &record) {
106//! // Handle parsed event.
107//! }
108//! },
109//!
110//! Err(e) => {
111//! println!("Error deserializing: {}", e);
112//! println!("input value {}", String::from_utf8_lossy(&record));
113//! },
114//! }
115//! }
116//! ```
117//!
118//! [netlog]:
119//! (https://www.chromium.org/developers/design-documents/network-stack/netlog/)
120use std::io::BufRead;
121
122use serde::Deserialize;
123
124use crate::constants::Constants;
125use crate::constants::ConstantsLine;
126
127#[derive(Deserialize, Debug, Default)]
128pub struct EventSource {
129 #[serde(skip)]
130 pub start_time_int: u64,
131
132 pub id: i64,
133 pub start_time: String,
134 #[serde(rename = "type")]
135 pub ty: i64,
136}
137
138#[derive(Deserialize, Debug, Default)]
139pub struct EventHeader {
140 #[serde(skip)]
141 pub ty_string: String,
142 #[serde(skip)]
143 pub phase_string: String,
144 #[serde(skip)]
145 pub time_num: u64,
146
147 pub phase: i64,
148 pub source: EventSource,
149 pub time: String,
150 #[serde(rename = "type")]
151 pub ty: i64,
152}
153
154impl EventHeader {
155 /// Populate the event details based on the provided netlog file constants.
156 pub fn populate_strings(&mut self, constants: &constants::Constants) {
157 self.ty_string = constants.log_event_types_id_keyed[&self.ty].clone();
158 self.phase_string =
159 constants.log_event_phase_id_keyed[&self.phase].clone();
160 }
161}
162
163#[derive(Deserialize, Debug, Default)]
164pub struct SourceDependency {
165 pub id: i64,
166 #[serde(rename = "type")]
167 pub ty: i64,
168}
169
170/// The core netlog event type with several domain-specific variants.
171#[derive(Debug)]
172pub enum Event {
173 Http(http::Event),
174 H2(h2::Event),
175 H3(h3::Event),
176 Quic(quic::Event),
177}
178
179/// Read the netlog constants from a netlog file accessed by a BufRead.
180pub fn read_netlog_constants<R: BufRead>(
181 reader: &mut R,
182) -> Result<Constants, serde_json::Error> {
183 let mut buf = Vec::<u8>::new();
184
185 // Read the constants line and replace the trailing comma (,) with a brace
186 // (}) to close the object and make it parseable.
187 let len = reader.read_until(b'\n', &mut buf).unwrap();
188 buf[len - 2] = b'}';
189
190 let res: Result<ConstantsLine, serde_json::Error> =
191 serde_json::from_slice(&buf);
192
193 match res {
194 Ok(mut line) => {
195 line.constants.populate_id_keyed();
196
197 Ok(line.constants)
198 },
199
200 Err(e) => {
201 log::error!("Error deserializing constants: {}", e);
202
203 Err(e)
204 },
205 }
206}
207
208/// Reads a single record from a netlog file accessed by a BufRead.
209pub fn read_netlog_record<R: BufRead>(reader: &mut R) -> Option<Vec<u8>> {
210 let mut buf = Vec::<u8>::new();
211 let size = reader.read_until(b'\n', &mut buf).unwrap();
212
213 if size <= 1 {
214 return None;
215 }
216
217 // After netlog events, line holds polledData struct. Ignore it and return
218 if buf[0] != b'{' {
219 return None;
220 }
221
222 // Remove trailing comma and newline
223 buf.truncate(buf.len() - 2);
224
225 // Last line of events closes array. Lets ignore it.
226 if buf[buf.len() - 1] == b']' {
227 buf.truncate(buf.len() - 1);
228 }
229
230 log::trace!(
231 "read record={}",
232 String::from_utf8(buf.clone()).expect("from_utf8 failed")
233 );
234
235 Some(buf)
236}
237
238/// Parses the provided `event` based on the event type provided in `event_hdr`.
239pub fn parse_event(event_hdr: &EventHeader, event: &[u8]) -> Option<Event> {
240 if event_hdr.ty_string.starts_with("HTTP_") {
241 return http::parse_event(event_hdr, event);
242 } else if event_hdr.ty_string.starts_with("HTTP2_") {
243 return h2::parse_event(event_hdr, event);
244 } else if event_hdr.ty_string.starts_with("HTTP3_") {
245 return h3::parse_event(event_hdr, event);
246 } else if event_hdr.ty_string.starts_with("QUIC") {
247 return quic::parse_event(event_hdr, event);
248 }
249
250 None
251}
252
253pub mod constants;
254pub mod h2;
255pub mod h3;
256pub mod http;
257pub mod quic;