iceoryx2_log/
lib.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13#![cfg_attr(not(any(test, feature = "std")), no_std)]
14#![warn(clippy::alloc_instead_of_core)]
15#![warn(clippy::std_instead_of_alloc)]
16#![warn(clippy::std_instead_of_core)]
17
18//! The Logging API for iceoryx2. It has 6 [`LogLevel`]s which can be set via
19//! [`set_log_level()`] and read via [`get_log_level()`].
20//!
21//! The API includes convinience macros to combine error/panic handling
22//! directly with a logger selected from the `iceoryx2_loggers` crate.
23//! The [`fail!`] macro can return when the function which was called return an
24//! error containing result.
25//! The [`fatal_panic!`] macro calls [`panic!`].
26//!
27//! # Example
28//!
29//! ## Logging
30//! ```
31//! use iceoryx2_log::{debug, error, info, trace, warn};
32//!
33//! #[derive(Debug)]
34//! struct MyDataType {
35//!     value: u64
36//! }
37//!
38//! impl MyDataType {
39//!     fn log_stuff(&self) {
40//!         trace!("trace message");
41//!         trace!(from self, "trace message");
42//!         trace!(from "Custom::Origin", "trace message");
43//!
44//!         debug!("hello {} {}", 123, 456);
45//!         debug!(from self, "hello {}", 123);
46//!         debug!(from "Another::Origin", "hello {}", 123);
47//!
48//!         info!("world");
49//!         info!(from self, "world");
50//!         info!(from "hello", "world");
51//!
52//!         warn!("warn message");
53//!         warn!(from self, "warning");
54//!         warn!(from "Somewhere::Else", "warning!");
55//!
56//!         error!("bla {}", 1);
57//!         error!(from self, "bla {}", 1);
58//!         error!(from "error origin", "bla {}", 1);
59//!     }
60//!}
61//! ```
62//!
63//! ## Error Handling
64//! ```
65//! use iceoryx2_log::fail;
66//!
67//! #[derive(Debug)]
68//! struct MyDataType {
69//!     value: u64
70//! }
71//!
72//! impl MyDataType {
73//!     fn doStuff(&self, value: u64) -> Result<(), ()> {
74//!         if value == 0 { Err(()) } else { Ok(()) }
75//!     }
76//!
77//!     fn doMoreStuff(&self) -> Result<(), u64> {
78//!         // fail when doStuff.is_err() and return the error 1234
79//!         fail!(from self, when self.doStuff(0),
80//!                 with 1234, "Failed while calling doStuff");
81//!         Ok(())
82//!     }
83//!
84//!     fn doMore(&self) -> Result<(), u64> {
85//!         if self.value == 0 {
86//!             // without condition, return error 4567
87//!             fail!(from self, with 4567, "Value is zero");
88//!         }
89//!
90//!         Ok(())
91//!     }
92//!
93//!     fn evenMore(&self) -> Result<(), u64> {
94//!         // forward error when it is compatible or convertable
95//!         fail!(from self, when self.doMore(), "doMore failed");
96//!         Ok(())
97//!     }
98//! }
99//! ```
100//!
101//! ## Panic Handling
102//! ```
103//! use iceoryx2_log::fatal_panic;
104//!
105//! #[derive(Debug)]
106//! struct MyDataType {
107//!     value: u64
108//! }
109//!
110//! impl MyDataType {
111//!     fn doStuff(&self, value: u64) {
112//!         if value == 0 {
113//!             fatal_panic!(from self, "value is {}", value);
114//!         }
115//!     }
116//!
117//!     fn moreStuff(&self) -> Result<(), ()> {
118//!         if self.value == 0 { Err(()) } else { Ok(()) }
119//!     }
120//!
121//!     fn doIt(&self) {
122//!         fatal_panic!(from self, when self.moreStuff(), "moreStuff failed");
123//!     }
124//! }
125//! ```
126
127#[cfg(feature = "std")]
128pub use from_env::{set_log_level_from_env_or, set_log_level_from_env_or_default};
129
130// Re-export so library crates need only depend on this crate
131pub use iceoryx2_log_types::{Log, LogLevel};
132
133use core::fmt::Write;
134
135use iceoryx2_pal_concurrency_sync::atomic::AtomicU8;
136use iceoryx2_pal_concurrency_sync::atomic::Ordering;
137use iceoryx2_pal_concurrency_sync::once::Once;
138
139mod fail;
140mod log;
141
142const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Info;
143
144static mut LOGGER: Option<&'static dyn Log> = None;
145
146#[cfg(not(all(test, loom, feature = "std")))]
147static LOG_LEVEL: AtomicU8 = AtomicU8::new(DEFAULT_LOG_LEVEL as u8);
148#[cfg(all(test, loom, feature = "std"))]
149static LOG_LEVEL: std::sync::LazyLock<IoxAtomicU8> = std::sync::LazyLock::new(|| {
150    unimplemented!("loom does not provide const-initialization for atomic variables.")
151});
152
153#[cfg(not(all(test, loom, feature = "std")))]
154static INIT: Once = Once::new();
155#[cfg(all(test, loom, feature = "std"))]
156static INIT: std::sync::LazyLock<Once> = std::sync::LazyLock::new(|| {
157    unimplemented!("loom does not provide const-initialization for atomic variables.")
158});
159
160/// Sets the current log level. This is ignored for external frameworks like `log` or `tracing`.
161/// Here you have to use the log-level settings of that framework.
162pub fn set_log_level(v: LogLevel) {
163    LOG_LEVEL.store(v as u8, Ordering::Relaxed);
164}
165
166/// Returns the current log level
167pub fn get_log_level() -> u8 {
168    LOG_LEVEL.load(Ordering::Relaxed)
169}
170
171/// Get a reference to the current logger
172///
173/// This initializes the logger to NULL_LOGGER if it hasn't been set yet.
174fn get_logger() -> &'static dyn Log {
175    INIT.call_once(|| unsafe {
176        #[allow(static_mut_refs)]
177        if LOGGER.is_none() {
178            LOGGER = Some(__internal_default_logger());
179        }
180    });
181
182    // # Safety
183    // 1. The logger is always an immutable threadsafe object with only interior mutability.
184    // 2. Once::call_once ensures LOGGER can only be mutated during initialization
185    //    and the lifetime is 'static.
186    // 3. After INIT.call_once returns, LOGGER is guaranteed to be Some(_)
187    #[allow(static_mut_refs)]
188    unsafe {
189        LOGGER.unwrap()
190    }
191}
192
193/// Sets the [`Log`]ger. Can be only called once at the beginning of the program. If the
194/// [`Log`]ger is already set it returns false and does not update it.
195pub fn set_logger(logger: &'static dyn Log) -> bool {
196    let mut set_logger_success = false;
197    INIT.call_once(|| {
198        unsafe { LOGGER = Some(logger) };
199        set_logger_success = true;
200    });
201    set_logger_success
202}
203
204#[cfg(feature = "std")]
205mod from_env {
206    use super::{set_log_level, LogLevel, DEFAULT_LOG_LEVEL};
207    use std::env;
208
209    fn get_log_level_from_str_fuzzy(
210        log_level_string: &str,
211        log_level_fallback: LogLevel,
212    ) -> LogLevel {
213        match log_level_string.to_lowercase().as_str() {
214            "trace" => LogLevel::Trace,
215            "debug" => LogLevel::Debug,
216            "info" => LogLevel::Info,
217            "warn" => LogLevel::Warn,
218            "error" => LogLevel::Error,
219            "fatal" => LogLevel::Fatal,
220            _ => {
221                eprintln!(
222                    "Invalid value for 'IOX2_LOG_LEVEL' environment variable!\
223                    \nFound: {log_level_string:?}\
224                    \nAllowed is one of: fatal, error, warn, info, debug, trace\
225                    \nSetting log level as : {log_level_fallback:?}"
226                );
227                log_level_fallback
228            }
229        }
230    }
231
232    /// Sets the log level by reading environment variable "IOX2_LOG_LEVEL" or default it with LogLevel::INFO
233    pub fn set_log_level_from_env_or_default() {
234        set_log_level_from_env_or(DEFAULT_LOG_LEVEL);
235    }
236
237    /// Sets the log level by reading environment variable "IOX2_LOG_LEVEL", and if the environment variable
238    /// doesn't exit it sets it with a user-defined logging level
239    pub fn set_log_level_from_env_or(v: LogLevel) {
240        let log_level = env::var("IOX2_LOG_LEVEL")
241            .ok()
242            .map(|s| get_log_level_from_str_fuzzy(&s, v))
243            .unwrap_or(v);
244        set_log_level(log_level);
245    }
246}
247
248#[doc(hidden)]
249pub fn __internal_print_log_msg(
250    log_level: LogLevel,
251    origin: core::fmt::Arguments,
252    args: core::fmt::Arguments,
253) {
254    if get_log_level() <= log_level as u8 {
255        get_logger().log(log_level, origin, args)
256    }
257}
258
259extern "Rust" {
260    fn __internal_default_logger() -> &'static dyn Log;
261    fn __internal_stdout() -> &'static mut dyn Write;
262    fn __internal_stderr() -> &'static mut dyn Write;
263}
264
265#[inline(always)]
266pub fn stdout() -> &'static mut dyn Write {
267    unsafe { __internal_stdout() }
268}
269
270#[inline(always)]
271pub fn stderr() -> &'static mut dyn Write {
272    unsafe { __internal_stderr() }
273}
274
275#[macro_export]
276macro_rules! cout {
277    ($($arg:tt)*) => {{
278        let _ = core::writeln!($crate::stdout(), $($arg)*);
279    }};
280}
281
282#[macro_export]
283macro_rules! cerr {
284    ($($arg:tt)*) => {
285        let _ = core::writeln!($crate::stderr(), $($arg)*);
286    };
287}