bevy_ecs/error/handler.rs
1#[cfg(feature = "configurable_error_handler")]
2use bevy_platform::sync::OnceLock;
3use core::fmt::Display;
4
5use crate::{component::Tick, error::BevyError};
6use alloc::borrow::Cow;
7
8/// Context for a [`BevyError`] to aid in debugging.
9#[derive(Debug, PartialEq, Eq, Clone)]
10pub enum ErrorContext {
11 /// The error occurred in a system.
12 System {
13 /// The name of the system that failed.
14 name: Cow<'static, str>,
15 /// The last tick that the system was run.
16 last_run: Tick,
17 },
18 /// The error occurred in a run condition.
19 RunCondition {
20 /// The name of the run condition that failed.
21 name: Cow<'static, str>,
22 /// The last tick that the run condition was evaluated.
23 last_run: Tick,
24 },
25 /// The error occurred in a command.
26 Command {
27 /// The name of the command that failed.
28 name: Cow<'static, str>,
29 },
30 /// The error occurred in an observer.
31 Observer {
32 /// The name of the observer that failed.
33 name: Cow<'static, str>,
34 /// The last tick that the observer was run.
35 last_run: Tick,
36 },
37}
38
39impl Display for ErrorContext {
40 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41 match self {
42 Self::System { name, .. } => {
43 write!(f, "System `{}` failed", name)
44 }
45 Self::Command { name } => write!(f, "Command `{}` failed", name),
46 Self::Observer { name, .. } => {
47 write!(f, "Observer `{}` failed", name)
48 }
49 Self::RunCondition { name, .. } => {
50 write!(f, "Run condition `{}` failed", name)
51 }
52 }
53 }
54}
55
56impl ErrorContext {
57 /// The name of the ECS construct that failed.
58 pub fn name(&self) -> &str {
59 match self {
60 Self::System { name, .. }
61 | Self::Command { name, .. }
62 | Self::Observer { name, .. }
63 | Self::RunCondition { name, .. } => name,
64 }
65 }
66
67 /// A string representation of the kind of ECS construct that failed.
68 ///
69 /// This is a simpler helper used for logging.
70 pub fn kind(&self) -> &str {
71 match self {
72 Self::System { .. } => "system",
73 Self::Command { .. } => "command",
74 Self::Observer { .. } => "observer",
75 Self::RunCondition { .. } => "run condition",
76 }
77 }
78}
79
80/// A global error handler. This can be set at startup, as long as it is set before
81/// any uses. This should generally be configured _before_ initializing the app.
82///
83/// This should be set inside of your `main` function, before initializing the Bevy app.
84/// The value of this error handler can be accessed using the [`default_error_handler`] function,
85/// which calls [`OnceLock::get_or_init`] to get the value.
86///
87/// **Note:** this is only available when the `configurable_error_handler` feature of `bevy_ecs` (or `bevy`) is enabled!
88///
89/// # Example
90///
91/// ```
92/// # use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, warn};
93/// GLOBAL_ERROR_HANDLER.set(warn).expect("The error handler can only be set once, globally.");
94/// // initialize Bevy App here
95/// ```
96///
97/// To use this error handler in your app for custom error handling logic:
98///
99/// ```rust
100/// use bevy_ecs::error::{default_error_handler, GLOBAL_ERROR_HANDLER, BevyError, ErrorContext, panic};
101///
102/// fn handle_errors(error: BevyError, ctx: ErrorContext) {
103/// let error_handler = default_error_handler();
104/// error_handler(error, ctx);
105/// }
106/// ```
107///
108/// # Warning
109///
110/// As this can *never* be overwritten, library code should never set this value.
111#[cfg(feature = "configurable_error_handler")]
112pub static GLOBAL_ERROR_HANDLER: OnceLock<fn(BevyError, ErrorContext)> = OnceLock::new();
113
114/// The default error handler. This defaults to [`panic()`],
115/// but if set, the [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization.
116/// The `configurable_error_handler` feature must be enabled to change this from the panicking default behavior,
117/// as there may be runtime overhead.
118#[inline]
119pub fn default_error_handler() -> fn(BevyError, ErrorContext) {
120 #[cfg(not(feature = "configurable_error_handler"))]
121 return panic;
122
123 #[cfg(feature = "configurable_error_handler")]
124 return *GLOBAL_ERROR_HANDLER.get_or_init(|| panic);
125}
126
127macro_rules! inner {
128 ($call:path, $e:ident, $c:ident) => {
129 $call!(
130 "Encountered an error in {} `{}`: {}",
131 $c.kind(),
132 $c.name(),
133 $e
134 );
135 };
136}
137
138/// Error handler that panics with the system error.
139#[track_caller]
140#[inline]
141pub fn panic(error: BevyError, ctx: ErrorContext) {
142 inner!(panic, error, ctx);
143}
144
145/// Error handler that logs the system error at the `error` level.
146#[track_caller]
147#[inline]
148pub fn error(error: BevyError, ctx: ErrorContext) {
149 inner!(log::error, error, ctx);
150}
151
152/// Error handler that logs the system error at the `warn` level.
153#[track_caller]
154#[inline]
155pub fn warn(error: BevyError, ctx: ErrorContext) {
156 inner!(log::warn, error, ctx);
157}
158
159/// Error handler that logs the system error at the `info` level.
160#[track_caller]
161#[inline]
162pub fn info(error: BevyError, ctx: ErrorContext) {
163 inner!(log::info, error, ctx);
164}
165
166/// Error handler that logs the system error at the `debug` level.
167#[track_caller]
168#[inline]
169pub fn debug(error: BevyError, ctx: ErrorContext) {
170 inner!(log::debug, error, ctx);
171}
172
173/// Error handler that logs the system error at the `trace` level.
174#[track_caller]
175#[inline]
176pub fn trace(error: BevyError, ctx: ErrorContext) {
177 inner!(log::trace, error, ctx);
178}
179
180/// Error handler that ignores the system error.
181#[track_caller]
182#[inline]
183pub fn ignore(_: BevyError, _: ErrorContext) {}