TeaSpeakLibrary/src/log/LogUtils.cpp

207 lines
7.2 KiB
C++

#include "LogUtils.h"
#include "LogSinks.h"
#include <iomanip>
#include <fstream>
#include <map>
#include <spdlog/spdlog.h>
#include <experimental/filesystem>
#include <StringVariable.h>
#include <mutex>
using namespace std;
using namespace std::chrono;
using namespace spdlog;
namespace fs = std::experimental::filesystem;
#define ASYNC_LOG
namespace logger {
recursive_mutex loggerLock;
map<int, std::shared_ptr<spdlog::logger>> loggers;
shared_ptr<LoggerConfig> logConfig;
shared_ptr<::logger::TerminalSink> terminalSink;
shared_ptr<::logger::CostumeFormatter> costumeFormatter;
shared_ptr<spdlog::logger> logger(int serverId) {
if(!::logger::currentConfig())
return nullptr;
int group = 0;
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
if(group > 0){
logger(0)->debug("Creating new grouped logger for group {}", group);
}
vector<spdlog::sink_ptr> sinks;
string path;
if(logConfig->logfileLevel != spdlog::level::off) {
path = 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";
tm* tm_info;
auto secs = duration_cast<seconds>(logConfig->timestamp.time_since_epoch()).count();
tm_info = localtime((time_t*) &secs);
char timeBuffer[1024];
if(strftime(timeBuffer, 1024, pattern.c_str(), tm_info) == 0) {
return string("string is longer than the buffer");
}
return string(timeBuffer);
})
);
auto logFile = fs::u8path(path);
if(!logFile.parent_path().empty())
fs::create_directories(logFile.parent_path());
auto fileSink = make_shared<ColoredFileSink>(logFile.string(), 1024 * 1024 * 50, 12);
sinks.push_back(fileSink);
} else {
path = "/dev/null (" + to_string(serverId) + ")";
}
sinks.push_back(terminalSink);
#ifdef ASYNC_LOG
auto logger = create_async("Logger (" + path + ")", sinks.begin(), sinks.end(), 8192, async_overflow_policy::discard_log_msg, [](){}, std::chrono::milliseconds(500));
#else
auto logger = create("Logger (" + path + ")", sinks.begin(), sinks.end());
#endif
logger->set_formatter(costumeFormatter);
for(const auto& sink : logger->sinks())
if(dynamic_pointer_cast<TerminalSink>(sink)) {
sink->set_level(::logger::currentConfig()->terminalLevel);
} else if(dynamic_pointer_cast<ColoredFileSink>(sink)) {
sink->set_level(::logger::currentConfig()->logfileLevel);
} else {
sink->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel));
}
logger->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel));
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);
costumeFormatter = make_shared<CostumeFormatter>();
logger(0)->debug("Log successfully started!");
}
void updateLogLevels() {
lock_guard lock(loggerLock);
for(const auto& loggerEntry : loggers) {
auto logger = loggerEntry.second;
for(const auto& sink : logger->sinks())
if(dynamic_pointer_cast<TerminalSink>(sink))
sink->set_level(::logger::currentConfig()->terminalLevel);
else if(dynamic_pointer_cast<ColoredFileSink>(sink))
sink->set_level(::logger::currentConfig()->logfileLevel);
else
sink->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel));
logger->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel));
}
}
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();
logConfig = nullptr;
terminalSink = nullptr;
costumeFormatter = 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());
}