Skip to main content

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}