diff --git a/doc/design/diagrams/logging/logger_compile_time_replacement.puml b/doc/design/diagrams/logging/logger_compile_time_replacement.puml index 302090deda..7bacf1ee06 100644 --- a/doc/design/diagrams/logging/logger_compile_time_replacement.puml +++ b/doc/design/diagrams/logging/logger_compile_time_replacement.puml @@ -1,16 +1,15 @@ @startuml participant User -participant "iox::log" as logFreeFunction << free function >> -participant Logger << static functions >> -participant "logger : DefaultLogger" as defaultLoggerInstance +participant Logger << static >> +participant "logger : Logger" as loggerInstance -User -> logFreeFunction ++ : initLogger +User -> Logger ++ : init -logFreeFunction -> Logger ++ : getLogger <> +Logger -> Logger ++ : get Logger -> Logger ++ : activeLogger -Logger -> defaultLoggerInstance ** -note over defaultLoggerInstance +Logger -> loggerInstance ** +note over loggerInstance the default logger is configured via the alias in @@ -19,8 +18,8 @@ end note return return -logFreeFunction -> defaultLoggerInstance ++ : Logger::initLogger <> -defaultLoggerInstance -> defaultLoggerInstance ++ : initiLoggerHook +Logger -> loggerInstance ++ : initLoggerInternal +loggerInstance -> loggerInstance ++ : initiLogger return return diff --git a/doc/design/diagrams/logging/logger_runtime_replacement.puml b/doc/design/diagrams/logging/logger_runtime_replacement.puml index 1a7f79f823..1536a87607 100644 --- a/doc/design/diagrams/logging/logger_runtime_replacement.puml +++ b/doc/design/diagrams/logging/logger_runtime_replacement.puml @@ -2,15 +2,14 @@ participant User participant MyLogger << static >> -participant "iox::log" as logFreeFunction << free function >> -participant Logger +participant Logger << static >> +participant "logger : Logger" as loggerInstance participant "myLogger : MyLogger" as myLoggerInstance -participant "logger : DefaultLogger" as DefaultLogger User -> MyLogger ++ : init MyLogger -> myLoggerInstance ** -MyLogger -> logFreeFunction ++ : setActiveLogger(&myLogger) -logFreeFunction -> Logger ++ : activeLogger(&myLogger) +MyLogger -> Logger ++ : setActiveLogger(&myLogger) +Logger -> Logger ++ : activeLogger(&myLogger) note left Logger the static pointer to the active logger @@ -18,8 +17,8 @@ note left Logger myLogger end note -Logger -> DefaultLogger ** -note over DefaultLogger +Logger -> loggerInstance ** +note over loggerInstance calling activeLogger without parameter will create this anyway @@ -34,13 +33,13 @@ end note return return -MyLogger -> logFreeFunction ++ : initLogger -logFreeFunction -> Logger ++ : getLogger <> +MyLogger -> Logger ++ : init +Logger -> Logger ++ : get Logger -> Logger ++ : activeLogger() return return -logFreeFunction -> myLoggerInstance ++ : Logger::initLogger <> -myLoggerInstance -> myLoggerInstance ++ : initiLoggerHook +Logger -> loggerInstance ++ : initLoggerInternal +loggerInstance -> myLoggerInstance ++ : initiLogger return return return diff --git a/doc/design/diagrams/logging/logger_testing_sequence.puml b/doc/design/diagrams/logging/logger_testing_sequence.puml index 0cf3a1e8dd..f2726a5171 100644 --- a/doc/design/diagrams/logging/logger_testing_sequence.puml +++ b/doc/design/diagrams/logging/logger_testing_sequence.puml @@ -2,7 +2,7 @@ participant User participant TestingLogger << static >> -participant "iox::log" as logFreeFunction << free function >> +participant Logger << static >> participant "logger : TestingLogger" as testingLoggerInstance participant LogPrinter participant gTest @@ -11,9 +11,9 @@ participant TestEventListener User -> TestingLogger ++ : init TestingLogger -> testingLoggerInstance ** -TestingLogger -> logFreeFunction ++: setActiveLogger +TestingLogger -> Logger ++: setActiveLogger return -TestingLogger -> logFreeFunction ++: initLogger +TestingLogger -> Logger ++: init return TestingLogger -> UnitTest ++ : listeners return @@ -24,14 +24,14 @@ return User -> gTest ++ : RUN_ALL_TESTS gTest -> LogPrinter ++ : OnTestStart -LogPrinter -> logFreeFunction ++: getLogger +LogPrinter -> Logger ++: get return LogPrinter -> testingLoggerInstance ++ : clearLogBuffer return return gTest -> LogPrinter ++ : OnTestPartResult -LogPrinter -> logFreeFunction ++: getLogger +LogPrinter -> Logger ++: get return LogPrinter -> testingLoggerInstance ++ : printLogBuffer return diff --git a/doc/design/diagrams/logging/logging_classes.puml b/doc/design/diagrams/logging/logging_classes.puml index 1593a1c058..40204c87ea 100644 --- a/doc/design/diagrams/logging/logging_classes.puml +++ b/doc/design/diagrams/logging/logging_classes.puml @@ -12,15 +12,25 @@ enum LogLevel { TRACE, } -class Logger { +class Logger { + {static} void init(constLogLevel logLevel = LogLevelFromEnvOr(LogLevel::Info)) + + {static} Logger& get() + {static} LogLevel minimalLogLevel() + {static} bool ignoreLogLevel() + + {static} Logger* setActiveLogger(Logger* newLogger) + - void initLoggerInternal(LogLevel logLevel) + - {static} Logger* activeLogger(Logger* newLogger = nullptr) +} + +class ConsoleLogger { + {static} LogLevel getLogLevel() + void setLogLevel(LogLevel logLevel) - # {abstract} void initLoggerHook(LogLevel logLevel) + # ConsoleLogger() + # {abstract} void initLogger(LogLevel logLevel) # {abstract} void setupNewLogMessage(const char* file, const int line, const char* function, LogLevel logLevel) # {abstract} void flush() + # std::tuple getLogBuffer() const + # void assumeFlushed() # void LogString(const char* message) # void logI64Dec(const int64_t value) # void logU64Dec(const uint64_t value) @@ -30,15 +40,6 @@ class Logger { # void logFloat(const float value) # void logDouble(const double value) # void logLongDouble(const long double value) - - void initLogger(LogLevel logLevel) - - {static} Logger& get() - - {static} Logger* activeLogger(Logger* newLogger = nullptr) -} - -class ConsoleLogger { - # ConsoleLogger() - # void setupNewLogMessage(const char* file, const int line, const char* function, LogLevel logLevel) - # void flush() } } @@ -76,8 +77,8 @@ class LogStream { } -ConsoleLogger --|> Logger TestingLogger --|> ConsoleLogger +ConsoleLogger <|-- Logger : via Impl Logger <.. LogStream : <> LogStream .> LogHex : <> LogStream .> LogOct : <> diff --git a/doc/design/diagrams/logging/logging_with_logstream.puml b/doc/design/diagrams/logging/logging_with_logstream.puml index f525fac9ab..03d7ba4e13 100644 --- a/doc/design/diagrams/logging/logging_with_logstream.puml +++ b/doc/design/diagrams/logging/logging_with_logstream.puml @@ -2,14 +2,12 @@ participant User participant LogStream -participant "iox::log" as logFreeFunction << free function >> participant Logger << static >> -participant "logger : DefaultLogger" as DefaultLogger +participant "logger : Logger" as loggerInstance User -> LogStream ** : IOX_LOG(...) activate LogStream -LogStream -> logFreeFunction ++ : getLogger -logFreeFunction -> Logger ++ : get <> +LogStream -> Logger ++ : get Logger -> Logger ++ : activeLogger() note right activeLogger is only called @@ -19,24 +17,23 @@ note right end note Logger -> Logger : std::lock -Logger -> DefaultLogger ** +Logger -> loggerInstance ** -return return return -LogStream -> DefaultLogger ++ : setupNewLogMessage +LogStream -> loggerInstance ++ : setupNewLogMessage return return User -> LogStream ++ : << Hello World -LogStream -> DefaultLogger ++ : logString +LogStream -> loggerInstance ++ : logString return return User -> LogStream ++ : dTor via ; on temporary -LogStream -> DefaultLogger ++ : flush +LogStream -> loggerInstance ++ : flush return return destroy LogStream diff --git a/doc/design/logging.md b/doc/design/logging.md index 9b0c1b690c..dee4b1a020 100644 --- a/doc/design/logging.md +++ b/doc/design/logging.md @@ -6,8 +6,8 @@ Logging is a crucial part of a framework since it informs the developer and user of the framework about anomalies and helps with debugging by providing contextual information. -Additionally, when integrated into a separate framework, the logging should be -forwarded in order to have a single logging infrastructure. +Additionally, when integrated into a separate framework, the log messages should +be forwarded in order to have a single logging infrastructure. The logging should also be performant and not allocate memory. Ideally it should be possible to disable it at compile time and let the compiler optimize it away. @@ -84,6 +84,9 @@ in combination with the error handler. ![logging class diagram](../website/images/logging_classes.svg) +The logger can be customized at compile time and at runtime. The former is done +by the `Impl` template parameter and the latter by deriving from the logger. + #### Logging with LogStream ![logging with logstream](../website/images/logging_with_logstream.svg) @@ -115,10 +118,10 @@ This is the log macro with lazy evaluation #define IOX_LOG(level) IOX_LOG_INTERNAL(__FILE__, __LINE__, __FUNCTION__, iox::log::LogLevel::level) ``` -With `minimalLogLevel` and `ignoreActiveLogLevel` being static constexpr functions +With `minimalLogLevel` and `ignoreLogLevel` being static constexpr functions the compiler will optimize this either to `if (false) iox::log::LogStream(...)` and finally completely away or -`if ((level) <= iox::log::Logger::activeLogLevel()) iox::log::LogStream(...)`. +`if ((level) <= iox::log::Logger::getLogLevel()) iox::log::LogStream(...)`. The minimal log level check is intended to fully optimize away a log statement and the ignore active log level check to always forward the log message to the logger, independent of the active log level. @@ -137,15 +140,15 @@ Although the macro contains an incomplete if-statement, the `LogStream` object a the end makes it safe to use since the compiler will complain if something else than a streaming operator or semicolon is used. -#### Behavior before calling initLogger +#### Behavior before calling Logger::init -In order to have log messages before `initLogger` is called, the default logger +In order to have log messages before `Logger::init` is called, the default logger is used with `LogLevel::INFO`. It is up to the implementation of the default logger what to do with these messages. For iceoryx the default logger is the `ConsoleLogger` (this can be changed via the platform abstraction) which will print the log messages to the console. -Although it is possible to use the logger without calling `initLogger`, this is +Although it is possible to use the logger without calling `Logger::init`, this is not recommended. This behaviour is only intended to catch important log messages from pre-main logger calls. @@ -160,7 +163,7 @@ must be activated by calling the static `Logger::activeLogger` function and passing the new logger as argument to the function. The logger must have a static lifetime and should therefore be placed in the data segment. -The call to `iox::log::initLogger` will finalize the option to replace the logger +The call to `iox::log::Logger::init` will finalize the option to replace the logger at runtime. Further efforts to replace the logger will call the error handler with a `MODERATE` error level and a log message to the old and new logger @@ -176,28 +179,46 @@ header. ```cpp using LogLevel = pbb::LogLevel; using pbb::asStringLiteral; +using pbb::logLevelFromEnvOr; -using Logger = pbb::Logger; -using DefaultLogger = pbb::ConsoleLogger; -using TestingLoggerBase = pbb::ConsoleLogger; +using Logger = pbb::Logger; +using TestingLoggerBase = pbb::Logger; ``` With `LogLevel` enum being the log level enum with the values defined in the [class diagram](#class-diagram). The `Logger` must be a class specifying the -interface from the class diagram and `DefaultLogger` and `TestingLoggerBase` -are the actual implementations which are used by default in the library and for -the tests. +interface from the class diagram. It is recommended to provide a custom +implementation via the template parameter instead of implementing everything +from scratch. The `pbb::ConsoleLogger` is an example of such an implementation +and can also be a base for customization. + +The `Impl` part of the logger must fulfil the following interface -It is recommended to derive from `pbb::Logger` to create a custom -logger but it would also be possible to recreate it from scratch as long as the -interface is satisfied. +```cpp +public: + static LogLevel getLogLevel(); + void setLogLevel(LogLevel logLevel); +protected: + virtual void initLogger(LogLevel logLevel); + virtual void setupNewLogMessage(const char* file, const int line, const char* function, LogLevel logLevel); + virtual void flush(); + std::tuple getLogBuffer() const; + void assumeFlushed(); + void LogString(const char* message); + void logI64Dec(const int64_t value); + void logU64Dec(const uint64_t value); + void logU64Hex(const uint64_t value); + void logU64Oct(const uint64_t value); + void logBool(const bool value); + void logFloat(const float value); + void logDouble(const double value); + void logLongDouble(const long double value); +``` -The implementation of a custom default logger is similar to the logger which -can be replaced at runtime, except the static `init` function. Since the default -logger must be initialized by the `iox::log::initLogger` call, it is not -recommended to provide a public init function. If some initialization is needed, -the `initLoggerHook` virtual function can be overloaded. This is called after -the base logger is initialized. +Tests should be silent and not flood the console with expected error messages. +`TestingLoggerBase` will be used as base class for the testing logger to suppress +the output for passed tests. This class must also be derived from `Logger` in +order to replace the logger at runtime. ![logger compile time replacement](../website/images/logger_compile_time_replacement.svg) @@ -220,16 +241,16 @@ This is the sequence diagram of the setup of the testing logger: #### Environment variables The behavior of the logger can be altered via environment variables and the -`initLogger` function. Calling this function without arguments, it will check +`Logger::init` function. Calling this function without arguments, it will check whether the environment variable `IOX_LOG_LEVEL` is set and use that value or `LogLevel::INFO` if the environment variable is not set. To have a different fallback log level, the `logLevelFromEnvOr` function can be used, e.g. ```cpp -iox::log::initLogger(iox::log::logLevelFromEnvOr(iox::log::LogLevel::DEBUG)); +iox::log::Logger::init(iox::log::logLevelFromEnvOr(iox::log::LogLevel::DEBUG)); ``` -If the logger shall not be altered via environment variables, `initLogger` must +If the logger shall not be altered via environment variables, `Logger::init` must be called with the fitting log level. For the `TestingLogger` there is an additional environment variable called @@ -256,7 +277,7 @@ re-implemented via the platform abstraction. int main() { - iox::log::initLogger(iox::log::logLevelFromEnvOr(iox::log::LogLevel::DEBUG)); + iox::log::Logger::init(iox::log::logLevelFromEnvOr(iox::log::LogLevel::DEBUG)); IOX_LOG(DEBUG) << "Hello World"; @@ -308,7 +329,7 @@ class MyLogger : public iox::log::Logger { static MyLogger myLogger; iox::log::setActiveLogger(&myLogger); - iox::log::initLogger(iox::log::logLevelFromEnvOr(iox::log::LogLevel::INFO)); + iox::log::Logger::init(iox::log::logLevelFromEnvOr(iox::log::LogLevel::INFO)); } private: @@ -361,7 +382,7 @@ int main() ## Open issues -- do we need to change the log level after `initLogger` +- do we need to change the log level after `Logger::init` - do we want a `IOX_LOG_IF(cond, level)` macro - shall the TestingLogger register signals to catch SIGTERM, etc. and print the log messages when the signal is raised? It might be necessary to wait for the diff --git a/doc/website/images/logger_compile_time_replacement.svg b/doc/website/images/logger_compile_time_replacement.svg index b7541c27cb..622ff9d479 100644 --- a/doc/website/images/logger_compile_time_replacement.svg +++ b/doc/website/images/logger_compile_time_replacement.svg @@ -1 +1 @@ -UserUser«free function»iox::log«free function»iox::log«static functions»Logger«static functions»Loggerlogger : DefaultLoggerinitLoggergetLogger <<private>>activeLoggerlogger : DefaultLoggerthe default loggeris configured viathe alias inplatform_settings.hppLogger::initLogger <<private>>initiLoggerHook \ No newline at end of file +UserUser«static»Logger«static»Loggerlogger : LoggerinitgetactiveLoggerlogger : Loggerthe default loggeris configured viathe alias inplatform_settings.hppinitLoggerInternalinitiLogger \ No newline at end of file diff --git a/doc/website/images/logger_runtime_replacement.svg b/doc/website/images/logger_runtime_replacement.svg index c085dbe2d4..02ba7d7e64 100644 --- a/doc/website/images/logger_runtime_replacement.svg +++ b/doc/website/images/logger_runtime_replacement.svg @@ -1 +1 @@ -UserUser«static»MyLogger«static»MyLogger«free function»iox::log«free function»iox::logLoggerLoggermyLogger : MyLoggerlogger : DefaultLoggerinitmyLogger : MyLoggersetActiveLogger(&myLogger)activeLogger(&myLogger)the static pointerto the active loggerwill be set tomyLoggerlogger : DefaultLoggercalling activeLoggerwithout parameterwill create this anyway(e.g. by logging beforecalling initLogger)so there is no needto to make it morecomplicated bybranchinginitLoggergetLogger <<private>>activeLogger()Logger::initLogger <<private>>initiLoggerHook \ No newline at end of file +UserUser«static»MyLogger«static»MyLogger«static»Logger«static»Loggerlogger : LoggermyLogger : MyLoggerinitmyLogger : MyLoggersetActiveLogger(&myLogger)activeLogger(&myLogger)the static pointerto the active loggerwill be set tomyLoggerlogger : Loggercalling activeLoggerwithout parameterwill create this anyway(e.g. by logging beforecalling initLogger)so there is no needto to make it morecomplicated bybranchinginitgetactiveLogger()initLoggerInternalinitiLogger \ No newline at end of file diff --git a/doc/website/images/logger_testing_sequence.svg b/doc/website/images/logger_testing_sequence.svg index 10dcf91774..5050042c18 100644 --- a/doc/website/images/logger_testing_sequence.svg +++ b/doc/website/images/logger_testing_sequence.svg @@ -1 +1 @@ -UserUser«static»TestingLogger«static»TestingLogger«free function»iox::log«free function»iox::loglogger : TestingLoggerLogPrinterLogPrintergTestgTestUnitTestUnitTestTestEventListenerTestEventListenerinitlogger : TestingLoggersetActiveLoggerinitLoggerlistenersAppend(LogPrinter*)RUN_ALL_TESTSOnTestStartgetLoggerclearLogBufferOnTestPartResultgetLoggerprintLogBuffer \ No newline at end of file +UserUser«static»TestingLogger«static»TestingLogger«static»Logger«static»Loggerlogger : TestingLoggerLogPrinterLogPrintergTestgTestUnitTestUnitTestTestEventListenerTestEventListenerinitlogger : TestingLoggersetActiveLoggerinitlistenersAppend(LogPrinter*)RUN_ALL_TESTSOnTestStartgetclearLogBufferOnTestPartResultgetprintLogBuffer \ No newline at end of file diff --git a/doc/website/images/logging_classes.svg b/doc/website/images/logging_classes.svg index 8db87fa717..f639e38b24 100644 --- a/doc/website/images/logging_classes.svg +++ b/doc/website/images/logging_classes.svg @@ -1,16 +1,16 @@ -platform_building_blocksiceoryx_hoofs_testingiceoryx_hoofsLogLevelOFF = 0,FATAL,ERROR,WARN,INFO,DEBUG,TRACE,Loggervoid init(constLogLevel logLevel = LogLevelFromEnvOr(LogLevel::Info))LogLevel minimalLogLevel()bool ignoreLogLevel()LogLevel getLogLevel()void setLogLevel(LogLevel logLevel)void initLoggerHook(LogLevel logLevel)void setupNewLogMessage(const char* file, const int line, const char* function, LogLevel logLevel)void flush()void LogString(const char* message)void logI64Dec(const int64_t value)void logU64Dec(const uint64_t value)void logU64Hex(const uint64_t value)void logU64Oct(const uint64_t value)void logBool(const bool value)void logFloat(const float value)void logDouble(const double value)void logLongDouble(const long double value)void initLogger(LogLevel logLevel)Logger& get<T>()Logger* activeLogger<T>(Logger* newLogger = nullptr)ConsoleLoggerConsoleLogger()void setupNewLogMessage(const char* file, const int line, const char* function, LogLevel logLevel)void flush()TestingLoggervoid init()uint64_t getNumberOfLogMessages()std::vector<std::string> getLogMessages()void clearLogBuffer()void printLogBuffer()void TestingLogger()void flush()LogHexTLogOctTLogStreamLogStream(const char* file, const int line, const char* function, LogLevel logLevel)void flush()LogStream& self()LogStream& operator<<(const char* cstr)LogStream& operator<<(const T value)LogStream& operator<<(const LogHex<T>&& value)LogStream& operator<<(const LogOct<T>&& value)LogStream& operator<<(const Callable&& c)«friend»«friend»«friend» \ No newline at end of file +platform_building_blocksiceoryx_hoofs_testingiceoryx_hoofsLogLevelOFF = 0,FATAL,ERROR,WARN,INFO,DEBUG,TRACE,LoggerImplvoid init(constLogLevel logLevel = LogLevelFromEnvOr(LogLevel::Info))Logger& get()LogLevel minimalLogLevel()bool ignoreLogLevel()Logger* setActiveLogger(Logger* newLogger)void initLoggerInternal(LogLevel logLevel)Logger* activeLogger<T>(Logger* newLogger = nullptr)ConsoleLoggerLogLevel getLogLevel()void setLogLevel(LogLevel logLevel)ConsoleLogger()void initLogger(LogLevel logLevel)void setupNewLogMessage(const char* file, const int line, const char* function, LogLevel logLevel)void flush()std::tuple<const char*, uint64_t> getLogBuffer() constvoid assumeFlushed()void LogString(const char* message)void logI64Dec(const int64_t value)void logU64Dec(const uint64_t value)void logU64Hex(const uint64_t value)void logU64Oct(const uint64_t value)void logBool(const bool value)void logFloat(const float value)void logDouble(const double value)void logLongDouble(const long double value)TestingLoggervoid init()uint64_t getNumberOfLogMessages()std::vector<std::string> getLogMessages()void clearLogBuffer()void printLogBuffer()void TestingLogger()void flush()LogHexTLogOctTLogStreamLogStream(const char* file, const int line, const char* function, LogLevel logLevel)void flush()LogStream& self()LogStream& operator<<(const char* cstr)LogStream& operator<<(const T value)LogStream& operator<<(const LogHex<T>&& value)LogStream& operator<<(const LogOct<T>&& value)LogStream& operator<<(const Callable&& c)via Impl«friend»«friend»«friend» \ No newline at end of file diff --git a/doc/website/images/logging_with_logstream.svg b/doc/website/images/logging_with_logstream.svg index d80d4ac91d..0dec77f650 100644 --- a/doc/website/images/logging_with_logstream.svg +++ b/doc/website/images/logging_with_logstream.svg @@ -1 +1 @@ -UserUserLogStream«free function»iox::log«free function»iox::log«static»Logger«static»Loggerlogger : DefaultLoggerIOX_LOG(...)LogStreamgetLoggerget <<private>>activeLogger()activeLogger is only calledto initialize the thread locallogger pointer or when theactive flag of the logger is falsestd::locklogger : DefaultLoggersetupNewLogMessage<< Hello WorldlogStringdTor via ; on temporaryflush \ No newline at end of file +UserUserLogStream«static»Logger«static»Loggerlogger : LoggerIOX_LOG(...)LogStreamgetactiveLogger()activeLogger is only calledto initialize the thread locallogger pointer or when theactive flag of the logger is falsestd::locklogger : LoggersetupNewLogMessage<< Hello WorldlogStringdTor via ; on temporaryflush \ No newline at end of file