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}