Skip to main content

qubit_progress/reporter/
text_reporter.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Line-oriented human-readable event reporter.
9
10use std::{
11    fmt::Write as _,
12    io::Write,
13    sync::{
14        Mutex,
15        PoisonError,
16    },
17};
18
19use crate::{
20    Event,
21    Reporter,
22    ReporterError,
23};
24
25/// Writes one complete human-readable record for each reported event.
26pub struct TextReporter<W> {
27    /// Writer serialized across concurrent reporter calls.
28    writer: Mutex<W>,
29}
30
31impl<W> TextReporter<W> {
32    /// Creates a text reporter that owns `writer`.
33    #[must_use]
34    pub const fn new(writer: W) -> Self {
35        Self {
36            writer: Mutex::new(writer),
37        }
38    }
39
40    /// Consumes the reporter and returns its writer.
41    ///
42    /// Returns the contained writer in the error when a reporting thread
43    /// panicked while holding the mutex.
44    pub fn into_inner(self) -> Result<W, PoisonError<W>> {
45        self.writer.into_inner()
46    }
47}
48
49impl<W> Reporter for TextReporter<W>
50where
51    W: Write + Send,
52{
53    /// Formats and writes one complete event line under the writer lock.
54    fn report(&self, event: &Event) -> Result<(), ReporterError> {
55        let mut line = format_event(event);
56        line.push('\n');
57        let mut writer = self.writer.lock().map_err(|_| {
58            ReporterError::message("text reporter mutex is poisoned")
59        })?;
60        writer
61            .write_all(line.as_bytes())
62            .map_err(ReporterError::new)
63    }
64}
65
66/// Produces one complete human-readable event record with escaped metadata.
67fn format_event(event: &Event) -> String {
68    let mut line = format!(
69        "operation={} sequence={} phase={} elapsed={:?}",
70        event.operation_id(),
71        event.sequence(),
72        event.phase().as_str(),
73        event.elapsed(),
74    );
75    if let Some(stage) = event.stage() {
76        let _ = write!(
77            line,
78            " stage={}({}) position={:?} total={:?}",
79            stage.id().escape_default(),
80            stage.name().escape_default(),
81            stage.position_value(),
82            stage.total(),
83        );
84    }
85    for (key, value) in event.attributes().iter() {
86        let _ = write!(
87            line,
88            " attribute={}({})",
89            key.escape_default(),
90            value.escape_default(),
91        );
92    }
93    for metric in event.metrics() {
94        let _ = write!(
95            line,
96            " metric={}({}) total={:?} completed={} active={} succeeded={} failed={} cancelled={}",
97            metric.id().escape_default(),
98            metric.name().escape_default(),
99            metric.total(),
100            metric.completed(),
101            metric.active(),
102            metric.succeeded(),
103            metric.failed(),
104            metric.cancelled(),
105        );
106    }
107    line
108}