diff --git a/CMakeLists.txt b/CMakeLists.txt
index 777575cab4af4021a9c2a4790378341082ecfc32..83b896ee61e607fc74b32303403f4f30f8d9d96b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,7 +33,6 @@ option(USE_BUNDLED_COEURL "Use a bundled version of the Curl wrapper"
 	${HUNTER_ENABLED})
 option(USE_BUNDLED_LIBEVENT "Use the bundled version of spdlog." ${HUNTER_ENABLED})
 option(USE_BUNDLED_LIBCURL "Use the bundled version of spdlog." ${HUNTER_ENABLED})
-option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog." ${HUNTER_ENABLED})
 
 
 if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
@@ -155,7 +154,13 @@ endif()
 if(USE_BUNDLED_SPDLOG)
 	hunter_add_package(spdlog)
 endif()
-find_package(spdlog 1.0.0 CONFIG REQUIRED)
+find_package(spdlog 1.0.0 CONFIG)
+set_package_properties(spdlog PROPERTIES
+    DESCRIPTION "Very fast, header only, C++ logging library"
+    URL "https://github.com/gabime/spdlog"
+    TYPE REQUIRED
+)
+target_link_libraries(matrix_client PUBLIC spdlog::spdlog)
 
 if(USE_BUNDLED_COEURL)
 	include(FetchContent)
diff --git a/README.md b/README.md
index a268c3e78ea6a106c1ab7e80c337c73e9c18b670..01cc381a00848b9f3ffac969db4956803f99bb2b 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Client API library for the Matrix protocol.
 - C++ 17 compiler
 - CMake 3.15 or greater (lower versions can work, but not all build system options may work.)
 - Google Test (for testing)
+- spdlog (for logging)
 
 If you are missing some or all of those above dependencies, you can add `-DHUNTER_ENABLED=ON` to the cmake configure command to use bundled dependencies. You can finetune them with the following variables. They default to ON, if Hunter is enabled and to OFF otherwise.
 
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 34edc0613f0c58a38df61e75aba96fcd9ce6770a..e6b779dd1f133091d0ce2f1b247e918ff7052154 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,13 +1,3 @@
-if(USE_BUNDLED_SPDLOG)
-	hunter_add_package(spdlog)
-endif()
-find_package(spdlog 1.0.0 CONFIG)
-set_package_properties(spdlog PROPERTIES
-    DESCRIPTION "Very fast, header only, C++ logging library"
-    URL "https://github.com/gabime/spdlog"
-    TYPE REQUIRED
-)
-
 include_directories(../tests)
 
 add_executable(room_feed room_feed.cpp)
diff --git a/examples/meson.build b/examples/meson.build
index 845142e42bd75cd6102a8429450076caad98e2ee..5f456ab4e1279badf20e6581b5ecd212e0e21999 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -1,5 +1,3 @@
-spdlog_dep = dependency('spdlog', fallback: ['spdlog', 'spdlog_dep'])
-
 room_feed = executable('room_feed',
     'room_feed.cpp',
     dependencies: [matrix_client_dep],
@@ -26,7 +24,7 @@ simple_bot = executable('simple_bot',
     )
 crypto_bot = executable('crypto_bot',
     'crypto_bot.cpp',
-    dependencies: [matrix_client_dep, spdlog_dep],
+    dependencies: [matrix_client_dep],
     include_directories : '../tests'
     )
 online_backup_exporter = executable('online_backup_exporter',
diff --git a/include/mtx/log.hpp b/include/mtx/log.hpp
index 9877c63182720a08d28bd66b9ecaec1229632392..97ea42766f02b3a929500bdc318d3716874fb932 100644
--- a/include/mtx/log.hpp
+++ b/include/mtx/log.hpp
@@ -1,14 +1,13 @@
 #pragma once
 
-#include <string_view>
+#include <memory>
+#include <spdlog/spdlog.h>
 
 namespace mtx {
 namespace utils {
 namespace log {
-void
-log_warning(const std::string_view &msg);
-void
-log_error(const std::string_view &msg);
+std::shared_ptr<spdlog::logger>
+log();
 }
 }
 }
diff --git a/include/mtxclient/crypto/utils.hpp b/include/mtxclient/crypto/utils.hpp
index 395b278a03ec5e0d06cc57d5ce84873249112f2b..6a2e95b8b67f7fa5a87e04e896acb894c44f4b86 100644
--- a/include/mtxclient/crypto/utils.hpp
+++ b/include/mtxclient/crypto/utils.hpp
@@ -165,9 +165,6 @@ uint8_to_uint32(uint8_t b[4], uint32_t &u32);
 void
 uint32_to_uint8(uint8_t b[4], uint32_t u32);
 
-void
-print_binary_buf(const BinaryBuf &buf);
-
 //! Convert base64 to binary
 std::string
 base642bin(const std::string &b64);
diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp
index aaf29aa9a0923208d94de1bf17639dc0f57a0b8f..5bf077ddcab387e0317a1d4dc56c633afcdc90a7 100644
--- a/lib/crypto/client.cpp
+++ b/lib/crypto/client.cpp
@@ -1,5 +1,3 @@
-#include <iostream>
-
 #include <nlohmann/json.hpp>
 #include <utility>
 
@@ -10,6 +8,8 @@
 #include "mtxclient/crypto/types.hpp"
 #include "mtxclient/crypto/utils.hpp"
 
+#include "mtx/log.hpp"
+
 using json = nlohmann::json;
 using namespace mtx::crypto;
 
@@ -840,7 +840,7 @@ mtx::crypto::verify_identity_signature(const DeviceKeys &device_keys,
         return ed25519_verify_signature(signing_key, nlohmann::json(device_keys), signature);
 
     } catch (const nlohmann::json::exception &e) {
-        std::cerr << "verify_identity_signature: " << e.what();
+        mtx::utils::log::log()->error("verify_identity_signature: {}", e.what());
     }
 
     return false;
@@ -876,7 +876,7 @@ mtx::crypto::ed25519_verify_signature(std::string signing_key,
 
         return true;
     } catch (const nlohmann::json::exception &e) {
-        std::cerr << "verify_signature: " << e.what();
+        mtx::utils::log::log()->error("verify_signature: {}", e.what());
     }
 
     return false;
diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp
index 48b2f8501d1cc23f575f8ebf99be8e2cd855e5bc..521e8f10f9443f65ab4de116ae5ebf64dfe6a69a 100644
--- a/lib/crypto/utils.cpp
+++ b/lib/crypto/utils.cpp
@@ -12,9 +12,8 @@
 #include <olm/pk.h>
 
 #include <algorithm>
-#include <iomanip>
-#include <iostream>
 
+#include "mtx/log.hpp"
 #include "mtxclient/crypto/client.hpp"
 
 namespace mtx {
@@ -545,15 +544,6 @@ HMAC_SHA256(const BinaryBuf &hmacKey, const BinaryBuf &data)
     return output;
 }
 
-void
-print_binary_buf(const BinaryBuf &buf)
-{
-    for (uint8_t val : buf) {
-        std::cout << std::to_string(val) << ",";
-    }
-    std::cout << std::endl;
-}
-
 void
 uint8_to_uint32(uint8_t b[4], uint32_t &u32)
 {
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index 18af2e57e23db72487ef29c6dc4a3d4d2b089054..293ff80001e9088f96e3d53dd73a041ff4d01ef8 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -2,7 +2,6 @@
 #include "mtx/log.hpp"
 #include "mtxclient/http/client_impl.hpp"
 
-#include <iostream>
 #include <mutex>
 #include <thread>
 
@@ -14,6 +13,7 @@
 
 #include "mtxclient/utils.hpp"
 
+#include "mtx/log.hpp"
 #include "mtx/requests.hpp"
 #include "mtx/responses.hpp"
 
@@ -981,7 +981,7 @@ Client::registration(const std::string &user,
           request,
           [this, cb, h](auto &r, RequestErr e) {
               if (e && e->status_code == 401) {
-                  std::cout << e->matrix_error.error << "\n";
+                  mtx::utils::log::log()->debug("{}", e->matrix_error.error);
                   h.prompt(h, e->matrix_error.unauthorized);
               } else {
                   if (!e && !r.access_token.empty()) {
diff --git a/lib/log.cpp b/lib/log.cpp
index 44e5164e66c9c3758cd66fd5183f73faebe04056..9b8edd3df46327ee3476142349dce199e6e3cdb5 100644
--- a/lib/log.cpp
+++ b/lib/log.cpp
@@ -1,17 +1,14 @@
 #include <mtx/log.hpp>
 
-#include <iostream>
+#include "spdlog/sinks/stdout_color_sinks.h"
 
 namespace mtx::utils::log {
-void
-log_warning(const std::string_view &msg)
+std::shared_ptr<spdlog::logger>
+log()
 {
-    std::cerr << "warning:" << msg << "\n";
-}
+    static auto mtx_logger = std::make_shared<spdlog::logger>(
+      "mtx", std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
 
-void
-log_error(const std::string_view &msg)
-{
-    std::cerr << "error:" << msg << "\n";
+    return mtx_logger;
 }
 }
diff --git a/lib/structs/events/collections.cpp b/lib/structs/events/collections.cpp
index cb369e126a0b27171fa0347473c708fe214c6621..70d860f34cea40772c80f87d21bfe7cc0a018fb4 100644
--- a/lib/structs/events/collections.cpp
+++ b/lib/structs/events/collections.cpp
@@ -263,8 +263,7 @@ from_json(const json &obj, TimelineEvent &e)
                 e.data = events::RoomEvent<events::msg::Redacted>(obj);
                 return;
             } catch (json::exception &err) {
-                mtx::utils::log::log_error(std::string("Invalid event type: ") + err.what() + " " +
-                                           obj.dump(2));
+                mtx::utils::log::log()->error("Invalid event type: {} {}", err.what(), obj.dump(2));
                 return;
             }
         }
diff --git a/lib/structs/requests.cpp b/lib/structs/requests.cpp
index c907c2733e70c30ec72b250afa8f503a7cadb6e8..8ee88da2548965d5ea7bb32a0f647056deb88796 100644
--- a/lib/structs/requests.cpp
+++ b/lib/structs/requests.cpp
@@ -1,7 +1,7 @@
 #include "mtx/requests.hpp"
 #include "mtx/events/collections.hpp"
 #include "mtx/events/encrypted.hpp"
-#include <iostream>
+
 #include <nlohmann/json.hpp>
 
 using json = nlohmann::json;
diff --git a/lib/structs/responses/common.cpp b/lib/structs/responses/common.cpp
index d714e4d377ccb170ea34a8c98132d9da41a402b0..4fbb2ee04bdda25926a24ec8f420cfa25d3b94ba 100644
--- a/lib/structs/responses/common.cpp
+++ b/lib/structs/responses/common.cpp
@@ -19,8 +19,7 @@
 #include "mtx/events/tag.hpp"
 #include "mtx/events/topic.hpp"
 #include "mtx/events/voip.hpp"
-
-#include <iostream>
+#include "mtx/log.hpp"
 
 using json = nlohmann::json;
 using namespace mtx::events::account_data;
@@ -85,15 +84,13 @@ namespace utils {
 void
 log_error(std::exception &err, const json &event)
 {
-    std::cout << err.what() << std::endl;
-    std::cout << event.dump(2) << std::endl;
+    mtx::utils::log::log()->error("Error parsing events: {}, {}", err.what(), event.dump(2));
 }
 
 void
 log_error(const std::string &err, const json &event)
 {
-    std::cout << err << std::endl;
-    std::cout << event.dump(2) << std::endl;
+    mtx::utils::log::log()->error("Error parsing events: {}, {}", err, event.dump(2));
 }
 
 void
diff --git a/lib/structs/responses/members.cpp b/lib/structs/responses/members.cpp
index 287a6b2880958c5f8330a5cd5c825b6b00326bfa..21e15c6713f47ea4d216a4cec650c6fdc2e2216d 100644
--- a/lib/structs/responses/members.cpp
+++ b/lib/structs/responses/members.cpp
@@ -16,8 +16,8 @@ from_json(const nlohmann::json &obj, Members &res)
                 mtx::events::StateEvent<mtx::events::state::Member> member = e;
                 res.chunk.push_back(member);
             } catch (const std::exception &e) {
-                utils::log::log_warning(
-                  std::string("Failed to parse member event in members chunk: ") + e.what());
+                utils::log::log()->warn("Failed to parse member event in members chunk: {}",
+                                        e.what());
             }
         }
     }
diff --git a/meson.build b/meson.build
index fdb8aa53f749f52a06acdaed6732f03f0d64c990..579b9658b9d88589f617564acbabbfaadefb453f 100644
--- a/meson.build
+++ b/meson.build
@@ -17,6 +17,7 @@ endif
 coeurl_dep = dependency('coeurl', version: '>=0.1.1', required: true)
 thread_dep = dependency('threads', required: true)
 openssl_dep = dependency('openssl', version: '>=1.1', required: true)
+spdlog_dep = dependency('spdlog', fallback: ['spdlog', 'spdlog_dep'])
 
 json_dep = dependency('nlohmann_json', version: '>=3.2.0', required: true)
 
@@ -46,7 +47,8 @@ deps = [
   thread_dep,
   olm_dep,
   openssl_dep,
-  json_dep
+  json_dep,
+  spdlog_dep
 ]
 
 inc = include_directories('include')