godot_core/global/print.rs
1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8//! Printing and logging functionality.
9
10use crate::builtin::Variant;
11use crate::sys;
12
13// https://stackoverflow.com/a/40234666
14#[macro_export]
15#[doc(hidden)]
16macro_rules! inner_function {
17 () => {{
18 fn f() {}
19 fn type_name_of<T>(_: T) -> &'static str {
20 std::any::type_name::<T>()
21 }
22 let name = type_name_of(f);
23 name.strip_suffix("::f").unwrap()
24 }};
25}
26
27#[macro_export]
28#[doc(hidden)]
29macro_rules! inner_godot_msg {
30 ($level:expr; $fmt:literal $(, $args:expr)* $(,)?) => {
31 {
32 let description = format!($fmt $(, $args)*);
33 $crate::global::print_custom($crate::global::PrintRecord {
34 level: $level,
35 message: &description,
36 rationale: None,
37 source: Some($crate::global::PrintSource {
38 function: $crate::inner_function!(),
39 file: file!(),
40 line: line!(),
41 }),
42 editor_notify: false,
43 });
44 }
45 };
46}
47
48/// Severity level for [`print_custom()`].
49#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
50#[non_exhaustive]
51pub enum PrintLevel {
52 /// Plain message. Used by [`godot_print!`][crate::global::godot_print].
53 ///
54 /// Godot's GDExtension API has no info-level print function that accepts a source location, so if a location is provided, godot-rust
55 /// will append "at:" information to the message.
56 Info,
57
58 /// Warning. Used by [`godot_warn!`][crate::global::godot_warn].
59 Warn,
60
61 /// Error. Used by [`godot_error!`][crate::global::godot_error].
62 Error,
63
64 /// Script error (rarely needed in Rust). Used by [`godot_script_error!`][crate::global::godot_script_error].
65 ScriptError,
66}
67
68impl PrintLevel {
69 /// Returns the upper-case prefix used by Godot to print on stdout/stderr.
70 ///
71 /// E.g. `Some("WARNING")` for `Warn` and `None` (no prefix) for `Info`.
72 pub fn godot_title(self) -> Option<&'static str> {
73 match self {
74 Self::Warn => Some("WARNING"),
75 Self::Error => Some("ERROR"),
76 Self::ScriptError => Some("SCRIPT ERROR"),
77 Self::Info => None,
78 }
79 }
80}
81
82// ----------------------------------------------------------------------------------------------------------------------------------------------
83
84/// Source location associated with a [`PrintRecord`].
85///
86/// Can be built directly from explicit values, or via [`from_location()`][Self::from_location] from a [`std::panic::Location`].
87///
88/// If any of the string-based fields contain interior null bytes (`\0`), the string may be cut off at that point.
89#[derive(Copy, Clone, Debug)]
90pub struct PrintSource<'a> {
91 pub function: &'a str,
92 pub file: &'a str,
93 pub line: u32,
94}
95
96impl<'a> PrintSource<'a> {
97 /// Build a `PrintSource` from a [`std::panic::Location`] and a function name.
98 ///
99 /// Function name must be supplied separately, since [`Location`][std::panic::Location] does not capture it.
100 pub fn from_location(location: &'a std::panic::Location<'a>, function: &'a str) -> Self {
101 Self {
102 function,
103 file: location.file(),
104 line: location.line(),
105 }
106 }
107
108 /// Build a `PrintSource` from the caller's location, with an empty function name.
109 ///
110 /// Uses [`std::panic::Location::caller()`]; place `#[track_caller]` on intermediate functions to forward the caller through.
111 #[track_caller]
112 pub fn caller() -> Self {
113 Self::from_location(std::panic::Location::caller(), "")
114 }
115}
116
117// ----------------------------------------------------------------------------------------------------------------------------------------------
118
119/// Log record passed to [`print_custom()`].
120///
121/// Allows callers to provide an *explicit* source location, rather than the call site of the print macro.
122/// See also the [`Logger`][crate::classes::ILogger] interface for intercepting printed messages.
123///
124/// If any of the string-based fields contain interior null bytes (`\0`), the string may be cut off at that point.
125#[derive(Copy, Clone, Debug)]
126pub struct PrintRecord<'a> {
127 /// Severity level.
128 pub level: PrintLevel,
129
130 /// Primary message. Shown in the editor's debugger panel (warn/error) or output panel (info), and on the OS terminal.
131 pub message: &'a str,
132
133 /// Optional secondary message, displayed separately in editor UI for warn/error/script-error.
134 ///
135 /// For [`PrintLevel::Info`], it is appended to the description as `"description: message"`.
136 pub rationale: Option<&'a str>,
137
138 /// Source location.
139 ///
140 /// - For warn/error/script-error: if `None`, falls back to [`PrintSource::caller()`] (function name is empty).
141 /// - For [`PrintLevel::Info`]: if `Some`, formatted into the message; if `None`, no source suffix is appended.
142 pub source: Option<PrintSource<'a>>,
143
144 /// Whether to create a toast notification in the editor. Ignored for [`PrintLevel::Info`].
145 pub editor_notify: bool,
146}
147
148// ----------------------------------------------------------------------------------------------------------------------------------------------
149
150/// Low-level printing of log messages with full control over level, source location and editor toast.
151///
152/// Most users should prefer the [`godot_print!`][crate::global::godot_print], [`godot_warn!`][crate::global::godot_warn] and
153/// [`godot_error!`][crate::global::godot_error] macros. `print_custom()` is intended for low-level configurability or integration with crates
154/// like `tracing` or `log`.
155///
156/// See [`PrintRecord`] and [`PrintLevel`] for routing and source-location behavior. Due to the use of C-strings, if any of the string fields in
157/// `PrintRecord` or [`PrintSource`] have a nul byte (`\0`) in the middle, the printed text will be cut off at that nul byte. Consider this
158/// when working with user-provided texts (e.g. for logging).
159#[track_caller]
160pub fn print_custom(record: PrintRecord<'_>) {
161 // Engine not yet loaded -- fall back to stderr.
162 if !sys::is_initialized() {
163 let level = record
164 .level
165 .godot_title()
166 .map_or(String::new(), |t| format!("{t}:"));
167
168 match record.rationale {
169 Some(msg) => eprintln!("{level}{} ({msg})", record.message),
170 None => eprintln!("{level}{}", record.message),
171 }
172 return;
173 }
174
175 if record.level == PrintLevel::Info {
176 print_info(record.message, record.rationale, record.source);
177 return;
178 }
179
180 // Default location from caller, when source is not given.
181 let source = record.source.unwrap_or_else(PrintSource::caller);
182 let PrintSource {
183 function,
184 file,
185 line,
186 } = source;
187
188 // Null-terminate strings (Godot expects C strings).
189 let desc_nul = format!("{}\0", record.message);
190 let func_nul = format!("{function}\0");
191 let file_nul = format!("{file}\0");
192 let msg_nul = record.rationale.map(|m| format!("{m}\0"));
193 let editor_notify = sys::conv::bool_to_sys(record.editor_notify);
194
195 let desc_ptr = sys::c_str_from_str(&desc_nul);
196 let func_ptr = sys::c_str_from_str(&func_nul);
197 let file_ptr = sys::c_str_from_str(&file_nul);
198 let line = line as i32;
199
200 // SAFETY: engine initialized; interface functions valid; pointers live for the call duration.
201 unsafe {
202 if let Some(msg_z) = &msg_nul {
203 let godot_fn = match record.level {
204 PrintLevel::Warn => sys::interface_fn!(print_warning_with_message),
205 PrintLevel::Error => sys::interface_fn!(print_error_with_message),
206 PrintLevel::ScriptError => sys::interface_fn!(print_script_error_with_message),
207 PrintLevel::Info => unreachable!(),
208 };
209 godot_fn(
210 desc_ptr,
211 sys::c_str_from_str(msg_z),
212 func_ptr,
213 file_ptr,
214 line,
215 editor_notify,
216 );
217 } else {
218 let godot_fn = match record.level {
219 PrintLevel::Warn => sys::interface_fn!(print_warning),
220 PrintLevel::Error => sys::interface_fn!(print_error),
221 PrintLevel::ScriptError => sys::interface_fn!(print_script_error),
222 PrintLevel::Info => unreachable!(),
223 };
224 godot_fn(desc_ptr, func_ptr, file_ptr, line, editor_notify);
225 }
226 }
227}
228
229fn print_info(description: &str, message: Option<&str>, source: Option<PrintSource<'_>>) {
230 // `format!` pre-sizes the buffer to fit; each branch is a single allocation with no realloc.
231 let full = match (message, source) {
232 (Some(m), None) => format!("{description}: {m}"),
233 (Some(m), Some(s)) => {
234 format!(
235 "{description}: {m}\n\tat: {} ({}:{})",
236 s.function, s.file, s.line
237 )
238 }
239 (None, Some(s)) => format!(
240 "{description}\n\tat: {} ({}:{})",
241 s.function, s.file, s.line
242 ),
243 (None, None) => description.to_string(),
244 };
245
246 crate::global::print(&[Variant::from(full)]);
247}
248
249/// Pushes a warning message to Godot's built-in debugger and to the OS terminal.
250///
251/// # See also
252/// [`godot_print!`](macro.godot_print.html) and [`godot_error!`](macro.godot_error.html).
253///
254/// Related to the utility function [`global::push_warning()`](crate::global::push_warning).
255///
256/// _Godot equivalent: [`@GlobalScope.push_warning()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-push-warning)_.
257#[macro_export]
258macro_rules! godot_warn {
259 ($fmt:literal $(, $args:expr_2021)* $(,)?) => {
260 $crate::inner_godot_msg!($crate::global::PrintLevel::Warn; $fmt $(, $args)*);
261 };
262}
263
264/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
265///
266/// # See also
267/// [`godot_print!`](macro.godot_print.html) and [`godot_warn!`](macro.godot_warn.html).
268/// For script errors (less relevant in Rust), use [`godot_script_error!`](macro.godot_script_error.html).
269///
270/// Related to the utility function [`global::push_error()`][crate::global::push_error].
271///
272/// _Godot equivalent: [`@GlobalScope.push_error()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-push-error)_.
273#[macro_export]
274macro_rules! godot_error {
275 ($fmt:literal $(, $args:expr_2021)* $(,)?) => {
276 $crate::inner_godot_msg!($crate::global::PrintLevel::Error; $fmt $(, $args)*);
277 };
278}
279
280/// Logs a script error to Godot's built-in debugger and to the OS terminal.
281///
282/// This is rarely needed in Rust; script errors are typically emitted by the GDScript parser.
283///
284/// # See also
285/// [`godot_error!`](macro.godot_error.html) for a general error message.
286///
287///
288#[macro_export]
289macro_rules! godot_script_error {
290 ($fmt:literal $(, $args:expr_2021)* $(,)?) => {
291 $crate::inner_godot_msg!($crate::global::PrintLevel::ScriptError; $fmt $(, $args)*);
292 };
293}
294
295/// Prints to the Godot console.
296///
297/// Automatically appends a newline character at the end of the message.
298///
299/// Used exactly like standard [`println!`]:
300/// ```no_run
301/// use godot::global::godot_print;
302///
303/// let version = 4;
304/// godot_print!("Hello, Godot {version}!");
305/// ```
306///
307/// # See also
308/// [`godot_print_rich!`](macro.godot_print_rich.html) for a slower alternative that supports BBCode, color and URL tags.
309/// To print Godot errors and warnings, use [`godot_error!`](macro.godot_error.html) and [`godot_warn!`](macro.godot_warn.html), respectively.
310///
311/// This uses the underlying [`global::print()`][crate::global::print] function, which takes a variable-length slice of variants.
312///
313/// _Godot equivalent: [`@GlobalScope.print()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-print)_.
314#[macro_export]
315macro_rules! godot_print {
316 ($fmt:literal $(, $args:expr_2021)* $(,)?) => {
317 $crate::global::print(&[
318 $crate::builtin::Variant::from(
319 format!($fmt $(, $args)*)
320 )
321 ])
322 };
323}
324
325/// Prints to the Godot console. Supports BBCode, color and URL tags.
326///
327/// Slower than [`godot_print!`](macro.godot_print_rich.html).
328///
329/// _Godot equivalent: [`@GlobalScope.print_rich()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-print-rich)_.
330#[macro_export]
331macro_rules! godot_print_rich {
332 ($fmt:literal $(, $args:expr_2021)* $(,)?) => {
333 $crate::global::print_rich(&[
334 $crate::builtin::Variant::from(
335 format!($fmt $(, $args)*)
336 )
337 ])
338 };
339}
340
341/// Concatenates format-style arguments into a `GString`.
342///
343/// Works similar to Rust's standard [`format!`] macro but returns a Godot `GString`.
344///
345/// # Example
346/// ```no_run
347/// use godot::builtin::GString;
348/// use godot::global::godot_str;
349///
350/// let name = "Player";
351/// let score = 100;
352/// let message: GString = godot_str!("The {name} scored {score} points!");
353/// ```
354///
355/// # See also
356/// This macro uses the underlying [`global::str()`][crate::global::str] function, which takes a variable-length slice of variants.
357///
358/// _Godot equivalent: [`@GlobalScope.str()`](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-str)_.
359#[macro_export]
360macro_rules! godot_str {
361 ($fmt:literal $(, $args:expr_2021)* $(,)?) => {
362 $crate::global::str(&[
363 $crate::builtin::Variant::from(
364 format!($fmt $(, $args)*)
365 )
366 ])
367 };
368}