Merge pull request #14317 from Naios/typesafelog

Type safe logging
This commit is contained in:
Carbenium
2015-03-18 01:25:38 +01:00
34 changed files with 5003 additions and 147 deletions

View File

@@ -61,6 +61,7 @@ include_directories(
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour
${CMAKE_SOURCE_DIR}/dep/SFMT
${CMAKE_SOURCE_DIR}/dep/cppformat
${CMAKE_SOURCE_DIR}/dep/utf8cpp
${CMAKE_SOURCE_DIR}/src/server
${CMAKE_CURRENT_SOURCE_DIR}

View File

@@ -18,6 +18,10 @@
#include "Appender.h"
#include "Common.h"
#include "Util.h"
#include "StringFormat.h"
#include <utility>
#include <sstream>
std::string LogMessage::getTimeStr(time_t time)
{
@@ -68,38 +72,36 @@ void Appender::setLogLevel(LogLevel _level)
level = _level;
}
void Appender::write(LogMessage& message)
void Appender::write(LogMessage* message)
{
if (!level || level > message.level)
if (!level || level > message->level)
return;
message.prefix.clear();
std::ostringstream ss;
if (flags & APPENDER_FLAGS_PREFIX_TIMESTAMP)
message.prefix.append(message.getTimeStr());
ss << message->getTimeStr();
if (flags & APPENDER_FLAGS_PREFIX_LOGLEVEL)
{
if (!message.prefix.empty())
message.prefix.push_back(' ');
if (ss.rdbuf()->in_avail() == 0)
ss << ' ';
char text[MAX_QUERY_LEN];
snprintf(text, MAX_QUERY_LEN, "%-5s", Appender::getLogLevelString(message.level));
message.prefix.append(text);
ss << Trinity::StringFormat("%-5s", Appender::getLogLevelString(message->level));
}
if (flags & APPENDER_FLAGS_PREFIX_LOGFILTERTYPE)
{
if (!message.prefix.empty())
message.prefix.push_back(' ');
if (ss.rdbuf()->in_avail() == 0)
ss << ' ';
message.prefix.push_back('[');
message.prefix.append(message.type);
message.prefix.push_back(']');
ss << '[' << message->type << ']';
}
if (!message.prefix.empty())
message.prefix.push_back(' ');
if (ss.rdbuf()->in_avail() == 0)
ss << ' ';
message->prefix = std::move(ss.str());
_write(message);
}

View File

@@ -21,6 +21,7 @@
#include <unordered_map>
#include <string>
#include <time.h>
#include <type_traits>
#include "Define.h"
// Values assigned have their equivalent in enum ACE_Log_Priority
@@ -57,16 +58,16 @@ enum AppenderFlags
struct LogMessage
{
LogMessage(LogLevel _level, std::string const& _type, std::string const& _text)
: level(_level), type(_type), text(_text), mtime(time(NULL))
LogMessage(LogLevel _level, std::string const& _type, std::string&& _text)
: level(_level), type(_type), text(std::forward<std::string>(_text)), mtime(time(NULL))
{ }
static std::string getTimeStr(time_t time);
std::string getTimeStr();
LogLevel level;
std::string type;
std::string text;
LogLevel const level;
std::string const type;
std::string const text;
std::string prefix;
std::string param1;
time_t mtime;
@@ -91,11 +92,11 @@ class Appender
AppenderFlags getFlags() const;
void setLogLevel(LogLevel);
void write(LogMessage& message);
void write(LogMessage* message);
static const char* getLogLevelString(LogLevel level);
private:
virtual void _write(LogMessage const& /*message*/) = 0;
virtual void _write(LogMessage const* /*message*/) = 0;
uint8 id;
std::string name;

View File

@@ -158,14 +158,14 @@ void AppenderConsole::ResetColor(bool stdout_stream)
#endif
}
void AppenderConsole::_write(LogMessage const& message)
void AppenderConsole::_write(LogMessage const* message)
{
bool stdout_stream = !(message.level == LOG_LEVEL_ERROR || message.level == LOG_LEVEL_FATAL);
bool stdout_stream = !(message->level == LOG_LEVEL_ERROR || message->level == LOG_LEVEL_FATAL);
if (_colored)
{
uint8 index;
switch (message.level)
switch (message->level)
{
case LOG_LEVEL_TRACE:
index = 5;
@@ -189,9 +189,9 @@ void AppenderConsole::_write(LogMessage const& message)
}
SetColor(stdout_stream, _colors[index]);
utf8printf(stdout_stream ? stdout : stderr, "%s%s", message.prefix.c_str(), message.text.c_str());
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", message.prefix.c_str(), message.text.c_str());
utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str());
}

View File

@@ -51,7 +51,7 @@ class AppenderConsole: public Appender
private:
void SetColor(bool stdout_stream, ColorTypes color);
void ResetColor(bool stdout_stream);
void _write(LogMessage const& message) override;
void _write(LogMessage const* message) override;
bool _colored;
ColorTypes _colors[MaxLogLevels];
};

View File

@@ -23,18 +23,18 @@ AppenderDB::AppenderDB(uint8 id, std::string const& name, LogLevel level)
AppenderDB::~AppenderDB() { }
void AppenderDB::_write(LogMessage const& message)
void AppenderDB::_write(LogMessage const* message)
{
// Avoid infinite loop, PExecute triggers Logging with "sql.sql" type
if (!enabled || (message.type.find("sql") != std::string::npos))
if (!enabled || (message->type.find("sql") != std::string::npos))
return;
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_LOG);
stmt->setUInt64(0, message.mtime);
stmt->setUInt64(0, message->mtime);
stmt->setUInt32(1, realmId);
stmt->setString(2, message.type);
stmt->setUInt8(3, uint8(message.level));
stmt->setString(4, message.text);
stmt->setString(2, message->type);
stmt->setUInt8(3, uint8(message->level));
stmt->setString(4, message->text);
LoginDatabase.Execute(stmt);
}

View File

@@ -31,7 +31,7 @@ class AppenderDB: public Appender
private:
uint32 realmId;
bool enabled;
void _write(LogMessage const& message) override;
void _write(LogMessage const* message) override;
};
#endif

View File

@@ -43,21 +43,21 @@ AppenderFile::~AppenderFile()
CloseFile();
}
void AppenderFile::_write(LogMessage const& message)
void AppenderFile::_write(LogMessage const* message)
{
bool exceedMaxSize = maxFileSize > 0 && (fileSize.load() + message.Size()) > maxFileSize;
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());
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", message.prefix.c_str(), message.text.c_str());
fprintf(file, "%s%s", message->prefix.c_str(), message->text.c_str());
fflush(file);
fileSize += uint64(message.Size());
fileSize += uint64(message->Size());
fclose(file);
return;
}
@@ -67,9 +67,9 @@ void AppenderFile::_write(LogMessage const& message)
if (!logfile)
return;
fprintf(logfile, "%s%s", message.prefix.c_str(), message.text.c_str());
fprintf(logfile, "%s%s\n", message->prefix.c_str(), message->text.c_str());
fflush(logfile);
fileSize += uint64(message.Size());
fileSize += uint64(message->Size());
}
FILE* AppenderFile::OpenFile(std::string const &filename, std::string const &mode, bool backup)

View File

@@ -30,7 +30,7 @@ class AppenderFile: public Appender
private:
void CloseFile();
void _write(LogMessage const& message) override;
void _write(LogMessage const* message) override;
FILE* logfile;
std::string filename;
std::string logDir;

View File

@@ -264,30 +264,18 @@ void Log::ReadLoggersFromConfig()
}
}
void Log::vlog(std::string const& filter, LogLevel level, char const* str, va_list argptr)
{
char text[MAX_QUERY_LEN];
vsnprintf(text, MAX_QUERY_LEN, str, argptr);
write(new LogMessage(level, filter, text));
}
void Log::write(LogMessage* msg) const
void Log::write(std::unique_ptr<LogMessage>&& msg) const
{
Logger const* logger = GetLoggerByType(msg->type);
msg->text.append("\n");
if (_ioService)
{
auto logOperation = std::shared_ptr<LogOperation>(new LogOperation(logger, msg));
auto logOperation = std::shared_ptr<LogOperation>(new LogOperation(logger, std::forward<std::unique_ptr<LogMessage>>(msg)));
_ioService->post(_strand->wrap([logOperation](){ logOperation->call(); }));
}
else
{
logger->write(*msg);
delete msg;
}
logger->write(msg.get());
}
std::string Log::GetTimestampStr()
@@ -349,33 +337,13 @@ void Log::outCharDump(char const* str, uint32 accountId, uint64 guid, char const
ss << "== START DUMP == (account: " << accountId << " guid: " << guid << " name: " << name
<< ")\n" << str << "\n== END DUMP ==\n";
LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, "entities.player.dump", ss.str());
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(msg);
}
void Log::outCommand(uint32 account, const char * str, ...)
{
if (!str || !ShouldLog("commands.gm", LOG_LEVEL_INFO))
return;
va_list ap;
va_start(ap, str);
char text[MAX_QUERY_LEN];
vsnprintf(text, MAX_QUERY_LEN, str, ap);
va_end(ap);
LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, "commands.gm", text);
std::ostringstream ss;
ss << account;
msg->param1 = ss.str();
write(msg);
write(std::move(msg));
}
void Log::SetRealmId(uint32 id)

View File

@@ -22,12 +22,13 @@
#include "Define.h"
#include "Appender.h"
#include "Logger.h"
#include <stdarg.h>
#include "StringFormat.h"
#include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <unordered_map>
#include <string>
#include <memory>
#define LOGGER_ROOT "root"
@@ -59,17 +60,34 @@ class Log
bool ShouldLog(std::string const& type, LogLevel level) const;
bool SetLogLevel(std::string const& name, char const* level, bool isLogger = true);
void outMessage(std::string const& f, LogLevel level, char const* str, ...) ATTR_PRINTF(4, 5);
template<typename... Args>
inline void outMessage(std::string const& filter, LogLevel const level, const char* fmt, Args const&... args)
{
write(std::move(std::unique_ptr<LogMessage>(new LogMessage(level, filter, std::move(Trinity::StringFormat(fmt, args...))))));
}
template<typename... Args>
void outCommand(uint32 account, const char* fmt, Args const&... args)
{
if (!ShouldLog("commands.gm", LOG_LEVEL_INFO))
return;
std::unique_ptr<LogMessage> msg(new LogMessage(LOG_LEVEL_INFO, "commands.gm", std::move(Trinity::StringFormat(fmt, args...))));
std::ostringstream ss;
ss << account;
msg->param1 = ss.str();
write(std::move(msg));
}
void outCommand(uint32 account, const char * str, ...) ATTR_PRINTF(3, 4);
void outCharDump(char const* str, uint32 account_id, uint64 guid, char const* name);
void SetRealmId(uint32 id);
private:
static std::string GetTimestampStr();
void vlog(std::string const& f, LogLevel level, char const* str, va_list argptr);
void write(LogMessage* msg) const;
void write(std::unique_ptr<LogMessage>&& msg) const;
Logger const* GetLoggerByType(std::string const& type) const;
Appender* GetAppenderByName(std::string const& name);
@@ -126,23 +144,34 @@ inline bool Log::ShouldLog(std::string const& type, LogLevel level) const
return logLevel != LOG_LEVEL_DISABLED && logLevel <= level;
}
inline void Log::outMessage(std::string const& filter, LogLevel level, const char * str, ...)
{
va_list ap;
va_start(ap, str);
vlog(filter, level, str, ap);
va_end(ap);
}
#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* format, ...) ATTR_PRINTF(1, 2);
// This will catch format errors on build time
#define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \
do { \
if (sLog->ShouldLog(filterType__, level__)) \
sLog->outMessage(filterType__, level__, __VA_ARGS__); \
{ \
if (false) \
check_args(__VA_ARGS__); \
\
LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \
} \
} while (0)
#else
#define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \
@@ -150,7 +179,7 @@ inline void Log::outMessage(std::string const& filter, LogLevel level, const cha
__pragma(warning(disable:4127)) \
do { \
if (sLog->ShouldLog(filterType__, level__)) \
sLog->outMessage(filterType__, level__, __VA_ARGS__); \
LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \
} while (0) \
__pragma(warning(pop))
#endif

View File

@@ -18,14 +18,8 @@
#include "LogOperation.h"
#include "Logger.h"
LogOperation::~LogOperation()
{
delete msg;
}
int LogOperation::call()
{
if (logger && msg)
logger->write(*msg);
logger->write(msg.get());
return 0;
}

View File

@@ -18,23 +18,25 @@
#ifndef LOGOPERATION_H
#define LOGOPERATION_H
#include <memory>
class Logger;
struct LogMessage;
class LogOperation
{
public:
LogOperation(Logger const* _logger, LogMessage* _msg)
: logger(_logger), msg(_msg)
LogOperation(Logger const* _logger, std::unique_ptr<LogMessage>&& _msg)
: logger(_logger), msg(std::forward<std::unique_ptr<LogMessage>>(_msg))
{ }
~LogOperation();
~LogOperation() { }
int call();
protected:
Logger const* logger;
LogMessage* msg;
std::unique_ptr<LogMessage> msg;
};
#endif

View File

@@ -50,9 +50,9 @@ void Logger::setLogLevel(LogLevel _level)
level = _level;
}
void Logger::write(LogMessage& message) const
void Logger::write(LogMessage* message) const
{
if (!level || level > message.level || message.text.empty())
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;

View File

@@ -32,7 +32,7 @@ class Logger
std::string const& getName() const;
LogLevel getLogLevel() const;
void setLogLevel(LogLevel level);
void write(LogMessage& message) const;
void write(LogMessage* message) const;
private:
std::string name;

View File

@@ -0,0 +1,34 @@
/*
* 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_STRING_FORMAT_H
#define TRINITYCORE_STRING_FORMAT_H
#include <format.h>
namespace Trinity
{
//! Default TC string format function
template<typename... Args>
inline std::string StringFormat(const char* fmt, Args const&... args)
{
return fmt::sprintf(fmt, args...);
}
}
#endif