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}