diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f00a0f523aabfcafa08a8a003be6c1fb988fff7d
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,39 @@
+use tracing::error;
+
+/// Trait to instrument common error handling for Result & Option types
+pub trait ErrInstrument {
+    type Inner;
+
+    fn log_expect(self, msg: &str) -> Self::Inner;
+}
+
+impl<T, E> ErrInstrument for Result<T, E>
+where
+    E: std::fmt::Debug,
+{
+    type Inner = T;
+
+    fn log_expect(self, msg: &str) -> Self::Inner {
+        match self {
+            Ok(t) => t,
+            Err(e) => {
+                error!("{}: {:?}", msg, e);
+                panic!("{}: {:?}", msg, e);
+            }
+        }
+    }
+}
+
+impl<T> ErrInstrument for Option<T> {
+    type Inner = T;
+
+    fn log_expect(self, msg: &str) -> Self::Inner {
+        match self {
+            Some(t) => t,
+            None => {
+                error!("{}", msg);
+                panic!("{}", msg);
+            }
+        }
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index b4b269e9b7b5275e1e928d20691a9cc313ccfe95..4c268010ecbb74a1660cc1b9584ef925a700ca20 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,6 @@
+#![warn(clippy::expect_used)]
+
+mod error;
 mod mavlink;
 mod ui;
 
@@ -6,25 +9,30 @@ use std::{
     sync::{LazyLock, OnceLock},
 };
 
-use mavlink::{MessageBroker, ReflectionContext};
 use parking_lot::Mutex;
 use tokio::runtime::Runtime;
 use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
+
+use error::ErrInstrument;
+use mavlink::{MessageBroker, ReflectionContext};
 use ui::ComposableView;
 
+/// MessageBroker singleton, used to fetch & filter Mavlink messages collected
 static MSG_MANAGER: OnceLock<Mutex<MessageBroker>> = OnceLock::new();
+/// ReflectionContext singleton, used to get access to the Mavlink message definitions
 static MAVLINK_PROFILE: LazyLock<ReflectionContext> = LazyLock::new(ReflectionContext::new);
 
 static APP_NAME: &str = "segs";
 
 fn main() -> Result<(), eframe::Error> {
-    // set up logging (USE RUST_LOG=debug to see logs)
+    // Set up logging (USE RUST_LOG=debug to see logs)
     let env_filter = EnvFilter::builder().from_env_lossy();
     tracing_subscriber::registry()
         .with(tracing_subscriber::fmt::layer().with_filter(env_filter))
         .init();
 
-    let rt = Runtime::new().expect("Unable to create Runtime");
+    // Start Tokio runtime (TODO: decide whether to use Tokio or a simpler thread-based approach)
+    let rt = Runtime::new().log_expect("Unable to create Tokio Runtime");
     let _enter = rt.enter();
 
     let native_options = eframe::NativeOptions {
@@ -35,21 +43,20 @@ fn main() -> Result<(), eframe::Error> {
         ..Default::default()
     };
 
-    // To create an app, eframe wants an `AppCreator`, which is a
-    // Box<dyn FnOnce(&CreationContext<'_>) -> Result<Box<dyn App + 'app>, ...>
-    //
     // CreationContext constains information useful to initilize our app, like storage.
     // Storage allows to store custom data in a way that persist whan you restart the app.
     eframe::run_native(
         APP_NAME, // This is the app id, used for example by Wayland
         native_options,
         Box::new(|ctx| {
+            // First we initialize the MSGManager, as a global singleton available to all the panes
             MSG_MANAGER
                 .set(Mutex::new(MessageBroker::new(
+                    // FIXME: Choose where to put the channel size of the MessageBroker
                     NonZeroUsize::new(50).unwrap(),
                     ctx.egui_ctx.clone(),
                 )))
-                .expect("Unable to set MessageManager");
+                .log_expect("Unable to set MessageManager");
             let app = ctx
                 .storage
                 .map(|storage| ComposableView::new(APP_NAME, storage))