From 1ddd1eda2d57bd7a4e755e0dfc3377902cd03b17 Mon Sep 17 00:00:00 2001
From: kongr45gpen <electrovesta@gmail.com>
Date: Wed, 7 Aug 2019 17:01:02 +0300
Subject: [PATCH] Use streams for logging to allow displaying different
 variables

---
 CMakeLists.txt              |  1 +
 inc/Logger.hpp              | 69 +++++++++++++++++++++++++++----------
 inc/Service.hpp             |  3 --
 src/Logger.cpp              | 16 +++++++++
 src/Platform/x86/Logger.cpp |  2 +-
 5 files changed, 69 insertions(+), 22 deletions(-)
 create mode 100644 src/Logger.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b8d5802a..ab4a0c23 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
diff --git a/inc/Logger.hpp b/inc/Logger.hpp
index e5b51a6e..3f8a4335 100644
--- a/inc/Logger.hpp
+++ b/inc/Logger.hpp
@@ -3,37 +3,41 @@
 
 #include <cstdint>
 #include <etl/String.hpp>
+#include <etl/to_string.h>
 #include <ECSS_Definitions.hpp>
 
 #if defined LOGLEVEL_TRACE
-	#define LOGLEVEL Logger::trace
+#define LOGLEVEL Logger::trace
 #elif defined LOGLEVEL_DEBUG
-	#define LOGLEVEL Logger::debug
+#define LOGLEVEL Logger::debug
 #elif defined LOGLEVEL_INFO
-	#define LOGLEVEL Logger::info
+#define LOGLEVEL Logger::info
 #elif defined LOGLEVEL_NOTICE
-	#define LOGLEVEL Logger::notice
+#define LOGLEVEL Logger::notice
 #elif defined LOGLEVEL_WARNING
-	#define LOGLEVEL Logger::warning
+#define LOGLEVEL Logger::warning
 #elif defined LOGLEVEL_ERROR
-	#define LOGLEVEL Logger::error
+#define LOGLEVEL Logger::error
 #elif defined LOGLEVEL_EMERGENCY
-	#define LOGLEVEL Logger::emergency
+#define LOGLEVEL Logger::emergency
 #else
-	#define LOGLEVEL Logger::disabled
+#define LOGLEVEL Logger::disabled
 #endif
 
 #define _ac_LOGGER_ENABLED_LEVEL(level) (( (Logger::LogLevelType) LOGLEVEL) <= ( (Logger::LogLevelType) level))
 
-#define LOG(level, message) if (_ac_LOGGER_ENABLED_LEVEL(level)) { Logger::log(level, message); }
+#define LOG(level) \
+    if (_ac_LOGGER_ENABLED_LEVEL(level)) \
+        if (Logger::LogEntry entry(level); true) \
+            entry
 
-#define LOG_TRACE(message)     LOG(Logger::trace, message)
-#define LOG_DEBUG(message)     LOG(Logger::debug, message)
-#define LOG_INFO(message)      LOG(Logger::info, message)
-#define LOG_NOTICE(message)    LOG(Logger::notice, message)
-#define LOG_WARNING(message)   LOG(Logger::warning, message)
-#define LOG_ERROR(message)     LOG(Logger::error, message)
-#define LOG_EMERGENCY(message) LOG(Logger::emergency, message)
+#define LOG_TRACE     LOG(Logger::trace)
+#define LOG_DEBUG     LOG(Logger::debug)
+#define LOG_INFO      LOG(Logger::info)
+#define LOG_NOTICE    LOG(Logger::notice)
+#define LOG_WARNING   LOG(Logger::warning)
+#define LOG_ERROR     LOG(Logger::error)
+#define LOG_EMERGENCY LOG(Logger::emergency)
 
 /**
  * A logging class for ECSS Services that supports ETL's String and is lightweight enough to be used in embedded
@@ -72,10 +76,31 @@ public:
 		disabled = 255, ///< Use this log level to disable logging entirely. No message should be logged as disabled.
 	};
 
+	/**
+	 * A class that defines a log message.
+	 *
+	 * Instead of using this class, prefer one of the above macros
+	 */
+	struct LogEntry {
+		String<LOGGER_MAX_MESSAGE_SIZE> message = ""; ///< The current log message itself
+		etl::format_spec format; ///< ETL's string format specification
+		LogLevel level;
+
+		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.
+		 */
+		~LogEntry();
+
+		LogEntry(LogEntry const&) = delete; ///< Unimplemented copy constructor
+	};
+
 	/**
 	 * @brief Unimplemented copy constructor
 	 *
-	 * Does not allow Loggers should be copied. There should be only one instance for each Logger.
+	 * Does not allow Loggers to be copied. There should be only one instance for each Logger.
 	 */
 	Logger(Logger const&) = delete;
 
@@ -104,7 +129,15 @@ public:
 	/**
 	 * Store a new log message
 	 */
-	static void log(LogLevel level, String<LOGGER_MAX_MESSAGE_SIZE> message);
+	static void log(LogLevel level, String<LOGGER_MAX_MESSAGE_SIZE>& message);
 };
 
+template <class T>
+Logger::LogEntry& operator<<(Logger::LogEntry& entry, const T value) {
+	etl::to_string(value, entry.message, entry.format, true);
+
+	return entry;
+}
+
+
 #endif //ECSS_SERVICES_LOGGER_HPP
diff --git a/inc/Service.hpp b/inc/Service.hpp
index 32e77e19..d7a8ee43 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/src/Logger.cpp b/src/Logger.cpp
new file mode 100644
index 00000000..aba0f7a0
--- /dev/null
+++ b/src/Logger.cpp
@@ -0,0 +1,16 @@
+#include <Logger.hpp>
+
+template <>
+Logger::LogEntry& operator<<(Logger::LogEntry& entry, const char* value) {
+	entry.message.append(value);
+
+	return entry;
+}
+
+Logger::LogEntry::LogEntry(LogLevel level) : level(level) {
+	format.precision(3);
+}
+
+Logger::LogEntry::~LogEntry() {
+	Logger::log(level, message);
+}
\ No newline at end of file
diff --git a/src/Platform/x86/Logger.cpp b/src/Platform/x86/Logger.cpp
index f97705ea..554cd3f7 100644
--- a/src/Platform/x86/Logger.cpp
+++ b/src/Platform/x86/Logger.cpp
@@ -7,7 +7,7 @@
 #include <iomanip>
 
 // The implementation of this function appends ANSI codes that should add colours to a compatible terminal
-void Logger::log(Logger::LogLevel level, String<LOGGER_MAX_MESSAGE_SIZE> message) {
+void Logger::log(Logger::LogLevel level, String<LOGGER_MAX_MESSAGE_SIZE> & message) {
 	// Get the current time & date
 	std::time_t t = std::time(nullptr);
 	std::tm tm = *std::localtime(&t);
-- 
GitLab