diff options
Diffstat (limited to 'src/common/Logging')
-rw-r--r-- | src/common/Logging/Appender.cpp | 109 | ||||
-rw-r--r-- | src/common/Logging/Appender.h | 132 | ||||
-rw-r--r-- | src/common/Logging/AppenderConsole.cpp | 200 | ||||
-rw-r--r-- | src/common/Logging/AppenderConsole.h | 62 | ||||
-rw-r--r-- | src/common/Logging/AppenderFile.cpp | 125 | ||||
-rw-r--r-- | src/common/Logging/AppenderFile.h | 46 | ||||
-rw-r--r-- | src/common/Logging/Log.cpp | 348 | ||||
-rw-r--r-- | src/common/Logging/Log.h | 216 | ||||
-rw-r--r-- | src/common/Logging/LogOperation.cpp | 25 | ||||
-rw-r--r-- | src/common/Logging/LogOperation.h | 42 | ||||
-rw-r--r-- | src/common/Logging/Logger.cpp | 64 | ||||
-rw-r--r-- | src/common/Logging/Logger.h | 43 |
12 files changed, 1412 insertions, 0 deletions
diff --git a/src/common/Logging/Appender.cpp b/src/common/Logging/Appender.cpp new file mode 100644 index 00000000000..d19ef8cf96f --- /dev/null +++ b/src/common/Logging/Appender.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Appender.h" +#include "Common.h" +#include "Util.h" +#include "StringFormat.h" + +#include <utility> +#include <sstream> + +std::string LogMessage::getTimeStr(time_t time) +{ + tm aTm; + localtime_r(&time, &aTm); + char buf[20]; + snprintf(buf, 20, "%04d-%02d-%02d_%02d:%02d:%02d", aTm.tm_year+1900, aTm.tm_mon+1, aTm.tm_mday, aTm.tm_hour, aTm.tm_min, aTm.tm_sec); + return std::string(buf); +} + +std::string LogMessage::getTimeStr() +{ + return getTimeStr(mtime); +} + +Appender::Appender(uint8 _id, std::string const& _name, LogLevel _level /* = LOG_LEVEL_DISABLED */, AppenderFlags _flags /* = APPENDER_FLAGS_NONE */): +id(_id), name(_name), level(_level), flags(_flags) { } + +Appender::~Appender() { } + +uint8 Appender::getId() const +{ + return id; +} + +std::string const& Appender::getName() const +{ + return name; +} + +LogLevel Appender::getLogLevel() const +{ + return level; +} + +AppenderFlags Appender::getFlags() const +{ + return flags; +} + +void Appender::setLogLevel(LogLevel _level) +{ + level = _level; +} + +void Appender::write(LogMessage* message) +{ + if (!level || level > message->level) + return; + + std::ostringstream ss; + + if (flags & APPENDER_FLAGS_PREFIX_TIMESTAMP) + ss << message->getTimeStr() << ' '; + + if (flags & APPENDER_FLAGS_PREFIX_LOGLEVEL) + ss << Trinity::StringFormat("%-5s ", Appender::getLogLevelString(message->level)); + + if (flags & APPENDER_FLAGS_PREFIX_LOGFILTERTYPE) + ss << '[' << message->type << "] "; + + message->prefix = ss.str(); + _write(message); +} + +const char* Appender::getLogLevelString(LogLevel level) +{ + switch (level) + { + case LOG_LEVEL_FATAL: + return "FATAL"; + case LOG_LEVEL_ERROR: + return "ERROR"; + case LOG_LEVEL_WARN: + return "WARN"; + case LOG_LEVEL_INFO: + return "INFO"; + case LOG_LEVEL_DEBUG: + return "DEBUG"; + case LOG_LEVEL_TRACE: + return "TRACE"; + default: + return "DISABLED"; + } +} diff --git a/src/common/Logging/Appender.h b/src/common/Logging/Appender.h new file mode 100644 index 00000000000..6382399a0b4 --- /dev/null +++ b/src/common/Logging/Appender.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef APPENDER_H +#define APPENDER_H + +#include <unordered_map> +#include <stdexcept> +#include <string> +#include <time.h> +#include <type_traits> +#include <vector> +#include <utility> +#include "Define.h" + +// Values assigned have their equivalent in enum ACE_Log_Priority +enum LogLevel +{ + LOG_LEVEL_DISABLED = 0, + LOG_LEVEL_TRACE = 1, + LOG_LEVEL_DEBUG = 2, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_WARN = 4, + LOG_LEVEL_ERROR = 5, + LOG_LEVEL_FATAL = 6 +}; + +const uint8 MaxLogLevels = 6; + +enum AppenderType +{ + APPENDER_NONE, + APPENDER_CONSOLE, + APPENDER_FILE, + APPENDER_DB +}; + +enum AppenderFlags +{ + APPENDER_FLAGS_NONE = 0x00, + APPENDER_FLAGS_PREFIX_TIMESTAMP = 0x01, + APPENDER_FLAGS_PREFIX_LOGLEVEL = 0x02, + APPENDER_FLAGS_PREFIX_LOGFILTERTYPE = 0x04, + APPENDER_FLAGS_USE_TIMESTAMP = 0x08, // only used by FileAppender + APPENDER_FLAGS_MAKE_FILE_BACKUP = 0x10 // only used by FileAppender +}; + +struct LogMessage +{ + LogMessage(LogLevel _level, std::string const& _type, std::string&& _text) + : level(_level), type(_type), text(std::forward<std::string>(_text)), mtime(time(NULL)) + { } + + LogMessage(LogMessage const& /*other*/) = delete; + LogMessage& operator=(LogMessage const& /*other*/) = delete; + + static std::string getTimeStr(time_t time); + std::string getTimeStr(); + + LogLevel const level; + std::string const type; + std::string const text; + std::string prefix; + std::string param1; + time_t mtime; + + ///@ Returns size of the log message content in bytes + uint32 Size() const + { + return static_cast<uint32>(prefix.size() + text.size()); + } +}; + +class Appender +{ + public: + Appender(uint8 _id, std::string const& name, LogLevel level = LOG_LEVEL_DISABLED, AppenderFlags flags = APPENDER_FLAGS_NONE); + virtual ~Appender(); + + uint8 getId() const; + std::string const& getName() const; + virtual AppenderType getType() const = 0; + LogLevel getLogLevel() const; + AppenderFlags getFlags() const; + + void setLogLevel(LogLevel); + void write(LogMessage* message); + static const char* getLogLevelString(LogLevel level); + virtual void setRealmId(uint32 /*realmId*/) { } + + private: + virtual void _write(LogMessage const* /*message*/) = 0; + + uint8 id; + std::string name; + LogLevel level; + AppenderFlags flags; +}; + +typedef std::unordered_map<uint8, Appender*> AppenderMap; + +typedef std::vector<char const*> ExtraAppenderArgs; +typedef Appender*(*AppenderCreatorFn)(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs); +typedef std::unordered_map<uint8, AppenderCreatorFn> AppenderCreatorMap; + +template<class AppenderImpl> +Appender* CreateAppender(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs) +{ + return new AppenderImpl(id, name, level, flags, std::forward<ExtraAppenderArgs>(extraArgs)); +} + +class InvalidAppenderArgsException : public std::length_error +{ +public: + explicit InvalidAppenderArgsException(std::string const& message) : std::length_error(message) { } +}; + +#endif diff --git a/src/common/Logging/AppenderConsole.cpp b/src/common/Logging/AppenderConsole.cpp new file mode 100644 index 00000000000..531df266aa1 --- /dev/null +++ b/src/common/Logging/AppenderConsole.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sstream> + +#include "AppenderConsole.h" +#include "Config.h" +#include "Util.h" + +#if PLATFORM == PLATFORM_WINDOWS + #include <Windows.h> +#endif + +AppenderConsole::AppenderConsole(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs) + : Appender(id, name, level, flags), _colored(false) +{ + for (uint8 i = 0; i < MaxLogLevels; ++i) + _colors[i] = ColorTypes(MaxColors); + + if (!extraArgs.empty()) + InitColors(extraArgs[0]); +} + +void AppenderConsole::InitColors(std::string const& str) +{ + if (str.empty()) + { + _colored = false; + return; + } + + int color[MaxLogLevels]; + + std::istringstream ss(str); + + for (uint8 i = 0; i < MaxLogLevels; ++i) + { + ss >> color[i]; + + if (!ss) + return; + + if (color[i] < 0 || color[i] >= MaxColors) + return; + } + + for (uint8 i = 0; i < MaxLogLevels; ++i) + _colors[i] = ColorTypes(color[i]); + + _colored = true; +} + +void AppenderConsole::SetColor(bool stdout_stream, ColorTypes color) +{ +#if PLATFORM == PLATFORM_WINDOWS + static WORD WinColorFG[MaxColors] = + { + 0, // BLACK + FOREGROUND_RED, // RED + FOREGROUND_GREEN, // GREEN + FOREGROUND_RED | FOREGROUND_GREEN, // BROWN + FOREGROUND_BLUE, // BLUE + FOREGROUND_RED | FOREGROUND_BLUE, // MAGENTA + FOREGROUND_GREEN | FOREGROUND_BLUE, // CYAN + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // WHITE + // YELLOW + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, + // RED_BOLD + FOREGROUND_RED | FOREGROUND_INTENSITY, + // GREEN_BOLD + FOREGROUND_GREEN | FOREGROUND_INTENSITY, + FOREGROUND_BLUE | FOREGROUND_INTENSITY, // BLUE_BOLD + // MAGENTA_BOLD + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + // CYAN_BOLD + FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + // WHITE_BOLD + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY + }; + + HANDLE hConsole = GetStdHandle(stdout_stream ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + SetConsoleTextAttribute(hConsole, WinColorFG[color]); +#else + enum ANSITextAttr + { + TA_NORMAL = 0, + TA_BOLD = 1, + TA_BLINK = 5, + TA_REVERSE = 7 + }; + + enum ANSIFgTextAttr + { + FG_BLACK = 30, + FG_RED, + FG_GREEN, + FG_BROWN, + FG_BLUE, + FG_MAGENTA, + FG_CYAN, + FG_WHITE, + FG_YELLOW + }; + + enum ANSIBgTextAttr + { + BG_BLACK = 40, + BG_RED, + BG_GREEN, + BG_BROWN, + BG_BLUE, + BG_MAGENTA, + BG_CYAN, + BG_WHITE + }; + + static uint8 UnixColorFG[MaxColors] = + { + FG_BLACK, // BLACK + FG_RED, // RED + FG_GREEN, // GREEN + FG_BROWN, // BROWN + FG_BLUE, // BLUE + FG_MAGENTA, // MAGENTA + FG_CYAN, // CYAN + FG_WHITE, // WHITE + FG_YELLOW, // YELLOW + FG_RED, // LRED + FG_GREEN, // LGREEN + FG_BLUE, // LBLUE + FG_MAGENTA, // LMAGENTA + FG_CYAN, // LCYAN + FG_WHITE // LWHITE + }; + + fprintf((stdout_stream? stdout : stderr), "\x1b[%d%sm", UnixColorFG[color], (color >= YELLOW && color < MaxColors ? ";1" : "")); + #endif +} + +void AppenderConsole::ResetColor(bool stdout_stream) +{ + #if PLATFORM == PLATFORM_WINDOWS + HANDLE hConsole = GetStdHandle(stdout_stream ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); + SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + #else + fprintf((stdout_stream ? stdout : stderr), "\x1b[0m"); + #endif +} + +void AppenderConsole::_write(LogMessage const* message) +{ + bool stdout_stream = !(message->level == LOG_LEVEL_ERROR || message->level == LOG_LEVEL_FATAL); + + if (_colored) + { + uint8 index; + switch (message->level) + { + case LOG_LEVEL_TRACE: + index = 5; + break; + case LOG_LEVEL_DEBUG: + index = 4; + break; + case LOG_LEVEL_INFO: + index = 3; + break; + case LOG_LEVEL_WARN: + index = 2; + break; + case LOG_LEVEL_FATAL: + index = 0; + break; + case LOG_LEVEL_ERROR: // No break on purpose + default: + index = 1; + break; + } + + SetColor(stdout_stream, _colors[index]); + utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str()); + ResetColor(stdout_stream); + } + else + utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str()); +} diff --git a/src/common/Logging/AppenderConsole.h b/src/common/Logging/AppenderConsole.h new file mode 100644 index 00000000000..6b30505c6bd --- /dev/null +++ b/src/common/Logging/AppenderConsole.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef APPENDERCONSOLE_H +#define APPENDERCONSOLE_H + +#include <string> +#include "Appender.h" + +enum ColorTypes +{ + BLACK, + RED, + GREEN, + BROWN, + BLUE, + MAGENTA, + CYAN, + GREY, + YELLOW, + LRED, + LGREEN, + LBLUE, + LMAGENTA, + LCYAN, + WHITE +}; + +const uint8 MaxColors = uint8(WHITE) + 1; + +class AppenderConsole : public Appender +{ + public: + typedef std::integral_constant<AppenderType, APPENDER_CONSOLE>::type TypeIndex; + + AppenderConsole(uint8 _id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs); + void InitColors(const std::string& init_str); + AppenderType getType() const override { return TypeIndex::value; } + + private: + void SetColor(bool stdout_stream, ColorTypes color); + void ResetColor(bool stdout_stream); + void _write(LogMessage const* message) override; + bool _colored; + ColorTypes _colors[MaxLogLevels]; +}; + +#endif diff --git a/src/common/Logging/AppenderFile.cpp b/src/common/Logging/AppenderFile.cpp new file mode 100644 index 00000000000..bb15aed5e0b --- /dev/null +++ b/src/common/Logging/AppenderFile.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "AppenderFile.h" +#include "Common.h" +#include "StringFormat.h" +#include "Log.h" + +#if PLATFORM == PLATFORM_WINDOWS +# include <Windows.h> +#endif + +AppenderFile::AppenderFile(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs) : + Appender(id, name, level, flags), + logfile(NULL), + _logDir(sLog->GetLogsDir()), + _maxFileSize(0), + _fileSize(0) +{ + if (extraArgs.empty()) + throw InvalidAppenderArgsException(Trinity::StringFormat("Log::CreateAppenderFromConfig: Missing file name for appender %s\n", name.c_str())); + + _fileName = extraArgs[0]; + + char const* mode = "a"; + if (extraArgs.size() > 1) + mode = extraArgs[1]; + + if (flags & APPENDER_FLAGS_USE_TIMESTAMP) + { + size_t dot_pos = _fileName.find_last_of("."); + if (dot_pos != std::string::npos) + _fileName.insert(dot_pos, sLog->GetLogsTimestamp()); + else + _fileName += sLog->GetLogsTimestamp(); + } + + if (extraArgs.size() > 2) + _maxFileSize = atoi(extraArgs[2]); + + _dynamicName = std::string::npos != _fileName.find("%s"); + _backup = (flags & APPENDER_FLAGS_MAKE_FILE_BACKUP) != 0; + + if (!_dynamicName) + logfile = OpenFile(_fileName, mode, !strcmp(mode, "w") && _backup); +} + +AppenderFile::~AppenderFile() +{ + CloseFile(); +} + +void AppenderFile::_write(LogMessage const* message) +{ + bool exceedMaxSize = _maxFileSize > 0 && (_fileSize.load() + message->Size()) > _maxFileSize; + + if (_dynamicName) + { + char namebuf[TRINITY_PATH_MAX]; + snprintf(namebuf, TRINITY_PATH_MAX, _fileName.c_str(), message->param1.c_str()); + // always use "a" with dynamic name otherwise it could delete the log we wrote in last _write() call + FILE* file = OpenFile(namebuf, "a", _backup || exceedMaxSize); + if (!file) + return; + fprintf(file, "%s%s\n", message->prefix.c_str(), message->text.c_str()); + fflush(file); + _fileSize += uint64(message->Size()); + fclose(file); + return; + } + else if (exceedMaxSize) + logfile = OpenFile(_fileName, "w", true); + + if (!logfile) + return; + + fprintf(logfile, "%s%s\n", message->prefix.c_str(), message->text.c_str()); + fflush(logfile); + _fileSize += uint64(message->Size()); +} + +FILE* AppenderFile::OpenFile(std::string const& filename, std::string const& mode, bool backup) +{ + std::string fullName(_logDir + filename); + if (backup) + { + CloseFile(); + std::string newName(fullName); + newName.push_back('.'); + newName.append(LogMessage::getTimeStr(time(NULL))); + std::replace(newName.begin(), newName.end(), ':', '-'); + rename(fullName.c_str(), newName.c_str()); // no error handling... if we couldn't make a backup, just ignore + } + + if (FILE* ret = fopen(fullName.c_str(), mode.c_str())) + { + _fileSize = ftell(ret); + return ret; + } + + return NULL; +} + +void AppenderFile::CloseFile() +{ + if (logfile) + { + fclose(logfile); + logfile = NULL; + } +} diff --git a/src/common/Logging/AppenderFile.h b/src/common/Logging/AppenderFile.h new file mode 100644 index 00000000000..c2781eb1ee9 --- /dev/null +++ b/src/common/Logging/AppenderFile.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef APPENDERFILE_H +#define APPENDERFILE_H + +#include <atomic> +#include "Appender.h" + +class AppenderFile : public Appender +{ + public: + typedef std::integral_constant<AppenderType, APPENDER_FILE>::type TypeIndex; + + AppenderFile(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, ExtraAppenderArgs extraArgs); + ~AppenderFile(); + FILE* OpenFile(std::string const& name, std::string const& mode, bool backup); + AppenderType getType() const override { return TypeIndex::value; } + + private: + void CloseFile(); + void _write(LogMessage const* message) override; + FILE* logfile; + std::string _fileName; + std::string _logDir; + bool _dynamicName; + bool _backup; + uint64 _maxFileSize; + std::atomic<uint64> _fileSize; +}; + +#endif diff --git a/src/common/Logging/Log.cpp b/src/common/Logging/Log.cpp new file mode 100644 index 00000000000..c9ac4dfb9a2 --- /dev/null +++ b/src/common/Logging/Log.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2008 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Log.h" +#include "Common.h" +#include "Config.h" +#include "Util.h" +#include "AppenderConsole.h" +#include "AppenderFile.h" +#include "LogOperation.h" + +#include <cstdarg> +#include <cstdio> +#include <sstream> + +Log::Log() : _ioService(nullptr), _strand(nullptr) +{ + m_logsTimestamp = "_" + GetTimestampStr(); + RegisterAppender<AppenderConsole>(); + RegisterAppender<AppenderFile>(); +} + +Log::~Log() +{ + delete _strand; + Close(); +} + +uint8 Log::NextAppenderId() +{ + return AppenderId++; +} + +int32 GetConfigIntDefault(std::string base, const char* name, int32 value) +{ + base.append(name); + return sConfigMgr->GetIntDefault(base.c_str(), value); +} + +std::string GetConfigStringDefault(std::string base, const char* name, const char* value) +{ + base.append(name); + return sConfigMgr->GetStringDefault(base.c_str(), value); +} + +Appender* Log::GetAppenderByName(std::string const& name) +{ + AppenderMap::iterator it = appenders.begin(); + while (it != appenders.end() && it->second && it->second->getName() != name) + ++it; + + return it == appenders.end() ? NULL : it->second; +} + +void Log::CreateAppenderFromConfig(std::string const& appenderName) +{ + if (appenderName.empty()) + return; + + // Format=type, level, flags, optional1, optional2 + // if type = File. optional1 = file and option2 = mode + // if type = Console. optional1 = Color + std::string options = sConfigMgr->GetStringDefault(appenderName.c_str(), ""); + + Tokenizer tokens(options, ','); + Tokenizer::const_iterator iter = tokens.begin(); + + size_t size = tokens.size(); + std::string name = appenderName.substr(9); + + if (size < 2) + { + fprintf(stderr, "Log::CreateAppenderFromConfig: Wrong configuration for appender %s. Config line: %s\n", name.c_str(), options.c_str()); + return; + } + + AppenderFlags flags = APPENDER_FLAGS_NONE; + AppenderType type = AppenderType(atoi(*iter++)); + LogLevel level = LogLevel(atoi(*iter++)); + + if (level > LOG_LEVEL_FATAL) + { + fprintf(stderr, "Log::CreateAppenderFromConfig: Wrong Log Level %d for appender %s\n", level, name.c_str()); + return; + } + + if (size > 2) + flags = AppenderFlags(atoi(*iter++)); + + auto factoryFunction = appenderFactory.find(type); + if (factoryFunction == appenderFactory.end()) + { + fprintf(stderr, "Log::CreateAppenderFromConfig: Unknown type %d for appender %s\n", type, name.c_str()); + return; + } + + try + { + Appender* appender = factoryFunction->second(NextAppenderId(), name, level, flags, ExtraAppenderArgs(iter, tokens.end())); + appenders[appender->getId()] = appender; + } + catch (InvalidAppenderArgsException const& iaae) + { + fprintf(stderr, "%s", iaae.what()); + } +} + +void Log::CreateLoggerFromConfig(std::string const& appenderName) +{ + if (appenderName.empty()) + return; + + LogLevel level = LOG_LEVEL_DISABLED; + uint8 type = uint8(-1); + + std::string options = sConfigMgr->GetStringDefault(appenderName.c_str(), ""); + std::string name = appenderName.substr(7); + + if (options.empty()) + { + fprintf(stderr, "Log::CreateLoggerFromConfig: Missing config option Logger.%s\n", name.c_str()); + return; + } + + Tokenizer tokens(options, ','); + Tokenizer::const_iterator iter = tokens.begin(); + + if (tokens.size() != 2) + { + fprintf(stderr, "Log::CreateLoggerFromConfig: Wrong config option Logger.%s=%s\n", name.c_str(), options.c_str()); + return; + } + + Logger& logger = loggers[name]; + if (!logger.getName().empty()) + { + fprintf(stderr, "Error while configuring Logger %s. Already defined\n", name.c_str()); + return; + } + + level = LogLevel(atoi(*iter++)); + if (level > LOG_LEVEL_FATAL) + { + fprintf(stderr, "Log::CreateLoggerFromConfig: Wrong Log Level %u for logger %s\n", type, name.c_str()); + return; + } + + if (level < lowestLogLevel) + lowestLogLevel = level; + + logger.Create(name, level); + //fprintf(stdout, "Log::CreateLoggerFromConfig: Created Logger %s, Level %u\n", name.c_str(), level); + + std::istringstream ss(*iter); + std::string str; + + ss >> str; + while (ss) + { + if (Appender* appender = GetAppenderByName(str)) + { + logger.addAppender(appender->getId(), appender); + //fprintf(stdout, "Log::CreateLoggerFromConfig: Added Appender %s to Logger %s\n", appender->getName().c_str(), name.c_str()); + } + else + fprintf(stderr, "Error while configuring Appender %s in Logger %s. Appender does not exist", str.c_str(), name.c_str()); + ss >> str; + } +} + +void Log::ReadAppendersFromConfig() +{ + std::list<std::string> keys = sConfigMgr->GetKeysByString("Appender."); + + while (!keys.empty()) + { + CreateAppenderFromConfig(keys.front()); + keys.pop_front(); + } +} + +void Log::ReadLoggersFromConfig() +{ + std::list<std::string> keys = sConfigMgr->GetKeysByString("Logger."); + + while (!keys.empty()) + { + CreateLoggerFromConfig(keys.front()); + keys.pop_front(); + } + + // Bad config configuration, creating default config + if (loggers.find(LOGGER_ROOT) == loggers.end()) + { + fprintf(stderr, "Wrong Loggers configuration. Review your Logger config section.\n" + "Creating default loggers [root (Error), server (Info)] to console\n"); + + Close(); // Clean any Logger or Appender created + + AppenderConsole* appender = new AppenderConsole(NextAppenderId(), "Console", LOG_LEVEL_DEBUG, APPENDER_FLAGS_NONE, ExtraAppenderArgs()); + appenders[appender->getId()] = appender; + + Logger& logger = loggers[LOGGER_ROOT]; + logger.Create(LOGGER_ROOT, LOG_LEVEL_ERROR); + logger.addAppender(appender->getId(), appender); + + logger = loggers["server"]; + logger.Create("server", LOG_LEVEL_ERROR); + logger.addAppender(appender->getId(), appender); + } +} + +void Log::write(std::unique_ptr<LogMessage>&& msg) const +{ + Logger const* logger = GetLoggerByType(msg->type); + + if (_ioService) + { + auto logOperation = std::shared_ptr<LogOperation>(new LogOperation(logger, std::move(msg))); + + _ioService->post(_strand->wrap([logOperation](){ logOperation->call(); })); + } + else + logger->write(msg.get()); +} + +std::string Log::GetTimestampStr() +{ + time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + std::tm aTm; + localtime_r(&tt, &aTm); + + // YYYY year + // MM month (2 digits 01-12) + // DD day (2 digits 01-31) + // HH hour (2 digits 00-23) + // MM minutes (2 digits 00-59) + // SS seconds (2 digits 00-59) + return Trinity::StringFormat("%04d-%02d-%02d_%02d-%02d-%02d", + aTm.tm_year + 1900, aTm.tm_mon + 1, aTm.tm_mday, aTm.tm_hour, aTm.tm_min, aTm.tm_sec); +} + +bool Log::SetLogLevel(std::string const& name, const char* newLevelc, bool isLogger /* = true */) +{ + LogLevel newLevel = LogLevel(atoi(newLevelc)); + if (newLevel < 0) + return false; + + if (isLogger) + { + LoggerMap::iterator it = loggers.begin(); + while (it != loggers.end() && it->second.getName() != name) + ++it; + + if (it == loggers.end()) + return false; + + it->second.setLogLevel(newLevel); + + if (newLevel != LOG_LEVEL_DISABLED && newLevel < lowestLogLevel) + lowestLogLevel = newLevel; + } + else + { + Appender* appender = GetAppenderByName(name); + if (!appender) + return false; + + appender->setLogLevel(newLevel); + } + + return true; +} + +void Log::outCharDump(char const* str, uint32 accountId, uint64 guid, char const* name) +{ + if (!str || !ShouldLog("entities.player.dump", LOG_LEVEL_INFO)) + return; + + std::ostringstream ss; + ss << "== START DUMP == (account: " << accountId << " guid: " << guid << " name: " << name + << ")\n" << str << "\n== END DUMP ==\n"; + + std::unique_ptr<LogMessage> msg(new LogMessage(LOG_LEVEL_INFO, "entities.player.dump", ss.str())); + std::ostringstream param; + param << guid << '_' << name; + + msg->param1 = param.str(); + + write(std::move(msg)); +} + +void Log::SetRealmId(uint32 id) +{ + for (AppenderMap::iterator it = appenders.begin(); it != appenders.end(); ++it) + it->second->setRealmId(id); +} + +void Log::Close() +{ + loggers.clear(); + for (AppenderMap::iterator it = appenders.begin(); it != appenders.end(); ++it) + delete it->second; + + appenders.clear(); +} + +void Log::Initialize(boost::asio::io_service* ioService) +{ + if (ioService) + { + _ioService = ioService; + _strand = new boost::asio::strand(*ioService); + } + + LoadFromConfig(); +} + +void Log::LoadFromConfig() +{ + Close(); + + lowestLogLevel = LOG_LEVEL_FATAL; + AppenderId = 0; + m_logsDir = sConfigMgr->GetStringDefault("LogsDir", ""); + if (!m_logsDir.empty()) + if ((m_logsDir.at(m_logsDir.length() - 1) != '/') && (m_logsDir.at(m_logsDir.length() - 1) != '\\')) + m_logsDir.push_back('/'); + + ReadAppendersFromConfig(); + ReadLoggersFromConfig(); +} diff --git a/src/common/Logging/Log.h b/src/common/Logging/Log.h new file mode 100644 index 00000000000..a15bb4ad485 --- /dev/null +++ b/src/common/Logging/Log.h @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_LOG_H +#define TRINITYCORE_LOG_H + +#include "Define.h" +#include "Appender.h" +#include "Logger.h" +#include "StringFormat.h" +#include "Common.h" +#include <boost/asio/io_service.hpp> +#include <boost/asio/strand.hpp> + +#include <stdarg.h> +#include <unordered_map> +#include <string> +#include <memory> + +#define LOGGER_ROOT "root" + +class Log +{ + typedef std::unordered_map<std::string, Logger> LoggerMap; + + private: + Log(); + ~Log(); + + public: + + static Log* instance() + { + static Log instance; + return &instance; + } + + void Initialize(boost::asio::io_service* ioService); + void LoadFromConfig(); + void Close(); + bool ShouldLog(std::string const& type, LogLevel level) const; + bool SetLogLevel(std::string const& name, char const* level, bool isLogger = true); + + template<typename Format, typename... Args> + inline void outMessage(std::string const& filter, LogLevel const level, Format&& fmt, Args&&... args) + { + write(Trinity::make_unique<LogMessage>(level, filter, + Trinity::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...))); + } + + template<typename Format, typename... Args> + void outCommand(uint32 account, Format&& fmt, Args&&... args) + { + if (!ShouldLog("commands.gm", LOG_LEVEL_INFO)) + return; + + std::unique_ptr<LogMessage> msg = + Trinity::make_unique<LogMessage>(LOG_LEVEL_INFO, "commands.gm", + Trinity::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...)); + + msg->param1 = std::to_string(account); + + write(std::move(msg)); + } + + void outCharDump(char const* str, uint32 account_id, uint64 guid, char const* name); + + void SetRealmId(uint32 id); + + template<class AppenderImpl> + void RegisterAppender() + { + using Index = typename AppenderImpl::TypeIndex; + auto itr = appenderFactory.find(Index::value); + ASSERT(itr == appenderFactory.end()); + appenderFactory[Index::value] = &CreateAppender<AppenderImpl>; + } + + std::string const& GetLogsDir() const { return m_logsDir; } + std::string const& GetLogsTimestamp() const { return m_logsTimestamp; } + + private: + static std::string GetTimestampStr(); + void write(std::unique_ptr<LogMessage>&& msg) const; + + Logger const* GetLoggerByType(std::string const& type) const; + Appender* GetAppenderByName(std::string const& name); + uint8 NextAppenderId(); + void CreateAppenderFromConfig(std::string const& name); + void CreateLoggerFromConfig(std::string const& name); + void ReadAppendersFromConfig(); + void ReadLoggersFromConfig(); + + AppenderCreatorMap appenderFactory; + AppenderMap appenders; + LoggerMap loggers; + uint8 AppenderId; + LogLevel lowestLogLevel; + + std::string m_logsDir; + std::string m_logsTimestamp; + + boost::asio::io_service* _ioService; + boost::asio::strand* _strand; +}; + +inline Logger const* Log::GetLoggerByType(std::string const& type) const +{ + LoggerMap::const_iterator it = loggers.find(type); + if (it != loggers.end()) + return &(it->second); + + if (type == LOGGER_ROOT) + return NULL; + + std::string parentLogger = LOGGER_ROOT; + size_t found = type.find_last_of("."); + if (found != std::string::npos) + parentLogger = type.substr(0,found); + + return GetLoggerByType(parentLogger); +} + +inline bool Log::ShouldLog(std::string const& type, LogLevel level) const +{ + // TODO: Use cache to store "Type.sub1.sub2": "Type" equivalence, should + // Speed up in cases where requesting "Type.sub1.sub2" but only configured + // Logger "Type" + + // Don't even look for a logger if the LogLevel is lower than lowest log levels across all loggers + if (level < lowestLogLevel) + return false; + + Logger const* logger = GetLoggerByType(type); + if (!logger) + return false; + + LogLevel logLevel = logger->getLogLevel(); + return logLevel != LOG_LEVEL_DISABLED && logLevel <= level; +} + +#define sLog Log::instance() + +#define LOG_EXCEPTION_FREE(filterType__, level__, ...) \ + { \ + try \ + { \ + sLog->outMessage(filterType__, level__, __VA_ARGS__); \ + } \ + catch (std::exception& e) \ + { \ + sLog->outMessage("server", LOG_LEVEL_ERROR, "Wrong format occurred (%s) at %s:%u.", \ + e.what(), __FILE__, __LINE__); \ + } \ + } + +#if PLATFORM != PLATFORM_WINDOWS +void check_args(const char*, ...) ATTR_PRINTF(1, 2); +void check_args(std::string const&, ...); + +// This will catch format errors on build time +#define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \ + do { \ + if (sLog->ShouldLog(filterType__, level__)) \ + { \ + if (false) \ + check_args(__VA_ARGS__); \ + \ + LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \ + } \ + } while (0) +#else +#define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + do { \ + if (sLog->ShouldLog(filterType__, level__)) \ + LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \ + } while (0) \ + __pragma(warning(pop)) +#endif + +#define TC_LOG_TRACE(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_TRACE, __VA_ARGS__) + +#define TC_LOG_DEBUG(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_DEBUG, __VA_ARGS__) + +#define TC_LOG_INFO(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_INFO, __VA_ARGS__) + +#define TC_LOG_WARN(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_WARN, __VA_ARGS__) + +#define TC_LOG_ERROR(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_ERROR, __VA_ARGS__) + +#define TC_LOG_FATAL(filterType__, ...) \ + TC_LOG_MESSAGE_BODY(filterType__, LOG_LEVEL_FATAL, __VA_ARGS__) + +#endif diff --git a/src/common/Logging/LogOperation.cpp b/src/common/Logging/LogOperation.cpp new file mode 100644 index 00000000000..bcd923c705e --- /dev/null +++ b/src/common/Logging/LogOperation.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "LogOperation.h" +#include "Logger.h" + +int LogOperation::call() +{ + logger->write(msg.get()); + return 0; +} diff --git a/src/common/Logging/LogOperation.h b/src/common/Logging/LogOperation.h new file mode 100644 index 00000000000..ffdd35c3c09 --- /dev/null +++ b/src/common/Logging/LogOperation.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LOGOPERATION_H +#define LOGOPERATION_H + +#include <memory> + +class Logger; +struct LogMessage; + +class LogOperation +{ + public: + LogOperation(Logger const* _logger, std::unique_ptr<LogMessage>&& _msg) + : logger(_logger), msg(std::forward<std::unique_ptr<LogMessage>>(_msg)) + { } + + ~LogOperation() { } + + int call(); + + protected: + Logger const* logger; + std::unique_ptr<LogMessage> msg; +}; + +#endif diff --git a/src/common/Logging/Logger.cpp b/src/common/Logging/Logger.cpp new file mode 100644 index 00000000000..3b02eb47575 --- /dev/null +++ b/src/common/Logging/Logger.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Logger.h" + +Logger::Logger(): name(""), level(LOG_LEVEL_DISABLED) { } + +void Logger::Create(std::string const& _name, LogLevel _level) +{ + name = _name; + level = _level; +} + +std::string const& Logger::getName() const +{ + return name; +} + +LogLevel Logger::getLogLevel() const +{ + return level; +} + +void Logger::addAppender(uint8 id, Appender* appender) +{ + appenders[id] = appender; +} + +void Logger::delAppender(uint8 id) +{ + appenders.erase(id); +} + +void Logger::setLogLevel(LogLevel _level) +{ + level = _level; +} + +void Logger::write(LogMessage* message) const +{ + if (!level || level > message->level || message->text.empty()) + { + //fprintf(stderr, "Logger::write: Logger %s, Level %u. Msg %s Level %u WRONG LEVEL MASK OR EMPTY MSG\n", getName().c_str(), getLogLevel(), message.text.c_str(), message.level); + return; + } + + for (AppenderMap::const_iterator it = appenders.begin(); it != appenders.end(); ++it) + if (it->second) + it->second->write(message); +} diff --git a/src/common/Logging/Logger.h b/src/common/Logging/Logger.h new file mode 100644 index 00000000000..1aee75c5d72 --- /dev/null +++ b/src/common/Logging/Logger.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include "Appender.h" + +class Logger +{ + public: + Logger(); + + void Create(std::string const& name, LogLevel level); + void addAppender(uint8 type, Appender *); + void delAppender(uint8 type); + + std::string const& getName() const; + LogLevel getLogLevel() const; + void setLogLevel(LogLevel level); + void write(LogMessage* message) const; + + private: + std::string name; + LogLevel level; + AppenderMap appenders; +}; + +#endif |