| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 | /* * CLogger.cpp, 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 * */#include "StdInc.h"#include "CLogger.h"#include "../CThreadHelper.h"#ifdef VCMI_ANDROID#include <android/log.h>VCMI_LIB_NAMESPACE_BEGINnamespace ELogLevel{	int toAndroid(ELogLevel logLevel)	{		switch (logLevel)		{			case TRACE: return ANDROID_LOG_VERBOSE;			case DEBUG: return ANDROID_LOG_DEBUG;			case INFO:  return ANDROID_LOG_INFO;			case WARN:  return ANDROID_LOG_WARN;			case ERROR: return ANDROID_LOG_ERROR;		default:			break;		}		return ANDROID_LOG_UNKNOWN;	}}VCMI_LIB_NAMESPACE_END#elif defined(VCMI_IOS)#import "iOS_utils.h"extern "C" {#include <os/log.h>}#endifVCMI_LIB_NAMESPACE_BEGINnamespace vstd{CLoggerBase::~CLoggerBase() = default;CTraceLogger::CTraceLogger(const CLoggerBase * logger, const std::string & beginMessage, const std::string & endMessage)	: logger(logger), endMessage(endMessage){	logger->trace(beginMessage);}CTraceLogger::~CTraceLogger(){	logger->trace(endMessage);}}//namespace vstdconst std::string CLoggerDomain::DOMAIN_GLOBAL = "global";CLoggerDomain::CLoggerDomain(std::string name) : name(std::move(name)){	if (this->name.empty())		throw std::runtime_error("Logger domain cannot be empty.");}CLoggerDomain CLoggerDomain::getParent() const{	if(isGlobalDomain())		return *this;	const size_t pos = name.find_last_of('.');	if(pos != std::string::npos)		return CLoggerDomain(name.substr(0, pos));	return CLoggerDomain(DOMAIN_GLOBAL);}bool CLoggerDomain::isGlobalDomain() const { return name == DOMAIN_GLOBAL; }const std::string& CLoggerDomain::getName() const { return name; }std::recursive_mutex CLogger::smx;std::recursive_mutex CLogManager::smx;DLL_LINKAGE vstd::CLoggerBase * logGlobal = CLogger::getGlobalLogger();DLL_LINKAGE vstd::CLoggerBase * logBonus = CLogger::getLogger(CLoggerDomain("bonus"));DLL_LINKAGE vstd::CLoggerBase * logNetwork = CLogger::getLogger(CLoggerDomain("network"));DLL_LINKAGE vstd::CLoggerBase * logAi = CLogger::getLogger(CLoggerDomain("ai"));DLL_LINKAGE vstd::CLoggerBase * logAnim = CLogger::getLogger(CLoggerDomain("animation"));DLL_LINKAGE vstd::CLoggerBase * logMod = CLogger::getLogger(CLoggerDomain("mod"));CLogger * CLogger::getLogger(const CLoggerDomain & domain){	TLockGuardRec _(smx);	CLogger * logger = CLogManager::get().getLogger(domain);	if(!logger) // Create new logger	{		logger = new CLogger(domain);		if(domain.isGlobalDomain())		{			logger->setLevel(ELogLevel::TRACE);		}		CLogManager::get().addLogger(logger);		if (logGlobal != nullptr)		{			logGlobal->debug("Created logger %s", domain.getName());		}	}	return logger;}CLogger * CLogger::getGlobalLogger(){	return getLogger(CLoggerDomain(CLoggerDomain::DOMAIN_GLOBAL));}CLogger::CLogger(const CLoggerDomain & domain) : domain(domain){	if(domain.isGlobalDomain())	{		level = ELogLevel::TRACE;		parent = nullptr;	}	else	{		level = ELogLevel::NOT_SET;		parent = getLogger(domain.getParent());	}}void CLogger::log(ELogLevel::ELogLevel level, const std::string & message) const{	if(getEffectiveLevel() <= level)		callTargets(LogRecord(domain, level, message));}void CLogger::log(ELogLevel::ELogLevel level, const boost::format & fmt) const{	try	{		log(level, fmt.str());	}	catch(...)	{		log(ELogLevel::ERROR, "Invalid log format!");	}}ELogLevel::ELogLevel CLogger::getLevel() const{	TLockGuard _(mx);	return level;}void CLogger::setLevel(ELogLevel::ELogLevel level){	TLockGuard _(mx);	if (!domain.isGlobalDomain() || level != ELogLevel::NOT_SET)		this->level = level;}const CLoggerDomain & CLogger::getDomain() const { return domain; }void CLogger::addTarget(std::unique_ptr<ILogTarget> && target){	TLockGuard _(mx);	targets.push_back(std::move(target));}ELogLevel::ELogLevel CLogger::getEffectiveLevel() const{	for(const CLogger * 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 CLogger::callTargets(const LogRecord & record) const{	TLockGuard _(mx);	for(const CLogger * logger = this; logger != nullptr; logger = logger->parent)		for(const auto & target : logger->targets)			target->write(record);}void CLogger::clearTargets(){	TLockGuard _(mx);	targets.clear();}bool CLogger::isDebugEnabled() const { return getEffectiveLevel() <= ELogLevel::DEBUG; }bool CLogger::isTraceEnabled() const { return getEffectiveLevel() <= ELogLevel::TRACE; }CLogManager & CLogManager::get(){	TLockGuardRec _(smx);	static CLogManager instance;	return instance;}CLogManager::CLogManager() = default;CLogManager::~CLogManager(){	for(auto & i : loggers)		delete i.second;}void CLogManager::addLogger(CLogger * logger){	TLockGuard _(mx);	loggers[logger->getDomain().getName()] = logger;}CLogger * CLogManager::getLogger(const CLoggerDomain & domain){	TLockGuard _(mx);	auto it = loggers.find(domain.getName());	if(it != loggers.end())		return it->second;	else		return nullptr;}std::vector<std::string> CLogManager::getRegisteredDomains() const{	std::vector<std::string> domains;	domains.reserve(loggers.size());	for(const auto & pair : loggers)	{		domains.push_back(pair.second->getDomain().getName());	}	return domains;}CLogFormatter::CLogFormatter()	: CLogFormatter("%m"){}CLogFormatter::CLogFormatter(std::string pattern):	pattern(std::move(pattern)){}std::string CLogFormatter::format(const LogRecord & record) const{	std::string message = pattern;	//Format date//	boost::algorithm::replace_first(message, "%d", boost::posix_time::to_simple_string (record.timeStamp));	//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_first(message, "%l", level);	//Format name, thread id and message	boost::algorithm::replace_first(message, "%n", record.domain.getName());	boost::algorithm::replace_first(message, "%t", record.threadId);	boost::algorithm::replace_first(message, "%m", record.message);	boost::algorithm::replace_first(message, "%c", boost::posix_time::to_simple_string(record.timeStamp));	//return boost::str (boost::format("%d %d %d[%d] - %d") % dateStream.str() % level % record.domain.getName() % record.threadId % record.message);	return message;}void CLogFormatter::setPattern(const std::string & pattern) { this->pattern = pattern; }void CLogFormatter::setPattern(std::string && pattern) { this->pattern = std::move(pattern); }const std::string & CLogFormatter::getPattern() const { return pattern; }CColorMapping::CColorMapping(){	// Set default mappings	auto & levelMap = map[CLoggerDomain::DOMAIN_GLOBAL];	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){	assert(level != ELogLevel::NOT_SET);	map[domain.getName()][level] = color;}EConsoleTextColor::EConsoleTextColor CColorMapping::getColorFor(const CLoggerDomain & domain, ELogLevel::ELogLevel level) const{	CLoggerDomain currentDomain = domain;	while(true)	{		const auto & loggerPair = map.find(currentDomain.getName());		if(loggerPair != map.end())		{			const auto & levelMap = loggerPair->second;			const auto & levelPair = levelMap.find(level);			if(levelPair != levelMap.end())				return levelPair->second;		}		if (currentDomain.isGlobalDomain())			break;		currentDomain = currentDomain.getParent();	}	throw std::runtime_error("failed to find color for requested domain/level pair");}CLogConsoleTarget::CLogConsoleTarget(CConsoleHandler * console) :#if !defined(VCMI_MOBILE)	console(console),#endif	threshold(ELogLevel::INFO), coloredOutputEnabled(true){	formatter.setPattern("%m");}void CLogConsoleTarget::write(const LogRecord & record){	if(threshold > record.level)		return;	std::string message = formatter.format(record);#ifdef VCMI_ANDROID	__android_log_write(ELogLevel::toAndroid(record.level), ("VCMI-" + record.domain.getName()).c_str(), message.c_str());#elif defined(VCMI_IOS)	os_log_type_t type;	switch (record.level)	{	case ELogLevel::TRACE:		type = OS_LOG_TYPE_DEBUG;		break;	case ELogLevel::DEBUG:		type = OS_LOG_TYPE_DEFAULT;		break;	case ELogLevel::INFO:		type = OS_LOG_TYPE_INFO;		break;	case ELogLevel::WARN:		type = OS_LOG_TYPE_ERROR;		break;	case ELogLevel::ERROR:		type = OS_LOG_TYPE_FAULT;		break;	default:		return;	}	os_log_t currentLog;	static std::unordered_map<std::string, decltype(currentLog)> logs;	const auto& domainName = record.domain.getName();	auto logIt = logs.find(domainName);	if (logIt != logs.end())		currentLog = logIt->second;	else	{		currentLog = os_log_create(iOS_utils::bundleIdentifier(), domainName.c_str());		logs.insert({domainName, currentLog});	}	os_log_with_type(currentLog, type, "%{public}s", message.c_str());#else	const bool printToStdErr = record.level >= ELogLevel::WARN;	if(console)	{		const EConsoleTextColor::EConsoleTextColor textColor =			coloredOutputEnabled ? colorMapping.getColorFor(record.domain, record.level) : EConsoleTextColor::DEFAULT;		console->print(message, true, textColor, printToStdErr);	}	else	{		TLockGuard _(mx);		if(printToStdErr)			std::cerr << message << std::endl;		else			std::cout << message << std::endl;	}#endif}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; }CLogFileTarget::CLogFileTarget(const boost::filesystem::path & filePath, bool append):	file(filePath.c_str(), append ? std::ios_base::app : std::ios_base::out){	formatter.setPattern("[%c] %l %n [%t] - %m");}void CLogFileTarget::write(const LogRecord & record){	std::string message = formatter.format(record); //formatting is slow, do it outside the lock	TLockGuard _(mx);	file << message << std::endl;}const CLogFormatter & CLogFileTarget::getFormatter() const { return formatter; }void CLogFileTarget::setFormatter(const CLogFormatter & formatter) { this->formatter = formatter; }CLogFileTarget::~CLogFileTarget(){	file.close();}LogRecord::LogRecord(const CLoggerDomain & domain, ELogLevel::ELogLevel level, const std::string & message)	: domain(domain),	level(level),	message(message),	timeStamp(boost::posix_time::microsec_clock::local_time()),	threadId(getThreadName()){}VCMI_LIB_NAMESPACE_END
 |