argmin_observer_slog/
lib.rs

1// Copyright 2018-2024 argmin developers
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! This crate contains loggers based on the `slog` crate.
9//!
10//! These loggers write general information about the optimization and information about the
11//! progress of the optimization for each iteration of the algorithm to screen or into a file in
12//! JSON format.
13//! See [`SlogLogger`] for details regarding usage.
14//!
15//! # Usage
16//!
17//! Add the following line to your dependencies list:
18//!
19//! ```toml
20//! [dependencies]
21#![doc = concat!("argmin-observer-slog = \"", env!("CARGO_PKG_VERSION"), "\"")]
22//! ```
23//!
24//! # License
25//!
26//! Licensed under either of
27//!
28//!   * Apache License, Version 2.0,
29//!     ([LICENSE-APACHE](https://github.com/argmin-rs/argmin/blob/main/LICENSE-APACHE) or
30//!     <http://www.apache.org/licenses/LICENSE-2.0>)
31//!   * MIT License ([LICENSE-MIT](https://github.com/argmin-rs/argmin/blob/main/LICENSE-MIT) or
32//!     <http://opensource.org/licenses/MIT>)
33//!
34//! at your option.
35//!
36//! ## Contribution
37//!
38//! Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
39//! in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above,
40//! without any additional terms or conditions.
41
42use argmin::core::observers::Observe;
43use argmin::core::{Error, State, KV};
44use slog::{info, o, Drain, Key, Record, Serializer};
45use slog_async::OverflowStrategy;
46
47/// A logger using the [`slog`](https://crates.io/crates/slog) crate as backend.
48#[derive(Clone)]
49pub struct SlogLogger {
50    /// the logger
51    logger: slog::Logger,
52}
53
54impl SlogLogger {
55    /// Log to the terminal.
56    ///
57    /// Will block execution when buffer is full.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// use argmin_observer_slog::SlogLogger;
63    ///
64    /// let terminal_logger = SlogLogger::term();
65    /// ```
66    pub fn term() -> Self {
67        SlogLogger::term_internal(OverflowStrategy::Block)
68    }
69
70    /// Log to the terminal without blocking execution.
71    ///
72    /// Messages may be lost in case of buffer overflow.
73    ///
74    /// # Example
75    ///
76    /// ```
77    /// use argmin_observer_slog::SlogLogger;
78    ///
79    /// let terminal_logger = SlogLogger::term_noblock();
80    /// ```
81    pub fn term_noblock() -> Self {
82        SlogLogger::term_internal(OverflowStrategy::Drop)
83    }
84
85    /// Create terminal logger with a given `OverflowStrategy`.
86    fn term_internal(overflow_strategy: OverflowStrategy) -> Self {
87        let decorator = slog_term::TermDecorator::new().build();
88        let drain = slog_term::FullFormat::new(decorator)
89            .use_original_order()
90            .build()
91            .fuse();
92        let drain = slog_async::Async::new(drain)
93            .overflow_strategy(overflow_strategy)
94            .build()
95            .fuse();
96        SlogLogger {
97            logger: slog::Logger::root(drain, o!()),
98        }
99    }
100
101    /// Log JSON to a file while blocking execution in case of full buffers.
102    ///
103    /// If `truncate` is set to `true`, the content of existing log files will be cleared.
104    ///
105    /// Only available if the `serde1` feature is enabled.
106    ///
107    /// # Example
108    ///
109    /// ```
110    /// use argmin_observer_slog::SlogLogger;
111    ///
112    /// let file_logger = SlogLogger::file("logfile.log", true);
113    /// ```
114    #[cfg(feature = "serde1")]
115    pub fn file<N: AsRef<str>>(file: N, truncate: bool) -> Result<Self, Error> {
116        SlogLogger::file_internal(file, OverflowStrategy::Block, truncate)
117    }
118
119    /// Log JSON to a file without blocking execution.
120    ///
121    /// Messages may be lost in case of buffer overflow.
122    ///
123    /// If `truncate` is set to `true`, the content of existing log files will be cleared.
124    ///
125    /// Only available if the `serde1` feature is enabled.
126    ///
127    /// # Example
128    ///
129    /// ```
130    /// use argmin_observer_slog::SlogLogger;
131    ///
132    /// let file_logger = SlogLogger::file_noblock("logfile.log", true);
133    /// ```
134    #[cfg(feature = "serde1")]
135    pub fn file_noblock<N: AsRef<str>>(file: N, truncate: bool) -> Result<Self, Error> {
136        SlogLogger::file_internal(file, OverflowStrategy::Drop, truncate)
137    }
138
139    /// Create file logger with a given `OverflowStrategy`.
140    ///
141    /// Only available if the `serde1` feature is enabled.
142    #[cfg(feature = "serde1")]
143    fn file_internal<N: AsRef<str>>(
144        file: N,
145        overflow_strategy: OverflowStrategy,
146        truncate: bool,
147    ) -> Result<Self, Error> {
148        // Logging to file
149        let file = std::fs::OpenOptions::new()
150            .create(true)
151            .write(true)
152            .truncate(truncate)
153            .open(file.as_ref())?;
154        let drain = std::sync::Mutex::new(slog_json::Json::new(file).build()).map(slog::Fuse);
155        let drain = slog_async::Async::new(drain)
156            .overflow_strategy(overflow_strategy)
157            .build()
158            .fuse();
159        Ok(SlogLogger {
160            logger: slog::Logger::root(drain, o!()),
161        })
162    }
163}
164
165struct SlogKV<'a>(&'a KV);
166
167impl slog::KV for SlogKV<'_> {
168    fn serialize(&self, _record: &Record, serializer: &mut dyn Serializer) -> slog::Result {
169        for idx in self.0.kv.iter() {
170            serializer.emit_str(Key::from(idx.0.to_string()), &idx.1.to_string())?;
171        }
172        Ok(())
173    }
174}
175
176struct LogState<I>(I);
177
178impl<I> slog::KV for LogState<&'_ I>
179where
180    I: State,
181{
182    fn serialize(&self, _record: &Record, serializer: &mut dyn Serializer) -> slog::Result {
183        for (k, &v) in self.0.get_func_counts().iter() {
184            serializer.emit_u64(Key::from(k.clone()), v)?;
185        }
186        serializer.emit_str(Key::from("best_cost"), &self.0.get_best_cost().to_string())?;
187        serializer.emit_str(Key::from("cost"), &self.0.get_cost().to_string())?;
188        serializer.emit_u64(Key::from("iter"), self.0.get_iter())?;
189        Ok(())
190    }
191}
192
193impl<I> Observe<I> for SlogLogger
194where
195    I: State,
196{
197    /// Log basic information about the optimization after initialization.
198    fn observe_init(&mut self, msg: &str, _state: &I, kv: &KV) -> Result<(), Error> {
199        info!(self.logger, "{}", msg; SlogKV(kv));
200        Ok(())
201    }
202
203    /// Logs information about the progress of the optimization after every iteration.
204    fn observe_iter(&mut self, state: &I, kv: &KV) -> Result<(), Error> {
205        info!(self.logger, ""; LogState(state), SlogKV(kv));
206        Ok(())
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    // TODO
213}