#include "PatternFormatter.h"
#include "../exception.h"
#ifdef _WIN32
#include <process.h>
#define _PANDA_GETPID _getpid()
#define _PANDA_LOCALTIME(epoch_ptr, tm_ptr) (localtime_s(tm_ptr, epoch_ptr) == 0)
#else
#include <unistd.h>
#define _PANDA_GETPID getpid()
#define _PANDA_LOCALTIME(epoch_ptr, tm_ptr) (localtime_r(epoch_ptr, tm_ptr) != nullptr)
#endif
namespace panda { namespace log {
string_view default_format = "%1t %c[%L/%1M]%C %f:%l,%F(): %m";
#ifdef _WIN32
static const char* colors[] = {nullptr};
#else
static const char* colors[] = {nullptr, nullptr, nullptr, nullptr, "\e[33m", "\e[31m", "\e[41m", "\e[41m", "\e[41m"};
#endif
static const char clear_color[] = "\e[0m";
static const char* dates[] = {"%Y-%m-%d %H:%M:%S", "%y-%m-%d %H:%M:%S", "%H:%M:%S", "%Y/%m/%d %H:%M:%S"};
PatternFormatter::PatternFormatter (string_view fmt) : _fmt(string(fmt)) {}
static void add_mks (string& dest, const Info::time_point& tp, unsigned prec) {
long nsec = tp.time_since_epoch().count() % 1000000000L;
dest += '.';
if (nsec < 100000000) dest += '0';
if (nsec < 10000000) dest += '0';
if (nsec < 1000000) dest += '0';
if (nsec < 100000) dest += '0';
if (nsec < 10000) dest += '0';
if (nsec < 1000) dest += '0';
if (nsec < 100) dest += '0';
if (nsec < 10) dest += '0';
dest += panda::to_string(nsec);
dest.length(dest.length() - 9 + prec);
}
static inline void ymdhms (string& dest, time_t epoch, unsigned type) {
struct tm dt;
if (!_PANDA_LOCALTIME(&epoch, &dt)) return;
auto cap = 23;
string tmp(cap);
assert(type < 4);
auto len = strftime(tmp.buf(), cap, dates[type], &dt);
tmp.length(len);
dest += tmp;
}
static inline string format_multiline (const string& tmpl, size_t tmpl_pos, std::string& msg) {
size_t nlines = 0;
for (size_t pos = 0; pos != std::string::npos; pos = msg.find('\n', pos)) {
++nlines;
++pos;
}
string ret(tmpl.length()*nlines + msg.length());
auto msglen = msg.length();
size_t pos = 0, epos = 0;
while (1) {
epos = msg.find('\n', pos);
if (epos == std::string::npos) epos = msglen;
if (pos != 0) ret += '\n';
auto ins_pos = ret.length() + tmpl_pos;
ret += tmpl;
ret.insert(ins_pos, msg.data() + pos, epos - pos);
if (epos == msglen) break;
pos = epos + 1;
}
return ret;
}
string PatternFormatter::format (const string& fmt, std::string& msg, const Info& info) {
auto len = fmt.length();
auto s = fmt.data();
auto se = s + len;
string ret((len > 25 ? len*2 : 50) + msg.length());
auto multiline = string::npos;
while (s < se) {
char c = *s++;
if (c != '%' || s == se) {
ret += c;
continue;
}
unsigned x = 0, y = 0;
bool dot = false;
SWITCH:
switch (*s++) {
case 'F': {
if (info.func.length()) ret += info.func;
else ret += "<top>";
break;
}
case 'f': {
if (x) ret += info.file;
else {
auto file = info.file;
auto pos = file.rfind('/');
if (pos < file.length()) file = file.substr(pos+1);
#ifdef _WIN32
pos = file.rfind('\\');
if (pos < file.length()) file = file.substr(pos+1);
#endif
ret += file;
}
break;
}
case 'l': {
ret += panda::to_string(info.line);
break;
}
case 'm': {
if (x == 1 && msg.find('\n') != std::string::npos) {
multiline = ret.length();
} else {
ret += string_view(msg.data(), msg.length());
}
break;
}
case 'M': {
if (info.module->name().length()) ret += info.module->name();
else {
if (x && ret.length() >= x) ret.length(ret.length() - x);
s += y;
}
break;
}
case 'L': {
switch (info.level) {
case Level::VerboseDebug : ret += "DEBUG"; break;
case Level::Debug : ret += "debug"; break;
case Level::Info : ret += "info"; break;
case Level::Notice : ret += "notice"; break;
case Level::Warning : ret += "warning"; break;
case Level::Error : ret += "error"; break;
case Level::Critical : ret += "critical"; break;
case Level::Alert : ret += "alert"; break;
case Level::Emergency : ret += "emergency"; break;
default: break;
}
break;
}
case 't': {
if (x < 4) ymdhms(ret, std::chrono::system_clock::to_time_t(info.time), x);
else ret += panda::to_string(std::chrono::duration_cast<std::chrono::seconds>(info.time.time_since_epoch()).count());
if (y) add_mks(ret, info.time, y);
break;
}
case 'T': {
std::stringstream ss;
ss << std::this_thread::get_id();
auto str = ss.str();
ret.append(str.data(), str.length());
break;
}
case 'p': {
ret += panda::to_string(_PANDA_GETPID);
break;
}
case 'P': {
ret += info.program_name;
break;
}
case 'c': if (colors[(size_t)info.level]) ret += colors[(size_t)info.level]; break;
case 'C': if (colors[(size_t)info.level]) ret += clear_color; break;
case '.': {
if (s == se) throw exception("bad formatter pattern");
dot = true;
goto SWITCH;
}
default: {
char c = *(s-1);
if (s == se || c < '0' || c > '9') throw exception("bad formatter pattern");
(dot ? y : x) = c - '0';
goto SWITCH;
}
}
}
if (multiline != string::npos) return format_multiline(ret, multiline, msg);
return ret;
}
string PatternFormatter::format (std::string& msg, const Info& info) const {
return format(_fmt, msg, info);
}
}}