1mod common;
2
3pub mod entry;
4pub mod error;
5pub mod klogctl;
7pub mod kmsgfile;
9
10#[cfg(feature = "sync")]
11use std::iter::Iterator;
12
13#[cfg(feature = "async")]
14use core::pin::Pin;
15#[cfg(feature = "async")]
16use futures::stream::Stream;
17#[cfg(feature = "async")]
18use futures::task::{Context, Poll};
19#[cfg(feature = "async")]
20use pin_project::pin_project;
21
22#[derive(Clone, Copy, Debug)]
23pub enum Backend {
24 Default,
25 KLogCtl,
26 DevKMsg,
27}
28
29#[cfg(feature = "sync")]
30pub enum EntriesIterator {
31 KLogCtl(klogctl::KLogEntries),
32 DevKMsg(kmsgfile::KMsgEntriesIter),
33}
34#[cfg(feature = "sync")]
35impl Iterator for EntriesIterator {
36 type Item = Result<entry::Entry, error::RMesgError>;
37 fn next(&mut self) -> Option<Self::Item> {
38 match self {
39 Self::KLogCtl(k) => k.next(),
40 Self::DevKMsg(d) => d.next(),
41 }
42 }
43}
44
45#[pin_project(project = EntriesStreamPinnedProjection)]
46#[cfg(feature = "async")]
47pub enum EntriesStream {
48 KLogCtl(#[pin] klogctl::KLogEntries),
49 DevKMsg(#[pin] kmsgfile::KMsgEntriesStream),
50}
51#[cfg(feature = "async")]
52impl Stream for EntriesStream {
53 type Item = Result<entry::Entry, error::RMesgError>;
54 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
55 match self.project() {
56 EntriesStreamPinnedProjection::KLogCtl(k) => k.poll_next(cx),
57 EntriesStreamPinnedProjection::DevKMsg(d) => d.poll_next(cx),
58 }
59 }
60}
61
62pub fn log_entries(b: Backend, clear: bool) -> Result<Vec<entry::Entry>, error::RMesgError> {
63 match b {
64 Backend::Default => match kmsgfile::kmsg(None) {
65 Ok(e) => Ok(e),
66 Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
67 eprintln!(
68 "Falling back from device file to klogctl syscall due to error: {}",
69 s
70 );
71 klogctl::klog(clear)
72 }
73 Err(e) => Err(e),
74 },
75 Backend::KLogCtl => klogctl::klog(clear),
76 Backend::DevKMsg => kmsgfile::kmsg(None),
77 }
78}
79
80pub fn logs_raw(b: Backend, clear: bool) -> Result<String, error::RMesgError> {
81 match b {
82 Backend::Default => match kmsgfile::kmsg_raw(None) {
83 Ok(e) => Ok(e),
84 Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
85 eprintln!(
86 "Falling back from device file to klogctl syscall due to error: {}",
87 s
88 );
89 klogctl::klog_raw(clear)
90 }
91 Err(e) => Err(e),
92 },
93 Backend::KLogCtl => klogctl::klog_raw(clear),
94 Backend::DevKMsg => kmsgfile::kmsg_raw(None),
95 }
96}
97
98#[cfg(feature = "sync")]
99pub fn logs_iter(b: Backend, clear: bool, raw: bool) -> Result<EntriesIterator, error::RMesgError> {
100 match b {
101 Backend::Default => match kmsgfile::KMsgEntriesIter::with_options(None, raw) {
102 Ok(e) => Ok(EntriesIterator::DevKMsg(e)),
103 Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
104 eprintln!(
105 "Falling back from device file to klogctl syscall due to error: {}",
106 s
107 );
108 Ok(EntriesIterator::KLogCtl(
109 klog_entries_only_if_timestamp_enabled(clear)?,
110 ))
111 }
112 Err(e) => Err(e),
113 },
114 Backend::KLogCtl => Ok(EntriesIterator::KLogCtl(
115 klog_entries_only_if_timestamp_enabled(clear)?,
116 )),
117 Backend::DevKMsg => Ok(EntriesIterator::DevKMsg(
118 kmsgfile::KMsgEntriesIter::with_options(None, raw)?,
119 )),
120 }
121}
122
123#[cfg(feature = "async")]
124pub async fn logs_stream(
125 b: Backend,
126 clear: bool,
127 raw: bool,
128) -> Result<EntriesStream, error::RMesgError> {
129 match b {
130 Backend::Default => match kmsgfile::KMsgEntriesStream::with_options(None, raw).await {
131 Ok(e) => Ok(EntriesStream::DevKMsg(e)),
132 Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
133 eprintln!(
134 "Falling back from device file to klogctl syscall due to error: {}",
135 s
136 );
137 Ok(EntriesStream::KLogCtl(
138 klog_entries_only_if_timestamp_enabled(clear)?,
139 ))
140 }
141 Err(e) => Err(e),
142 },
143 Backend::KLogCtl => Ok(EntriesStream::KLogCtl(
144 klog_entries_only_if_timestamp_enabled(clear)?,
145 )),
146 Backend::DevKMsg => Ok(EntriesStream::DevKMsg(
147 kmsgfile::KMsgEntriesStream::with_options(None, raw).await?,
148 )),
149 }
150}
151
152fn klog_entries_only_if_timestamp_enabled(
153 clear: bool,
154) -> Result<klogctl::KLogEntries, error::RMesgError> {
155 let log_timestamps_enabled = klogctl::klog_timestamps_enabled()?;
156
157 if !log_timestamps_enabled {
159 eprintln!("WARNING: Timestamps are disabled but tailing/following logs (as you've requested) requires them.");
160 eprintln!("Aboring program.");
161 eprintln!("You can enable timestamps by running the following: ");
162 eprintln!(" echo Y > /sys/module/printk/parameters/time");
163 return Err(error::RMesgError::KLogTimestampsDisabled);
164 }
165
166 klogctl::KLogEntries::with_options(clear, klogctl::SUGGESTED_POLL_INTERVAL)
167}
168
169#[cfg(all(test, target_os = "linux"))]
173mod test {
174 use super::*;
175 #[cfg(feature = "async")]
176 use tokio_stream::StreamExt;
177
178 #[test]
179 fn test_log_entries() {
180 let entries = log_entries(Backend::Default, false);
181 assert!(entries.is_ok(), "Response from kmsg not Ok");
182 assert!(!entries.unwrap().is_empty(), "Should have non-empty logs");
183 }
184
185 #[cfg(feature = "sync")]
186 #[test]
187 fn test_iterator() {
188 let iterator_result = logs_iter(Backend::Default, false, false);
194 assert!(iterator_result.is_ok());
195
196 let iterator = iterator_result.unwrap();
197
198 for (count, entry) in iterator.enumerate() {
200 assert!(entry.is_ok());
201 if count > 10 {
202 break;
203 }
204 }
205 }
206
207 #[cfg(feature = "async")]
208 #[tokio::test]
209 async fn test_stream() {
210 let stream_result = logs_stream(Backend::Default, false, false).await;
216 assert!(stream_result.is_ok());
217
218 let mut stream = stream_result.unwrap();
219
220 let mut count: u32 = 0;
222 while let Some(entry) = stream.next().await {
223 assert!(entry.is_ok());
224 count += 1;
225 if count > 10 {
226 break;
227 }
228 }
229 }
230}