2019-06-26 16:11:22 -04:00
|
|
|
#include "LogUtils.h"
|
|
|
|
#include "LogSinks.h"
|
|
|
|
#include <iomanip>
|
|
|
|
#include <fstream>
|
|
|
|
#include <map>
|
|
|
|
#include <experimental/filesystem>
|
|
|
|
#include <StringVariable.h>
|
|
|
|
#include <mutex>
|
|
|
|
|
2019-11-23 15:16:55 -05:00
|
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <spdlog/async.h>
|
|
|
|
#include <spdlog/sinks/rotating_file_sink.h>
|
|
|
|
|
2019-11-24 17:23:07 -05:00
|
|
|
#ifdef HAVE_CXX_TERMINAL
|
|
|
|
#include <CXXTerminal/Terminal.h>
|
|
|
|
#endif
|
|
|
|
|
2019-06-26 16:11:22 -04:00
|
|
|
using namespace std;
|
|
|
|
using namespace std::chrono;
|
|
|
|
using namespace spdlog;
|
|
|
|
namespace fs = std::experimental::filesystem;
|
|
|
|
|
|
|
|
#define ASYNC_LOG
|
|
|
|
namespace logger {
|
|
|
|
recursive_mutex loggerLock;
|
2019-09-22 10:57:01 -04:00
|
|
|
map<size_t, std::shared_ptr<spdlog::logger>> loggers;
|
2019-06-26 16:11:22 -04:00
|
|
|
shared_ptr<LoggerConfig> logConfig;
|
|
|
|
shared_ptr<::logger::TerminalSink> terminalSink;
|
2019-11-23 15:16:55 -05:00
|
|
|
|
|
|
|
std::shared_ptr<spdlog::details::thread_pool> logging_threads{nullptr};
|
|
|
|
|
|
|
|
spdlog::level::level_enum min_level{spdlog::level::trace};
|
|
|
|
|
|
|
|
void updater_logger_levels(const std::shared_ptr<spdlog::logger>& logger) {
|
|
|
|
for(const auto& sink : logger->sinks())
|
|
|
|
if(dynamic_pointer_cast<TerminalSink>(sink)) {
|
|
|
|
sink->set_level(::logger::currentConfig()->terminalLevel);
|
|
|
|
} else if(dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)) {
|
|
|
|
sink->set_level(::logger::currentConfig()->logfileLevel);
|
|
|
|
} else if(dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_st>(sink)) {
|
|
|
|
sink->set_level(::logger::currentConfig()->logfileLevel);
|
|
|
|
} else {
|
|
|
|
sink->set_level(min_level);
|
|
|
|
}
|
|
|
|
logger->set_level(min_level);
|
|
|
|
}
|
|
|
|
|
2019-11-24 17:23:07 -05:00
|
|
|
std::string generate_log_file(size_t group) {
|
2019-11-23 15:16:55 -05:00
|
|
|
return strvar::transform(logConfig->logPath,
|
|
|
|
strvar::StringValue{"group", group != -1 ? to_string(group) : "general"},
|
|
|
|
strvar::FunctionValue("time", (strvar::FunctionValue::FValueFNEasy) [](std::deque<std::string> value) -> std::string {
|
|
|
|
auto pattern = !value.empty() ? value[0] : "%Y-%m-%d_%H:%M:%S";
|
|
|
|
|
|
|
|
auto secs = duration_cast<seconds>(logConfig->timestamp.time_since_epoch()).count();
|
|
|
|
tm* tm_info;
|
|
|
|
#ifdef WIN32
|
|
|
|
tm _tm_info{};
|
|
|
|
localtime_s(&_tm_info, &secs);
|
|
|
|
tm_info = &_tm_info;
|
|
|
|
#else
|
|
|
|
tm_info = localtime((time_t*) &secs);
|
|
|
|
#endif
|
|
|
|
char timeBuffer[1024];
|
|
|
|
if(strftime(timeBuffer, 1024, pattern.c_str(), tm_info) == 0) {
|
|
|
|
return string("string is longer than the buffer");
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(timeBuffer);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::mutex default_lock{};
|
|
|
|
bool default_setup{false};
|
|
|
|
|
|
|
|
std::shared_ptr<spdlog::logger> default_logger() {
|
|
|
|
lock_guard lock{default_lock};
|
|
|
|
if(!default_setup) {
|
|
|
|
default_setup = true;
|
|
|
|
|
|
|
|
spdlog::default_logger()->sinks().clear();
|
|
|
|
auto terminal_sink = make_shared<TerminalSink>();
|
|
|
|
terminal_sink->set_level(spdlog::level::trace);
|
|
|
|
spdlog::default_logger()->sinks().push_back(terminal_sink);
|
|
|
|
|
|
|
|
spdlog::default_logger()->set_formatter(std::make_unique<LogFormatter>(true));
|
|
|
|
}
|
|
|
|
return spdlog::default_logger();
|
|
|
|
}
|
2019-06-26 16:11:22 -04:00
|
|
|
|
|
|
|
shared_ptr<spdlog::logger> logger(int serverId) {
|
|
|
|
if(!::logger::currentConfig())
|
2019-11-23 15:16:55 -05:00
|
|
|
return default_logger();
|
2019-06-26 16:11:22 -04:00
|
|
|
|
2019-09-22 10:57:01 -04:00
|
|
|
size_t group = 0;
|
2019-06-26 16:11:22 -04:00
|
|
|
if(::logger::currentConfig()->vs_group_size > 0 && serverId > 0)
|
|
|
|
group = serverId / ::logger::currentConfig()->vs_group_size;
|
|
|
|
else group = -1;
|
|
|
|
|
|
|
|
if(loggers.count(group) == 0) {
|
|
|
|
lock_guard lock(loggerLock);
|
|
|
|
if(loggers.count(group) > 0) return loggers[group];
|
|
|
|
//Create a new logger
|
2019-10-13 14:26:12 -04:00
|
|
|
if(group != 0 && group != -1)
|
2019-06-26 16:11:22 -04:00
|
|
|
logger(0)->debug("Creating new grouped logger for group {}", group);
|
|
|
|
|
|
|
|
vector<spdlog::sink_ptr> sinks;
|
|
|
|
string path;
|
|
|
|
if(logConfig->logfileLevel != spdlog::level::off) {
|
2019-11-23 15:16:55 -05:00
|
|
|
path = generate_log_file(group);
|
2019-06-26 16:11:22 -04:00
|
|
|
|
|
|
|
auto logFile = fs::u8path(path);
|
|
|
|
if(!logFile.parent_path().empty())
|
|
|
|
fs::create_directories(logFile.parent_path());
|
|
|
|
|
2019-11-22 14:50:58 -05:00
|
|
|
try {
|
2019-11-23 15:16:55 -05:00
|
|
|
auto sink = make_shared<spdlog::sinks::rotating_file_sink_mt>(logFile.string(), 1024 * 1024 * 50, 12);
|
|
|
|
sink->set_formatter(std::make_unique<LogFormatter>(::logger::currentConfig()->file_colored));
|
|
|
|
sinks.push_back(sink);
|
2019-11-22 14:50:58 -05:00
|
|
|
} catch(std::exception& ex) {
|
|
|
|
if(group != 0 && group != -1)
|
|
|
|
logger(0)->critical("Failed to create file for new log group: {}", ex.what());
|
|
|
|
else
|
2019-11-24 17:23:07 -05:00
|
|
|
#ifdef HAVE_CXX_TERMINAL
|
2019-11-22 14:50:58 -05:00
|
|
|
terminal::instance()->writeMessage("§4[CRITICAL] §eFailed to create main log file: " + string{ex.what()}, false);
|
2019-11-24 17:23:07 -05:00
|
|
|
#else
|
|
|
|
std::cout << "[CRITICAL] Failed to create main log file: " << ex.what() << "\n";
|
|
|
|
#endif
|
2019-11-22 14:50:58 -05:00
|
|
|
}
|
2019-06-26 16:11:22 -04:00
|
|
|
} else {
|
2019-11-23 15:16:55 -05:00
|
|
|
path = "/dev/null (" + to_string(group) + ")";
|
2019-06-26 16:11:22 -04:00
|
|
|
}
|
|
|
|
sinks.push_back(terminalSink);
|
|
|
|
|
2019-11-23 15:16:55 -05:00
|
|
|
if(!logging_threads)
|
|
|
|
logging_threads = std::make_shared<spdlog::details::thread_pool>(8192, 1); //Only one thread possible here, else elements get reordered
|
2019-06-26 16:11:22 -04:00
|
|
|
#ifdef ASYNC_LOG
|
2019-11-23 15:16:55 -05:00
|
|
|
auto logger = std::make_shared<spdlog::async_logger>("Logger (" + path + ")", sinks.begin(), sinks.end(), logging_threads, async_overflow_policy::block);
|
2019-06-26 16:11:22 -04:00
|
|
|
#else
|
2019-11-23 15:16:55 -05:00
|
|
|
//FIXME!
|
2019-06-26 16:11:22 -04:00
|
|
|
auto logger = create("Logger (" + path + ")", sinks.begin(), sinks.end());
|
|
|
|
#endif
|
2019-11-23 15:16:55 -05:00
|
|
|
updater_logger_levels(logger);
|
2019-06-26 16:11:22 -04:00
|
|
|
loggers[group] = logger;
|
|
|
|
}
|
|
|
|
|
|
|
|
return loggers[group];
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::shared_ptr<LoggerConfig>& currentConfig() {
|
|
|
|
return logConfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
extern void setup(const shared_ptr<LoggerConfig>& config) {
|
|
|
|
logConfig = config;
|
|
|
|
config->timestamp = system_clock::now();
|
|
|
|
|
|
|
|
terminalSink = make_shared<TerminalSink>();
|
|
|
|
terminalSink->set_level(::logger::currentConfig()->terminalLevel);
|
2019-11-23 15:16:55 -05:00
|
|
|
terminalSink->set_formatter(std::make_unique<LogFormatter>(true));
|
|
|
|
min_level = ::min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel);
|
|
|
|
|
2019-06-26 16:11:22 -04:00
|
|
|
logger(0)->debug("Log successfully started!");
|
|
|
|
}
|
|
|
|
|
2019-11-23 15:16:55 -05:00
|
|
|
|
|
|
|
bool should_log(spdlog::level::level_enum level) {
|
|
|
|
return level >= min_level;
|
|
|
|
}
|
|
|
|
|
|
|
|
void log(spdlog::level::forceable level, int server_id, const std::string_view& buffer) {
|
|
|
|
auto logger = ::logger::logger(server_id);
|
|
|
|
|
|
|
|
auto message_format = "§8{0:>5} | §r{1}";
|
|
|
|
if(server_id <= 0) {
|
|
|
|
switch (server_id) {
|
|
|
|
case LOG_INSTANCE:
|
|
|
|
message_format = "§8GLOBL | §r{1}";
|
|
|
|
break;
|
|
|
|
case LOG_QUERY:
|
|
|
|
message_format = "§8QUERY | §r{1}";
|
|
|
|
break;
|
|
|
|
case LOG_FT:
|
|
|
|
message_format = "§8 FILE | §r{1}";
|
|
|
|
break;
|
|
|
|
case LOG_GENERAL:
|
|
|
|
message_format = "§8 GEN | §r{1}";
|
|
|
|
break;
|
|
|
|
case LOG_LICENSE_CONTROLL:
|
|
|
|
message_format = "§8 CONTR | §r{1}";
|
|
|
|
break;
|
|
|
|
case LOG_LICENSE_WEB:
|
|
|
|
message_format = "§8 WEBST | §r{1}";
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
logger->log(level, message_format, server_id, buffer);
|
|
|
|
} catch (const std::exception &ex) {
|
|
|
|
//TODO better?
|
|
|
|
std::cerr << "An exception has raised while logging a message (" << ex.what() << "): " << buffer << "\n";
|
|
|
|
} catch(...) {
|
|
|
|
std::cerr << "An unknown exception has raised while logging a message: " << buffer << "\n";
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-26 16:11:22 -04:00
|
|
|
void updateLogLevels() {
|
|
|
|
lock_guard lock(loggerLock);
|
2019-11-23 15:16:55 -05:00
|
|
|
min_level = ::min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel);
|
|
|
|
for(const auto& logger : loggers) {
|
|
|
|
updater_logger_levels(logger.second);
|
2019-06-26 16:11:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void flush() {
|
|
|
|
unique_lock lock(loggerLock);
|
|
|
|
auto _loggers = loggers;
|
|
|
|
lock.unlock();
|
|
|
|
|
|
|
|
for(const auto& loggerEntry : _loggers) {
|
|
|
|
loggerEntry.second->flush();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extern void uninstall() {
|
|
|
|
lock_guard lock(loggerLock);
|
|
|
|
for(auto& loggerEntry : loggers) {
|
|
|
|
loggerEntry.second->flush();
|
|
|
|
loggerEntry.second.reset();
|
|
|
|
}
|
|
|
|
loggers.clear();
|
2019-11-22 14:50:58 -05:00
|
|
|
spdlog::drop_all();
|
2019-06-26 16:11:22 -04:00
|
|
|
|
|
|
|
logConfig = nullptr;
|
|
|
|
terminalSink = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void hexDump(void *addr, int len, int pad,int columnLength, void (*print)(string));
|
|
|
|
void hexDump(void *addr, int len, int pad,int columnLength) {
|
|
|
|
hexDump(addr, len, pad, columnLength, [](string str){ logMessage(0, "\n{}", str); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void hexDump(void *addr, int len, int pad,int columnLength, void (*print)(string)) {
|
|
|
|
int i;
|
|
|
|
uint8_t* buff = new uint8_t[pad+1];
|
|
|
|
unsigned char* pc = (unsigned char*)addr;
|
|
|
|
|
|
|
|
if (len <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stringstream line;
|
|
|
|
line << uppercase << hex << setfill('0');
|
|
|
|
// Process every byte in the data.
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
// Multiple of 16 means new line (with line offset).
|
|
|
|
|
|
|
|
if ((i % pad) == 0) {
|
|
|
|
// Just don't print ASCII for the zeroth line.
|
|
|
|
if (i != 0) {
|
|
|
|
line << buff;
|
|
|
|
print(line.str());
|
|
|
|
line = stringstream{};
|
|
|
|
line << hex;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Output the offset.
|
|
|
|
line << setw(4) << i << " ";
|
|
|
|
}
|
|
|
|
if(i % columnLength == 0 && i % pad != 0){
|
|
|
|
line << "| ";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now the hex code for the specific character.
|
|
|
|
line << setw(2) << (int) pc[i] << " ";
|
|
|
|
|
|
|
|
// And store a printable ASCII character for later.
|
|
|
|
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
|
|
|
|
buff[i % pad] = '.';
|
|
|
|
else
|
|
|
|
buff[i % pad] = pc[i];
|
|
|
|
buff[(i % pad) + 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pad out last line if not exactly 16 characters.
|
|
|
|
while ((i % pad) != 0) {
|
|
|
|
line << " ";
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
line << buff;
|
|
|
|
delete[] buff;
|
|
|
|
|
|
|
|
print(line.str());
|
|
|
|
line = stringstream{};
|
|
|
|
line << "Length: " << dec << len << " Addr: " << addr;
|
|
|
|
print(line.str());
|
|
|
|
}
|