diff --git a/CMakeLists.txt b/CMakeLists.txt
index 32c21ea22d5c30d94b2dfd33261aceebdfa4b7b5..3948bc4b09a0619a1b2969e74a3e0aa200393e1b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -102,6 +102,7 @@ add_library(matrix_client
             lib/crypto/client.cpp
             lib/crypto/utils.cpp
             lib/utils.cpp
+            lib/log.cpp
             lib/structs/common.cpp
             lib/structs/errors.cpp
             lib/structs/events.cpp
diff --git a/include/mtx/events.hpp b/include/mtx/events.hpp
index 1c0220dc14885f5689961dd83e8152b3de9bfc2c..2bd8b7fb69c1f07b6dba6fabf9b61586841265e6 100644
--- a/include/mtx/events.hpp
+++ b/include/mtx/events.hpp
@@ -2,8 +2,6 @@
 
 #include <nlohmann/json.hpp>
 
-#include <iostream>
-
 #include "mtx/events/messages/image.hpp"
 #include "mtx/identifiers.hpp"
 
@@ -178,7 +176,7 @@ to_json(json &obj, const Event<Content> &event)
                 obj["type"] = "m.tag";
                 break;
         case EventType::Unsupported:
-                std::cout << "Unsupported type to serialize" << std::endl;
+        default:
                 break;
         }
 }
diff --git a/include/mtx/identifiers.hpp b/include/mtx/identifiers.hpp
index 6e05d963010794b27d13c5a2c21e2a98f19ec538..324c6ae08678b264aed3a8919125fcd5fa133997 100644
--- a/include/mtx/identifiers.hpp
+++ b/include/mtx/identifiers.hpp
@@ -1,7 +1,6 @@
 #pragma once
 
 #include <exception>
-#include <iostream>
 
 #include <nlohmann/json.hpp>
 
diff --git a/include/mtx/log.hpp b/include/mtx/log.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..af19ba5e77a04065fd1cbcaf0b5bab9d930a1fc4
--- /dev/null
+++ b/include/mtx/log.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <string_view>
+
+namespace mtx::utils::log {
+void
+log_warning(const std::string_view &msg);
+void
+log_error(const std::string_view &msg);
+}
diff --git a/lib/log.cpp b/lib/log.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0632570d66d35566167cd112cc7a43f2fda3a932
--- /dev/null
+++ b/lib/log.cpp
@@ -0,0 +1,17 @@
+#include <mtx/log.hpp>
+
+#include <iostream>
+
+namespace mtx::utils::log {
+void
+log_warning(const std::string_view &msg)
+{
+        std::cerr << "warning:" << msg << "\n";
+}
+
+void
+log_error(const std::string_view &msg)
+{
+        std::cerr << "error:" << msg << "\n";
+}
+}
diff --git a/lib/structs/events/collections.cpp b/lib/structs/events/collections.cpp
index 84ae5d6db5418f83bdf08c2b989f901d7ef8546f..671d2791b2766c2f3dcba557c561f6212f9fee7a 100644
--- a/lib/structs/events/collections.cpp
+++ b/lib/structs/events/collections.cpp
@@ -1,4 +1,5 @@
 #include "mtx/events/collections.hpp"
+#include "mtx/log.hpp"
 
 namespace mtx::events::collections {
 void
@@ -84,8 +85,8 @@ from_json(const json &obj, TimelineEvent &e)
                                 e.data = events::RoomEvent<events::msg::Redacted>(obj);
                                 return;
                         } catch (json::exception &err) {
-                                std::cout << "Invalid event type: " << err.what() << " "
-                                          << obj.dump(2) << '\n';
+                                mtx::utils::log::log_error(std::string("Invalid event type: ") +
+                                                           err.what() + " " + obj.dump(2));
                                 return;
                         }
                 }
diff --git a/lib/structs/responses/sync.cpp b/lib/structs/responses/sync.cpp
index cdb538fac9b29669c4d58de00f82bb6a6a32ec35..71c94ade721056e2b5591f9432f7a6645371fc00 100644
--- a/lib/structs/responses/sync.cpp
+++ b/lib/structs/responses/sync.cpp
@@ -1,5 +1,6 @@
 #include "mtx/responses/sync.hpp"
 #include "mtx/events/collections.hpp"
+#include "mtx/log.hpp"
 #include "mtx/responses/common.hpp"
 
 #include <variant>
@@ -73,9 +74,9 @@ from_json(const json &obj, Ephemeral &ephemeral)
                                         try {
                                                 ts = uit.value().at("ts");
                                         } catch (json::type_error &) {
-                                                std::cerr
-                                                  << "mtxclient: Workaround synapse bug #4898, "
-                                                     "ignoring timestamp for m.receipt event\n";
+                                                mtx::utils::log::log_warning(
+                                                  "mtxclient: Workaround synapse bug #4898, "
+                                                  "ignoring timestamp for m.receipt event");
                                         }
                                         user_times.emplace(uit.key(), ts);
                                 }