diff --git b/.gitignore a/.gitignore new file mode 100644 index 0000000..0ff047c --- /dev/null +++ a/.gitignore @@ -0,0 +1,2 @@ +build/ +CMakeLists.txt.user diff --git b/CMakeLists.txt a/CMakeLists.txt new file mode 100644 index 0000000..316f57e --- /dev/null +++ a/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +# Check to see if there is versioning information available +if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake) + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake) + include(osdevversion) +endif() + +include(projectheader) +project_header(osdev_logutils) + +add_subdirectory(src) +add_subdirectory(tests) + +# include(packaging) +# package_component() diff --git b/README.md a/README.md new file mode 100644 index 0000000..e69de29 --- /dev/null +++ a/README.md diff --git b/cmake a/cmake new file mode 120000 index 0000000..8e8a460 --- /dev/null +++ a/cmake @@ -0,0 +1 @@ +../cmake/ \ No newline at end of file diff --git b/src/CMakeLists.txt a/src/CMakeLists.txt new file mode 100644 index 0000000..a80fc4f --- /dev/null +++ a/src/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake) +include(projectheader) +project_header(logutils) + +# find_library( log4cplus REQUIRED ) +find_package( Qt5Core REQUIRED ) +find_package( Qt5Sql REQUIRED ) + +include_directories( SYSTEM + ${Qt5Core_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../global +) + +include(compiler) + +set(SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/log.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/threadcontext.cpp +) + +include(library) +add_libraries( + ${Qt5Core_LIBRARIES} + # ${log4cplus_LIBRARIES} +) + +include(installation) +install_component() diff --git b/src/log.cpp a/src/log.cpp new file mode 100644 index 0000000..9a425f0 --- /dev/null +++ a/src/log.cpp @@ -0,0 +1,228 @@ +/* ************************************************************************************************************************************************************************** + * 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. * + * **************************************************************************************************************************************************************************/ + +// std +#include +#include +#include +#include + +//#include "timeutils.h" +#include "log.h" +#include "threadcontext.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace osdev { +namespace components { + +namespace { + +QString 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"; +} + +} // anonymous + +QString Log::s_context = QString(); +QString Log::s_fileName = QString(); +LogLevel Log::s_logLevel = LogLevel::Debug; +QMutex Log::m_mutex; + +void Log::init(const QString& context, const QString& logFile, LogLevel logDepth) +{ + s_logLevel = logDepth; + s_context = context; + + if ( !logFile.isEmpty() ) + { + s_fileName = logFile; + } +} + +void Log::terminate() +{ + s_context.clear(); + s_fileName.clear(); + s_logLevel = LogLevel::Info; +} + +void Log::log(const QString& category, const QString& message, LogLevel level) +{ + QMutexLocker lock( &m_mutex ); + + QString logCategory = s_context + '|' + toString(level) + '|' + ThreadContext::instance().context() + '|' + category; + QString logMessage = message; + if(logMessage.isEmpty()) + { + static const QString emptyMessage("--"); + logMessage = emptyMessage; + } + else + { + // Replace % signs. + logMessage.replace('%', "%%"); + } + + writeLog( logCategory, logMessage, level ); +} + +QString Log::fileinfoMessage(const char* file, int line, const QString& message) +{ + static const QString templ("%1:%2| %3"); + QFileInfo fi(file); + return templ.arg( fi.fileName() ).arg(line).arg(message); +} + +void Log::trace(const QString &category, const QString &message) +{ + log(category, message, LogLevel::Trace); +} + +void Log::debug(const QString& category, const QString& message) +{ + log( category, message, LogLevel::Debug ); +} + +void Log::info(const QString& category, const QString& message) +{ + log( category, message, LogLevel::Info ); +} + +void Log::warning(const QString& category, const QString& message) +{ + log(category, message, LogLevel::Warning ); +} + +void Log::error(const QString& category, const QString& message) +{ + log(category, message, LogLevel::Error ); +} + +void Log::logObject(const QString& category, QObject* object) +{ + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + + QDataStream datastream(&buffer); + QString data; + + datastream << object; + datastream >> data; + + log( category, data, LogLevel::Debug ); +} + +void Log::trace(const char *file, int line, const QString &category, const QString &message) +{ + log(category, fileinfoMessage(file, line, message), LogLevel::Trace ); +} + +void Log::debug(const char* file, int line, const QString& category, const QString& message) +{ + log( category, fileinfoMessage(file, line, message), LogLevel::Debug ); +} + +void Log::info(const char* file, int line, const QString& category, const QString& message) +{ + log( category, fileinfoMessage(file, line, message), LogLevel::Info ); +} + +void Log::warning(const char* file, int line, const QString& category, const QString& message) +{ + log( category, fileinfoMessage(file, line, message), LogLevel::Warning); +} + +void Log::error(const char* file, int line, const QString& category, const QString& message) +{ + log( category, fileinfoMessage(file, line, message), LogLevel::Error ); +} + +void Log::logObject(const char* file, int line, const QString& category, QObject* object) +{ + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + + QDataStream datastream(&buffer); + QString data; + + datastream << object; + datastream >> data; + + log( " [LOGOBJECT] " + category, fileinfoMessage(file, line, data), LogLevel::Debug ); +} + +QString Log::getDateTime() +{ + return QDateTime::currentDateTime().toString( "dd-MM-yyyy HH:mm:ss.zzz" ); +} + +void Log::writeLog(const QString& category, const QString& message, LogLevel level) +{ + + if ( level >= s_logLevel ) + { + std::ostringstream threadIdStr; + threadIdStr << std::right << std::setfill('0') << std::setw(12) << std::hex << std::this_thread::get_id(); + + QString logLine; + QTextStream(&logLine) << getDateTime() << '|' << threadIdStr.str().c_str() << '|' << category << '|' << message; + if ( !s_fileName.isEmpty() ) + { + QFile mFile( s_fileName ); + if ( !mFile.open( QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text ) ) + { + qWarning() << "Error opening the logfile : " << s_fileName << " for writing."; + qWarning() << logLine; + } + else + { + QTextStream out( &mFile ); + out << logLine << '\n'; + mFile.close(); + } + } + else + { + qDebug() << logLine; + } + } +} + +} /* End namespace components */ +} /* End namespace osdev */ diff --git b/src/log.h a/src/log.h new file mode 100644 index 0000000..322c7ba --- /dev/null +++ a/src/log.h @@ -0,0 +1,257 @@ +/* ************************************************************************************************************************************************************************** + * 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_LOG_H +#define OSDEV_COMPONENTS_LOG_H + +// local +#include "logutilslibexport.h" + +// qt +#include +#include + +class QObject; + +namespace osdev { +namespace components { + +#define LogDebug(context, text) \ + if (osdev::components::Log::level() <= osdev::components::LogLevel::Debug) \ + { \ + osdev::components::Log::debug(__FILE__, __LINE__, context, text); \ + } + +#define LogObject(context, object) \ + if (osdev::components::Log::level() <= osdev::components::LogLevel::Debug) \ + { \ + osdev::components::Log::logObject(__FILE__, __LINE__, context, object); \ + } + +#define LogInfo(context, text) \ + if (osdev::components::Log::level() <= osdev::components::LogLevel::Info) \ + { \ + osdev::components::Log::info(__FILE__, __LINE__, context,text); \ + } + +#define LogWarning(context, text) \ + if (osdev::components::Log::level() <= osdev::components::LogLevel::Warning) \ + { \ + osdev::components::Log::warning(__FILE__, __LINE__, context, text); \ + } + +#define LogError(context, text) \ + if (osdev::components::Log::level() <= osdev::components::LogLevel::Error) \ + { \ + osdev::components::Log::error(__FILE__, __LINE__, context, text); \ + } + +#define LogTrace(context, text) \ + if (osdev::components::Log::level() <= osdev::components::LogLevel::Trace) \ + { \ + osdev::components::Log::trace(__FILE__, __LINE__, context, text); \ + } + + +enum class LogLevel +{ + Trace, + Debug, + Info, + Warning, + Error +}; + +/*! \class Log + \brief Basic logging mechanism. +*/ +class LOGUTILSLIBINTERFACE Log +{ +public: + /** + * @brief Initialise the logging mechanism + * @param context The main context + * @param logFile Logfile if available + * @param logDepth Initial log-depth + */ + static void init( const QString& context, + const QString& logFile = QString(), + 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 QString& category, const QString& 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 QString& category, const QString& 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 QString& category, const QString& 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 QString& category, const QString& 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 QString& category, const QString& message ); + + /** + * @brief Dump a complete Qt Object in the logging stream at LogLevel::Debug + * @param file Name of the source-file + * @param line The line number in the source-file + * @param category The category of the message + * @param object The object to print + */ + static void logObject( const char* file, int line, + const QString& category, QObject* object ); + + /** + * @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 debug(const QString& category, const QString& 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 QString& category, const QString& 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 QString& category, const QString& 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 QString& category, const QString& message ); + + /** + * @brief Log a trace message in a category. + * @param category The category of the message. + * @param message The string to print. + */ + static void trace( const QString& category, const QString& message ); + + /** + * @brief Dump a complete Qt Object in the logging stream at LogLevel::Info + * @param category The category of the message + * @param object The object to print + */ + static void logObject( const QString& category, QObject* object ); + + //! Create a convenient timestamp + static QString 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 QString& category, + const QString& 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 QString& category, + const QString& 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 QString fileinfoMessage(const char* file, int line, + const QString& message); + + //! The main context. + static QString s_context; + + //! The name of the LogFile. + static QString s_fileName; + + //! The amount of logging + static LogLevel s_logLevel; + + //! Mutex to keep it thread-safe + static QMutex m_mutex; +}; + +} // End namespace components +} // End namespace osdev + +#endif // OSDEV_COMPONENTS_LOG_H diff --git b/src/logutilslibexport.h a/src/logutilslibexport.h new file mode 100644 index 0000000..70d1c3c --- /dev/null +++ a/src/logutilslibexport.h @@ -0,0 +1,32 @@ +/* ************************************************************************************************************************************************************************** + * 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_CAELUS_LOGUTILSLIBEXPORT_H +#define OSDEV_CAELUS_LOGUTILSLIBEXPORT_H + +#ifdef WIN32 + +#ifdef LOGUTILSLIB +#define LOGUTILSLIBINTERFACE __declspec(dllexport) +#else +#define LOGUTILSLIBINTERFACE __declspec(dllimport) +#endif + +#else +#define LOGUTILSLIBINTERFACE +#endif + + +#endif diff --git b/src/threadcontext.cpp a/src/threadcontext.cpp new file mode 100644 index 0000000..87a8fb2 --- /dev/null +++ a/src/threadcontext.cpp @@ -0,0 +1,45 @@ +/* ************************************************************************************************************************************************************************** + * 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 QString& _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 b/src/threadcontext.h a/src/threadcontext.h new file mode 100644 index 0000000..fc35694 --- /dev/null +++ a/src/threadcontext.h @@ -0,0 +1,99 @@ +/* ************************************************************************************************************************************************************************** + * 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 QString& context); + ~ThreadContextScope(); + + // Non copyable and non movable + ThreadContextScope(const ThreadContextScope&) = delete; + ThreadContextScope& operator=(const ThreadContextScope&) = delete; + ThreadContextScope(ThreadContextScope&&) = delete; + ThreadContextScope& operator=(ThreadContextScope&&) = delete; + +private: + QString 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 QString& context() const + { + return m_context; + } + +private: + /** + * @brief Set the thread context. + */ + void setContext(const QString& 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; + + QString m_context; ///< The context string +}; + +} /* End namespace components */ +} /* End namespace osdev */ + +#endif // OSDEV_COMPONENTS_THREADCONTEXT_H diff --git b/tests/CMakeLists.txt a/tests/CMakeLists.txt new file mode 100644 index 0000000..3635320 --- /dev/null +++ a/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) + +include(projectheader) +project_header(test_logutils) + +include_directories( SYSTEM + ${CMAKE_CURRENT_SOURCE_DIR}/../../src +) + +include(compiler) +set(SRC_LIST +) + +# add_executable( ${PROJECT_NAME} +# ${SRC_LIST} +# ) + +# target_link_libraries( +# ${PROJECT_NAME} +# ) + +# 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()