12个重大版本

0.13.0 2024年4月15日
0.12.0 2024年2月2日
0.11.0 2024年1月9日
0.10.0 2023年11月10日
0.1.0 2019年12月11日

#757 in 网络编程

Download history 5689/week @ 2024-04-27 3596/week @ 2024-05-04 3416/week @ 2024-05-11 4134/week @ 2024-05-18 2742/week @ 2024-05-25 2615/week @ 2024-06-01 3006/week @ 2024-06-08 4159/week @ 2024-06-15 3533/week @ 2024-06-22 4181/week @ 2024-06-29 4113/week @ 2024-07-06 3944/week @ 2024-07-13 3297/week @ 2024-07-20 5747/week @ 2024-07-27 4094/week @ 2024-08-03 2911/week @ 2024-08-10

16,595每月下载量
3个包中使用(通过 quiche

BSD-2-Clause

135KB
2.5K SLoC

qlog包是qlog 主日志模式QUIC事件定义HTTP/3和QPACK事件定义的实现。该包提供了一个可用于具有事件的跟踪的数据模型。它支持序列化和反序列化,但将日志I/O选择推迟到应用程序中。

该包使用Serde在Rust和JSON之间进行转换。

概述

qlog是一种分层日志格式,大致结构为

  • 日志
    • 跟踪
      • 事件

实际上,一个QUIC连接映射到一个包含一个或多个事件的单独跟踪文件。应用程序可以决定是否将不同连接的跟踪组合到同一个日志中。

带有标准JSON的缓冲跟踪

Trace 是一个单独的JSON对象。它包含元数据,如捕获的 VantagePointConfiguration,以及 Event 数组中的协议事件数据。

JSON跟踪允许应用程序在最终序列化为完整的JSON对象之前向其中追加事件。

创建跟踪

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 对象添加到 qlog::Trace.events

以下示例演示了如何记录包含单个加密帧的 qlog QUIC packet_sent 事件。它构建了 Event 所需的元素,然后使用 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));

序列化

qlog crate 仅与 serde_json 进行过测试,但是其他序列化目标可能也可以工作。

例如,序列化上面创建的跟踪

serde_json::to_string_pretty(&trace).unwrap();

将生成以下内容

{
  "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
          }
        ]
      }
    }
  ]
}

流式传输跟踪 JSON 文本序列(JSON-SEQ)

为了支持 qlog 的流式序列化,draft-ietf-quic-qlog-main-schema-01 引入了对 RFC 7464 JSON 文本序列(JSON-SEQ)的支持。qlog crate 支持此格式并提供帮助流式传输的实用工具。

TraceSeq 包含元数据,如捕获的 VantagePoint 和配置 Configuration。然而,协议事件数据作为包含记录分隔符字符、序列化的 Event 和换行符的单独行进行处理。

创建 TraceSeq

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,
);

创建具有 Write 特性的对象

let mut file = std::fs::File::create("foo.sqlog").unwrap();

创建一个 QlogStreamer 并使用 start_log() 方法开始将序列化到 foo.sqlog

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();

添加简单事件

一旦开始记录,您就可以流式传输事件。简单事件可以使用 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();

添加带有帧的事件

某些事件包含可选的 QUIC 帧数组。如果事件有 Some(Vec<QuicFrame>),即使它是空的,流式传输器也会进入一个帧序列化模式,必须在记录其他事件之前完成。

在这个示例中,创建了一个具有空帧数组的 PacketSent 事件,稍后写入帧

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();

在这个示例中,QUIC 数据包中包含的帧是 PING 和 PADDING。每个帧都使用 add_frame() 方法写入。使用 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();

一旦所有事件都已写入,可以使用 finish_log() 将日志最终化。

streamer.finish_log().ok();

序列化

JSON序列化在调用QlogStreamer的方法时发生。无需额外步骤。

依赖项

~2.4–3.5MB
~69K SLoC