diff --git a/doc/releaseNotes.md b/doc/releaseNotes.md index 93bdbaf..bacf8aa 100644 --- a/doc/releaseNotes.md +++ b/doc/releaseNotes.md @@ -141,3 +141,7 @@ This file describes the main feature changes for each InfoLogger released versio ## v2.6.0 - 24/01/2024 - Updated SWIG version requirement to allow generation of Python3 bindings. + +## v2.7.0 - 25/07/2024 +- Added functions to keep a history of latest log messages: see historyReset() and historyGetSummary(). +- Added function to register error codes descriptions to format message summaries in history. diff --git a/include/InfoLogger/InfoLogger.hxx b/include/InfoLogger/InfoLogger.hxx index b8f15b4..38f5bec 100644 --- a/include/InfoLogger/InfoLogger.hxx +++ b/include/InfoLogger/InfoLogger.hxx @@ -29,6 +29,7 @@ #include #include #include +#include // here are some macros to help including source code info in infologger messages // to be used to quickly specify "infoLoggerMessageOption" argument in some logging functions @@ -396,6 +397,33 @@ class InfoLogger /// Reset counters of messages void resetMessageCount(); + + /// Functions to keep an history of messages + + /// Reset history + /// This function enables storing next log messages injected in a buffer (eg for later error reporting). Existing history is cleared. + /// parameters: + /// messagesToKeep: number of messages stored in buffer + /// rotate: if set, newer messages overwrite stored messages stored in buffer (i.e. history keeps latest messages). By default, history keeps first messages. + /// filterSeverity: messages with lower severity are not stored in history. + /// filterLevel: messages with lower level are not stored in history. + void historyReset(unsigned int messagesToKeep = 0, bool rotate = false, InfoLogger::Severity filterSeverity = InfoLogger::Severity::Error, InfoLogger::Level filterLevel = InfoLogger::Level::Support); + + /// Get a summary of messages stored in history buffer. + /// The buffer is not cleared (use historyReset to do so). + /// Each message is a simplified text. + /// parameters: + /// summary: a variable where to store a copy of the messages currently available in history buffer + /// returns: 0 on success + void historyGetSummary(std::vector &summary); + + /// Function to register a table converting error codes to a meaningful text. + /// Used for formatting message summary. + /// parameters: + /// errorCodes: a vector of key-value pairs. First is the error code, second is a text description + /// clear: if set, existing table is cleared. Otherwise, it is appended (in this case, duplication of codes is not checked. First one found is used). + void registerErrorCodes(const std::vector> errorCodes, bool clear = 0); + /////////////////////// /// internals /////////////////////// diff --git a/src/InfoLogger.cxx b/src/InfoLogger.cxx index 5bf286e..7dabfcf 100644 --- a/src/InfoLogger.cxx +++ b/src/InfoLogger.cxx @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include "InfoLoggerMessageHelper.h" #include "Common/LineBuffer.h" @@ -167,6 +169,26 @@ int infoLoggerLogDebug(InfoLoggerHandle handle, const char* message, ...) return err; } + +// function to convert severities to a priority number (high priority = small number) +int getIdFromSeverity(InfoLogger::Severity s) { + switch (s) { + case InfoLogger::Severity::Fatal : + return 1; + case InfoLogger::Severity::Error : + return 2; + case InfoLogger::Severity::Warning : + return 3; + case InfoLogger::Severity::Info : + return 4; + case InfoLogger::Severity::Debug : + return 5; + default: + return 6; + } +} + + ///////////////////////////////////////////////////////// namespace AliceO2 @@ -420,7 +442,18 @@ class InfoLogger::Impl bool filterDiscardFileEnabled = false; // when set, discarded messages go to file bool filterDiscardFileIgnoreDebug = false; // when set, debug messages even when discarded and filterDiscardFileEnabled=true do not go to file SimpleLog filterDiscardFile; // file object where to save discarded messages + + // history + unsigned int historyMessagesToKeep = 0; + bool historyRotate = false; + int historyFilterSeverity = -1; + int historyFilterLevel = -1; + std::queue historyMessages; // messages currently in history queue + std::mutex historyMutex; // lock to avoid concurrent calls on history functions + // error code conversion table + std::vector> errorCodesTable; // first: error code, second: description + // message flood prevention // constants bool flood_protection = 1; // if set, flood protection mechanism enabled @@ -787,6 +820,32 @@ int InfoLogger::Impl::pushMessage(const InfoLoggerMessageOption& options, const msgHelper.MessageToText(&msg, buffer, sizeof(buffer), InfoLoggerMessageHelper::Format::Debug); puts(buffer); } + + // keep history + std::unique_lock lock(historyMutex); + if (historyMessagesToKeep > 0) { + if (historyRotate || (historyMessagesToKeep > historyMessages.size())) { + if ((getIdFromSeverity(options.severity) <= historyFilterSeverity) && (options.level <= historyFilterLevel)) { + std::string summary; + summary += "code "+ std::to_string(options.errorCode); + if (options.errorCode != undefinedMessageOption.errorCode) { + for(const auto &p: errorCodesTable) { + if (p.first == options.errorCode) { + summary += " : " + p.second; + break; + } + } + summary += " - "; + } + summary += messageBody; + historyMessages.push(std::move(summary)); + if (historyRotate && (historyMessagesToKeep > historyMessages.size())) { + historyMessages.pop(); + } + } + } + } + return 0; } @@ -1282,6 +1341,37 @@ void InfoLogger::resetMessageCount() { mPimpl->resetMessageCount(); } +void InfoLogger::historyReset(unsigned int messagesToKeep, bool rotate, InfoLogger::Severity filterSeverity, InfoLogger::Level filterLevel) { + std::unique_lock lock(mPimpl->historyMutex); + mPimpl->historyMessagesToKeep = messagesToKeep; + mPimpl->historyRotate = rotate; + mPimpl->historyFilterSeverity = getIdFromSeverity(filterSeverity); + mPimpl->historyFilterLevel = (int)filterLevel; + mPimpl->historyMessages = {}; // clear queue +} + +void InfoLogger::historyGetSummary(std::vector &summary) { + std::unique_lock lock(mPimpl->historyMutex); + summary.clear(); + summary.reserve(mPimpl->historyMessages.size()); + while (!mPimpl->historyMessages.empty()) + { + summary.push_back(std::move(mPimpl->historyMessages.front())); + mPimpl->historyMessages.pop(); + } +} + +void InfoLogger::registerErrorCodes(const std::vector> errorCodes, bool clear) { + std::unique_lock lock(mPimpl->historyMutex); + if (clear) { + mPimpl->errorCodesTable.clear(); + } + for(const auto &c: errorCodes) { + mPimpl->errorCodesTable.push_back({c.first, c.second}); + } +} + + // end of namespace } // namespace InfoLogger } // namespace AliceO2 diff --git a/test/testInfoLogger.cxx b/test/testInfoLogger.cxx index 2bd41df..0a04971 100644 --- a/test/testInfoLogger.cxx +++ b/test/testInfoLogger.cxx @@ -25,6 +25,23 @@ int main() { InfoLogger theLog; + if (0) { + theLog.historyReset(2); + theLog.registerErrorCodes({{123, "error"}, {124, "fatal"}}); + theLog.log(LogInfoSupport_(100), "test info"); + theLog.log(LogErrorSupport_(123), "test error 123"); + theLog.log(LogInfoDevel_(100), "test info"); + theLog.log(LogFatalSupport_(124), "test fatal 124"); + std::vector m; + theLog.historyGetSummary(m); + printf("log summary:\n"); + for(const auto&s: m) { + printf(" %s\n",s.c_str()); + } + printf("\n"); + return 0; + } + theLog.log("infoLogger message test"); printf("Message on stdout (initial stdout)\n"); theLog.setStandardRedirection(1);