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; }