atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git

Blame: xtask/src/log/sync.rs

7ff8adafe9e4atagen2026-10-22 12:30 UTC1
use std::{
7ff8adafe9e4atagen2026-10-22 12:30 UTC2
    collections::BTreeMap,
7ff8adafe9e4atagen2026-10-22 12:30 UTC3
    fs,
7ff8adafe9e4atagen2026-10-22 12:30 UTC4
    io::{self, BufRead, Write},
7ff8adafe9e4atagen2026-10-22 12:30 UTC5
    ops::Sub,
7ff8adafe9e4atagen2026-10-22 12:30 UTC6
    path,
7ff8adafe9e4atagen2026-10-22 12:30 UTC7
    time::Duration,
7ff8adafe9e4atagen2026-10-22 12:30 UTC8
};
7ff8adafe9e4atagen2026-10-22 12:30 UTC9
7ff8adafe9e4atagen2026-10-22 12:30 UTC10
use chrono::{DateTime, FixedOffset, TimeDelta};
7ff8adafe9e4atagen2026-10-22 12:30 UTC11
use indexmap::{IndexMap, map::Entry};
7ff8adafe9e4atagen2026-10-22 12:30 UTC12
use regex::RegexBuilder;
7ff8adafe9e4atagen2026-10-22 12:30 UTC13
use url::{Position as UrlPosition, Url};
7ff8adafe9e4atagen2026-10-22 12:30 UTC14
7ff8adafe9e4atagen2026-10-22 12:30 UTC15
use crate::Result;
7ff8adafe9e4atagen2026-10-22 12:30 UTC16
7ff8adafe9e4atagen2026-10-22 12:30 UTC17
const OUTPUT_TEMPLATE: &str = include_str!("sync.template.html");
7ff8adafe9e4atagen2026-10-22 12:30 UTC18
7ff8adafe9e4atagen2026-10-22 12:30 UTC19
pub(super) fn run(log_path: path::PathBuf, output_path: path::PathBuf) -> Result<()> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC20
    let find_sync = RegexBuilder::new(
7ff8adafe9e4atagen2026-10-22 12:30 UTC21
        r#"
7ff8adafe9e4atagen2026-10-22 12:30 UTC22
            # Datetime of the log line.
7ff8adafe9e4atagen2026-10-22 12:30 UTC23
            (?<datetime>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)
7ff8adafe9e4atagen2026-10-22 12:30 UTC24
7ff8adafe9e4atagen2026-10-22 12:30 UTC25
            # Ensure it's about the `http_client` scope.
7ff8adafe9e4atagen2026-10-22 12:30 UTC26
            .*matrix_sdk::http_client
7ff8adafe9e4atagen2026-10-22 12:30 UTC27
7ff8adafe9e4atagen2026-10-22 12:30 UTC28
            # Ensure it's about a sync.
7ff8adafe9e4atagen2026-10-22 12:30 UTC29
            .*>\ssync_once\{
7ff8adafe9e4atagen2026-10-22 12:30 UTC30
                conn_id="(?<connection_id>[^"]+)"
7ff8adafe9e4atagen2026-10-22 12:30 UTC31
7ff8adafe9e4atagen2026-10-22 12:30 UTC32
                # Since https://github.com/matrix-org/matrix-rust-sdk/pull/6118,
7ff8adafe9e4atagen2026-10-22 12:30 UTC33
                # we have `pos` and `timeout`. We must consider they are
7ff8adafe9e4atagen2026-10-22 12:30 UTC34
                # optional.
7ff8adafe9e4atagen2026-10-22 12:30 UTC35
                (
7ff8adafe9e4atagen2026-10-22 12:30 UTC36
                    .*?
7ff8adafe9e4atagen2026-10-22 12:30 UTC37
                    (\spos="(?<pos>[^"]+)")?
7ff8adafe9e4atagen2026-10-22 12:30 UTC38
                    \stimeout=(?<timeout>\d+)
7ff8adafe9e4atagen2026-10-22 12:30 UTC39
                )?
7ff8adafe9e4atagen2026-10-22 12:30 UTC40
            \}
7ff8adafe9e4atagen2026-10-22 12:30 UTC41
7ff8adafe9e4atagen2026-10-22 12:30 UTC42
            # Let's capture some data about `send()`!
7ff8adafe9e4atagen2026-10-22 12:30 UTC43
            \s>\ssend\{
7ff8adafe9e4atagen2026-10-22 12:30 UTC44
                request_id="REQ-(?<request_id>\d+)"
7ff8adafe9e4atagen2026-10-22 12:30 UTC45
                \smethod=(?<method>\S+)
7ff8adafe9e4atagen2026-10-22 12:30 UTC46
                \suri="(?<uri>[^"]+)"
7ff8adafe9e4atagen2026-10-22 12:30 UTC47
                # If there is a `request_size`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC48
                (.*\srequest_size="(?<request_size>[^"]+)")?
7ff8adafe9e4atagen2026-10-22 12:30 UTC49
                # If this is a response, there is a `status`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC50
                (.*\sstatus=(?<status>\d+))?
7ff8adafe9e4atagen2026-10-22 12:30 UTC51
                # If there is a `response_size`.
7ff8adafe9e4atagen2026-10-22 12:30 UTC52
                (.*\sresponse_size="(?<response_size>[^"]+)")?
7ff8adafe9e4atagen2026-10-22 12:30 UTC53
        "#,
7ff8adafe9e4atagen2026-10-22 12:30 UTC54
    )
7ff8adafe9e4atagen2026-10-22 12:30 UTC55
    .ignore_whitespace(true)
7ff8adafe9e4atagen2026-10-22 12:30 UTC56
    .build()
7ff8adafe9e4atagen2026-10-22 12:30 UTC57
    .expect("Failed to build the `find_sync` regex");
7ff8adafe9e4atagen2026-10-22 12:30 UTC58
7ff8adafe9e4atagen2026-10-22 12:30 UTC59
    let log_file = fs::File::open(&log_path)?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC60
    let mut output_file = fs::File::create(&output_path)?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC61
7ff8adafe9e4atagen2026-10-22 12:30 UTC62
    let reader = io::BufReader::new(log_file);
7ff8adafe9e4atagen2026-10-22 12:30 UTC63
    let mut number_of_analysed_lines = 0;
7ff8adafe9e4atagen2026-10-22 12:30 UTC64
    let mut number_of_matched_lines = 0;
7ff8adafe9e4atagen2026-10-22 12:30 UTC65
    let mut smallest_start_at = None;
7ff8adafe9e4atagen2026-10-22 12:30 UTC66
    let mut largest_end_at = None;
7ff8adafe9e4atagen2026-10-22 12:30 UTC67
7ff8adafe9e4atagen2026-10-22 12:30 UTC68
    let mut spans: BTreeMap<ConnectionId, IndexMap<RequestId, Span>> = BTreeMap::new();
7ff8adafe9e4atagen2026-10-22 12:30 UTC69
7ff8adafe9e4atagen2026-10-22 12:30 UTC70
    for (line_nth, line) in reader.lines().enumerate().map(|(nth, line)| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC71
        (
7ff8adafe9e4atagen2026-10-22 12:30 UTC72
            nth + 1,
7ff8adafe9e4atagen2026-10-22 12:30 UTC73
            line.unwrap_or_else(|error| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC74
                panic!("Failed to read line #{nth}\n{error}");
7ff8adafe9e4atagen2026-10-22 12:30 UTC75
            }),
7ff8adafe9e4atagen2026-10-22 12:30 UTC76
        )
7ff8adafe9e4atagen2026-10-22 12:30 UTC77
    }) {
7ff8adafe9e4atagen2026-10-22 12:30 UTC78
        number_of_analysed_lines += 1;
7ff8adafe9e4atagen2026-10-22 12:30 UTC79
7ff8adafe9e4atagen2026-10-22 12:30 UTC80
        if let Some(captures) = find_sync.captures(&line) {
7ff8adafe9e4atagen2026-10-22 12:30 UTC81
            number_of_matched_lines += 1;
7ff8adafe9e4atagen2026-10-22 12:30 UTC82
7ff8adafe9e4atagen2026-10-22 12:30 UTC83
            let date_time = DateTime::parse_from_rfc3339(
7ff8adafe9e4atagen2026-10-22 12:30 UTC84
                captures.name("datetime").expect("Failed to capture `datetime`").as_str(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC85
            )
7ff8adafe9e4atagen2026-10-22 12:30 UTC86
            .expect("Failed to parse `datetime`");
7ff8adafe9e4atagen2026-10-22 12:30 UTC87
            let connection_id =
7ff8adafe9e4atagen2026-10-22 12:30 UTC88
                captures.name("connection_id").expect("Failed to capture `connection_id`").as_str();
7ff8adafe9e4atagen2026-10-22 12:30 UTC89
            let pos = captures.name("pos").map(|pos| pos.as_str());
7ff8adafe9e4atagen2026-10-22 12:30 UTC90
            let timeout = captures.name("timeout").and_then(|timeout| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC91
                timeout.as_str().parse::<u64>().ok().map(Duration::from_millis)
7ff8adafe9e4atagen2026-10-22 12:30 UTC92
            });
7ff8adafe9e4atagen2026-10-22 12:30 UTC93
            let request_id = captures
7ff8adafe9e4atagen2026-10-22 12:30 UTC94
                .name("request_id")
7ff8adafe9e4atagen2026-10-22 12:30 UTC95
                .expect("Failed to capture `request_id`")
7ff8adafe9e4atagen2026-10-22 12:30 UTC96
                .as_str()
7ff8adafe9e4atagen2026-10-22 12:30 UTC97
                .parse()
7ff8adafe9e4atagen2026-10-22 12:30 UTC98
                .expect("Failed to parse `request_id`");
7ff8adafe9e4atagen2026-10-22 12:30 UTC99
            let method = captures.name("method").expect("Failed to capture `method`").as_str();
7ff8adafe9e4atagen2026-10-22 12:30 UTC100
            let uri = captures.name("uri").expect("Failed to capture `uri`").as_str();
7ff8adafe9e4atagen2026-10-22 12:30 UTC101
            let request_size =
7ff8adafe9e4atagen2026-10-22 12:30 UTC102
                captures.name("request_size").map(|request_size| request_size.as_str());
7ff8adafe9e4atagen2026-10-22 12:30 UTC103
            let response_size =
7ff8adafe9e4atagen2026-10-22 12:30 UTC104
                captures.name("response_size").map(|response_size| response_size.as_str());
7ff8adafe9e4atagen2026-10-22 12:30 UTC105
            let status = captures.name("status").and_then(|status| status.as_str().parse().ok());
7ff8adafe9e4atagen2026-10-22 12:30 UTC106
7ff8adafe9e4atagen2026-10-22 12:30 UTC107
            if let Some(smallest_start_at_inner) = smallest_start_at {
7ff8adafe9e4atagen2026-10-22 12:30 UTC108
                if smallest_start_at_inner > date_time {
7ff8adafe9e4atagen2026-10-22 12:30 UTC109
                    smallest_start_at = Some(date_time);
7ff8adafe9e4atagen2026-10-22 12:30 UTC110
                }
7ff8adafe9e4atagen2026-10-22 12:30 UTC111
            } else {
7ff8adafe9e4atagen2026-10-22 12:30 UTC112
                smallest_start_at = Some(date_time);
7ff8adafe9e4atagen2026-10-22 12:30 UTC113
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC114
7ff8adafe9e4atagen2026-10-22 12:30 UTC115
            if let Some(largest_end_at_inner) = largest_end_at {
7ff8adafe9e4atagen2026-10-22 12:30 UTC116
                if largest_end_at_inner < date_time {
7ff8adafe9e4atagen2026-10-22 12:30 UTC117
                    largest_end_at = Some(date_time);
7ff8adafe9e4atagen2026-10-22 12:30 UTC118
                }
7ff8adafe9e4atagen2026-10-22 12:30 UTC119
            } else {
7ff8adafe9e4atagen2026-10-22 12:30 UTC120
                largest_end_at = Some(date_time);
7ff8adafe9e4atagen2026-10-22 12:30 UTC121
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC122
7ff8adafe9e4atagen2026-10-22 12:30 UTC123
            let spans_for_connection_id = spans.entry(connection_id.to_owned()).or_default();
7ff8adafe9e4atagen2026-10-22 12:30 UTC124
7ff8adafe9e4atagen2026-10-22 12:30 UTC125
            match spans_for_connection_id.entry(request_id) {
7ff8adafe9e4atagen2026-10-22 12:30 UTC126
                Entry::Vacant(entry) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC127
                    entry.insert(Span {
7ff8adafe9e4atagen2026-10-22 12:30 UTC128
                        status: None,
7ff8adafe9e4atagen2026-10-22 12:30 UTC129
                        method: method.to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC130
                        uri: uri.to_owned(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC131
                        pos: pos.map(ToOwned::to_owned),
7ff8adafe9e4atagen2026-10-22 12:30 UTC132
                        timeout,
7ff8adafe9e4atagen2026-10-22 12:30 UTC133
                        request_size: request_size.map(ToOwned::to_owned),
7ff8adafe9e4atagen2026-10-22 12:30 UTC134
                        response_size: response_size.map(ToOwned::to_owned),
7ff8adafe9e4atagen2026-10-22 12:30 UTC135
                        start_at: date_time,
7ff8adafe9e4atagen2026-10-22 12:30 UTC136
                        duration: TimeDelta::zero(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC137
                        request_log_line: line_nth,
7ff8adafe9e4atagen2026-10-22 12:30 UTC138
                        response_log_line: None,
7ff8adafe9e4atagen2026-10-22 12:30 UTC139
                    });
7ff8adafe9e4atagen2026-10-22 12:30 UTC140
                }
7ff8adafe9e4atagen2026-10-22 12:30 UTC141
                Entry::Occupied(mut entry) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC142
                    let span = entry.get_mut();
7ff8adafe9e4atagen2026-10-22 12:30 UTC143
7ff8adafe9e4atagen2026-10-22 12:30 UTC144
                    if let Some(status) = status {
7ff8adafe9e4atagen2026-10-22 12:30 UTC145
                        span.status = Some(status);
7ff8adafe9e4atagen2026-10-22 12:30 UTC146
                    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC147
7ff8adafe9e4atagen2026-10-22 12:30 UTC148
                    span.duration = date_time.sub(&span.start_at);
7ff8adafe9e4atagen2026-10-22 12:30 UTC149
7ff8adafe9e4atagen2026-10-22 12:30 UTC150
                    if let Some(request_size) = request_size {
7ff8adafe9e4atagen2026-10-22 12:30 UTC151
                        span.request_size = Some(request_size.to_owned());
7ff8adafe9e4atagen2026-10-22 12:30 UTC152
                    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC153
7ff8adafe9e4atagen2026-10-22 12:30 UTC154
                    if let Some(response_size) = response_size {
7ff8adafe9e4atagen2026-10-22 12:30 UTC155
                        span.response_size = Some(response_size.to_owned());
7ff8adafe9e4atagen2026-10-22 12:30 UTC156
                    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC157
7ff8adafe9e4atagen2026-10-22 12:30 UTC158
                    span.response_log_line = Some(line_nth);
7ff8adafe9e4atagen2026-10-22 12:30 UTC159
                }
7ff8adafe9e4atagen2026-10-22 12:30 UTC160
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC161
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC162
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC163
7ff8adafe9e4atagen2026-10-22 12:30 UTC164
    let smallest_start_at =
7ff8adafe9e4atagen2026-10-22 12:30 UTC165
        smallest_start_at.map(|date_time| date_time.timestamp_millis()).unwrap_or_default();
7ff8adafe9e4atagen2026-10-22 12:30 UTC166
    let largest_end_at =
7ff8adafe9e4atagen2026-10-22 12:30 UTC167
        largest_end_at.map(|date_time| date_time.timestamp_millis()).unwrap_or_default();
7ff8adafe9e4atagen2026-10-22 12:30 UTC168
    let end_at = largest_end_at.saturating_sub(smallest_start_at).to_string();
7ff8adafe9e4atagen2026-10-22 12:30 UTC169
    let rows = spans
7ff8adafe9e4atagen2026-10-22 12:30 UTC170
        .iter()
7ff8adafe9e4atagen2026-10-22 12:30 UTC171
        .flat_map(|(connection_id, spans)| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC172
            spans.iter().map(
7ff8adafe9e4atagen2026-10-22 12:30 UTC173
                |(
7ff8adafe9e4atagen2026-10-22 12:30 UTC174
                    request_id,
7ff8adafe9e4atagen2026-10-22 12:30 UTC175
                    Span {
7ff8adafe9e4atagen2026-10-22 12:30 UTC176
                        status,
7ff8adafe9e4atagen2026-10-22 12:30 UTC177
                        method,
7ff8adafe9e4atagen2026-10-22 12:30 UTC178
                        uri,
7ff8adafe9e4atagen2026-10-22 12:30 UTC179
                        pos,
7ff8adafe9e4atagen2026-10-22 12:30 UTC180
                        timeout,
7ff8adafe9e4atagen2026-10-22 12:30 UTC181
                        request_size,
7ff8adafe9e4atagen2026-10-22 12:30 UTC182
                        response_size,
7ff8adafe9e4atagen2026-10-22 12:30 UTC183
                        start_at,
7ff8adafe9e4atagen2026-10-22 12:30 UTC184
                        duration,
7ff8adafe9e4atagen2026-10-22 12:30 UTC185
                        request_log_line,
7ff8adafe9e4atagen2026-10-22 12:30 UTC186
                        response_log_line
7ff8adafe9e4atagen2026-10-22 12:30 UTC187
                    },
7ff8adafe9e4atagen2026-10-22 12:30 UTC188
                )| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC189
                    let uri = Url::parse(uri);
7ff8adafe9e4atagen2026-10-22 12:30 UTC190
                    let duration = duration.num_milliseconds().unsigned_abs();
7ff8adafe9e4atagen2026-10-22 12:30 UTC191
                    let timeout: Option<u64> = timeout.map(|timeout| timeout.as_millis().try_into().expect("Failed to cast a u128 to u64"));
7ff8adafe9e4atagen2026-10-22 12:30 UTC192
7ff8adafe9e4atagen2026-10-22 12:30 UTC193
                    format!(
7ff8adafe9e4atagen2026-10-22 12:30 UTC194
                        "    <tr id=\"{connection_id}-{request_id}\">
7ff8adafe9e4atagen2026-10-22 12:30 UTC195
      <td><code>{connection_id}</code></td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC196
      <td><a href=\"#{connection_id}-{request_id}\" title=\"Permalink to this line\"><code>{request_id}</code></a></td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC197
      <td data-status-family=\"{status_family}\"><span>{status}</span></td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC198
      <td><code>{method}</code></td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC199
      <td title=\"{domain}\">{domain}</td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC200
      <td title=\"{path}\">{path}</td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC201
      <td>{timeout_label}</td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC202
      <td>{request_size}</td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC203
      <td>{response_size}</td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC204
      <td><time datetime=\"{date_time}\">{time}</time></td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC205
      <td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC206
        <div class=\"span\" style=\"--start-at: {start_at}; --timeout: {timeout}; --duration: {duration}\"><span>{duration_label}</span></div>
7ff8adafe9e4atagen2026-10-22 12:30 UTC207
        <details>
7ff8adafe9e4atagen2026-10-22 12:30 UTC208
          <summary><span class=\"hidden\">information</span></summary>
7ff8adafe9e4atagen2026-10-22 12:30 UTC209
          <ul>
7ff8adafe9e4atagen2026-10-22 12:30 UTC210
            <li>Request log line number: {request_log_line}</li>
7ff8adafe9e4atagen2026-10-22 12:30 UTC211
            <li>Response log line number: {response_log_line}</li>
7ff8adafe9e4atagen2026-10-22 12:30 UTC212
            <li>Sliding Sync <code>pos</code>: <span class=\"text-overflow\">{pos}</span></li>
7ff8adafe9e4atagen2026-10-22 12:30 UTC213
          </ul>
7ff8adafe9e4atagen2026-10-22 12:30 UTC214
        </details>
7ff8adafe9e4atagen2026-10-22 12:30 UTC215
      </td>
7ff8adafe9e4atagen2026-10-22 12:30 UTC216
    </tr>
7ff8adafe9e4atagen2026-10-22 12:30 UTC217
",
7ff8adafe9e4atagen2026-10-22 12:30 UTC218
                        connection_id = connection_id.clone(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC219
                        status = status
7ff8adafe9e4atagen2026-10-22 12:30 UTC220
                            .map(|status| status.to_string())
7ff8adafe9e4atagen2026-10-22 12:30 UTC221
                            .unwrap_or_else(|| "×".to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC222
                        status_family = status
7ff8adafe9e4atagen2026-10-22 12:30 UTC223
                            .map(|status| (if status > 0 { status / 100 } else { 0 } ).to_string())
7ff8adafe9e4atagen2026-10-22 12:30 UTC224
                            .unwrap_or_else(|| "cancelled".to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC225
                        domain = uri
7ff8adafe9e4atagen2026-10-22 12:30 UTC226
                            .as_ref()
7ff8adafe9e4atagen2026-10-22 12:30 UTC227
                            .map(|uri| uri[UrlPosition::BeforeHost..UrlPosition::BeforePath].to_owned())
7ff8adafe9e4atagen2026-10-22 12:30 UTC228
                            .unwrap_or_default(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC229
                        path = uri
7ff8adafe9e4atagen2026-10-22 12:30 UTC230
                            .as_ref()
7ff8adafe9e4atagen2026-10-22 12:30 UTC231
                            .map(|uri| uri[UrlPosition::BeforePath..].to_owned())
7ff8adafe9e4atagen2026-10-22 12:30 UTC232
                            .unwrap_or_default(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC233
                        timeout_label = timeout
7ff8adafe9e4atagen2026-10-22 12:30 UTC234
                            .map(|timeout| format!("{timeout}ms"))
7ff8adafe9e4atagen2026-10-22 12:30 UTC235
                            .unwrap_or_else(|| "(unknown)".to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC236
                        timeout = timeout
7ff8adafe9e4atagen2026-10-22 12:30 UTC237
                            .map(|timeout| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC238
                                // The response was received before hitting the timeout.
7ff8adafe9e4atagen2026-10-22 12:30 UTC239
                                if duration < timeout {
7ff8adafe9e4atagen2026-10-22 12:30 UTC240
                                    0
7ff8adafe9e4atagen2026-10-22 12:30 UTC241
                                } else {
7ff8adafe9e4atagen2026-10-22 12:30 UTC242
                                    timeout
7ff8adafe9e4atagen2026-10-22 12:30 UTC243
                                }
7ff8adafe9e4atagen2026-10-22 12:30 UTC244
                            })
7ff8adafe9e4atagen2026-10-22 12:30 UTC245
                            .unwrap_or(0),
7ff8adafe9e4atagen2026-10-22 12:30 UTC246
                        request_size = request_size
7ff8adafe9e4atagen2026-10-22 12:30 UTC247
                            .clone()
7ff8adafe9e4atagen2026-10-22 12:30 UTC248
                            .map(|request_size| request_size.to_string())
7ff8adafe9e4atagen2026-10-22 12:30 UTC249
                            .unwrap_or_else(|| "".to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC250
                        response_size = response_size
7ff8adafe9e4atagen2026-10-22 12:30 UTC251
                            .clone()
7ff8adafe9e4atagen2026-10-22 12:30 UTC252
                            .map(|response_size| response_size.to_string())
7ff8adafe9e4atagen2026-10-22 12:30 UTC253
                            .unwrap_or_else(|| "".to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC254
                        date_time = start_at.format("%+"),
7ff8adafe9e4atagen2026-10-22 12:30 UTC255
                        time = start_at.format("%H:%M:%S%.3f"),
7ff8adafe9e4atagen2026-10-22 12:30 UTC256
                        start_at = start_at
7ff8adafe9e4atagen2026-10-22 12:30 UTC257
                            .timestamp_millis()
7ff8adafe9e4atagen2026-10-22 12:30 UTC258
                            .saturating_sub(smallest_start_at),
7ff8adafe9e4atagen2026-10-22 12:30 UTC259
                        duration_label = if duration > 0 { format!("{duration}ms") } else { "<em>cancelled</em>".to_owned() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC260
                        response_log_line = response_log_line.map(|line| line.to_string()).unwrap_or_else(|| "(none)".to_owned()),
7ff8adafe9e4atagen2026-10-22 12:30 UTC261
                        pos = pos.clone().unwrap_or_else(|| "(unknown)".to_owned())
7ff8adafe9e4atagen2026-10-22 12:30 UTC262
                    )
7ff8adafe9e4atagen2026-10-22 12:30 UTC263
                },
7ff8adafe9e4atagen2026-10-22 12:30 UTC264
            )
7ff8adafe9e4atagen2026-10-22 12:30 UTC265
        })
7ff8adafe9e4atagen2026-10-22 12:30 UTC266
        .collect::<String>();
7ff8adafe9e4atagen2026-10-22 12:30 UTC267
7ff8adafe9e4atagen2026-10-22 12:30 UTC268
    let output = OUTPUT_TEMPLATE
7ff8adafe9e4atagen2026-10-22 12:30 UTC269
        .replace("{log_file}", &log_path.to_string_lossy())
7ff8adafe9e4atagen2026-10-22 12:30 UTC270
        .replace("{end_at}", &end_at)
7ff8adafe9e4atagen2026-10-22 12:30 UTC271
        .replace("{rows}", &rows);
7ff8adafe9e4atagen2026-10-22 12:30 UTC272
7ff8adafe9e4atagen2026-10-22 12:30 UTC273
    output_file.write_all(output.as_bytes()).expect("Failed to write the output");
7ff8adafe9e4atagen2026-10-22 12:30 UTC274
7ff8adafe9e4atagen2026-10-22 12:30 UTC275
    println!(
7ff8adafe9e4atagen2026-10-22 12:30 UTC276
        "\nNumber of analysed log lines: {number_of_analysed_lines}\n\
7ff8adafe9e4atagen2026-10-22 12:30 UTC277
        Number of matched lines: {number_of_matched_lines}\n\
7ff8adafe9e4atagen2026-10-22 12:30 UTC278
        Output file: {output_path:?}\n\
7ff8adafe9e4atagen2026-10-22 12:30 UTC279
        Done!"
7ff8adafe9e4atagen2026-10-22 12:30 UTC280
    );
7ff8adafe9e4atagen2026-10-22 12:30 UTC281
7ff8adafe9e4atagen2026-10-22 12:30 UTC282
    Ok(())
7ff8adafe9e4atagen2026-10-22 12:30 UTC283
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC284
7ff8adafe9e4atagen2026-10-22 12:30 UTC285
type ConnectionId = String;
7ff8adafe9e4atagen2026-10-22 12:30 UTC286
type RequestId = u32;
7ff8adafe9e4atagen2026-10-22 12:30 UTC287
7ff8adafe9e4atagen2026-10-22 12:30 UTC288
#[derive(Debug)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC289
struct Span {
7ff8adafe9e4atagen2026-10-22 12:30 UTC290
    status: Option<u8>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC291
    method: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC292
    uri: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC293
    pos: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC294
    timeout: Option<Duration>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC295
    request_size: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC296
    response_size: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC297
    start_at: DateTime<FixedOffset>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC298
    duration: TimeDelta,
7ff8adafe9e4atagen2026-10-22 12:30 UTC299
    request_log_line: usize,
7ff8adafe9e4atagen2026-10-22 12:30 UTC300
    response_log_line: Option<usize>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC301
}