coap_message_demos/
log.rs

1//! Logging demo application
2//!
3//! This contains three components, from the most universal to the most demo-like:
4//!
5//! * A ringbuffered implementation of the [log] interface (based on [scroll-ring])
6//! * A way of accessing messages logged into this from CoAP
7//! * A way of injecting arbitrary log messages through CoAP
8//!
9//! It's implemented in a rather stupid-but-straightforward fashion; a better implementation would
10//! would likely use [defmt](https://docs.rs/defmt/).
11//!
12//!
13//! There is no separate handler builder for all the implemented handlers; see the top-level
14//! [crate::full_application_tree] builder for one that includes everything from here.
15
16use coap_message::{Code, MinimalWritableMessage};
17use coap_message_utils::Error;
18use coap_numbers::code;
19
20/// A ring-buffered log structure
21pub struct Log {
22    data: scroll_ring::Buffer<1024>,
23}
24
25impl Log {
26    pub fn new() -> Self {
27        Self {
28            data: Default::default(),
29        }
30    }
31
32    pub fn init(&'static self) -> Result<(), log::SetLoggerError> {
33        log::set_logger(self)?;
34        log::set_max_level(log::LevelFilter::Info);
35        Ok(())
36    }
37
38    /// Allocate a log in static memory, and set it up
39    pub fn start_once() -> &'static Self {
40        use static_cell::StaticCell;
41        static S: StaticCell<Log> = StaticCell::new();
42
43        let s = S.init_with(|| Self::new());
44        s.init()
45            .expect("No other log must be set before calling start_once");
46        s
47    }
48
49    /// Obtain a [coap_handler::Handler] implementation from the Log
50    ///
51    /// This is exposed as a separate type rather than on Log in order to reuse the same handler
52    /// implementation for more than just the log.
53    pub fn handler(&self) -> coap_scroll_ring_server::BufferHandler<'_, 1024> {
54        coap_scroll_ring_server::BufferHandler::new(&self.data)
55    }
56}
57
58impl log::Log for Log {
59    fn enabled(&self, metadata: &log::Metadata) -> bool {
60        metadata.level() <= log::Level::Info
61    }
62
63    fn log(&self, record: &log::Record) {
64        if self.enabled(record.metadata()) {
65            // This is terribly lock-unlock-y; not doing anything about it because both structured
66            // logging and getting-an-actual-lock-on-something are better options -- this is, after
67            // all, primarily emulating what stdout does.
68            struct WriteWrapper<'a>(&'a scroll_ring::Buffer<1024>);
69            impl<'a> core::fmt::Write for WriteWrapper<'a> {
70                fn write_str(&mut self, s: &str) -> core::fmt::Result {
71                    self.0.write(s.as_bytes());
72                    Ok(())
73                }
74            }
75
76            use core::fmt::Write;
77            writeln!(
78                WriteWrapper(&self.data),
79                "{} {}",
80                record.level(),
81                record.args()
82            )
83            .unwrap();
84        }
85    }
86
87    fn flush(&self) {}
88}
89
90/// CoAP handler that accepts POST requests and dumps the request's plain-text payload into the log
91/// at the configured log level
92pub struct LogMessagePostHandler {
93    level: log::Level,
94}
95
96impl LogMessagePostHandler {
97    pub fn new_info() -> Self {
98        Self {
99            level: log::Level::Info,
100        }
101    }
102
103    pub fn new_warn() -> Self {
104        Self {
105            level: log::Level::Warn,
106        }
107    }
108}
109
110impl coap_handler::Handler for LogMessagePostHandler {
111    type ExtractRequestError = Error;
112    type BuildResponseError<M: MinimalWritableMessage> = M::UnionError;
113    type RequestData = ();
114
115    fn extract_request_data<M: coap_message::ReadableMessage>(
116        &mut self,
117        request: &M,
118    ) -> Result<Self::RequestData, Error> {
119        use coap_message_utils::OptionsExt;
120
121        match request.code().into() {
122            code::POST => (),
123            _ => Err(Error::method_not_allowed())?,
124        }
125
126        request.options().ignore_elective_others()?;
127
128        let payload = core::str::from_utf8(request.payload())
129            .map_err(|e| Error::bad_request_with_rbep(e.valid_up_to()))?;
130
131        use log::log;
132        log!(self.level, "{}", payload);
133        Ok(())
134    }
135    fn estimate_length(&mut self, _: &Self::RequestData) -> usize {
136        8
137    }
138    fn build_response<M: coap_message::MutableWritableMessage>(
139        &mut self,
140        response: &mut M,
141        _request: (),
142    ) -> Result<(), M::UnionError> {
143        response.set_code(M::Code::new(code::CHANGED)?);
144        Ok(())
145    }
146}