diff --git a/ci/cppcheck-misra.sh b/ci/cppcheck-misra.sh
index 3ea5c6a1c5f70008bf8f88c4f6b9d67d3204a045..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,7 +33,7 @@ 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
-RETURN=$?
+EXIT_CODE=$?
 
 # clean up old files
 echo -e "\u001b[34;1mRemoving dump files...\u001b[0m"
@@ -38,5 +41,5 @@ 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 $RETURN
+exit $EXIT_CODE
 
diff --git a/inc/Logger.hpp b/inc/Logger.hpp
index f1ec6c95149e2044f35343d93de5186e459c3746..1fce8729d8aa83757680b014f74d62765189c8b6 100644
--- a/inc/Logger.hpp
+++ b/inc/Logger.hpp
@@ -24,70 +24,20 @@
 #define LOGLEVEL Logger::disabled // Ignore-MISRA
 #endif
 
-/**
- * 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: \n
- * `LOG(Logger::debug) << "Reached point of no return";` \n
- * `LOG(Logger::error) << "More than " << 50 << " dogs found!";`
- *
- * 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: \n
- * `LOG_DEBUG << "Reached point of no return";` \n
- * `LOG_ERROR << "More than " << 50 << " dogs found!";`
- *
- * See \ref Logger::LogLevel for an explanation of the different log levels.
- *
- * @par Implementation details
- * This macro uses a trick to pass an object where the `<<` operator can be used, and which is logged when the statement
- * is complete. It uses an `if` statement, initializing a variable within its condition. According to the C++98
- * standard (1998), Clause 3.3.2.4, "Names declared in the [..] condition of the if statement are local to the if [...]
- * statement (including the controlled statement) [...]". This result in the \ref Logger::LogEntry::~LogEntry()
- * to be called as soon as the statement is complete. The bottom `if` statement serves this purpose, and is always
- * evaluated to true to ensure execution.
- *
- * @par
- * Additionally, the top `if` checks the sufficiency of the log level. It should be optimized away at compile-time on
- * invisible log entries, meaning that there is no performance overhead for unused calls to LOG.
- *
- * @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
- * @param level The log level. A value of \ref Logger::LogEntry
- */
-#define LOG(level)  /* Ignore-MISRA */ \
-    if (Logger::isLogged(level)) \
-        if (Logger::LogEntry entry(level); true) \
-            entry
-
-#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
+#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 macro and its associated utility macros to log. Do not directly use the Logger class.
+ * @note Always use the \ref LOG function and its associated utility macros to log. Do not directly use the Logger
+ * class.
  */
 class Logger {
 public:
@@ -118,13 +68,21 @@ public:
 		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
-   	 * @internal
-   	 */
-   	struct LogEntry {
+	 * 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
 		etl::format_spec format; ///< ETL's string format specification
 		LogLevel level; ///< The log level of this message
@@ -134,10 +92,31 @@ public:
 		/**
 		 * 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
+		 * @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);
 	};
 
 	/**
@@ -156,21 +135,85 @@ public:
 };
 
 /**
- * Stream operator to append new values to a log record
+ * 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 T The type of value to append
- * @param entry The already existing Logger::LogEntry
- * @param value The new value to add
- * @return The new Logger::LogEntry where the value has been appended
+ * @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 <class T>
-Logger::LogEntry& operator<<(Logger::LogEntry& entry, const T value) {
-	etl::to_string(value, entry.message, entry.format, true);
+template <Logger::LogLevel level>
+constexpr inline auto LOG() {
+	if constexpr (Logger::isLogged(level)) {
+		return Logger::LogEntry(level);
+	} else {
+		return Logger::NoLogEntry();
+	}
+};
 
-	return entry;
+/**
+ * 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;
 }
 
-Logger::LogEntry& operator<<(Logger::LogEntry& entry, const std::string & value);
-
 #endif //ECSS_SERVICES_LOGGER_HPP
diff --git a/src/Logger.cpp b/src/Logger.cpp
index bb3e40d83f6a77a10de0c5ff546c605b59a1a779..a6ca19f81ca2807fa60c0d997cc974cb8eac9b4c 100644
--- a/src/Logger.cpp
+++ b/src/Logger.cpp
@@ -2,16 +2,16 @@
 
 // Reimplementation of the function for variable C strings
 template <>
-Logger::LogEntry& operator<<(Logger::LogEntry& entry, char* value) {
-	entry.message.append(value);
-	return entry;
+Logger::LogEntry& Logger::LogEntry::operator<<(char* value) {
+	message.append(value);
+	return *this;
 }
 
 // Reimplementation of the function for C strings
 template <>
-Logger::LogEntry& operator<<(Logger::LogEntry& entry, const char* value) {
-	entry.message.append(value);
-	return entry;
+Logger::LogEntry& Logger::LogEntry::operator<<(const char* value) {
+	message.append(value);
+	return *this;
 }
 
 Logger::LogEntry::LogEntry(LogLevel level) : level(level) {
diff --git a/src/Platform/x86/Logger.cpp b/src/Platform/x86/Logger.cpp
index 57af7a38a79c215cedb0d10d295d3bef668f1133..7848d22695bfe5149a2762eef313e76a46849782 100644
--- a/src/Platform/x86/Logger.cpp
+++ b/src/Platform/x86/Logger.cpp
@@ -61,8 +61,8 @@ void Logger::log(Logger::LogLevel level, String<LOGGER_MAX_MESSAGE_SIZE> & messa
 
 // 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& operator<<(Logger::LogEntry& entry, const std::string & value) {
-	entry.message.append(value.c_str());
+Logger::LogEntry& Logger::LogEntry::operator<<(const std::string & value) {
+	message.append(value.c_str());
 
-	return entry;
+	return *this;
 }