diff --git a/applications/gui2/src/modules/camera.cpp b/applications/gui2/src/modules/camera.cpp
index afe99d88988a1355ba23e9e8ff24509dcdcb8d86..1222e55205959476149ec309a1fad00b79c7726c 100644
--- a/applications/gui2/src/modules/camera.cpp
+++ b/applications/gui2/src/modules/camera.cpp
@@ -11,6 +11,7 @@ using ftl::gui2::Camera;
 using ftl::codecs::Channel;
 using ftl::rgbd::Capability;
 using namespace std::literals::chrono_literals;
+using ftl::data::MessageType;
 
 void Camera::init() {
 
@@ -74,7 +75,32 @@ void Camera::update(double delta) {
 				if (caps.count(Capability::TOUCH)) jmeta["Touch"] = nlohmann::json{{"icon", ENTYPO_ICON_MOUSE_POINTER},{"value", true}};
 				if (caps.count(Capability::MOVABLE)) jmeta["Movable"] = nlohmann::json{{"icon", ENTYPO_ICON_COMPASS},{"value", true}};
 				if (caps.count(Capability::VR)) jmeta["VR"] = nlohmann::json{{"value", true}};
-			}			
+			}
+
+			std::list<ftl::data::Message> messages;
+			{
+				UNIQUE_LOCK(mtx_, lk);
+				std::swap(messages, messages_);
+			}
+
+			auto &jmsgs = mod->getJSON(StatisticsPanel::LOGGING);
+			jmsgs.clear();
+			if (messages.size() > 0) {
+				for (const auto &m : messages) {
+					auto &data = jmsgs.emplace_back();
+					data["value"] = m.message;
+					data["nokey"] = true;
+					if (m.type == MessageType::MSG_ERROR) {
+						data["icon"] = ENTYPO_ICON_WARNING;
+						data["colour"] = "#0000ff";
+					} else if (m.type == MessageType::MSG_WARNING) {
+						data["icon"] = ENTYPO_ICON_WARNING;
+						data["colour"] = "#00a6f0";
+					} else {
+
+					}
+				}
+			}	
 		}
 	}
 }
@@ -190,6 +216,15 @@ void Camera::activate(ftl::data::FrameID id) {
 				//cv.notify_one();
 			}
 
+			auto &frame = fs->frames[frame_idx];
+			if (frame.has(Channel::Messages)) {
+				const auto &msgs = frame.get<std::list<ftl::data::Message>>(Channel::Messages);
+				//auto &jmsgs = mod->getJSON(StatisticsPanel::LOGGING);
+
+				UNIQUE_LOCK(mtx_, lk);
+				messages_.insert(messages_.end(), msgs.begin(), msgs.end());
+			}
+
 			if (!view) return true;
 
 			if (live_ && touch_) {
diff --git a/applications/gui2/src/modules/camera.hpp b/applications/gui2/src/modules/camera.hpp
index 5288f3a5db0fb874a820b7dc4c5f09f06397a1d8..0b18f33ee545de28ee8cea21b0155365b6b14905 100644
--- a/applications/gui2/src/modules/camera.hpp
+++ b/applications/gui2/src/modules/camera.hpp
@@ -79,6 +79,8 @@ private:
 	std::unique_ptr<ftl::render::Colouriser> colouriser_;
 	std::unique_ptr<ftl::overlay::Overlay> overlay_;
 
+	std::list<ftl::data::Message> messages_;
+
 	CameraView* view = nullptr;
 
 	MUTEX mtx_;
diff --git a/applications/gui2/src/modules/statistics.hpp b/applications/gui2/src/modules/statistics.hpp
index 2f7f3e374ca02f91b8d0ce9d0f0a46201403327e..1a6474625eb213bf4dbfb3207263fd9cc9083092 100644
--- a/applications/gui2/src/modules/statistics.hpp
+++ b/applications/gui2/src/modules/statistics.hpp
@@ -42,7 +42,7 @@ public:
 private:
 	struct StatsGroup {
 		// TODO: Other properties...
-		nlohmann::json json = nlohmann::json::object_t();
+		nlohmann::json json; // = nlohmann::json::object_t();
 		bool visible=true;
 	};
 
diff --git a/applications/gui2/src/views/statistics.cpp b/applications/gui2/src/views/statistics.cpp
index ccc9f017944d41e68022c8d2ff482fe7872a5bd0..7a512c096ad2c7fe31b8656adfb6ad2b42645c90 100644
--- a/applications/gui2/src/views/statistics.cpp
+++ b/applications/gui2/src/views/statistics.cpp
@@ -39,7 +39,7 @@ void StatisticsWidget::draw(NVGcontext *ctx) {
 	const auto pos = absolutePosition();
 	auto panels = ctrl_->get();
 	for (unsigned int i = 0; i < panels.size(); i++) {
-		if (panels[i].second.is_object()) {
+		if (panels[i].second.is_structured()) {
 			for (auto j : panels[i].second.items()) {
 				std::string msg = j.key();
 
@@ -87,7 +87,8 @@ void StatisticsWidget::draw(NVGcontext *ctx) {
 				float tw = 0.0f;
 
 				if (msg.size() > 0) {
-					nvgFontFace(ctx, "sans-bold");
+					if (panels[i].first == ftl::gui2::StatisticsPanel::LOGGING) nvgFontFace(ctx, "sans");
+					else nvgFontFace(ctx, "sans-bold");
 					nvgFillColor(ctx, nanogui::Color(8, 8, 8, 255)); // shadow
 					tw = nvgTextBounds(ctx, pos[0] + width(), rowh, msg.c_str(), nullptr, nullptr);
 					nvgFillColor(ctx, colour);
diff --git a/components/operators/src/operator.cpp b/components/operators/src/operator.cpp
index 27e73f6998a90243442898ebd3d55f8f25ae5d71..5c729697c1ebbc6a34d44a2350330d1f1acddc7e 100644
--- a/components/operators/src/operator.cpp
+++ b/components/operators/src/operator.cpp
@@ -83,6 +83,7 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
 						instance->apply(in.frames[j].cast<ftl::rgbd::Frame>(), out.frames[j].cast<ftl::rgbd::Frame>(), stream_actual);
 					} catch (const std::exception &e) {
 						LOG(ERROR) << "Operator exception for '" << instance->config()->getID() << "': " << e.what();
+						in.frames[j].error("Operator exception");
 						success = false;
 						break;
 					}
@@ -97,6 +98,7 @@ bool Graph::apply(FrameSet &in, FrameSet &out, cudaStream_t stream) {
 					instance->apply(in, out, stream_actual);
 				} catch (const std::exception &e) {
 					LOG(ERROR) << "Operator exception for '" << instance->config()->getID() << "': " << e.what();
+					if (in.frames.size() > 0) in.frames[0].error("Operator exception");
 					success = false;
 					break;
 				}
diff --git a/components/rgbd-sources/src/sources/realsense/realsense_source.cpp b/components/rgbd-sources/src/sources/realsense/realsense_source.cpp
index 0aa4a1776b22f7c927dd5bd218400bdeb8a9c8fe..fc80affcc9471d7116fef10b7dfc31ab2e106445 100644
--- a/components/rgbd-sources/src/sources/realsense/realsense_source.cpp
+++ b/components/rgbd-sources/src/sources/realsense/realsense_source.cpp
@@ -106,7 +106,7 @@ bool RealsenseSource::retrieve(ftl::rgbd::Frame &frame) {
 
 	// TODO: Move to capture function
 	try {
-		frames = pipe_.wait_for_frames(10);
+		frames = pipe_.wait_for_frames(0);
 	} catch (const std::exception &e) {
 		return false;
 	}
diff --git a/components/streams/src/builder.cpp b/components/streams/src/builder.cpp
index 72dd73c5ad37b87a7bb71697c2c12871ab8bc8b2..ab0991a5367973c33cf0bcf9a491a5b6ccb25297 100644
--- a/components/streams/src/builder.cpp
+++ b/components/streams/src/builder.cpp
@@ -139,6 +139,7 @@ void IntervalSourceBuilder::start() {
 		for (auto *s : srcs_) {
 			if (!s->retrieve(fs->firstFrame())) {
 				LOG(WARNING) << "Frame is being skipped: " << ts;
+				fs->firstFrame().warning("Frame is being skipped");
 			}
 		}
 
diff --git a/components/structures/include/ftl/data/messages.hpp b/components/structures/include/ftl/data/messages.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..13fcc870b4abe5bf5cf3e3be373e0694e8b75d0c
--- /dev/null
+++ b/components/structures/include/ftl/data/messages.hpp
@@ -0,0 +1,29 @@
+#ifndef _FTL_DATA_MESSAGES_HPP_
+#define _FTL_DATA_MESSAGES_HPP_
+
+#include <ftl/utility/msgpack.hpp>
+
+namespace ftl {
+namespace data {
+
+enum class MessageType : int {
+	MSG_INFORMATION=0,
+	MSG_WARNING,
+	MSG_ERROR,
+	CHAT
+};
+
+struct Message {
+	MessageType type;
+	int authorID;
+	std::string message;
+
+	MSGPACK_DEFINE(type, authorID, message);
+};
+
+}
+}
+
+MSGPACK_ADD_ENUM(ftl::data::MessageType);
+
+#endif
\ No newline at end of file
diff --git a/components/structures/include/ftl/data/new_frame.hpp b/components/structures/include/ftl/data/new_frame.hpp
index 7640369381bcbef8729432b0e81c211ca120cd7f..8a935688ef19ee25f1be734a348d50a3b9501c07 100644
--- a/components/structures/include/ftl/data/new_frame.hpp
+++ b/components/structures/include/ftl/data/new_frame.hpp
@@ -17,6 +17,7 @@
 #include <ftl/data/channels.hpp>
 #include <ftl/exception.hpp>
 #include <ftl/handle.hpp>
+#include <ftl/data/messages.hpp>
 
 template<typename T> struct is_list : public std::false_type {};
 
@@ -628,6 +629,21 @@ class Frame {
 
 	inline FrameMode mode() const { return mode_; }
 
+	// ==== Wrapper functions ==================================================
+
+	void message(ftl::data::MessageType type, int id, const std::string &msg);
+
+	void error(const std::string &msg);
+	void error(const ftl::Formatter &msg);
+
+	void warning(const std::string &msg);
+	void warning(const ftl::Formatter &msg);
+
+	void information(const std::string &msg);
+	void information(const ftl::Formatter &msg);
+
+	// =========================================================================
+
 	protected:
 	std::any &createAnyChange(ftl::codecs::Channel c, ftl::data::ChangeType t);
 
diff --git a/components/structures/src/new_frame.cpp b/components/structures/src/new_frame.cpp
index b211e360acfaf79a4d8a7eb7a2b613fd975cb380..69780dab1c32aeff4dd134a42537b49641d5f96b 100644
--- a/components/structures/src/new_frame.cpp
+++ b/components/structures/src/new_frame.cpp
@@ -8,6 +8,7 @@ using ftl::data::ChannelConfig;
 using ftl::data::StorageMode;
 using ftl::data::FrameStatus;
 using ftl::codecs::Channel;
+using ftl::data::MessageType;
 
 #define LOGURU_REPLACE_GLOG 1
 #include <loguru.hpp>
@@ -429,6 +430,39 @@ std::unordered_set<ftl::codecs::Channel> Frame::allChannels() const {
 	return res;
 }
 
+void Frame::message(ftl::data::MessageType type, int id, const std::string &msg) {
+	auto msgs = create<std::list<ftl::data::Message>>(Channel::Messages);
+	ftl::data::Message msgdata;
+	msgdata.authorID = id;
+	msgdata.type = type;
+	msgdata.message = msg;
+	msgs = msgdata;
+}
+
+void Frame::error(const std::string &msg) {
+	message(MessageType::MSG_ERROR, -1, msg);
+}
+
+void Frame::error(const ftl::Formatter &msg) {
+	error(msg.str());
+}
+
+void Frame::warning(const std::string &msg) {
+	message(MessageType::MSG_WARNING, -1, msg);
+}
+
+void Frame::warning(const ftl::Formatter &msg) {
+	warning(msg.str());
+}
+
+void Frame::information(const std::string &msg) {
+	message(MessageType::MSG_INFORMATION, -1, msg);
+}
+
+void Frame::information(const ftl::Formatter &msg) {
+	information(msg.str());
+}
+
 // ==== Session ================================================================
 
 ftl::Handle Session::onChange(uint32_t pid, ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) {