diff --git a/CMakeLists.txt b/CMakeLists.txt index 6996aa8..5555d8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/submodules/cmake) include(projectheader) project_header(osdev_mqtt) -add_subdirectory(submodules/logger) add_subdirectory(src) add_subdirectory(examples/pub) add_subdirectory(examples/sub) diff --git a/scripts/submodules.list b/scripts/submodules.list index a05b06e..e324929 100644 --- a/scripts/submodules.list +++ b/scripts/submodules.list @@ -1,4 +1,3 @@ -SUB_MODULES_OPEN="cmake -logger" +SUB_MODULES_OPEN="cmake" SUB_MODULES_CLOSED="" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0876440..dedee11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,8 @@ include_directories( ) set(SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/log.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/threadcontext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mqttpublisherbase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mqttsubscriberbase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/clientpaho.cpp @@ -47,7 +49,6 @@ add_libraries( Boost::boost Boost::regex paho-mqtt3a - ${CMAKE_SOURCE_DIR}/build/lib/liblogger.a ) include(installation) diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..a97f151 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,229 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#include "log.h" +#include "threadcontext.h" + +namespace osdev { +namespace components { +namespace log { + +int toInt( LogLevel level ) +{ + switch( level ) + { + case LogLevel::Emergency: + return 0; + case LogLevel::Alert: + return 1; + case LogLevel::Critical: + return 2; + case LogLevel::Error: + return 3; + case LogLevel::Warning: + return 4; + case LogLevel::Notice: + return 5; + case LogLevel::Info: + return 6; + case LogLevel::Debug: + return 7; + } + return -1; +} + +std::string Log::s_context = std::string(); +std::string Log::s_fileName = std::string(); +LogLevel Log::s_logLevel = LogLevel::Error; +LogMask Log::s_logMask = LogMask::None; + +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::Debug; + s_logMask = LogMask::None; +} + +void Log::write( const int &priority, const std::string &message, const std::string& context ) +{ + switch( s_logMask ) + { + case LogMask::Mask: + setlogmask( LOG_MASK( toInt( s_logLevel ) ) ); + break; + case LogMask::Upto: + setlogmask( LOG_UPTO( toInt( s_logLevel ) ) ); + break; + case LogMask::None: + setlogmask( LOG_UPTO( toInt( LogLevel::Debug ) ) ); + break; + default: + break; + } + + if( !context.empty() ) + { + s_context = context; + openlog( s_context.c_str(), LOG_PID, LOG_USER ); + } + else + { + openlog( NULL, LOG_PID, LOG_USER ); + } + + syslog( priority, "%s", message.c_str() ); + closelog(); +} + +std::string Log::fileinfoMessage( const char* file, int line, const std::string& message ) +{ + (void)file; + (void)line; + + return message; +} + +void Log::log( const std::string &category, const std::string &message, LogLevel level ) +{ + std::string logCategory = category; + std::string logMessage = message; + if( logMessage.empty() ) + { + static const std::string emptyMessage( "--" ); + logMessage = emptyMessage; + } + else + { + // Replace % signs + ReplaceAll( logMessage, "%", "%%" ); + } + + write( toInt( level ), logMessage, logCategory ); +} + +// Static functions +void Log::emergency( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Emergency ); +} + +void Log::alert( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Alert ); +} + +void Log::critical( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Critical ); +} + +void Log::error( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Error ); +} + +void Log::warning( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Warning ); +} + +void Log::notice( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Notice ); +} + +void Log::info( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Info ); +} + +void Log::debug( const char *file, int line, const std::string &category, const std::string &message ) +{ + log( category, fileinfoMessage( file, line, message ), LogLevel::Debug ); +} + +// Protected functions +void Log::emergency( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Emergency ), message, category ); +} + +void Log::alert( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Alert ), message, category ); +} + +void Log::critical( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Critical ), message, category ); +} + +void Log::error( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Error ), message, category ); +} + +void Log::warning( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Warning ), message, category ); +} + +void Log::notice( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Notice ), message, category ); +} + +void Log::info( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Info ), message, category ); +} + +void Log::debug( const std::string &category, const std::string &message ) +{ + write( toInt( LogLevel::Debug ), message, category ); +} + +void Log::ReplaceAll( std::string &strToReplace, const std::string& from_chars, const std::string& to_chars ) +{ + size_t start_pos = 0; + while( ( start_pos = strToReplace.find( from_chars, start_pos ) ) != std::string::npos ) + { + strToReplace.replace( start_pos, from_chars.length(), to_chars ); + start_pos += to_chars.length(); // Handles case where 'to' is a substring of 'from' + } +} + +} /* End namespace log */ +} /* End namespace components */ +} /* End namespace osdev */ diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..31e84a5 --- /dev/null +++ b/src/log.h @@ -0,0 +1,324 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +#pragma once + +#include +#include + +namespace osdev { +namespace components { +namespace log { + +#define LogEmergency(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Emergency)\ + { \ + osdev::components::log::Log::emergency(__FILE__, __LINE__, context, text); \ + } + +#define LogAlert(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Alert) \ + { \ + osdev::components::log::Log::alert(__FILE__, __LINE__, context, text); \ + } + +#define LogCritical(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Critical) \ + { \ + osdev::components::log::Log::critical(__FILE__, __LINE__, context, text); \ + } + +#define LogError(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Error) \ + { \ + osdev::components::log::Log::error(__FILE__, __LINE__, context, text); \ + } + +#define LogWarning(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Warning) \ + { \ + osdev::components::log::Log::warning(__FILE__, __LINE__, context, text); \ + } + +#define LogNotice(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Notice) \ + { \ + osdev::components::log::Log::notice(__FILE__, __LINE__, context, text); \ + } + +#define LogInfo(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Info) \ + { \ + osdev::components::log::Log::info(__FILE__, __LINE__, context, text); \ + } + +#define LogDebug(context, text) \ + if (osdev::components::log::Log::level() >= osdev::components::log::LogLevel::Debug) \ + { \ + osdev::components::log::Log::debug(__FILE__, __LINE__, context, text); \ + } + +/*! + * \brief The LogLevel enum + * Enumeration class dealing with LogLevels. + * Used to convert to syslog macro's. + */ +enum class LogLevel +{ + Emergency = 0, + Alert, + Critical, + Error, + Warning, + Notice, + Info, + Debug +}; + +/*! + * \brief The LogMask enum + * Enumeration class dealing with LogMask. + * Used to convert to syslog macro's. + */ +enum class LogMask +{ + Mask = 0, + Upto, + None +}; + +/*! \class Log + \brief Basic logging mechanism. +*/ +class Log +{ +public: + /** + * @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 = std::string(), + LogLevel logDepth = LogLevel::Info ); + + //! Shutdown the logging mechanism + static void terminate(); + + /*! + * \brief Write to syslog + * \param priority - priority level [from debug up to emergency] + * \param message - The string to print + * \param context - The context name. default name is the name of the executable. + */ + static void write( const int &priority, const std::string &message, const std::string &context = std::string() ); + + /** + * @brief Log an emergency 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 emergency( const char* file, int line, const std::string& category, const std::string& message ); + /** + * @brief Log an alert 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 alert ( const char* file, int line, const std::string& category, const std::string& message ); + /** + * @brief Log a critical 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 critical ( 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 ); + /** + * @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 a notice 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 notice ( 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 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 ); + + /** + * @return The current log level. + */ + static LogLevel level() + { + if( s_logMask == LogMask::None ) + { + return LogLevel::Debug; + } + return s_logLevel; + } + + /*! + * \brief setMask update the current logMask + * \param logMask - Enum defining the logmask used. + */ + static void setMask ( LogMask logMask ) { s_logMask = logMask; } + /*! + * \brief setLogLevel update the current logLevel + * \param logLevel - Enum defining the logLevel used, in combination with Mask. + */ + static void setLogLevel( LogLevel logLevel ) { s_logLevel = logLevel; } + /*! + * \brief setContext update the current context + * \param context - String containing the new context name. + */ + static void setContext ( std::string context ) { s_context = context; } + +protected: + /** + * @brief Log an emergency message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void emergency( const std::string& category, const std::string& message ); + /** + * @brief Log an alert message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void alert ( const std::string& category, const std::string& message ); + /** + * @brief Log a critical message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void critical ( 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 ); + /** + * @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 a notice message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void notice ( 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 an 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 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 Replace the characters in a std::string with other characters. + * @param strToReplace The string we want to replace characters in + * @param from_chars The characters we want to replace + * @param to_chars The characters we want to replace with. + * @return strToReplace This is the original string with the replaced characters. + */ + static void ReplaceAll( std::string &strToReplace, const std::string& from_chars, const std::string& to_chars ); + + /** + * @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; + + //! The mask + static LogMask s_logMask; +}; + +} // End namespace log +} // End namespace components +} // End namespace osdev + diff --git a/src/threadcontext.cpp b/src/threadcontext.cpp new file mode 100644 index 0000000..1e05d2e --- /dev/null +++ b/src/threadcontext.cpp @@ -0,0 +1,52 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#include "threadcontext.h" + +// std +#include + +using namespace osdev::components; + +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/src/threadcontext.h b/src/threadcontext.h new file mode 100644 index 0000000..bb4a434 --- /dev/null +++ b/src/threadcontext.h @@ -0,0 +1,105 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +#ifndef OSDEV_COMPONENTS_THREADCONTEXT_H +#define OSDEV_COMPONENTS_THREADCONTEXT_H + +// Qt +#include + +namespace osdev { +namespace components { + +/** + * @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 meant 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 logging 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 a ThreadContextScope object. +friend class ThreadContextScope; + +public: + static ThreadContext& instance(); + + /** + * @brief Return the thread context. + */ + const std::string& context() const + { + return m_context; + } + +private: + /** + * @brief Set 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 movable + 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 components */ +} /* End namespace osdev */ + +#endif // OSDEV_COMPONENTS_THREADCONTEXT_H