diff --git a/CMakeLists.txt b/CMakeLists.txt
index 044b9c0d8c9dc3adb125ddb46b8bfd6ace9e6b9b..1be913a658697eb33f7285d291d8cea22ac23714 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,6 +18,7 @@ add_custom_target(check
 add_library(common OBJECT
         src/Service.cpp
         src/ErrorHandler.cpp
+        src/Logger.cpp
         src/Message.cpp
         src/MessageParser.cpp
         src/ServicePool.cpp
@@ -39,10 +40,11 @@ add_library(common OBJECT
 # Specify the .cpp files for the executables
 file(GLOB x86_main_SRC "src/Platform/x86/*.cpp")
 add_executable(ecss_services
-        src/main.cpp
         $<TARGET_OBJECTS:common>
         ${x86_main_SRC}
         )
+set_target_properties(ecss_services
+        PROPERTIES COMPILE_DEFINITIONS LOGLEVEL_TRACE)
 
 IF (EXISTS "${PROJECT_SOURCE_DIR}/lib/Catch2/CMakeLists.txt")
     # Gather all the .cpp files corresponding to tests
diff --git a/ci/cppcheck-misra.sh b/ci/cppcheck-misra.sh
index 737eb6f217aeb121635dd5cf225b4d3121f1c7e9..b1313b80c7e7cee69cd69e42c85dd2fee6424223 100755
--- a/ci/cppcheck-misra.sh
+++ b/ci/cppcheck-misra.sh
@@ -11,9 +11,12 @@
 cd "$(dirname "$0")/.."
 
 echo -e "\u001b[34;1mGetting prerequisites...\u001b[0m"
+# get the current cppcheck version
+CPCVERSION=`cppcheck --version | awk '{print $2}'`
+
 # grab the MISRA addon and the cppcheck addon interface from github
-curl https://raw.githubusercontent.com/danmar/cppcheck/master/addons/misra.py > ci/misra.py
-curl https://raw.githubusercontent.com/danmar/cppcheck/master/addons/cppcheckdata.py > ci/cppcheckdata.py
+curl https://raw.githubusercontent.com/danmar/cppcheck/$CPCVERSION/addons/misra.py > ci/misra.py
+curl https://raw.githubusercontent.com/danmar/cppcheck/$CPCVERSION/addons/cppcheckdata.py > ci/cppcheckdata.py
 
 # generate dump files (XML representations of AST etc.) for all headers, source files etc.
 echo -e "\u001b[34;1mGenerating dump files...\u001b[0m"
@@ -30,8 +33,13 @@ sed -i -r 's/(.*Script.*)|(.*Checking.*)|(.*MISRA.*)|(.*Undefined: .*)|(.* \(-\)
 # run the summarizer for a nice, clean summary of errors
 echo -e "\u001b[34;1mSummarizing results...\u001b[0m"
 python3 ci/summarizer.py --report ci/report.msr --suppress 3.1 5.1 5.2 5.3 12.3 13.4 14.4 15.5 16.3 18.4 18.8
+EXIT_CODE=$?
 
 # clean up old files
 echo -e "\u001b[34;1mRemoving dump files...\u001b[0m"
 echo > ci/report.msr # clear the report file
 find inc/ src/ -type f -name "*.dump" | xargs rm -rf
+
+# finally return the return value of the summarizer.py script
+exit $EXIT_CODE
+
diff --git a/ci/summarizer.py b/ci/summarizer.py
index 15e48d03c61bffd4caeb8c43804393abbf00016e..5139df77ab8d09d9c8acf4d90b3cd3471023f3dd 100755
--- a/ci/summarizer.py
+++ b/ci/summarizer.py
@@ -58,7 +58,7 @@ class Summarizer(object):
                 with open(os.path.abspath(file_name)) as code_file:
                     code_lines = code_file.readlines()  # Read the source code file
                     line_of_interest = code_lines[int(violation[0]) - 1]  # Get the desired violation line
-                if line_of_interest.find("// Ignore-MISRA") >= 0:
+                if line_of_interest.find("// Ignore-MISRA") >= 0 or line_of_interest.find("/* Ignore-MISRA */") >= 0:
                     continue
 
                 if file_name not in self.violations_map.keys():
diff --git a/ci/vera.sh b/ci/vera.sh
index 4481cfb2b2b49d904725a211d4c58b51fe4c4048..2ece81da50bfefdefba08effef4bbadc3f7d415f 100755
--- a/ci/vera.sh
+++ b/ci/vera.sh
@@ -10,4 +10,5 @@
 echo -e "\033[0;34mRunning vera++...\033[0m"
 
 cd "$(dirname "$0")/.."
-vera++ --error --parameter max-line-length=120 --profile custom `find src inc test -type f -regextype posix-egrep -regex '.*\.(cpp|hpp|c|h)'`
+vera++ --error --parameter max-line-length=120 --profile custom \
+    `find src inc test -type f -regextype posix-egrep -regex '.*\.(cpp|hpp|c|h)' -not -wholename 'inc/Logger.hpp'`
diff --git a/inc/ECSS_Definitions.hpp b/inc/ECSS_Definitions.hpp
index 43a363c6cfcdc851364bad9a2a70127081c2a651..32b69187d174171fcaee147d8b5973a3145e3de2 100644
--- a/inc/ECSS_Definitions.hpp
+++ b/inc/ECSS_Definitions.hpp
@@ -58,4 +58,9 @@
 #define ECSS_MAX_DELTA_OF_RELEASE_TIME 60
 // release time and the actual release time
 
+/**
+ * @brief The maximum size of a log message
+ */
+#define LOGGER_MAX_MESSAGE_SIZE 512
+
 #endif // ECSS_SERVICES_ECSS_DEFINITIONS_H
diff --git a/inc/Logger.hpp b/inc/Logger.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..96c2101f84aeefb375400cfa0dbd54bc34f6b7e0
--- /dev/null
+++ b/inc/Logger.hpp
@@ -0,0 +1,224 @@
+#ifndef ECSS_SERVICES_LOGGER_HPP
+#define ECSS_SERVICES_LOGGER_HPP
+
+#include <cstdint>
+#include <etl/String.hpp>
+#include <etl/to_string.h>
+#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<LOGGER_MAX_MESSAGE_SIZE> 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/Service.hpp b/inc/Service.hpp
index 32e77e19387cd0b74236c082ffc271f3557535b7..d7a8ee4378365c925a90f12ff46f81238e1e5f27 100644
--- a/inc/Service.hpp
+++ b/inc/Service.hpp
@@ -3,7 +3,6 @@
 
 #include <cstdint>
 #include "Message.hpp"
-#include <iostream> // This file should be removed
 
 class ServicePool;
 
@@ -12,8 +11,6 @@ class ServicePool;
  *
  * A member of the Service class should be used as a singleton, i.e. must be created only once in
  * the code
- *
- * @todo Disable copy constructor
  */
 class Service {
 private:
diff --git a/lib/etl b/lib/etl
index f2f23395cde34704686233094d9619ad8d06453a..5a7e181100dae73659db133783ae964c7661437b 160000
--- a/lib/etl
+++ b/lib/etl
@@ -1 +1 @@
-Subproject commit f2f23395cde34704686233094d9619ad8d06453a
+Subproject commit 5a7e181100dae73659db133783ae964c7661437b
diff --git a/src/Logger.cpp b/src/Logger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..76e36d59af7a58a5b9c34cd9fa3aca3592fee4b1
--- /dev/null
+++ b/src/Logger.cpp
@@ -0,0 +1,24 @@
+#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/ErrorHandler.cpp b/src/Platform/x86/ErrorHandler.cpp
index a59e36514ddf1a7047ab4e2e2a47f05dfd4cfd89..ffddffd2d0501e688779674251ae1da83aa035d7 100644
--- a/src/Platform/x86/ErrorHandler.cpp
+++ b/src/Platform/x86/ErrorHandler.cpp
@@ -7,6 +7,7 @@
 #include <cxxabi.h>
 #include <ErrorHandler.hpp>
 #include <Message.hpp>
+#include <Logger.hpp>
 
 // TODO: Find a way to reduce the number of copies of this chunk
 template void ErrorHandler::logError(const Message&, ErrorHandler::AcceptanceErrorType);
@@ -18,23 +19,22 @@ template void ErrorHandler::logError(ErrorHandler::InternalErrorType);
 
 template <typename ErrorType>
 void ErrorHandler::logError(const Message& message, ErrorType errorType) {
-	std::cerr
+	LOG_ERROR
 	    /*
 	     * Gets the error class name from the template
 	     * Note: This is g++-dependent code and should only be used for debugging.
 	     */
 	    << abi::__cxa_demangle(typeid(ErrorType).name(), nullptr, nullptr, nullptr) << " Error "
 	    << "[" << static_cast<uint16_t>(message.serviceType) << "," << static_cast<uint16_t>(message.messageType)
-	    << "]: " << errorType << std::endl;
+	    << "]: " << errorType;
 }
 
 template <typename ErrorType>
 void ErrorHandler::logError(ErrorType errorType) {
-	std::cerr
+	LOG_ERROR
 	    /*
 	     * Gets the error class name from the template
 	     * Note: This is g++-dependent code and should only be used for debugging.
 	     */
-	    << abi::__cxa_demangle(typeid(ErrorType).name(), nullptr, nullptr, nullptr) << " Error: " << errorType
-	    << std::endl;
+	    << abi::__cxa_demangle(typeid(ErrorType).name(), nullptr, nullptr, nullptr) << " Error: " << errorType;
 }
diff --git a/src/Platform/x86/Logger.cpp b/src/Platform/x86/Logger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85d0797656a4831c718e5b64740eb977ddb98c1d
--- /dev/null
+++ b/src/Platform/x86/Logger.cpp
@@ -0,0 +1,68 @@
+#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;
+}
diff --git a/src/Platform/x86/Service.cpp b/src/Platform/x86/Service.cpp
index 7c20d2f90bea925cd43754c62a231c25bc2c5648..60786fc650c32da32cb98860886291537e378fce 100644
--- a/src/Platform/x86/Service.cpp
+++ b/src/Platform/x86/Service.cpp
@@ -1,21 +1,25 @@
 #include <iostream>
 #include <iomanip>
+#include <Logger.hpp>
 #include "Service.hpp"
 
 void Service::storeMessage(Message& message) {
 	// appends the remaining bits to complete a byte
 	message.finalize();
 
+	// Create a new stream to display the packet
+	std::ostringstream ss;
+
 	// Just print it to the screen
-	std::cout << "New " << ((message.packetType == Message::TM) ? "TM" : "TC") << "["
-	          << std::hex
-	          // << std::dec
-	          << static_cast<int>(message.serviceType) << "," << static_cast<int>(message.messageType)
-	          << "] message!\n";
-	// std::cout << std::hex << std::setfill('0') << std::setw(2);
-	for (int i = 0; i < message.dataSize; i++) {
-		std::cout << static_cast<int>(message.data[i]);
-		std::cout << " ";
+	ss << "New " << ((message.packetType == Message::TM) ? "TM" : "TC") << "["
+	   << std::hex
+	   << static_cast<int>(message.serviceType) << "," // Ignore-MISRA
+	   << static_cast<int>(message.messageType) // Ignore-MISRA
+	   << "] message! ";
+
+	for (unsigned int i = 0; i < message.dataSize; i++) {
+		ss << static_cast<int>(message.data[i]) << " "; // Ignore-MISRA
 	}
-	std::cout << std::endl;
+
+	LOG_DEBUG << ss.str();
 }
diff --git a/src/main.cpp b/src/Platform/x86/main.cpp
similarity index 99%
rename from src/main.cpp
rename to src/Platform/x86/main.cpp
index d317286d26f07da765aaf539f67bda78371783c3..57ad74b65072e65d5b56ada6adafe1bc03a5ca6e 100644
--- a/src/main.cpp
+++ b/src/Platform/x86/main.cpp
@@ -1,5 +1,6 @@
 #include <iostream>
 #include <ServicePool.hpp>
+#include <Logger.hpp>
 #include "Helpers/CRCHelper.hpp"
 #include "Helpers/TimeHelper.hpp"
 #include "Services/TestService.hpp"
@@ -20,6 +21,8 @@
 #include "etl/String.hpp"
 
 int main() {
+	LOG_NOTICE << "ECSS Services test application";
+
 	Message packet = Message(0, 0, Message::TC, 1);
 
 	packet.appendString<5>("hello");
@@ -336,5 +339,6 @@ int main() {
 	receivedMsg = Message(11, 12, Message::TC, 1);
 	timeBasedSchedulingService.summaryReportActivitiesByID(receivedMsg);
 
+	LOG_NOTICE << "ECSS Services test complete";
 	return 0;
 }
diff --git a/test/TestPlatform.cpp b/test/TestPlatform.cpp
index a4cbee4d07452934aec010d06061b4479cb2d775..beb76de1e0fb393521b3a22fe915de5c1c68f241 100644
--- a/test/TestPlatform.cpp
+++ b/test/TestPlatform.cpp
@@ -3,6 +3,7 @@
 #include <catch2/catch.hpp>
 #include <Message.hpp>
 #include <Service.hpp>
+#include <Logger.hpp>
 #include "Services/ServiceTests.hpp"
 
 // Explicit template specializations for the logError() function
@@ -34,6 +35,10 @@ void ErrorHandler::logError(ErrorType errorType) {
 	ServiceTests::addError(ErrorHandler::findErrorSource(errorType), errorType);
 }
 
+void Logger::log(Logger::LogLevel level, etl::istring & message) {
+	// Logs while testing are completely ignored
+}
+
 struct ServiceTestsListener : Catch::TestEventListenerBase {
 	using TestEventListenerBase::TestEventListenerBase; // inherit constructor