diff --git a/Global.h b/Global.h index 02ea78fe5..ecd9a7efd 100644 --- a/Global.h +++ b/Global.h @@ -1,14 +1,3 @@ -#pragma once - -// Standard include file -// Contents: -// Includes C/C++ libraries, STL libraries, IOStream and String libraries -// Includes the most important boost headers -// Defines the import + export, override and exception handling macros -// Defines the vstd library -// Includes the logger - -// This file shouldn't be changed, except if there is a important header file missing which is shared among several projects. /* * Global.h, part of VCMI engine @@ -20,11 +9,59 @@ * */ +#pragma once + +/* ---------------------------------------------------------------------------- */ +/* Compiler detection */ +/* ---------------------------------------------------------------------------- */ +// Fixed width bool data type is important for serialization +static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); + +#if defined _M_X64 && defined _WIN32 //Win64 -> cannot load 32-bit DLLs for video handling + #define DISABLE_VIDEO +#endif + +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__) +#endif + +#if defined(__GNUC__) && (GCC_VERSION == 470 || GCC_VERSION == 471) +#error This GCC version has buggy std::array::at version and should not be used. Please update to 4.7.2 or use 4.6.x. +#endif + +/* ---------------------------------------------------------------------------- */ +/* Guarantee compiler features */ +/* ---------------------------------------------------------------------------- */ +//defining available c++11 features + +//initialization lists - only gcc-4.4 or later +#if defined(__clang__) || (defined(__GNUC__) && (GCC_VERSION >= 440)) +#define CPP11_USE_INITIALIZERS_LIST +#endif + +//nullptr - only msvc and gcc-4.6 or later, othervice define it as NULL +#if !defined(_MSC_VER) && !(defined(__GNUC__) && (GCC_VERSION >= 460)) +#define nullptr NULL +#endif + +//override keyword - only msvc and gcc-4.7 or later. +#if !defined(_MSC_VER) && !(defined(__GNUC__) && (GCC_VERSION >= 470)) +#define override +#endif + +//workaround to support existing code +#define OVERRIDE override + +/* ---------------------------------------------------------------------------- */ +/* Suppress some compiler warnings */ +/* ---------------------------------------------------------------------------- */ #ifdef _MSC_VER #pragma warning (disable : 4800 ) /* disable conversion to bool warning -- I think it's intended in all places */ -#endif //_MSC_VER - +#endif +/* ---------------------------------------------------------------------------- */ +/* Commonly used C++, Boost headers */ +/* ---------------------------------------------------------------------------- */ #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include #include @@ -50,7 +87,6 @@ #include #include #include -//#include #include #include @@ -70,6 +106,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +127,17 @@ #include #endif +/* ---------------------------------------------------------------------------- */ +/* Usings */ +/* ---------------------------------------------------------------------------- */ +using std::shared_ptr; +using std::unique_ptr; +using std::make_shared; +namespace range = boost::range; + +/* ---------------------------------------------------------------------------- */ +/* Typedefs */ +/* ---------------------------------------------------------------------------- */ // Integral data types typedef boost::uint64_t ui64; //unsigned int 64 bits (8 bytes) typedef boost::uint32_t ui32; //unsigned int 32 bits (4 bytes) @@ -100,21 +148,14 @@ typedef boost::int32_t si32; //signed int 32 bits (4 bytes) typedef boost::int16_t si16; //signed int 16 bits (2 bytes) typedef boost::int8_t si8; //signed int 8 bits (1 byte) -// Fixed width bool data type is important for serialization -static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); - -#if defined _M_X64 && defined _WIN32 //Win64 -> cannot load 32-bit DLLs for video handling - #define DISABLE_VIDEO -#endif - -#ifdef __GNUC__ -#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__) -#endif - -#if defined(__GNUC__) && (GCC_VERSION == 470 || GCC_VERSION == 471) -#error This GCC version has buggy std::array::at version and should not be used. Please update to 4.7.2 or use 4.6.x. -#endif +// Lock typedefs +typedef boost::unique_lock TWriteLock; +typedef boost::shared_lock TReadLock; +typedef boost::lock_guard TLockGuard; +/* ---------------------------------------------------------------------------- */ +/* Macros */ +/* ---------------------------------------------------------------------------- */ // Import + Export macro declarations #ifdef _WIN32 #ifdef __GNUC__ @@ -150,26 +191,46 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #define DLL_LINKAGE DLL_IMPORT #endif -//defining available c++11 features +#define THROW_FORMAT(message, formatting_elems) throw std::runtime_error(boost::str(boost::format(message) % formatting_elems)) -//initialization lists - only gcc-4.4 or later -#if defined(__clang__) || (defined(__GNUC__) && (GCC_VERSION >= 440)) -#define CPP11_USE_INITIALIZERS_LIST -#endif +#define ASSERT_IF_CALLED_WITH_PLAYER if(!player) {tlog1 << __FUNCTION__ << "\n"; assert(0);} -//nullptr - only msvc and gcc-4.6 or later, othervice define it as NULL -#if !defined(_MSC_VER) && !(defined(__GNUC__) && (GCC_VERSION >= 460)) -#define nullptr NULL -#endif +//XXX pls dont - 'debug macros' are usually more trouble than it's worth +#define HANDLE_EXCEPTION \ + catch (const std::exception& e) { \ + tlog1 << e.what() << std::endl; \ + throw; \ +} \ + catch (const std::exception * e) \ +{ \ + tlog1 << e->what()<< std::endl; \ + throw; \ +} \ + catch (const std::string& e) { \ + tlog1 << e << std::endl; \ + throw; \ +} -//override keyword - only msvc and gcc-4.7 or later. -#if !defined(_MSC_VER) && !(defined(__GNUC__) && (GCC_VERSION >= 470)) -#define override -#endif +#define HANDLE_EXCEPTIONC(COMMAND) \ + catch (const std::exception& e) { \ + COMMAND; \ + tlog1 << e.what() << std::endl; \ + throw; \ +} \ + catch (const std::string &e) \ +{ \ + COMMAND; \ + tlog1 << e << std::endl; \ + throw; \ +} -//workaround to support existing code -#define OVERRIDE override +// can be used for counting arrays +template char (&_ArrayCountObj(const T (&)[N]))[N]; +#define ARRAY_COUNT(arr) (sizeof(_ArrayCountObj(arr))) +/* ---------------------------------------------------------------------------- */ +/* VCMI standard library */ +/* ---------------------------------------------------------------------------- */ //a normal std::map with a const operator[] for sanity template class bmap : public std::map @@ -508,53 +569,10 @@ namespace vstd obj = (T)(((int)obj) + change); } } - -using std::shared_ptr; -using std::unique_ptr; -using std::make_shared; +using vstd::operator-=; using vstd::make_unique; -using vstd::operator-=; - -namespace range = boost::range; - -// can be used for counting arrays -template char (&_ArrayCountObj(const T (&)[N]))[N]; -#define ARRAY_COUNT(arr) (sizeof(_ArrayCountObj(arr))) - - -#define THROW_FORMAT(message, formatting_elems) throw std::runtime_error(boost::str(boost::format(message) % formatting_elems)) - -#define ASSERT_IF_CALLED_WITH_PLAYER if(!player) {tlog1 << __FUNCTION__ << "\n"; assert(0);} - -//XXX pls dont - 'debug macros' are usually more trouble than it's worth -#define HANDLE_EXCEPTION \ - catch (const std::exception& e) { \ - tlog1 << e.what() << std::endl; \ - throw; \ -} \ - catch (const std::exception * e) \ -{ \ - tlog1 << e->what()<< std::endl; \ - throw; \ -} \ - catch (const std::string& e) { \ - tlog1 << e << std::endl; \ - throw; \ -} - -#define HANDLE_EXCEPTIONC(COMMAND) \ - catch (const std::exception& e) { \ - COMMAND; \ - tlog1 << e.what() << std::endl; \ - throw; \ -} \ - catch (const std::string &e) \ -{ \ - COMMAND; \ - tlog1 << e << std::endl; \ - throw; \ -} - - +/* ---------------------------------------------------------------------------- */ +/* VCMI headers */ +/* ---------------------------------------------------------------------------- */ #include "lib/CLogger.h" diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 9764bef9f..d6f2bcc70 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -3,7 +3,7 @@ { "type" : "object", "$schema": "http://json-schema.org/draft-04/schema", - "required" : [ "general", "video", "adventure", "battle", "server" ], + "required" : [ "general", "video", "adventure", "battle", "server", "logging" ], "properties": { "general" : { @@ -133,6 +133,72 @@ "default" : "StupidAI" } } + }, + "logging" : { + "type" : "object", + "default" : {}, + "properties" : { + "console" : { + "type" : "object", + "properties" : { + "format" : { + "type" : "string", + "default" : "%l %n [%t] - %m" + }, + "threshold" : { + "type" : "string", + "default" : "info", + "enum" : [ "trace", "debug", "info", "warn", "error" ] + }, + "coloredOutputEnabled" : { + "type" : "boolean", + "default" : true + }, + "colorMapping" : { + "type" : "array", + "default" : [ + { "domain" : "global", "level" : "trace", "color" : "gray"}, + { "domain" : "global", "level" : "debug", "color" : "white"}, + { "domain" : "global", "level" : "info", "color" : "green"}, + { "domain" : "global", "level" : "warn", "color" : "yellow"}, + { "domain" : "global", "level" : "error", "color" : "red"} + ], + "items" : { + "type" : "object", + "required" : [ "domain", "level", "color" ], + "properties" : { + "domain" : { "type" : "string" }, + "level" : { "type" : "string", "enum" : [ "trace", "debug", "info", "warn", "error" ] }, + "color" : { "type" : "string", "enum" : [ "default", "green", "red", "magenta", "yellow", "white", "gray", "teal" ] } + } + } + } + } + }, + "file" : { + "type" : "object", + "properties" : { + "format" : { + "type" : "string", + "default" : "%d %l %n [%t] - %m" + } + } + }, + "loggers" : { + "type" : "object", + "additionalProperties" : { + "type":"object", + "required" : [ "level" ], + "properties" : { + "level" : { + "type" : "string", + "enum" : [ "trace", "debug", "info", "warn", "error" ] + } + } + + } + } + } } } } diff --git a/lib/CConsoleHandler.cpp b/lib/CConsoleHandler.cpp index 8040893e1..aa1c3c2d8 100644 --- a/lib/CConsoleHandler.cpp +++ b/lib/CConsoleHandler.cpp @@ -141,43 +141,43 @@ LONG WINAPI onUnhandledException(EXCEPTION_POINTERS* exception) #endif -void CConsoleHandler::setColor(int level) +void CConsoleHandler::setColor(EConsoleTextColor::EConsoleTextColor color) { - TColor color; - switch(level) + TColor colorCode; + switch(color) { - case -1: - color = defColor; + case EConsoleTextColor::DEFAULT: + colorCode = defColor; break; - case 0: - color = CONSOLE_GREEN; + case EConsoleTextColor::GREEN: + colorCode = CONSOLE_GREEN; break; - case 1: - color = CONSOLE_RED; + case EConsoleTextColor::RED: + colorCode = CONSOLE_RED; break; - case 2: - color = CONSOLE_MAGENTA; + case EConsoleTextColor::MAGENTA: + colorCode = CONSOLE_MAGENTA; break; - case 3: - color = CONSOLE_YELLOW; + case EConsoleTextColor::YELLOW: + colorCode = CONSOLE_YELLOW; break; - case 4: - color = CONSOLE_WHITE; + case EConsoleTextColor::WHITE: + colorCode = CONSOLE_WHITE; break; - case 5: - color = CONSOLE_GRAY; + case EConsoleTextColor::GRAY: + colorCode = CONSOLE_GRAY; break; - case -2: - color = CONSOLE_TEAL; + case EConsoleTextColor::TEAL: + colorCode = CONSOLE_TEAL; break; default: - color = defColor; + colorCode = defColor; break; } #ifdef _WIN32 - SetConsoleTextAttribute(handleOut,color); + SetConsoleTextAttribute(handleOut, colorCode); #else - std::cout << color; + std::cout << colorCode; #endif } @@ -210,7 +210,7 @@ int CConsoleHandler::run() } return -1; } -CConsoleHandler::CConsoleHandler() +CConsoleHandler::CConsoleHandler() : thread(nullptr) { #ifdef _WIN32 handleIn = GetStdHandle(STD_INPUT_HANDLE); @@ -224,8 +224,7 @@ CConsoleHandler::CConsoleHandler() #else defColor = "\x1b[0m"; #endif - cb = new boost::function; - thread = NULL; + cb = new boost::function; } CConsoleHandler::~CConsoleHandler() { diff --git a/lib/CConsoleHandler.h b/lib/CConsoleHandler.h index e72b80fa6..401122ce9 100644 --- a/lib/CConsoleHandler.h +++ b/lib/CConsoleHandler.h @@ -10,36 +10,64 @@ * */ +namespace EConsoleTextColor +{ +/** The color enum is used for colored text console output. */ +enum EConsoleTextColor +{ + DEFAULT = -1, + GREEN, + RED, + MAGENTA, + YELLOW, + WHITE, + GRAY, + TEAL = -2 +}; +} /// Class which wraps the native console. It can print text based on /// the chosen color class DLL_LINKAGE CConsoleHandler { public: - boost::function *cb; //function to be called when message is received - int curLvl; //logging level - boost::thread *thread; + CConsoleHandler(); //c-tor + ~CConsoleHandler(); //d-tor + void start(); //starts listening thread - int run(); - void setColor(int level); //sets color of text appropriate for given logging level - - CConsoleHandler(); //c-tor - ~CConsoleHandler(); //d-tor - void start(); //starts listening thread - void end(); //kills listening thread - - template void print(const T &data, int lvl) + template void print(const T &data, EConsoleTextColor::EConsoleTextColor color = EConsoleTextColor::DEFAULT, bool printToStdErr = false) { + TLockGuard _(mx); #ifndef _WIN32 // with love from ffmpeg - library is trying to print some warnings from separate thread // this results in broken console on Linux. Lock stdout to print all our data at once flockfile(stdout); #endif - setColor(lvl); - std::cout << data << std::flush; - setColor(-1); + if(color != EConsoleTextColor::DEFAULT) setColor(color); + if(printToStdErr) + { + std::cerr << data << std::flush; + } + else + { + std::cout << data << std::flush; + } + if(color != EConsoleTextColor::DEFAULT) setColor(EConsoleTextColor::DEFAULT); #ifndef _WIN32 funlockfile(stdout); #endif } + + boost::function *cb; //function to be called when message is received + +private: + int run(); + + void end(); //kills listening thread + + void setColor(EConsoleTextColor::EConsoleTextColor color); //sets color of text appropriate for given logging level + + mutable boost::mutex mx; + + boost::thread * thread; }; diff --git a/lib/CLogger.h b/lib/CLogger.h index 503da5efe..ad909b6d8 100644 --- a/lib/CLogger.h +++ b/lib/CLogger.h @@ -41,7 +41,7 @@ public: if(lvl < CLogger::CONSOLE_LOGGING_LEVEL) { if(console) - console->print(data, lvl); + console->print(data, static_cast(lvl)); else std::cout << data << std::flush; } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 805ac658a..167964a5b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -13,6 +13,12 @@ set(lib_SRCS Filesystem/CResourceLoader.cpp Filesystem/CFileInputStream.cpp Filesystem/CCompressedStream.cpp + Logging/CBasicLogConfigurator.cpp + Logging/CLogConsoleTarget.cpp + Logging/CLogFileTarget.cpp + Logging/CLogFormatter.cpp + Logging/CLogger.cpp + Logging/CLogManager.cpp Mapping/CCampaignHandler.cpp Mapping/CMap.cpp Mapping/CMapEditManager.cpp @@ -59,6 +65,14 @@ set(lib_SRCS set(lib_HEADERS Filesystem/CInputStream.h Filesystem/ISimpleResourceLoader.h + Logging/CBasicLogConfigurator.h + Logging/CLogConsoleTarget.h + Logging/CLogFileTarget.h + Logging/CLogFormatter.h + Logging/CLogger.h + Logging/CLogManager.h + Logging/ILogTarget.h + Logging/LogRecord.h Mapping/CCampaignHandler.h Mapping/CMap.h Mapping/CMapEditManager.h diff --git a/lib/Logging/CBasicLogConfigurator.cpp b/lib/Logging/CBasicLogConfigurator.cpp new file mode 100644 index 000000000..8f8f00713 --- /dev/null +++ b/lib/Logging/CBasicLogConfigurator.cpp @@ -0,0 +1,113 @@ +#include "StdInc.h" +#include "CBasicLogConfigurator.h" + +#include "../CConfigHandler.h" +#include "CLogConsoleTarget.h" +#include "CLogFileTarget.h" + +CBasicLogConfigurator::CBasicLogConfigurator(const std::string & filePath, CConsoleHandler * console) +{ + const JsonNode & logging = settings["logging"]; + + // Configure loggers + const JsonNode & loggers = logging["loggers"]; + if(!loggers.isNull()) + { + BOOST_FOREACH(auto & loggerPair, loggers.Struct()) + { + // Get logger + std::string name = loggerPair.first; + CGLogger * logger = CGLogger::getLogger(name); + + // Set log level + const JsonNode & loggerNode = loggerPair.second; + const JsonNode & levelNode = loggerNode["level"]; + if(!levelNode.isNull()) + { + logger->setLevel(getLogLevel(levelNode.String())); + } + } + } + + // Add console target + CLogConsoleTarget * consoleTarget = new CLogConsoleTarget(console); + const JsonNode & consoleNode = logging["console"]; + if(!consoleNode.isNull()) + { + const JsonNode & consoleFormatNode = consoleNode["format"]; + if(!consoleFormatNode.isNull()) consoleTarget->setFormatter(CLogFormatter(consoleFormatNode.String())); + const JsonNode & consoleThresholdNode = consoleNode["threshold"]; + if(!consoleThresholdNode.isNull()) consoleTarget->setThreshold(getLogLevel(consoleThresholdNode.String())); + const JsonNode & coloredConsoleEnabledNode = consoleNode["coloredOutputEnabled"]; + consoleTarget->setColoredOutputEnabled(coloredConsoleEnabledNode.Bool()); + + CColorMapping colorMapping; + const JsonNode & colorMappingNode = consoleNode["colorMapping"]; + if(!colorMappingNode.isNull()) + { + BOOST_FOREACH(const JsonNode & mappingNode, colorMappingNode.Vector()) + { + std::string domain = mappingNode["domain"].String(); + std::string level = mappingNode["level"].String(); + std::string color = mappingNode["color"].String(); + colorMapping.setColorFor(domain, getLogLevel(level), getConsoleColor(color)); + } + } + consoleTarget->setColorMapping(colorMapping); + } + CGLogger::getGlobalLogger()->addTarget(consoleTarget); + + // Add file target + CLogFileTarget * fileTarget = new CLogFileTarget(filePath); + const JsonNode & fileNode = logging["file"]; + if(!fileNode.isNull()) + { + const JsonNode & fileFormatNode = fileNode["format"]; + if(!fileFormatNode.isNull()) fileTarget->setFormatter(CLogFormatter(fileFormatNode.String())); + } + + // Add targets to the root logger by default + CGLogger::getGlobalLogger()->addTarget(consoleTarget); + CGLogger::getGlobalLogger()->addTarget(fileTarget); +} + +ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & level) const +{ + static const std::map levelMap = boost::assign::map_list_of + ("trace", ELogLevel::TRACE) + ("debug", ELogLevel::DEBUG) + ("info", ELogLevel::INFO) + ("warn", ELogLevel::WARN) + ("error", ELogLevel::ERROR); + const auto & levelPair = levelMap.find(level); + if(levelPair != levelMap.end()) + { + return levelPair->second; + } + else + { + throw std::runtime_error("Log level " + level + " unknown."); + } +} + +EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(const std::string & colorName) const +{ + static const std::map colorMap = boost::assign::map_list_of + ("default", EConsoleTextColor::DEFAULT) + ("green", EConsoleTextColor::GREEN) + ("red", EConsoleTextColor::RED) + ("magenta", EConsoleTextColor::MAGENTA) + ("yellow", EConsoleTextColor::YELLOW) + ("white", EConsoleTextColor::WHITE) + ("gray", EConsoleTextColor::GRAY) + ("teal", EConsoleTextColor::TEAL); + const auto & colorPair = colorMap.find(colorName); + if(colorPair != colorMap.end()) + { + return colorPair->second; + } + else + { + throw std::runtime_error("Color " + colorName + " unknown."); + } +} diff --git a/lib/Logging/CBasicLogConfigurator.h b/lib/Logging/CBasicLogConfigurator.h new file mode 100644 index 000000000..5e052bbd9 --- /dev/null +++ b/lib/Logging/CBasicLogConfigurator.h @@ -0,0 +1,55 @@ + +/* + * CBasicLogConfigurator.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "CLogger.h" + +class CConsoleHandler; + +/** + * The basic log configurator reads log properties from the settings.json and + * sets up the logging system. The file path of the log file can be specified + * via the constructor. + */ +class DLL_LINKAGE CBasicLogConfigurator +{ +public: + /** + * Constructor. + * + * @param fileName The file path of the log file. + * + * @throws std::runtime_error if the configuration has errors + */ + CBasicLogConfigurator(const std::string & filePath, CConsoleHandler * console); + +private: + /** + * Gets the log level enum from a string. + * + * @param level The log level string to parse. + * @return the corresponding log level enum value + * + * @throws std::runtime_error if the log level is unknown + */ + ELogLevel::ELogLevel getLogLevel(const std::string & level) const; + + /** + * Gets the console text color enum from a string. + * + * @param colorName The color string to parse. + * @return the corresponding console text color enum value + * + * @throws std::runtime_error if the log level is unknown + */ + EConsoleTextColor::EConsoleTextColor getConsoleColor(const std::string & colorName) const; +}; diff --git a/lib/Logging/CLogConsoleTarget.cpp b/lib/Logging/CLogConsoleTarget.cpp new file mode 100644 index 000000000..dfd125897 --- /dev/null +++ b/lib/Logging/CLogConsoleTarget.cpp @@ -0,0 +1,126 @@ +#include "StdInc.h" +#include "CLogConsoleTarget.h" + +#include "LogRecord.h" + +CColorMapping::CColorMapping() +{ + // Set default mappings + auto & levelMap = map[""]; + levelMap[ELogLevel::TRACE] = EConsoleTextColor::GRAY; + levelMap[ELogLevel::DEBUG] = EConsoleTextColor::WHITE; + levelMap[ELogLevel::INFO] = EConsoleTextColor::GREEN; + levelMap[ELogLevel::WARN] = EConsoleTextColor::YELLOW; + levelMap[ELogLevel::ERROR] = EConsoleTextColor::RED; +} + +void CColorMapping::setColorFor(const CLoggerDomain & domain, ELogLevel::ELogLevel level, EConsoleTextColor::EConsoleTextColor color) +{ + if(level == ELogLevel::NOT_SET) throw std::runtime_error("Log level NOT_SET not allowed for configuring the color mapping."); + map[domain.getName()][level] = color; +} + +EConsoleTextColor::EConsoleTextColor CColorMapping::getColorFor(const CLoggerDomain & domain, ELogLevel::ELogLevel level) const +{ + std::string name = domain.getName(); + while(true) + { + const auto & loggerPair = map.find(name); + if(loggerPair != map.end()) + { + const auto & levelMap = loggerPair->second; + const auto & levelPair = levelMap.find(level); + if(levelPair != levelMap.end()) + { + return levelPair->second; + } + } + + CLoggerDomain currentDomain(name); + if(!currentDomain.isGlobalDomain()) + { + name = currentDomain.getParent().getName(); + } + else + { + break; + } + } + throw std::runtime_error("No color mapping found. Should not happen."); +} + +CLogConsoleTarget::CLogConsoleTarget(CConsoleHandler * console) : console(console), threshold(ELogLevel::INFO), coloredOutputEnabled(true) +{ + +} + +void CLogConsoleTarget::write(const LogRecord & record) +{ + if(threshold > record.level) return; + + std::string message = formatter.format(record); + bool printToStdErr = record.level >= ELogLevel::WARN; + if(console) + { + if(coloredOutputEnabled) + { + console->print(message, colorMapping.getColorFor(record.domain, record.level)); + } + else + { + console->print(message, EConsoleTextColor::DEFAULT, printToStdErr); + } + } + else + { + TLockGuard _(mx); + if(printToStdErr) + { + std::cerr << message << std::flush; + } + else + { + std::cout << message << std::flush; + } + } +} + +bool CLogConsoleTarget::isColoredOutputEnabled() const +{ + return coloredOutputEnabled; +} + +void CLogConsoleTarget::setColoredOutputEnabled(bool coloredOutputEnabled) +{ + this->coloredOutputEnabled = coloredOutputEnabled; +} + +ELogLevel::ELogLevel CLogConsoleTarget::getThreshold() const +{ + return threshold; +} + +void CLogConsoleTarget::setThreshold(ELogLevel::ELogLevel threshold) +{ + this->threshold = threshold; +} + +const CLogFormatter & CLogConsoleTarget::getFormatter() const +{ + return formatter; +} + +void CLogConsoleTarget::setFormatter(const CLogFormatter & formatter) +{ + this->formatter = formatter; +} + +const CColorMapping & CLogConsoleTarget::getColorMapping() const +{ + return colorMapping; +} + +void CLogConsoleTarget::setColorMapping(const CColorMapping & colorMapping) +{ + this->colorMapping = colorMapping; +} diff --git a/lib/Logging/CLogConsoleTarget.h b/lib/Logging/CLogConsoleTarget.h new file mode 100644 index 000000000..f4c27d95d --- /dev/null +++ b/lib/Logging/CLogConsoleTarget.h @@ -0,0 +1,147 @@ + +/* + * CLogConsoleTarget.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "ILogTarget.h" +#include "CLogger.h" +#include "CLogFormatter.h" +#include "CConsoleHandler.h" + +class LogRecord; + +/** + * The color mapping maps a logger name and a level to a specific color. + */ +class DLL_LINKAGE CColorMapping +{ +public: + /** + * Constructor. There are default color mappings for the root logger, which child loggers inherit if not overriden. + */ + CColorMapping(); + + /** + * Sets a console text color for a logger name and a level. + * + * @param domain The domain of the logger. + * @param level The logger level. + * @param color The console text color to use as the mapping. + */ + void setColorFor(const CLoggerDomain & domain, ELogLevel::ELogLevel level, EConsoleTextColor::EConsoleTextColor color); + + /** + * Gets a console text color for a logger name and a level. + * + * @param domain The domain of the logger. + * @param level The logger level. + * @return the console text color which has been applied for the mapping + */ + EConsoleTextColor::EConsoleTextColor getColorFor(const CLoggerDomain & domain, ELogLevel::ELogLevel level) const; + +private: + /** The color mapping, 1. Key: Logger domain, 2. Key: Level, Value: Color. */ + std::map > map; +}; + +/** + * The console target is a logging target which writes message to the console. + */ +class DLL_LINKAGE CLogConsoleTarget : public ILogTarget +{ +public: + /** + * Constructor. + * + * @param console Optional. The console handler which is used to output messages to the console. + */ + explicit CLogConsoleTarget(CConsoleHandler * console); + + /** + * Writes a log record. + * + * @param record The log record to write. + */ + void write(const LogRecord & record); + + /** + * True if colored output is enabled. + * + * @return true if colored output is enabled, false if not + */ + bool isColoredOutputEnabled() const; + + /** + * Sets the colored output flag. + * + * @param coloredOutput True if the log target should write colored messages to the console, false if not. + */ + void setColoredOutputEnabled(bool coloredOutputEnabled); + + /** + * Gets the threshold log level. + * + * @return the threshold log level + */ + ELogLevel::ELogLevel getThreshold() const; + + /** + * Sets the threshold log level. + * + * @param threshold The threshold log level. + */ + void setThreshold(ELogLevel::ELogLevel threshold); + + /** + * Gets the log formatter. + * + * @return The log formatter. + */ + const CLogFormatter & getFormatter() const; + + /** + * Sets the log formatter. + * + * @param formatter The log formatter. + */ + void setFormatter(const CLogFormatter & formatter); + + /** + * Gets the color mapping. + */ + const CColorMapping & getColorMapping() const; + + /** + * Sets the color mapping. + * + * @param colorMapping The color mapping. + */ + void setColorMapping(const CColorMapping & colorMapping); + +private: + /** The console handler which is used to output messages to the console. */ + CConsoleHandler * console; + + /** The threshold log level. */ + ELogLevel::ELogLevel threshold; + + /** Flag whether colored console output is enabled. */ + bool coloredOutputEnabled; + + /** The log formatter to log messages. */ + CLogFormatter formatter; + + /** The color mapping which maps a logger and a log level to a color. */ + CColorMapping colorMapping; + + /** The shared mutex for providing synchronous thread-safe access to the log console target. */ + mutable boost::mutex mx; +}; diff --git a/lib/Logging/CLogFileTarget.cpp b/lib/Logging/CLogFileTarget.cpp new file mode 100644 index 000000000..00cdf9c26 --- /dev/null +++ b/lib/Logging/CLogFileTarget.cpp @@ -0,0 +1,28 @@ +#include "StdInc.h" +#include "CLogFileTarget.h" + +CLogFileTarget::CLogFileTarget(const std::string & filePath) : file(filePath) +{ + +} + +CLogFileTarget::~CLogFileTarget() +{ + file.close(); +} + +void CLogFileTarget::write(const LogRecord & record) +{ + TLockGuard _(mx); + file << formatter.format(record) << std::endl; +} + +const CLogFormatter & CLogFileTarget::getFormatter() const +{ + return formatter; +} + +void CLogFileTarget::setFormatter(const CLogFormatter & formatter) +{ + this->formatter = formatter; +} diff --git a/lib/Logging/CLogFileTarget.h b/lib/Logging/CLogFileTarget.h new file mode 100644 index 000000000..b2fa8200c --- /dev/null +++ b/lib/Logging/CLogFileTarget.h @@ -0,0 +1,66 @@ + +/* + * CLogFileTarget.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "ILogTarget.h" +#include "CLogFormatter.h" + +/** + * The log file target is a logging target which writes messages to a log file. + */ +class DLL_LINKAGE CLogFileTarget : public ILogTarget +{ +public: + /** + * Constructor. + * + * @param filePath The file path of the log file. + */ + explicit CLogFileTarget(const std::string & filePath); + + /** + * Destructor. + */ + ~CLogFileTarget(); + + /** + * Writes a log record. + * + * @param record The log record to write. + */ + void write(const LogRecord & record); + + /** + * Gets the log formatter. + * + * @return The log formatter. + */ + const CLogFormatter & getFormatter() const; + + /** + * Sets the log formatter. + * + * @param formatter The log formatter. + */ + void setFormatter(const CLogFormatter & formatter); + +private: + /** The output file stream. */ + std::ofstream file; + + /** The log formatter to log messages. */ + CLogFormatter formatter; + + /** The shared mutex for providing synchronous thread-safe access to the log file target. */ + mutable boost::mutex mx; +}; + diff --git a/lib/Logging/CLogFormatter.cpp b/lib/Logging/CLogFormatter.cpp new file mode 100644 index 000000000..9be34d55b --- /dev/null +++ b/lib/Logging/CLogFormatter.cpp @@ -0,0 +1,65 @@ +#include "StdInc.h" +#include "CLogFormatter.h" + +#include "LogRecord.h" + +CLogFormatter::CLogFormatter() : pattern("%m") +{ + +} + +CLogFormatter::CLogFormatter(const std::string & pattern) : pattern(pattern) +{ + +} + +std::string CLogFormatter::format(const LogRecord & record) const +{ + std::string message = pattern; + + // Format date + std::stringstream dateStream; + boost::posix_time::time_facet * facet = new boost::posix_time::time_facet("%d-%b-%Y %H:%M:%S"); + dateStream.imbue(std::locale(dateStream.getloc(), facet)); + dateStream << record.timeStamp; + boost::algorithm::replace_all(message, "%d", dateStream.str()); + + // Format log level + std::string level; + switch(record.level) + { + case ELogLevel::TRACE: + level = "TRACE"; + break; + case ELogLevel::DEBUG: + level = "DEBUG"; + break; + case ELogLevel::INFO: + level = "INFO"; + break; + case ELogLevel::WARN: + level = "WARN"; + break; + case ELogLevel::ERROR: + level = "ERROR"; + break; + } + boost::algorithm::replace_all(message, "%l", level); + + // Format name, thread id and message + boost::algorithm::replace_all(message, "%n", record.domain.getName()); + boost::algorithm::replace_all(message, "%t", boost::lexical_cast(record.threadId)); + boost::algorithm::replace_all(message, "%m", record.message); + + return message; +} + +void CLogFormatter::setPattern(const std::string & pattern) +{ + this->pattern = pattern; +} + +const std::string & CLogFormatter::getPattern() const +{ + return pattern; +} diff --git a/lib/Logging/CLogFormatter.h b/lib/Logging/CLogFormatter.h new file mode 100644 index 000000000..dfde3f5c7 --- /dev/null +++ b/lib/Logging/CLogFormatter.h @@ -0,0 +1,66 @@ + +/* + * CLogFormatter.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class LogRecord; + +/** + * The log formatter formats log records. + * + * There are several pattern characters which can be used to format a log record: + * %d = Date/Time + * %l = Log level + * %n = Logger name + * %t = Thread ID + * %m = Message + */ +class DLL_LINKAGE CLogFormatter +{ +public: + /** + * Default constructor. + */ + CLogFormatter(); + + /** + * Constructor. + * + * @param pattern The pattern to format the log record with. + */ + CLogFormatter(const std::string & pattern); + + /** + * Formats a log record. + * + * @param record The log record to format. + * @return the formatted log record as a string + */ + std::string format(const LogRecord & record) const; + + /** + * Sets the pattern. + * + * @param pattern The pattern string which describes how to format the log record. + */ + void setPattern(const std::string & pattern); + + /** + * Gets the pattern. + * + * @return the pattern string which describes how to format the log record + */ + const std::string & getPattern() const; + +private: + /** The pattern string which describes how to format the log record. */ + std::string pattern; +}; diff --git a/lib/Logging/CLogManager.cpp b/lib/Logging/CLogManager.cpp new file mode 100644 index 000000000..2ee4f38c3 --- /dev/null +++ b/lib/Logging/CLogManager.cpp @@ -0,0 +1,51 @@ +#include "StdInc.h" +#include "CLogManager.h" + +#include "CLogger.h" + +CLogManager * CLogManager::instance = nullptr; + +boost::mutex CLogManager::smx; + +CLogManager * CLogManager::get() +{ + TLockGuard _(smx); + if(!instance) + { + instance = new CLogManager(); + } + return instance; +} + +CLogManager::CLogManager() +{ + +} + +CLogManager::~CLogManager() +{ + BOOST_FOREACH(auto & i, loggers) + { + delete i.second; + } +} + +void CLogManager::addLogger(CGLogger * logger) +{ + TWriteLock _(mx); + loggers[logger->getDomain().getName()] = logger; +} + +CGLogger * CLogManager::getLogger(const CLoggerDomain & domain) +{ + TReadLock _(mx); + auto it = loggers.find(domain.getName()); + if(it != loggers.end()) + { + return it->second; + } + else + { + return nullptr; + } +} diff --git a/lib/Logging/CLogManager.h b/lib/Logging/CLogManager.h new file mode 100644 index 000000000..bf053816f --- /dev/null +++ b/lib/Logging/CLogManager.h @@ -0,0 +1,67 @@ + +/* + * CLogManager.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class CGLogger; +class CLoggerDomain; + +/** + * The log manager is a global storage of all logger objects. + */ +class DLL_LINKAGE CLogManager : public boost::noncopyable +{ +public: + /** + * Gets an instance of the log manager. + * + * @return an instance of the log manager + */ + static CLogManager * get(); + + /** + * Adds a logger. The log manager holds strong ownership of the logger object. + * + * @param logger The logger to add. + */ + void addLogger(CGLogger * logger); + + /** + * Gets a logger by domain. + * + * @param domain The domain of the logger. + * @return a logger by domain or nullptr if the logger was not found + */ + CGLogger * getLogger(const CLoggerDomain & domain); + + /** + * Destructor. + */ + ~CLogManager(); + +private: + /** + * Constructor. + */ + CLogManager(); + + /** The instance of the log manager. */ + static CLogManager * instance; + + /** The loggers map where the key is the logger name and the value the corresponding logger. */ + std::map loggers; + + /** The shared mutex for providing synchronous thread-safe access to the log manager. */ + mutable boost::shared_mutex mx; + + /** The unique mutex for providing thread-safe singleton access. */ + static boost::mutex smx; +}; diff --git a/lib/Logging/CLogger.cpp b/lib/Logging/CLogger.cpp new file mode 100644 index 000000000..3cf37896f --- /dev/null +++ b/lib/Logging/CLogger.cpp @@ -0,0 +1,165 @@ +#include "StdInc.h" +#include "CLogger.h" +#include "LogRecord.h" +#include "ILogTarget.h" +#include "CLogManager.h" + +boost::mutex CGLogger::smx; + +const std::string CLoggerDomain::DOMAIN_GLOBAL = "global"; + +CLoggerDomain::CLoggerDomain(const std::string & name) : name(name) +{ + if(name.empty()) throw std::runtime_error("Logger domain cannot be empty."); +} + +CLoggerDomain CLoggerDomain::getParent() const +{ + if(isGlobalDomain()) return *this; + + size_t pos = name.find_last_of("."); + if(pos != std::string::npos) + { + return CLoggerDomain(name.substr(0, pos)); + } + else + { + return CLoggerDomain(DOMAIN_GLOBAL); + } +} + +bool CLoggerDomain::isGlobalDomain() const +{ + return name == DOMAIN_GLOBAL; +} + +std::string CLoggerDomain::getName() const +{ + return name; +} + +CGLogger * CGLogger::getLogger(const CLoggerDomain & domain) +{ + TLockGuard _(smx); + CGLogger * logger = CLogManager::get()->getLogger(domain); + if(logger) + { + return logger; + } + else + { + logger = new CGLogger(domain); + if(domain.isGlobalDomain()) + { + logger->setLevel(ELogLevel::INFO); + } + CLogManager::get()->addLogger(logger); + return logger; + } +} + +CGLogger * CGLogger::getGlobalLogger() +{ + return getLogger(CLoggerDomain::DOMAIN_GLOBAL); +} + +CGLogger::CGLogger(const CLoggerDomain & domain) : domain(domain), level(ELogLevel::INFO) +{ + if(!domain.isGlobalDomain()) + { + parent = getLogger(domain.getParent()); + } +} + +CGLogger::~CGLogger() +{ + BOOST_FOREACH(ILogTarget * target, targets) + { + delete target; + } +} + +void CGLogger::trace(const std::string & message) const +{ + log(ELogLevel::TRACE, message); +} + +void CGLogger::debug(const std::string & message) const +{ + log(ELogLevel::DEBUG, message); +} + +void CGLogger::info(const std::string & message) const +{ + log(ELogLevel::INFO, message); +} + +void CGLogger::warn(const std::string & message) const +{ + log(ELogLevel::WARN, message); +} + +void CGLogger::error(const std::string & message) const +{ + log(ELogLevel::ERROR, message); +} + +void CGLogger::log(ELogLevel::ELogLevel level, const std::string & message) const +{ + if(getEffectiveLevel() <= level) + { + callTargets(LogRecord(domain, level, message)); + } +} + +ELogLevel::ELogLevel CGLogger::getLevel() const +{ + TReadLock _(mx); + return level; +} + +void CGLogger::setLevel(ELogLevel::ELogLevel level) +{ + TWriteLock _(mx); + if(domain.isGlobalDomain() && level == ELogLevel::NOT_SET) return; + this->level = level; +} + +const CLoggerDomain & CGLogger::getDomain() const +{ + return domain; +} + +void CGLogger::addTarget(ILogTarget * target) +{ + TWriteLock _(mx); + targets.push_back(target); +} + +std::list CGLogger::getTargets() const +{ + TReadLock _(mx); + return targets; +} + +ELogLevel::ELogLevel CGLogger::getEffectiveLevel() const +{ + for(const CGLogger * logger = this; logger != nullptr; logger = logger->parent) + { + if(logger->getLevel() != ELogLevel::NOT_SET) return logger->getLevel(); + } + + // This shouldn't be reached, as the root logger must have set a log level + return ELogLevel::INFO; +} + +void CGLogger::callTargets(const LogRecord & record) const +{ + for(const CGLogger * logger = this; logger != nullptr; logger = logger->parent) + { + BOOST_FOREACH(ILogTarget * target, logger->getTargets()) + { + target->write(record); + } + } +} diff --git a/lib/Logging/CLogger.h b/lib/Logging/CLogger.h new file mode 100644 index 000000000..77571caa0 --- /dev/null +++ b/lib/Logging/CLogger.h @@ -0,0 +1,230 @@ + +/* + * CLogger.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class CGLogger; +class LogRecord; +class ILogTarget; + +namespace ELogLevel +{ + /** + * The log level enum holds various log level definitions. + */ + enum ELogLevel + { + NOT_SET = 0, + TRACE, + DEBUG, + INFO, + WARN, + ERROR + }; +} + +/** + * The logger domain provides convenient access to super domains from a sub domain. + */ +class DLL_LINKAGE CLoggerDomain +{ +public: + /** + * Constructor. + * + * @param domain The domain name. Sub-domains can be specified by separating domains by a dot, e.g. "ai.battle". The global domain is named "global". + */ + CLoggerDomain(const std::string & name); + + /** + * Gets the parent logger domain. + * + * @return the parent logger domain or the same domain logger if this method has been called on the global domain + */ + CLoggerDomain getParent() const; + + /** + * Returns true if this domain is the global domain. + * + * @return true if this is the global domain or false if not + */ + bool isGlobalDomain() const; + + /** + * Gets the name of the domain. + * + * @return the name of the domain + */ + std::string getName() const; + + /** Constant to the global domain name. */ + static const std::string DOMAIN_GLOBAL; + +private: + /** The domain name. */ + std::string name; +}; + +/** + * The logger is used to log messages to certain targets of a specific domain/name. Temporary name is + * CGLogger, should be renamed to CLogger after refactoring. + */ +class DLL_LINKAGE CGLogger : public boost::noncopyable +{ +public: + /** + * Gets a logger by domain. + * + * @param domain The logger domain. + * @return the logger object + */ + static CGLogger * getLogger(const CLoggerDomain & domain); + + /** + * Gets the global logger which is the parent of all domain-specific loggers. + * + * @return the global logger object + */ + static CGLogger * getGlobalLogger(); + + /** + * Logs a message with the trace level. + * + * @param message The message to log. + */ + void trace(const std::string & message) const; + + /** + * Logs a message with the debug level. + * + * @param message The message to log. + */ + void debug(const std::string & message) const; + + /** + * Logs a message with the info level. + * + * @param message The message to log. + */ + void info(const std::string & message) const; + + /** + * Logs a message with the warn level. + * + * @param message The message to log. + */ + void warn(const std::string & message) const; + + /** + * Logs a message with the error level. + * + * @param message The message to log. + */ + void error(const std::string & message) const; + + /** + * Gets the log level applied for this logger. The default level for the root logger is INFO. + * + * @return the log level + */ + inline ELogLevel::ELogLevel getLevel() const; + + /** + * Sets the log level. + * + * @param level The log level. + */ + void setLevel(ELogLevel::ELogLevel level); + + /** + * Gets the logger domain. + * + * @return the domain of the logger + */ + const CLoggerDomain & getDomain() const; + + /** + * Adds a target to this logger and indirectly to all loggers which derive from this logger. + * The logger holds strong-ownership of the target object. + * + * @param target The log target to add. + */ + void addTarget(ILogTarget * target); + + /** + * Destructor. + */ + ~CGLogger(); + +private: + /** + * Constructor. + * + * @param domain The domain of the logger. + */ + explicit CGLogger(const CLoggerDomain & domain); + + /** + * Gets the parent logger. + * + * @return the parent logger or nullptr if this is the root logger + */ + CGLogger * getParent() const; + + /** + * Logs a message of the given log level. + * + * @param level The log level of the message to log. + * @param message The message to log. + */ + inline void log(ELogLevel::ELogLevel level, const std::string & message) const; + + /** + * Gets the effective log level. + * + * @return the effective log level with respect to parent log levels + */ + inline ELogLevel::ELogLevel getEffectiveLevel() const; + + /** + * Calls all targets in the hierarchy to write the message. + * + * @param record The log record to write. + */ + inline void callTargets(const LogRecord & record) const; + + /** + * Gets all log targets attached to this logger. + * + * @return all log targets as a list + */ + inline std::list getTargets() const; + + /** The domain of the logger. */ + CLoggerDomain domain; + + /** A reference to the parent logger. */ + CGLogger * parent; + + /** The log level of the logger. */ + ELogLevel::ELogLevel level; + + /** A list of log targets. */ + std::list targets; + + /** The shared mutex for providing synchronous thread-safe access to the logger. */ + mutable boost::shared_mutex mx; + + /** The unique mutex for providing thread-safe get logger access. */ + static boost::mutex smx; +}; + +//extern CLogger * logGlobal; diff --git a/lib/Logging/ILogTarget.h b/lib/Logging/ILogTarget.h new file mode 100644 index 000000000..47ec0ab53 --- /dev/null +++ b/lib/Logging/ILogTarget.h @@ -0,0 +1,31 @@ + +/* + * LogRecord.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class LogRecord; + +/** + * The interface log target is used by all log target implementations. It holds + * the abstract method write which sub-classes should implement. + */ +class DLL_LINKAGE ILogTarget : public boost::noncopyable +{ +public: + /** + * Writes a log record. + * + * @param record The log record to write. + */ + virtual void write(const LogRecord & record) = 0; + + virtual ~ILogTarget() { }; +}; diff --git a/lib/Logging/LogRecord.h b/lib/Logging/LogRecord.h new file mode 100644 index 000000000..b5662868f --- /dev/null +++ b/lib/Logging/LogRecord.h @@ -0,0 +1,44 @@ + +/* + * LogRecord.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "CLogger.h" + +/** + * The log records holds the log message and additional logging information. + */ +struct DLL_LINKAGE LogRecord +{ + /** + * Constructor. + */ + LogRecord(const CLoggerDomain & domain, ELogLevel::ELogLevel level, const std::string & message) + : domain(domain), level(level), message(message), timeStamp(boost::posix_time::second_clock::local_time()), threadId(boost::this_thread::get_id()) + { + + } + + /** The logger domain. */ + CLoggerDomain domain; + + /** The log level. */ + ELogLevel::ELogLevel level; + + /** The message. */ + std::string message; + + /** The time when the message was created. */ + boost::posix_time::ptime timeStamp; + + /** The thread id. */ + boost::thread::id threadId; +};