Skip to main content

rsiot/logging/
mod.rs

1#![allow(clippy::needless_doctest_main)]
2//! Настройки логгирования для разных платформ.
3//!
4//! Для настройки логгирования нужно задать переменную `RUST_LOG`.
5//!
6//! ## Способы задания RUST_LOG
7//!
8//! ### Запуск в контейнере
9//!
10//! В файле `docker-compose.yaml` для сервиса указать:
11//!
12//! ```yaml
13//! services:
14//!   rust_service:
15//!     environment:
16//!       - RUST_LOG=info
17//! ```
18//!
19//! Значение переменной можно задавать для каждого сервиса оданиково.
20//!
21//! ### Запуск в контейнере, сохранение в файле `.env`
22//!
23//! В файле `docker-compose.yaml` для сервиса указать:
24//!
25//! ```yaml
26//! services:
27//!   rust_service:
28//!     env_file: .env
29//! ```
30//!
31//! Значение переменной будет одинаково для всех сервисов
32//!
33//! ### Задание в compile-time
34//!
35//! Платформы WASM, ESP не могут считывать переменные окружения, поэтому значение необходимо
36//! прописывать на этапе компиляции.
37//!
38//! Чтобы значение переменной считывалось из файла:
39//!
40//! - создать файл .env в корне проекта
41//! - прописать в файле переменную в виде `RUST_LOG = info`
42//! - если изменить только переменную, без изменения кода, то перекомпиляции не будет. Поэтому можно
43//!   создать файл `build.rs` в корне проекта с содержимым:
44//!
45//! ```rust
46//! pub fn main() {
47//!     println!("cargo:rerun-if-changed=.env");
48//! }
49//! ```
50//!
51//! TODO - Примеры задания переменной `RUST_LOG`
52//!
53
54mod error;
55use std::env;
56
57pub use error::Error;
58
59// #[cfg(target_arch = "wasm32")]
60// mod target_wasm32;
61// #[cfg(target_arch = "wasm32")]
62// pub use target_wasm32::configure_logging;
63
64// #[cfg(any(
65//     aarch64_unknown_linux_gnu,
66//     armv7_unknown_linux_gnueabihf,
67//     x8664_unknown_linux_gnu
68// ))]
69// mod target_x86_64;
70// #[cfg(any(
71//     aarch64_unknown_linux_gnu,
72//     armv7_unknown_linux_gnueabihf,
73//     x8664_unknown_linux_gnu
74// ))]
75// pub use target_x86_64::configure_logging;
76
77// #[cfg(riscv32imc_esp_espidf)]
78// mod target_esp;
79// #[cfg(riscv32imc_esp_espidf)]
80// pub use target_esp::configure_logging;
81
82use tracing::info;
83
84use tracing_subscriber::{fmt::Layer, layer::SubscriberExt, registry, util::SubscriberInitExt};
85
86type Result<T> = std::result::Result<T, Error>;
87
88/// Получить настройку фильтрации - из переменной окружения или строки
89#[allow(dead_code)]
90fn filter_value(filter: &LogConfigFilter) -> Result<String> {
91    let filter = match filter {
92        LogConfigFilter::FromEnv => env::var("RUST_LOG")?,
93        LogConfigFilter::String(v) => v.to_string(),
94    };
95    Ok(filter)
96}
97
98/// Настройка логгирования
99pub struct LogConfig {
100    /// Строка с настройкой фильтрации логов
101    #[cfg(any(
102        feature = "log_console",
103        feature = "log_file",
104        feature = "log_loki",
105        feature = "log_webconsole"
106    ))]
107    pub filter: LogConfigFilter,
108
109    /// Адрес сервера Loki
110    ///
111    /// Пример:
112    /// ```rust
113    /// String::from("http://service_loki:3100")
114    /// ```
115    #[cfg(feature = "log_loki")]
116    pub loki_url: String,
117
118    /// Адрес для подключения tokio-console
119    ///
120    /// Пример:
121    /// ```rust
122    /// SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 6669)
123    /// ```
124    #[cfg(feature = "log_tokio")]
125    pub tokio_console_addr: std::net::SocketAddrV4,
126
127    /// Уровень логгирования в ESP
128    ///
129    /// Также необходимо настроить переменную CONFIG_LOG_DEFAULT_LEVEL_ в sdkconfig.defaults.
130    /// Возможные значения:
131    /// - No output (CONFIG_LOG_DEFAULT_LEVEL_NONE)
132    /// - Error (CONFIG_LOG_DEFAULT_LEVEL_ERROR)
133    /// - Warning (CONFIG_LOG_DEFAULT_LEVEL_WARN)
134    /// - Info (CONFIG_LOG_DEFAULT_LEVEL_INFO)
135    /// - Debug (CONFIG_LOG_DEFAULT_LEVEL_DEBUG)
136    /// - Verbose (CONFIG_LOG_DEFAULT_LEVEL_VERBOSE)
137    ///
138    /// В консоль будут попадать логи, которые являются минимумом двух значений:
139    /// - заданного в файле `sdkconfig.defaults` при компиляции
140    /// - значения аргумента `level` данной функции в рантайме
141    #[cfg(feature = "log_esp")]
142    pub esp_filter_level: tracing::level_filters::LevelFilter,
143}
144impl LogConfig {
145    /// Запуск логгирования
146    pub fn run(self) -> Result<()> {
147        // log_esp ---------------------------------------------------------------------------------
148        #[cfg(feature = "log_esp")]
149        {
150            use esp_idf_svc::log::{EspIdfLogFilter, EspLogger};
151            use log::LevelFilter as LogLevelFilter;
152            use tracing::level_filters::LevelFilter as TracingLevelFilter;
153
154            EspLogger::initialize_default();
155
156            let level = match self.esp_filter_level {
157                TracingLevelFilter::TRACE => LogLevelFilter::Trace,
158                TracingLevelFilter::DEBUG => LogLevelFilter::Debug,
159                TracingLevelFilter::INFO => LogLevelFilter::Info,
160                TracingLevelFilter::WARN => LogLevelFilter::Warn,
161                TracingLevelFilter::ERROR => LogLevelFilter::Error,
162                TracingLevelFilter::OFF => LogLevelFilter::Off,
163            };
164            EspIdfLogFilter::new().set_target_level("*", level)?;
165            // set_target_level("*", level)?;
166
167            info!("Logging in ESP started with level: {}", level);
168            return Ok(());
169        }
170
171        // log_console -----------------------------------------------------------------------------
172        #[cfg(feature = "log_console")]
173        let layer_console = {
174            use tracing_subscriber::{EnvFilter, Layer, fmt};
175
176            let global_filter = EnvFilter::new(filter_value(&self.filter)?);
177            let layer = fmt::Layer::new().pretty().with_filter(global_filter);
178            Some(layer)
179        };
180        #[cfg(not(feature = "log_console"))]
181        #[allow(unreachable_code)]
182        let layer_console: Option<Layer<_>> = None;
183
184        // log_file --------------------------------------------------------------------------------
185        #[cfg(feature = "log_file")]
186        let layer_file = {
187            use tracing_appender::rolling;
188            use tracing_subscriber::{EnvFilter, Layer, fmt};
189
190            let file_appender = rolling::hourly("./logs", "log");
191            // TODO - не работает неблокирующий
192            // let (non_blocking, _guard) = non_blocking(file_appender);
193            let global_filter = EnvFilter::new(filter_value(&self.filter)?);
194            let layer = fmt::layer()
195                .with_ansi(false)
196                .with_writer(file_appender)
197                .with_filter(global_filter);
198            Some(layer)
199        };
200        #[cfg(not(feature = "log_file"))]
201        #[allow(unreachable_code)]
202        let layer_file: Option<Layer<_>> = None;
203
204        // log_loki --------------------------------------------------------------------------------
205        #[cfg(feature = "log_loki")]
206        let layer_loki = {
207            use tracing_subscriber::{EnvFilter, Layer};
208
209            let global_filter = EnvFilter::new(filter_value(&self.filter)?);
210
211            let service = env::args().collect::<Vec<String>>()[0].clone();
212            let service = service_cleanup(&service)?;
213
214            let loki_url = url::Url::parse(&self.loki_url)?;
215            let (layer_loki, task_loki) = tracing_loki::builder()
216                .label("service", service)?
217                .build_url(loki_url.clone())?;
218            tokio::spawn(task_loki);
219            Some(layer_loki.with_filter(global_filter))
220        };
221        #[cfg(not(feature = "log_loki"))]
222        let layer_loki: Option<Layer<_>> = None;
223
224        // log_tokio -------------------------------------------------------------------------------
225        #[cfg(feature = "log_tokio")]
226        let layer_tokio = {
227            use tracing_subscriber::{EnvFilter, Layer};
228
229            let filter = EnvFilter::new("tokio=trace,runtime=trace");
230            let layer = console_subscriber::ConsoleLayer::builder()
231                .server_addr(self.tokio_console_addr)
232                .spawn()
233                .with_filter(filter);
234
235            Some(layer)
236        };
237        #[cfg(not(feature = "log_tokio"))]
238        let layer_tokio: Option<Layer<_>> = None;
239
240        // log_webconsole --------------------------------------------------------------------------
241        #[cfg(feature = "log_webconsole")]
242        let layer_webconsole = {
243            console_error_panic_hook::set_once();
244            use tracing_subscriber::{EnvFilter, Layer, fmt::time::LocalTime};
245            use tracing_web::MakeWebConsoleWriter;
246
247            let global_filter = EnvFilter::new(filter_value(&self.filter)?);
248
249            let layer = tracing_subscriber::fmt::layer()
250                .with_ansi(false)
251                .with_timer(LocalTime::rfc_3339())
252                .with_writer(MakeWebConsoleWriter::new())
253                .with_filter(global_filter);
254            Some(layer)
255        };
256        #[cfg(not(feature = "log_webconsole"))]
257        let layer_webconsole: Option<Layer<_>> = None;
258
259        // log_webconsole_perf ---------------------------------------------------------------------
260        // #[cfg(feature = "log_webconsole")]
261        // let layer_webconsole_perf = {
262        //     use tracing_subscriber::fmt::format::Pretty;
263        //     use tracing_web::performance_layer;
264
265        //     let layer = performance_layer().with_details_from_fields(Pretty::default());
266        //     Some(layer)
267        // };
268        // #[cfg(not(feature = "log_webconsole"))]
269        // let layer_webconsole_perf: Option<Layer<_>> = None;
270
271        // registry --------------------------------------------------------------------------------
272        registry()
273            .with(layer_console)
274            .with(layer_file)
275            .with(layer_loki)
276            .with(layer_tokio)
277            .with(layer_webconsole)
278            // .with(layer_webconsole_perf)
279            .init();
280
281        #[cfg(feature = "log_console")]
282        info!("Logging in console started with filter: {:?}", self.filter);
283
284        #[cfg(feature = "log_file")]
285        info!("Logging to file started with filter: {:?}", self.filter);
286
287        #[cfg(feature = "log_loki")]
288        info!(
289            "Logging in Loki started. Loki url: {}; filter: {:?}",
290            self.loki_url, self.filter
291        );
292
293        #[cfg(feature = "log_tokio")]
294        info!(
295            "Logging in tokio-console on address: {}",
296            self.tokio_console_addr
297        );
298
299        #[cfg(feature = "log_webconsole")]
300        info!(
301            "Logging in webconsole started with filter: {:?}",
302            self.filter
303        );
304
305        Ok(())
306    }
307}
308
309/// Откуда брать строку с фильтрацией логов
310#[derive(Debug)]
311pub enum LogConfigFilter {
312    /// Из переменной окружения `RUST_LOG`
313    FromEnv,
314    /// Задать значение в строке
315    String(&'static str),
316}
317
318/// Удалить путь из названия файла
319#[cfg(feature = "log_loki")]
320fn service_cleanup(input: &str) -> Result<&str> {
321    input.split('/').next_back().ok_or(Error::ServiceName)
322}
323
324#[cfg(test)]
325mod tests {
326    #[allow(unused)]
327    use super::*;
328
329    #[cfg(feature = "log_loki")]
330    #[test]
331    fn test_service_cleanup() -> anyhow::Result<()> {
332        assert_eq!("service", service_cleanup("./service")?);
333        assert_eq!("service", service_cleanup("../dir/service")?);
334        assert_eq!("service", service_cleanup("service")?);
335        assert_eq!("", service_cleanup("")?);
336        Ok(())
337    }
338}