diff --git a/CMakeLists.txt b/CMakeLists.txt index 285de61..165610d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,3 +35,5 @@ add_subdirectory(examples/sub) include(packaging) package_component() + +add_subdirectory(test) diff --git a/include/log.h b/include/log.h index 9ac86a3..d0fb9ce 100644 --- a/include/log.h +++ b/include/log.h @@ -1,21 +1,209 @@ #pragma once +// std #include #include +#include namespace osdev { namespace components { namespace mqtt { -static void LogTrace(); +#define LogTrace(context, text) \ + if ( osdev::components::mqtt::Log::level() <= osdev::components::mqtt::LogLevel::Trace ) { \ + osdev::components::mqtt::Log::trace(__FILE__, __LINE__, context, text); \ + } -static void LogDebug(); +#define LogDebug(context, text) \ + if ( osdev::components::mqtt::Log::level() <= osdev::components::mqtt::LogLevel::Debug ) { \ + osdev::components::mqtt::Log::debug(__FILE__, __LINE__, context, text); \ + } -static void LogInfo(); +#define LogInfo(context, text) \ + if ( osdev::components::mqtt::Log::level() <= osdev::components::mqtt::LogLevel::Info ) { \ + osdev::components::mqtt::Log::info(__FILE__, __LINE__, context, text); \ + } -static void LogWarning(); +#define LogWarning(context, text) \ + if ( osdev::components::mqtt::Log::level() <= osdev::components::mqtt::LogLevel::Warning ) {\ + osdev::components::mqtt::Log::warning(__FILE__, __LINE__, context, text); \ + } -static void LogError(); +#define LogError(context, text) \ + if ( osdev::components::mqtt::Log::level() <= osdev::components::mqtt::LogLevel::Error ) { \ + osdev::components::mqtt::Log::error(__FILE__, __LINE__, context, text); \ + } + +enum class LogLevel +{ + Trace, + Debug, + Info, + Warning, + Error +}; + +/*! \class Log + * \brief Basic logging mechanism. + */ +class Log +{ +public: + /** + * @brief String that identifies the start of the program in the logging. + */ + static const std::string s_startMarker; + + /** + * @brief Initialize the logging mechanism + * @param context - The main context + * @param logFile - Logfile if available + * @param logDepth - Initial log-depth + */ + static void init( const std::string &context, + const std::string &logFile, + LogLevel logDepth = LogLevel::Info); + + //! Shutdown the logging mechanism + static void terminate(); + + /** + * @brief Log a trace message in a category. + * @param file - Name of the source-file + * @param line - The line number in the source-file + * @param category - The category of the message. + * @param message - The string to print + */ + static void trace( const char *file, int line, + const std::string &category, const std::string &message ); + + /** + * @brief Log a debug message in a category. + * @param file - Name of the source-file + * @param line - The line number in the source-file + * @param category - The category of the message. + * @param message - The string to print + */ + static void debug( const char *file, int line, + const std::string &category, const std::string &message ); + + /** + * @brief Log an info message in a category. + * @param file - Name of the source-file + * @param line - The line number in the source-file + * @param category - The category of the message. + * @param message - The string to print + */ + static void info( const char *file, int line, + const std::string &category, const std::string &message ); + + /** + * @brief Log a warning message in a category. + * @param file - Name of the source-file + * @param line - The line number in the source-file + * @param category - The category of the message. + * @param message - The string to print + */ + static void warning( const char *file, int line, + const std::string &category, const std::string &message ); + + /** + * @brief Log an error message in a category. + * @param file - Name of the source-file + * @param line - The line number in the source-file + * @param category - The category of the message. + * @param message - The string to print + */ + static void error( const char *file, int line, + const std::string &category, const std::string &message ); + + /** + * @return The current log level. + */ + static LogLevel level() + { + return s_logLevel; + } + +protected: + /** + * @brief Log a debug message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void trace(const std::string &category, const std::string &message); + + /** + * @brief Log a debug message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void debug(const std::string &category, const std::string &message); + + /** + * @brief Log an info message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void info(const std::string &category, const std::string &message); + + /** + * @brief Log a warning message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void warning(const std::string &category, const std::string &message); + + /** + * @brief Log an error message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void error(const std::string &category, const std::string &message); + + //! Create a convenient timestamp + static std::string getDateTime(); + + /** + * @brief Write the message to the logfile + * @param category The category of the message. + * @param message The string to print. + * @param level Selected log level + */ + static void writeLog(const std::string &category, + const std::string &message, + LogLevel level); + + /** + * @brief Log an error message in a category. + * @param category The category of the message. + * @param message The string to print. + * @param level Selected log level + */ + static void log(const std::string &category, + const std::string &message, + LogLevel level); + +private: + /** + * @brief Put filename, line-number and message in one string + * @param file Source-file + * @param line Line in source-file + * @param message The string to print + * @return Formatted string with file, line and message + */ + static std::string fileinfoMessage(const char* file, int line, + const std::string &message); + + //! The main context. + static std::string s_context; + + //! The name of the LogFile. + static std::string s_fileName; + + //! The amount of logging + static LogLevel s_logLevel; +}; } /* End namespace mqtt */ } /* End namespace components */ diff --git a/include/threadcontext.h b/include/threadcontext.h new file mode 100644 index 0000000..a12a664 --- /dev/null +++ b/include/threadcontext.h @@ -0,0 +1,73 @@ +#pragma once + +// std +#include + +namespace osdev { +namespace components { +namespace mqtt { + +/** + * @brief Set the current thread context + * The context is restored to the previous context when this object goes out of scope. + * @note This object is meat to be used on the stack. + */ +class ThreadContextScope +{ +public: + /** + * @brief Construct a scoped object that sets the current thread context. + * @param context The context that will be used by the loggin framework. + */ + explicit ThreadContextScope( const std::string &context ); + ~ThreadContextScope(); + + // Non copyable and non movable + ThreadContextScope( const ThreadContextScope& ) = delete; + ThreadContextScope& operator=( const ThreadContextScope& ) = delete; + ThreadContextScope( ThreadContextScope&& ) = delete; + ThreadContextScope& operator=( ThreadContextScope&& ) = delete; + +private: + std::string m_previousContext; ///< Copy of the previous context. +}; + +/** + * @brief Add context to a thread. + * For every thread only one specific instance of this object will exist. + * @note Contexts can only be set by using a ThreadContextScope object. + */ +class ThreadContext +{ + // Contexts can only be set by using the ThreadContextScope object. + friend class ThreadContextScope; + +public: + static ThreadContext& instance(); + + /** + * @briefReturn the thread context. + */ + void setContext( const std::string &contextString) + { + m_context = contextString; + } + + /** + * Construct a ThreadContext object. + * The context is set to "default" + */ + ThreadContext(); + + // Non copyable and non moveable + ThreadContext(const ThreadContext&) = delete; + ThreadContext& operator=(const ThreadContext&) = delete; + ThreadContext(ThreadContext&&) = delete; + ThreadContext& operator=(ThreadContext&&) = delete; + + std::string m_context; ///< The context string +}; + +} // End namespace mqtt +} // End namespace components +} // End namespace osdev diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6820971..2d8873e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,8 @@ set(SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/sharedreaderlock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/stringutils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/uriparser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/log.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/threadcontext.cpp ) include(library) diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..b2ff503 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,70 @@ +#include "log.h" + +// std + + + +using namespace osdev::components::mqtt; + +namespace { + +std::string toString( LogLevel level ) +{ + switch (level) + { + case LogLevel::Trace: + return "T"; + case LogLevel::Debug: + return "D"; + case LogLevel::Info: + return "I"; + case LogLevel::Warning: + return "W"; + case LogLevel::Error: + return "E"; + } + return "U"; +} + +} // namespace + +const std::string Log::s_startMarker = std::string("|________________________________________________________________________________________________________________________________|"); +std::string Log::s_context = std::string(); +std::string Log::s_fileName = std::string(); +LogLevel Log::s_logLevel = LogLevel::Info; + +void Log::init( const std::string &context, const std::string &logFile, LogLevel logDepth ) +{ + s_logLevel = logDepth; + s_context = context; + + if( !logFile.empty()) + { + s_fileName = logFile; + } +} + +void Log::terminate() +{ + s_context.clear(); + s_fileName.clear(); + s_logLevel = LogLevel::Info; +} + +void Log::log( const std::string &category, const std::string &message, LogLevel level ) +{ + std::string logCategory = s_context + '|' + toString( level ) + '|' + ThreadContext::instance().context() + '|' + category; + std::string logMessage = message; + if( logMessage.empty() ) + { + static const std::string emptyMessage( "--" ); + logMessage = emptyMessage; + } + else + { + // Replace % signs + logMessage.replace( '%', "%%"); + } + + writeLog( logCategory, logMessage, level ); +} diff --git a/src/threadcontext.cpp b/src/threadcontext.cpp new file mode 100644 index 0000000..998bd67 --- /dev/null +++ b/src/threadcontext.cpp @@ -0,0 +1,30 @@ +#include "threadcontext.h" + +// std +#include + +using namespace osdev::components::mqtt; + +ThreadContextScope::ThreadContextScope( const std::string &_context ) + : m_previousContext( ThreadContext::instance().context() ) +{ + ThreadContext::instance().setContext( _context ); +} + +ThreadContextScope::~ThreadContextScope() +{ + ThreadContext::instance().setContext( m_previousContext ); +} + +// static +ThreadContext& ThreadContext::instance() +{ + static thread_local ThreadContext tc; + return tc; +} + +ThreadContext::ThreadContext() + : m_context( "default" ) +{ + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..2c88488 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.10) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) + +include(projectheader) +project_header(mqtt_test) + +find_package(GTest) + +include_directories( SYSTEM + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) + +include (compiler) +set(SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/mqtt_test.cpp +) + +add_executable( ${PROJECT_NAME} + ${SRC_LIST} +) + +target_link_libraries( + ${PROJECT_NAME} + mqtt-cpp +) + +set_target_properties( ${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/archive +) + +include(installation) +install_application() + diff --git a/test/mqtt_test.cpp b/test/mqtt_test.cpp new file mode 100644 index 0000000..089afb1 --- /dev/null +++ b/test/mqtt_test.cpp @@ -0,0 +1,6 @@ +#include + +TEST(MqttTest PublishWithoutConnect) +{ + +}