diff --git a/.gitmodules b/.gitmodules
index 2fc4ae0960502c851fe5afb7c4ffb3290db9f120..c30fec28aea7f8a5584f89122ff9fc8a58a6863a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,6 @@
 [submodule "ci/page_style/doxygen_dark_theme"]
 	path = ci/page_style/doxygen_dark_theme
 	url = https://github.com/MaJerle/doxygen_dark_theme.git
+[submodule "lib/logger"]
+	path = lib/logger
+	url = https://gitlab.com/acubesat/obc/logger.git
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index b361e80b9ac2795e185ae4f8a0ebeac8becdd851..85a1105c248f8e6e1cc46c1dfc393d31028d0525 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -5,5 +5,6 @@
     <mapping directory="$PROJECT_DIR$/ci/page_style/doxygen_dark_theme" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/lib/Catch2" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/lib/etl" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/lib/logger" vcs="Git" />
   </component>
-</project>
\ No newline at end of file
+</project>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 51091706143f679881f5743fe96f47001f63aa8a..99ba91acd1eaae9beffe3b64a8f7588c7e0ab9fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,8 @@ project(ecss_services)
 set(CMAKE_CXX_STANDARD 17)
 
 # Specify the directories for #includes
-include_directories("${PROJECT_SOURCE_DIR}/inc" "${PROJECT_SOURCE_DIR}/lib/etl/include")
+include_directories("${PROJECT_SOURCE_DIR}/inc" "${PROJECT_SOURCE_DIR}/lib/etl/include"
+        "${PROJECT_SOURCE_DIR}/lib/logger/inc")
 
 add_custom_target(check
         COMMAND ./cppcheck.sh
@@ -21,9 +22,9 @@ include_directories(${ECSS_CONFIGURATION})
 
 # Specify the .cpp files common across all targets
 add_library(common OBJECT
+        lib/logger/src/Logger.cpp
         src/Service.cpp
         src/ErrorHandler.cpp
-        src/Logger.cpp
         src/Message.cpp
         src/MessageParser.cpp
         src/ServicePool.cpp
@@ -45,12 +46,17 @@ add_library(common OBJECT
         )
 
 # Specify the .cpp files for the executables
-file(GLOB x86_main_SRC "src/Platform/x86/*.cpp")
+file(GLOB x86_main_SRC "src/Platform/x86/*.cpp" "lib/logger/src/Platform/x86/*.cpp")
+
 add_executable(ecss_services
         $<TARGET_OBJECTS:common>
         ${x86_main_SRC}
         )
 
+# Logs all levels of messages. This command can be added by other users of this
+# library to override the respective log level.
+target_compile_definitions(ecss_services PUBLIC LOGLEVEL_TRACE)
+
 IF (EXISTS "${PROJECT_SOURCE_DIR}/lib/Catch2/CMakeLists.txt")
     # Gather all the .cpp files corresponding to tests
     file(GLOB test_main_SRC "test/*.cpp")
diff --git a/ci/clang-tidy.sh b/ci/clang-tidy.sh
index 3ac4810e6a55343c491976dff0e11eac2a146689..07a04e541dfeb0f04d9aa79a28a854f35d791e12 100755
--- a/ci/clang-tidy.sh
+++ b/ci/clang-tidy.sh
@@ -13,7 +13,7 @@ cd "$(dirname "$0")"
 GCCVERSION=`g++ -dumpversion`
 
 clang-tidy `find ../src/ -type f -regextype posix-egrep -regex '.*\.(cpp|hpp|c|h)'` \
-    -extra-arg=-fcolor-diagnostics -- -std=c++17 -I../inc -I../lib/etl/include -I../inc/Platform/x86 \
+    -extra-arg=-fcolor-diagnostics -- -std=c++17 -I../inc -I../lib/etl/include -I../inc/Platform/x86 -I../lib/logger/inc/ \
     -I/usr/include/c++/$GCCVERSION -I/usr/include/x86_64-linux-gnu/c++/$GCCVERSION \
     -I/usr/include/c++/$GCCVERSION/$MACHTYPE
 
diff --git a/inc/Logger.hpp b/inc/Logger.hpp
deleted file mode 100644
index 158c8d65a5d11ad722138441e5ff9cdc056c05bd..0000000000000000000000000000000000000000
--- a/inc/Logger.hpp
+++ /dev/null
@@ -1,225 +0,0 @@
-#ifndef ECSS_SERVICES_LOGGER_HPP
-#define ECSS_SERVICES_LOGGER_HPP
-
-#include <cstdint>
-#include <etl/String.hpp>
-#include <etl/to_string.h>
-#include "ECSS_Configuration.hpp"
-#include "ECSS_Definitions.hpp"
-
-#if defined LOGLEVEL_TRACE
-#define LOGLEVEL Logger::trace // Ignore-MISRA
-#elif defined LOGLEVEL_DEBUG
-#define LOGLEVEL Logger::debug // Ignore-MISRA
-#elif defined LOGLEVEL_INFO
-#define LOGLEVEL Logger::info // Ignore-MISRA
-#elif defined LOGLEVEL_NOTICE
-#define LOGLEVEL Logger::notice // Ignore-MISRA
-#elif defined LOGLEVEL_WARNING
-#define LOGLEVEL Logger::warning // Ignore-MISRA
-#elif defined LOGLEVEL_ERROR
-#define LOGLEVEL Logger::error // Ignore-MISRA
-#elif defined LOGLEVEL_EMERGENCY
-#define LOGLEVEL Logger::emergency // Ignore-MISRA
-#elif !defined LOGLEVEL
-#define LOGLEVEL Logger::disabled // Ignore-MISRA
-#endif
-
-#define LOG_TRACE     (LOG<Logger::trace>())     ///< @see LOG @relates Logger
-#define LOG_DEBUG     (LOG<Logger::debug>())     ///< @see LOG @relates Logger
-#define LOG_INFO      (LOG<Logger::info>())      ///< @see LOG @relates Logger
-#define LOG_NOTICE    (LOG<Logger::notice>())    ///< @see LOG @relates Logger
-#define LOG_WARNING   (LOG<Logger::warning>())   ///< @see LOG @relates Logger
-#define LOG_ERROR     (LOG<Logger::error>())     ///< @see LOG @relates Logger
-#define LOG_EMERGENCY (LOG<Logger::emergency>()) ///< @see LOG @relates Logger
-
-/**
- * A logging class for ECSS Services that supports ETL's String and is lightweight enough to be used in embedded
- * development.
- *
- * @note Always use the \ref LOG function and its associated utility macros to log. Do not directly use the Logger
- * class.
- */
-class Logger {
-public:
-	/**
-	 * No need to instantiate a Logger object for now.
-	 */
-	Logger() = delete;
-
-	/**
-	 * The underlying type to be used for values of Logger::LogLevel.
-	 */
-	typedef uint8_t LogLevelType;
-
-	/**
-	 * ETL's string format specification, to be used for all logged messages
-	 */
-	static etl::format_spec format;
-
-	/**
-	 * Log levels supported by the logger. Each level represents a different severity of the logged Message,
-	 * and messages of lower severities can be filtered on top of more significant ones.
-	 *
-	 * Each severity is tied to a number. The higher the number, the higher the severity.
-	 */
-	enum LogLevel : LogLevelType {
-		trace = 32, ///< Very detailed information, useful for tracking the individual steps of an operation
-		debug = 64, ///< General debugging information
-		info = 96, ///< Noteworthy or periodical events
-		notice = 128, ///< Uncommon but expected events
-		warning = 160, ///< Unexpected events that do not compromise the operability of a function
-		error = 192, ///< Unexpected failure of an operation
-		emergency = 254, ///< Unexpected failure that renders the entire system unusable
-		disabled = 255, ///< Use this log level to disable logging entirely. No message should be logged as disabled.
-	};
-
-	/**
-	 * An empty enum representing a dummy log entry that will not be logged due to an insufficient level.
-	 *
-	 * @internal
-	 */
-	enum class NoLogEntry {};
-
-	/**
-	 * A class that defines a log message.
-	 *
-	 * Instead of using this class, prefer one of the above macros.
-	 * @see LOG
-	 * @internal
-	 */
-	struct LogEntry {
-		String<LoggerMaxMessageSize> message = ""; ///< The current log message itself, starting from a blank slate
-		LogLevel level; ///< The log level of this message
-
-		explicit LogEntry(LogLevel level); ///< Create a new LogEntry
-
-		/**
-		 * The LogEntry destructor gets called whenever a log message is finalized, and ready to be shown to the
-		 * user. This function is responsible for calling the Logger::log function.
-		 *
-		 * According to the C++ standard, a variable used only within an expression will be immediately destroyed once
-		 * the processing of this expression is over. This allows a syntax such as `LogEntry(...) << "some" << "text"`,
-		 * where the destructor will be called strictly **after** all the `<<` operations have been completed. This
-		 * allows the destructor to send the finalized log entry for further processing.
-		 */
-		~LogEntry();
-
-		LogEntry(LogEntry const&) = delete; ///< Unimplemented copy constructor
-
-		/**
-		 * Stream operator to append new values to a log record
-		 *
-		 * @tparam T The type of value to append
-		 * @param value The new value to add
-         * @todo See if noexcept can be added here without triggering warnings
-		 * @return The current Logger::LogEntry where the value has been appended
-		 */
-		template <class T>
-		Logger::LogEntry& operator<<(const T value) {
-			etl::to_string(value, message, format, true);
-
-			return *this;
-		}
-
-		Logger::LogEntry& operator<<(const std::string& value);
-	};
-
-	/**
-	 * Returns whether a log entry of level \p level is logged, based on the compilation constants
-	 * @param level The level of the log entry
-	 * @return True if the logging is enabled for \p level, false if not
-	 */
-	static constexpr bool isLogged(LogLevelType level) {
-		return static_cast<LogLevelType>(LOGLEVEL) <= level;
-	}
-
-	/**
-	 * Store a new log message
-	 */
-	static void log(LogLevel level, etl::istring & message);
-};
-
-/**
- * Create a stream to log a Message
- *
- * This functions appends one line to the Logs (which could be printed to screen, transferred via UART or stored for
- * later use.)
- *
- * Examples of usage:
- * @code
- * LOG<Logger::debug>() << "Reached point of no return";
- * LOG<Logger::error>() << "More than " << 50 << " dogs found!";
- * @endcode
- *
- * You can also use one of the \ref LOG_TRACE, \ref LOG_DEBUG, \ref LOG_INFO, \ref LOG_NOTICE, \ref LOG_WARNING,
- * \ref LOG_ERROR or \ref LOG_EMERGENCY defines, which avoid the need of explicitly passing the log level:
- * @code
- * LOG_DEBUG << "Reached point of no return";
- * LOG_ERROR << "More than " << 50 << " dogs found!";
- * @endcode
- *
- * See \ref Logger::LogLevel for an explanation of the different log levels.
- *
- * @par Implementation details
- * Functions here are defined as `constexpr` in order to let them be optimized as soon as possible. The LOG()
- * function returns an instance of \ref Logger::LogEntry if the level is high enough to be shown, or an instance of
- * \ref Logger::NoLogEntry if the log entry will not be displayed. As this is a templated function, it is acceptable
- * to support different return types using the `auto` keyword.
- *
- * @warning For messages that will not be logged, any calls to functions that contain **side effects will still take
- * place**.
- * @code
- * LOG_DEBUG << "The temperature is: " << getTemperature();
- * @endcode
- * In the above example, if `getTemperature()` will cause a side effect (e.g. an I2C connection or a `std::cout` print),
- * it will still be executed, even if the debug message will not be printed to the screen due to an insufficient
- * LOGLEVEL.
- *
- * @section GlobalLogLevels Global log levels
- * The **global log level** defines the minimum severity of events to be displayed. Log entries with a severity equal
- * to or higher than the global log level will be shown. Log entries with a severity smaller than the global log level
- * will not be shown.
- *
- * The global log level can be set by defining one of the following constants:
- * - `LOGLEVEL_TRACE`
- * - `LOGLEVEL_DEBUG`
- * - `LOGLEVEL_INFO`
- * - `LOGLEVEL_NOTICE`
- * - `LOGLEVEL_WARNING`
- * - `LOGLEVEL_ERROR`
- * - `LOGLEVEL_EMERGENCY`
- *
- * @relates Logger
- * @tparam level The log level. A value of \ref Logger::LogLevel
- * @return Returns \ref Logger::LogEntry if the level is sufficient to be logged, or \ref Logger::NoLogEntry if the
- * message will not be logged. This is determined at compile-time.
- */
-template <Logger::LogLevel level>
-constexpr __attribute__((always_inline)) inline auto LOG() {
-	if constexpr (Logger::isLogged(level)) {
-		return Logger::LogEntry(level);
-	} else {
-		return Logger::NoLogEntry();
-	}
-};
-
-/**
- * A no-op function that considers an empty log entry that will not be displayed, processed or stored.
- *
- * @warning Note that functions containing **side effects** will get properly executed. Only use functions that return
- * plain values as parts of the log function, so they might be optimized away at compile time.
- *
- * @tparam T The type of the data that will be ignored
- * @param noLogEntry A dummy no-op log entry
- * @param value The data that will be ignored
- * @return A dummy no-op log entry
- * @see Logger::LogEntry::operator<<(const T value)
- * @relates Logger::LogEntry
- */
-template <typename T>
-[[maybe_unused]] constexpr Logger::NoLogEntry operator<<(const Logger::NoLogEntry noLogEntry, T value) {
-	return noLogEntry;
-}
-
-#endif //ECSS_SERVICES_LOGGER_HPP
diff --git a/inc/Platform/x86/ECSS_Configuration.hpp b/inc/Platform/x86/ECSS_Configuration.hpp
index d1ee738207484dd77aa4c4f8b53b3cbd0d77f811..1e7f59f29fe3c3c2d081d35614c4ed2baa0f79db 100644
--- a/inc/Platform/x86/ECSS_Configuration.hpp
+++ b/inc/Platform/x86/ECSS_Configuration.hpp
@@ -9,9 +9,6 @@
  * @see ServiceDefinitions Define the service types that will be compiled
  */
 
-// Log all levels of messages
-#define LOGLEVEL_TRACE
-
 /**
  * @defgroup ServiceDefinitions Service compilation switches
  * These preprocessor defines control whether the compilation of each ECSS service is enabled. By not defining one of
diff --git a/lib/logger b/lib/logger
new file mode 160000
index 0000000000000000000000000000000000000000..4de567283a883fa4d93f454b6979cd4c90c5382b
--- /dev/null
+++ b/lib/logger
@@ -0,0 +1 @@
+Subproject commit 4de567283a883fa4d93f454b6979cd4c90c5382b
diff --git a/src/Logger.cpp b/src/Logger.cpp
deleted file mode 100644
index 76e36d59af7a58a5b9c34cd9fa3aca3592fee4b1..0000000000000000000000000000000000000000
--- a/src/Logger.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#include <Logger.hpp>
-
-etl::format_spec Logger::format;
-
-// Reimplementation of the function for variable C strings
-template <>
-Logger::LogEntry& Logger::LogEntry::operator<<(char* value) {
-	message.append(value);
-	return *this;
-}
-
-// Reimplementation of the function for C strings
-template <>
-Logger::LogEntry& Logger::LogEntry::operator<<(const char* value) {
-	message.append(value);
-	return *this;
-}
-
-Logger::LogEntry::LogEntry(LogLevel level) : level(level) {}
-
-Logger::LogEntry::~LogEntry() {
-	// When the destructor is called, the log message is fully "designed". Now we can finally "display" it to the user.
-	Logger::log(level, message);
-}
diff --git a/src/Platform/x86/Logger.cpp b/src/Platform/x86/Logger.cpp
deleted file mode 100644
index 85d0797656a4831c718e5b64740eb977ddb98c1d..0000000000000000000000000000000000000000
--- a/src/Platform/x86/Logger.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <Logger.hpp>
-#include <etl/String.hpp>
-#include <iostream>
-#include <ECSS_Definitions.hpp>
-
-#include <chrono>
-#include <iomanip>
-
-// The implementation of this function appends ANSI codes that should add colours to a compatible terminal
-void Logger::log(Logger::LogLevel level, etl::istring & message) {
-	// Get the current time & date
-	std::time_t t = std::time(nullptr);
-	std::tm tm = *std::localtime(&t);
-
-	// Get the log level and its colour
-	std::string name;
-	std::string colour;
-	bool keepColour = false; // Whether to keep the colour in the rest of the message
-
-	if (level <= Logger::trace) {
-		name = "trace";
-		colour = "90"; // bright black
-		keepColour = true;
-	} else if (level <= Logger::debug) {
-		name = "debug";
-		colour = "90"; // bright black
-	} else if (level <= Logger::info) {
-		name = "info";
-		colour = "32"; // green
-	} else if (level <= Logger::notice) {
-		name = "notice";
-		colour = "36"; // cyan
-	} else if (level <= Logger::warning) {
-		name = "warning";
-		colour = "33"; // yellow
-	} else if (level <= Logger::error) {
-		name = "error";
-		colour = "31"; // red
-	} else {
-		name = "emergency";
-		colour = "31"; // red
-		keepColour = true;
-	}
-
-	std::ostringstream ss; // A string stream to create the log message
-	ss << "\033" "[0;90m" << std::put_time(&tm, "%FT%T%z") << "\033" "[0m "; // The date
-	ss << "[\033" "[1;" << colour << "m" << std::setfill(' ') << std::setw(7) << std::right // Ignore-MISRA
-		<< name << std::setw(0) << "\033" "[0m] "; // The log level // Ignore-MISRA
-
-	if (keepColour) {
-		ss << "\033" "[0;" << colour << "m"; // Ignore-MISRA
-	}
-	ss << message.c_str(); // The message itself
-	if (keepColour) {
-		ss << "\033" "[0m";
-	}
-
-	ss << "\n";
-	std::cerr << ss.str();
-}
-
-// Reimplementation of the log function for C++ strings
-// This is kept in the Platform files, since we don't want to mess with std::strings in the microcontroller
-Logger::LogEntry& Logger::LogEntry::operator<<(const std::string & value) {
-	message.append(value.c_str());
-
-	return *this;
-}