unitbus/journal/
mod.rs

1use crate::Result;
2
3use std::sync::Arc;
4
5#[derive(Clone, Debug)]
6pub struct Journal {
7    inner: Arc<crate::Inner>,
8}
9
10impl Journal {
11    pub(crate) fn new(inner: Arc<crate::Inner>) -> Self {
12        Self { inner }
13    }
14
15    /// Query journald logs using the configured backend.
16    ///
17    /// Default backend: `sdjournal` (feature=`journal-sdjournal`).
18    /// Alternative backend: `journalctl --output=json` (feature=`journal-cli`).
19    ///
20    /// The result is always bounded by `filter.limit` and `filter.max_bytes`. When limits are hit,
21    /// `JournalResult.truncated` is set to `true`.
22    pub async fn query(
23        &self,
24        filter: crate::types::journal::JournalFilter,
25    ) -> Result<crate::types::journal::JournalResult> {
26        #[cfg(feature = "journal-cli")]
27        {
28            return crate::journal::cli::query_journalctl(&self.inner.opts, filter).await;
29        }
30
31        #[cfg(all(not(feature = "journal-cli"), feature = "journal-sdjournal"))]
32        {
33            return crate::journal::sdjournal::query_sdjournal(&self.inner.opts, filter).await;
34        }
35
36        #[cfg(all(not(feature = "journal-cli"), not(feature = "journal-sdjournal")))]
37        {
38            let _ = filter;
39            return Err(crate::Error::BackendUnavailable {
40                backend: "journald",
41                detail: "no journald backend enabled (enable journal-cli or journal-sdjournal)"
42                    .to_string(),
43            });
44        }
45    }
46
47    /// Convenience helper that fetches a status snapshot and a bounded log slice around "now".
48    ///
49    /// The default time window is `now - 30s` to `now + 10s` (see `DiagnosisOptions::default`).
50    pub async fn diagnose_unit_failure(
51        &self,
52        unit: &str,
53        opts: crate::types::journal::DiagnosisOptions,
54    ) -> Result<crate::types::journal::Diagnosis> {
55        let unit = crate::util::canonicalize_unit_name(unit)?;
56
57        #[cfg(feature = "tracing")]
58        tracing::info!(
59            unit = %unit,
60            limit = opts.limit,
61            max_bytes = opts.max_bytes,
62            max_message_bytes = opts.max_message_bytes,
63            "diagnose_unit_failure"
64        );
65
66        let status = crate::units::Units::new(self.inner.clone())
67            .get_status(&unit)
68            .await?;
69
70        let now = std::time::SystemTime::now();
71        let since = match now.checked_sub(opts.window_before) {
72            Some(t) => t,
73            None => std::time::UNIX_EPOCH,
74        };
75        let until = now.checked_add(opts.window_after);
76
77        let filter = crate::types::journal::JournalFilter {
78            unit: Some(unit),
79            since: Some(since),
80            until,
81            after_cursor: None,
82            limit: opts.limit,
83            max_bytes: opts.max_bytes,
84            max_message_bytes: opts.max_message_bytes,
85            timeout: opts.timeout,
86            parse_error: opts.parse_error,
87        };
88
89        let res = self.query(filter).await?;
90        Ok(crate::types::journal::Diagnosis {
91            status,
92            logs: res.entries,
93            truncated: res.truncated,
94        })
95    }
96}
97
98#[cfg(feature = "journal-cli")]
99mod cli;
100
101#[cfg(all(feature = "journal-sdjournal", not(feature = "journal-cli")))]
102mod sdjournal;