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}