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}