soroban_sdk/
logs.rs

1//! Logging contains types for logging debug events.
2//!
3//! See [`log`][crate::log] for how to conveniently log debug events.
4use core::fmt::Debug;
5
6use crate::{env::internal::EnvBase, Env, Val};
7
8/// Log a debug event.
9///
10/// Takes a [Env], a literal string, and an optional trailing sequence of
11/// arguments that may be any value that are convertible to [`Val`]. The
12/// string and arguments are appended as-is to the log, as the body of a
13/// structured diagnostic event. Such events may be emitted from the host as
14/// auxiliary diagnostic XDR, or converted to strings later for debugging.
15///
16/// `log!` statements are only enabled in non optimized builds that have
17/// `debug-assertions` enabled. To enable `debug-assertions` add the following
18/// lines to `Cargo.toml`, then build with the profile specified, `--profile
19/// release-with-logs`. See the cargo docs for how to use [custom profiles].
20///
21/// ```toml
22/// [profile.release-with-logs]
23/// inherits = "release"
24/// debug-assertions = true
25/// ```
26///
27/// [custom profiles]:
28///     https://doc.rust-lang.org/cargo/reference/profiles.html#custom-profiles
29///
30/// ### Examples
31///
32/// Log a string:
33///
34/// ```
35/// use soroban_sdk::{log, Env};
36///
37/// let env = Env::default();
38///
39/// log!(&env, "a log entry");
40/// ```
41///
42/// Log a string with values:
43///
44/// ```
45/// use soroban_sdk::{log, symbol_short, Symbol, Env};
46///
47/// let env = Env::default();
48///
49/// let value = 5;
50/// log!(&env, "a log entry", value, symbol_short!("another"));
51/// ```
52///
53/// Assert on logs in tests:
54///
55/// ```
56/// # #[cfg(feature = "testutils")]
57/// # {
58/// use soroban_sdk::{log, symbol_short, Symbol, Env};
59///
60/// let env = Env::default();
61///
62/// let value = 5;
63/// log!(&env, "a log entry", value, symbol_short!("another"));
64///
65/// use soroban_sdk::testutils::Logs;
66/// let logentry = env.logs().all().last().unwrap().clone();
67/// assert!(logentry.contains("[\"a log entry\", 5, another]"));
68/// # }
69/// ```
70#[macro_export]
71macro_rules! log {
72    ($env:expr, $fmt:literal $(,)?) => {
73        if cfg!(debug_assertions) {
74            $env.logs().add($fmt, &[]);
75        }
76    };
77    ($env:expr, $fmt:literal, $($args:expr),* $(,)?) => {
78        if cfg!(debug_assertions) {
79            $env.logs().add($fmt, &[
80                $(
81                    <_ as $crate::IntoVal<Env, $crate::Val>>::into_val(&$args, $env)
82                ),*
83            ]);
84        }
85    };
86}
87
88/// Logs logs debug events.
89///
90/// See [`log`][crate::log] for how to conveniently log debug events.
91#[derive(Clone)]
92pub struct Logs(Env);
93
94impl Debug for Logs {
95    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        write!(f, "Logs")
97    }
98}
99
100impl Logs {
101    #[inline(always)]
102    pub(crate) fn env(&self) -> &Env {
103        &self.0
104    }
105
106    #[inline(always)]
107    pub(crate) fn new(env: &Env) -> Logs {
108        Logs(env.clone())
109    }
110
111    #[deprecated(note = "use [Logs::add]")]
112    #[inline(always)]
113    pub fn log(&self, msg: &'static str, args: &[Val]) {
114        self.add(msg, args);
115    }
116
117    /// Log a debug event.
118    ///
119    /// Takes a literal string and a sequence of trailing values to add
120    /// as a log entry in the diagnostic event stream.
121    ///
122    /// See [`log`][crate::log] for how to conveniently log debug events.
123    #[inline(always)]
124    pub fn add(&self, msg: &'static str, args: &[Val]) {
125        if cfg!(debug_assertions) {
126            let env = self.env();
127            env.log_from_slice(msg, args).unwrap();
128
129            #[cfg(any(test, feature = "testutils"))]
130            {
131                use crate::testutils::Logs;
132                std::println!("{}", self.all().last().unwrap());
133            }
134        }
135    }
136}
137
138#[cfg(any(test, feature = "testutils"))]
139use crate::testutils;
140
141#[cfg(any(test, feature = "testutils"))]
142#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
143impl testutils::Logs for Logs {
144    fn all(&self) -> std::vec::Vec<String> {
145        use crate::xdr::{
146            ContractEventBody, ContractEventType, ScSymbol, ScVal, ScVec, StringM, VecM,
147        };
148        let env = self.env();
149        let log_sym = ScSymbol(StringM::try_from("log").unwrap());
150        let log_topics = ScVec(VecM::try_from(vec![ScVal::Symbol(log_sym)]).unwrap());
151        env.host()
152            .get_diagnostic_events()
153            .unwrap()
154            .0
155            .into_iter()
156            .filter_map(|e| match (&e.event.type_, &e.event.body) {
157                (ContractEventType::Diagnostic, ContractEventBody::V0(ce))
158                    if &ce.topics == &log_topics.0 =>
159                {
160                    Some(format!("{}", &e))
161                }
162                _ => None,
163            })
164            .collect::<std::vec::Vec<_>>()
165    }
166
167    fn print(&self) {
168        std::println!("{}", self.all().join("\n"))
169    }
170}