919 lines
24 KiB
C++
919 lines
24 KiB
C++
#include "ServerLogger.h"
|
||
#include <fmt/format.h>
|
||
#include <fmt/chrono.h>
|
||
#include <fmt/printf.h>
|
||
#include <fmt/args.h>
|
||
#include <sstream>
|
||
#include <iomanip>
|
||
#include <array>
|
||
|
||
// 格式化用的辅助函数
|
||
|
||
struct DurationView
|
||
{
|
||
long double value;
|
||
const char* unit;
|
||
};
|
||
|
||
inline std::tm ToTm(std::chrono::system_clock::time_point tp)
|
||
{
|
||
return fmt::localtime(std::chrono::system_clock::to_time_t(tp));
|
||
}
|
||
|
||
inline std::string FormatTime(const std::tm& tm, const std::string& spec)
|
||
{
|
||
char buf[128] = {};
|
||
|
||
const char* fmt = spec.empty() ? "%Y-%m-%d %H:%M:%S" : spec.c_str();
|
||
|
||
std::strftime(buf, sizeof(buf), fmt, &tm);
|
||
return buf;
|
||
}
|
||
|
||
inline DurationView NormalizeDuration(std::chrono::nanoseconds ns)
|
||
{
|
||
long double v = (long double)ns.count();
|
||
|
||
constexpr long double ns_1 = 1.0L;
|
||
constexpr long double us = 1000.0L;
|
||
constexpr long double ms = 1000000.0L;
|
||
constexpr long double s = 1000000000.0L;
|
||
constexpr long double m = 60.0L * s;
|
||
constexpr long double h = 60.0L * m;
|
||
constexpr long double d = 24.0L * h;
|
||
|
||
if (v >= d)
|
||
return { v / d, "d" };
|
||
if (v >= h)
|
||
return { v / h, "h" };
|
||
if (v >= m)
|
||
return { v / m, "m" };
|
||
if (v >= s)
|
||
return { v / s, "s" };
|
||
if (v >= ms)
|
||
return { v / ms, "ms" };
|
||
if (v >= us)
|
||
return { v / us, "us" };
|
||
return { v / ns_1, "ns" };
|
||
}
|
||
|
||
|
||
// ==================== 1. 锁死在 .cpp 内部的隐藏格式化器 ====================
|
||
// 让 ServerLogger.cpp 内部的 fmt 彻底看懂 uns::LogArg 变体
|
||
template <>
|
||
struct fmt::formatter<uns::LogArg>
|
||
{
|
||
constexpr auto parse(format_parse_context& ctx)
|
||
{
|
||
return ctx.begin();
|
||
}
|
||
|
||
template <typename FormatContext>
|
||
auto format(const uns::LogArg& la, FormatContext& ctx) const
|
||
{
|
||
return std::visit([&] (auto&& arg) -> decltype(ctx.out())
|
||
{
|
||
using T = std::decay_t<decltype(arg)>;
|
||
|
||
// A. 针对容器的运行时展开
|
||
if constexpr (std::is_same_v<T, uns::RangeCapturer>)
|
||
{
|
||
std::vector<uns::LogArg> items;
|
||
// 【修正】去掉瞎编的 container_ptr,直接传入 items 容器供内部闭包填充
|
||
arg.to_log_args(arg.ptr, items);
|
||
|
||
fmt::format_to(ctx.out(), "[");
|
||
for (size_t i = 0; i < items.size(); ++i)
|
||
{
|
||
if (i > 0)
|
||
fmt::format_to(ctx.out(), ", ");
|
||
fmt::format_to(ctx.out(), fmt::runtime("{}"), items[i]);
|
||
}
|
||
return fmt::format_to(ctx.out(), "]");
|
||
}
|
||
// B. 针对键值对的运行时展开
|
||
else if constexpr (std::is_same_v<T, uns::PairCapturer>)
|
||
{
|
||
std::vector<uns::LogArg> items;
|
||
arg.to_log_args(arg.ptr, items); // 假设 PairCapturer 也是相同的解包逻辑
|
||
if (items.size() >= 2)
|
||
fmt::format_to(ctx.out(), "{}: {}", items[0], items[1]);
|
||
return ctx.out();
|
||
}
|
||
// C. 针对延迟执行函数的运行时展开
|
||
else if constexpr (std::is_same_v<T, uns::FuncCapturer>)
|
||
{
|
||
// 【注意】请根据你 FuncCapturer 内部实际的求值函数名修改(如 .to_string() 或 .eval())
|
||
return fmt::format_to(ctx.out(), "Function{{ptr: {}, type: {}}}", arg.code_ptr, arg.is_closure ? "Closure" : "PureFunc");
|
||
}
|
||
// D. 时间相关的内容
|
||
else if constexpr (std::is_same_v<T, uns::TimeCapturer>)
|
||
{
|
||
auto out = ctx.out();
|
||
try
|
||
{
|
||
if (arg.mode == uns::TimeMode::Point)
|
||
{
|
||
std::tm tm = ToTm(arg.tp);
|
||
std::string s = FormatTime(tm, arg.format_spec);
|
||
return fmt::format_to(out, "{}", s);
|
||
}
|
||
else if (arg.mode == uns::TimeMode::TMPoint)
|
||
{
|
||
std::string s = FormatTime(arg._tm, arg.format_spec);
|
||
return fmt::format_to(out, "{}", s);
|
||
}
|
||
else
|
||
{
|
||
auto norm = NormalizeDuration(arg.duration);
|
||
return fmt::format_to(out, "{:.3f}{}", norm.value, norm.unit);
|
||
}
|
||
}
|
||
catch (...)
|
||
{
|
||
return fmt::format_to(out, "[time_error]");
|
||
}
|
||
}
|
||
// E. 基础原生类型
|
||
else
|
||
return fmt::format_to(ctx.out(), "{}", arg);
|
||
}, la.value);
|
||
}
|
||
};
|
||
|
||
std::string uns::toBinary(long number, int bits)
|
||
{
|
||
bool negitive = (number < 0);
|
||
unsigned long positive = (negitive ? -number : number);
|
||
return (negitive ? "-" + toBinary(positive, bits) : toBinary(positive, bits));
|
||
}
|
||
|
||
std::string uns::toBinary(std::uint32_t number, int bits)
|
||
{
|
||
return toBinary(static_cast<unsigned long>(number), bits);
|
||
}
|
||
|
||
std::string uns::toBinary(unsigned long number, int bits)
|
||
{
|
||
std::string res;
|
||
while (true)
|
||
{
|
||
res += std::to_string(number % 2);
|
||
number = number / 2;
|
||
if (number == 0)
|
||
break;
|
||
}
|
||
std::reverse(res.begin(), res.end());
|
||
while (res.size() < bits)
|
||
res = "0" + res;
|
||
return "0b" + res;
|
||
}
|
||
|
||
bool StartsWith(std::string_view str, std::string_view prefix)
|
||
{
|
||
if (str.size() < prefix.size())
|
||
return false;
|
||
return str.compare(0, prefix.size(), prefix) == 0;
|
||
}
|
||
|
||
bool IsSizeUnit(std::string_view str)
|
||
{
|
||
if (str == "bit")
|
||
return true;
|
||
if (str.empty())
|
||
return false;
|
||
char last = str.back();
|
||
return last == 'b' || last == 'B';
|
||
}
|
||
|
||
bool IsNonNegativeInteger(std::string_view str, int& value)
|
||
{
|
||
if (str.empty())
|
||
return false;
|
||
int result = 0;
|
||
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
|
||
if (ec != std::errc() || ptr != str.data() + str.size())
|
||
return false;
|
||
if (result < 0)
|
||
return false;
|
||
value = result;
|
||
return true;
|
||
}
|
||
|
||
std::pair<std::string, int> ParseSizeFormat(std::string_view input)
|
||
{
|
||
constexpr std::pair<std::string_view, int> default_value = { "B", 2 };
|
||
if (!StartsWith(input, "siz"))
|
||
return { std::string(default_value.first), default_value.second };
|
||
|
||
std::string_view body = input.substr(3);
|
||
if (body.empty() || body.front() != '-')
|
||
return { std::string(default_value.first), default_value.second };
|
||
|
||
body.remove_prefix(1);
|
||
size_t first_dash = body.find('-');
|
||
if (first_dash == std::string_view::npos)
|
||
{
|
||
// siz-xx 或 siz-x
|
||
if (IsSizeUnit(body))
|
||
return { std::string(body), 2 };
|
||
int precision = 0;
|
||
if (IsNonNegativeInteger(body, precision))
|
||
return { "B", precision };
|
||
return { std::string(default_value.first), default_value.second };
|
||
}
|
||
|
||
// siz-xx-y
|
||
std::string_view unit = body.substr(0, first_dash);
|
||
std::string_view precision_str = body.substr(first_dash + 1);
|
||
if (!IsSizeUnit(unit))
|
||
return { std::string(default_value.first), default_value.second };
|
||
int precision = 0;
|
||
if (!IsNonNegativeInteger(precision_str, precision))
|
||
return { std::string(default_value.first), default_value.second };
|
||
return { std::string(unit), precision };
|
||
}
|
||
|
||
std::string FormatFileSize(size_t size, const std::string& unit, int precision)
|
||
{
|
||
static constexpr double K = 1024.0;
|
||
static constexpr std::array<const char*, 9> units =
|
||
{
|
||
"B",
|
||
"KB",
|
||
"MB",
|
||
"GB",
|
||
"TB",
|
||
"PB",
|
||
"EB",
|
||
"ZB",
|
||
"YB"
|
||
};
|
||
|
||
static constexpr std::array<const char*, 9> iec_units =
|
||
{
|
||
"B",
|
||
"KIB",
|
||
"MIB",
|
||
"GIB",
|
||
"TIB",
|
||
"PIB",
|
||
"EIB",
|
||
"ZIB",
|
||
"YIB"
|
||
};
|
||
|
||
std::string input_unit = unit;
|
||
std::transform(input_unit.begin(), input_unit.end(), input_unit.begin(), [] (unsigned char c)
|
||
{
|
||
return static_cast<char>(std::toupper(c));
|
||
});
|
||
|
||
double bytes = static_cast<double>(size);
|
||
if ((input_unit == "BIT") || (input_unit == "BITS"))
|
||
bytes /= 8.0;
|
||
else
|
||
{
|
||
size_t unit_index = 0;
|
||
bool found = false;
|
||
for (size_t i = 0; i < units.size(); ++i)
|
||
{
|
||
if ((input_unit == units[i]) || (input_unit == iec_units[i]))
|
||
{
|
||
unit_index = i;
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found)
|
||
return std::format("{} {}", size, input_unit);
|
||
for (size_t i = 0; i < unit_index; ++i)
|
||
bytes *= K;
|
||
}
|
||
|
||
size_t output_index = 0;
|
||
while ((bytes >= K) && (output_index < (units.size() - 1)))
|
||
{
|
||
bytes /= K;
|
||
++output_index;
|
||
}
|
||
if (precision < 0)
|
||
precision = 0;
|
||
|
||
if (std::fabs(bytes - std::round(bytes)) < std::numeric_limits<double>::epsilon())
|
||
return fmt::format("{} {}", static_cast<size_t>(std::round(bytes)), units[output_index]);
|
||
|
||
std::string value = fmt::format("{:.{}f}", bytes, precision);
|
||
return fmt::format("{} {}", value, units[output_index]);
|
||
}
|
||
|
||
std::string ServerLogger::GenerateLogHeader(uns::ServerLogLevel LogLevel)
|
||
{
|
||
std::string hstr;
|
||
// 在日志行最开头添加对应日志级别的颜色控制码
|
||
switch (LogLevel)
|
||
{
|
||
case uns::llDebug:
|
||
hstr += "\033[36m"; // 青色
|
||
break;
|
||
case uns::llInfo:
|
||
hstr += "\033[32m"; // 绿色
|
||
break;
|
||
case uns::llWarning:
|
||
hstr += "\033[33m"; // 黄色
|
||
break;
|
||
case uns::llError:
|
||
hstr += "\033[31m"; // 红色
|
||
break;
|
||
case uns::llFatal:
|
||
hstr += "\033[1;37;41m"; // 亮白字 + 红底 (极度醒目)
|
||
break;
|
||
case uns::llTrace:
|
||
default:
|
||
break; // TRACE 与未知级别保持默认颜色,不追加转义码
|
||
}
|
||
time_t lt = time(NULL);
|
||
tm* loctim = localtime(<);
|
||
char timestr[250] = {};
|
||
sprintf(timestr, "{%04d-%02d-%02d %02d:%02d:%02d} ", loctim->tm_year + 1900, loctim->tm_mon + 1, loctim->tm_mday, loctim->tm_hour, loctim->tm_min, loctim->tm_sec);
|
||
hstr += timestr;
|
||
switch (LogLevel)
|
||
{
|
||
case uns::llTrace:
|
||
hstr += "[TRACE] ";
|
||
break;
|
||
case uns::llDebug:
|
||
hstr += "[DEBUG] ";
|
||
break;
|
||
case uns::llInfo:
|
||
hstr += "[INFO] ";
|
||
break;
|
||
case uns::llWarning:
|
||
hstr += "[WARNING] ";
|
||
break;
|
||
case uns::llError:
|
||
hstr += "[ERROR] ";
|
||
break;
|
||
case uns::llFatal:
|
||
hstr += "[FATAL] ";
|
||
break;
|
||
default:
|
||
hstr += "[UNKNOWN] ";
|
||
break;
|
||
}
|
||
return hstr;
|
||
}
|
||
|
||
std::string ServerLogger::GenerateFileInfo(std::string filename, int line_num)
|
||
{
|
||
// 使用 stringstream 替代 new/memset/sprintf
|
||
std::ostringstream oss;
|
||
oss << "(" << filename << " -> LINE=" << line_num << ") ";
|
||
return oss.str();
|
||
}
|
||
|
||
// 高性能、100% 异常安全的 ANSI 颜色码剥离函数
|
||
inline std::string StripAnsiCodes(const std::string& input) noexcept
|
||
{
|
||
std::string result;
|
||
// 预分配内存,避免多次 Realloc(即使底层内存极度匮乏,noexcept 也会兜底)
|
||
try
|
||
{
|
||
result.reserve(input.size());
|
||
}
|
||
catch (...)
|
||
{
|
||
// 极罕见的内存耗尽情况,直接降级返回原串或空串,绝不崩溃
|
||
return input;
|
||
}
|
||
bool in_escape = false;
|
||
for (char c : input)
|
||
{
|
||
if (c == '\033') // 遇到转义字符 '\033' (ESC)
|
||
{
|
||
in_escape = true;
|
||
continue;
|
||
}
|
||
if (in_escape)
|
||
{
|
||
// ANSI 颜色控制码以 'm' 结尾(例如 \033[31m 或 \033[0m)
|
||
if (c == 'm')
|
||
in_escape = false;
|
||
continue; // 跳过转义序列内的所有字符
|
||
}
|
||
result.push_back(c);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
void ServerLogger::WriteBatchToOutputs(const std::deque<std::string>& batch)
|
||
{
|
||
if (batch.empty())
|
||
return;
|
||
std::time_t now = std::time(nullptr);
|
||
for (const auto& item : batch)
|
||
{
|
||
if (item.empty())
|
||
continue;
|
||
std::fwrite(item.c_str(), 1, item.size(), stdout);
|
||
if (LogStream.is_open())
|
||
LogStream << StripAnsiCodes(item);
|
||
RotateIfNeeded(now, true);
|
||
}
|
||
std::fflush(stdout);
|
||
if (LogStream.is_open())
|
||
LogStream.flush();
|
||
}
|
||
|
||
void ServerLogger::WorkerLoop()
|
||
{
|
||
constexpr std::size_t kMaxBatchCount = 128;
|
||
|
||
while (WorkerRunning)
|
||
{
|
||
std::deque<std::string> local_batch;
|
||
{
|
||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||
QueueCV.wait(lock, [this] ()
|
||
{
|
||
return !LogQueue.empty() || !WorkerRunning;
|
||
});
|
||
if (!WorkerRunning && LogQueue.empty())
|
||
break;
|
||
std::size_t count = 0;
|
||
while (!LogQueue.empty() && (count < kMaxBatchCount))
|
||
{
|
||
local_batch.push_back(std::move(LogQueue.front()));
|
||
LogQueue.pop_front();
|
||
count++;
|
||
}
|
||
}
|
||
if (!local_batch.empty())
|
||
WriteBatchToOutputs(local_batch);
|
||
}
|
||
// 退出前把剩余日志尽量写完
|
||
for (;;)
|
||
{
|
||
std::deque<std::string> local_batch;
|
||
{
|
||
std::lock_guard<std::mutex> lock(QueueMutex);
|
||
if (LogQueue.empty())
|
||
break;
|
||
std::size_t count = 0;
|
||
while (!LogQueue.empty() && (count < kMaxBatchCount))
|
||
{
|
||
local_batch.push_back(std::move(LogQueue.front()));
|
||
LogQueue.pop_front();
|
||
count++;
|
||
}
|
||
}
|
||
WriteBatchToOutputs(local_batch);
|
||
}
|
||
ThreadRunningFlag = false;
|
||
}
|
||
|
||
std::string ServerLogger::MakeRotatedFileName(const std::string& base, std::time_t t, int osl_index)
|
||
{
|
||
tm* loctim = std::localtime(&t);
|
||
std::ostringstream oss;
|
||
oss << base;
|
||
if (RotatePeriod == uns::RP_Hourly)
|
||
oss << "_" << std::setw(4) << (loctim->tm_year + 1900) << std::setw(2) << std::setfill('0') << (loctim->tm_mon + 1) << std::setw(2) << std::setfill('0') << loctim->tm_mday << "_" << std::setw(2) << std::setfill('0') << loctim->tm_hour;
|
||
else if (RotatePeriod == uns::RP_Daily)
|
||
oss << "_" << std::setw(4) << (loctim->tm_year + 1900) << std::setw(2) << std::setfill('0') << (loctim->tm_mon + 1) << std::setw(2) << std::setfill('0') << loctim->tm_mday;
|
||
if (osl_index > 0)
|
||
oss << "_OSL" << osl_index;
|
||
oss << ".log";
|
||
return oss.str();
|
||
}
|
||
|
||
void ServerLogger::RotateIfNeeded(std::time_t now, bool check_size_after_write)
|
||
{
|
||
//控制台模式不进行轮转
|
||
if (LogFileName.empty())
|
||
return;
|
||
|
||
// 时间轮转优先:若 period 发生变化则重建文件并重置 OSL 索引
|
||
if (RotatePeriod != uns::RP_None)
|
||
{
|
||
std::time_t new_period_start = (RotatePeriod == uns::RP_Hourly ? (now / 3600) * 3600 : (now / 86400) * 86400);
|
||
if (new_period_start != CurrentFilePeriodStart)
|
||
{
|
||
// 时间轮转:关闭流并打开新的 period 文件,重置 OSL 索引
|
||
if (LogStream.is_open())
|
||
{
|
||
LogStream.flush();
|
||
LogStream.close();
|
||
}
|
||
CurrentOSLIndex = 0;
|
||
std::string actual_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex);
|
||
LogStream.open(actual_file.c_str(), std::ios::out | std::ios::app);
|
||
CurrentFilePeriodStart = new_period_start;
|
||
// 完成时间轮转后不做 size 检查(新文件刚创建,肯定小于阈值)
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果要求检查大小(一般在写入之后调用),并且 MaxFileSizeBytes > 0,则进行大小轮转
|
||
if (check_size_after_write && (MaxFileSizeBytes > 0) && LogStream.is_open())
|
||
{
|
||
// 尝试使用 tellp 获取当前文件位置
|
||
std::streampos pos = LogStream.tellp();
|
||
std::size_t filesize = 0;
|
||
if (pos != static_cast<std::streampos>(-1))
|
||
filesize = static_cast<std::size_t>(pos);
|
||
else
|
||
{
|
||
// 备用:通过打开文件获取大小
|
||
// 构造当前文件名(基于当前 period 和索引)
|
||
std::string current_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex);
|
||
std::ifstream ifs(current_file.c_str(), std::ios::binary | std::ios::ate);
|
||
if (ifs.is_open())
|
||
{
|
||
filesize = static_cast<std::size_t>(ifs.tellg());
|
||
ifs.close();
|
||
}
|
||
}
|
||
|
||
if (filesize >= MaxFileSizeBytes)
|
||
{
|
||
// 增加 OSL 索引并打开新文件
|
||
if (LogStream.is_open())
|
||
{
|
||
LogStream.flush();
|
||
LogStream.close();
|
||
}
|
||
CurrentOSLIndex++;
|
||
std::string new_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex);
|
||
LogStream.open(new_file.c_str(), std::ios::out | std::ios::app);
|
||
}
|
||
}
|
||
}
|
||
|
||
size_t GetUnsignedInteger(const uns::LogVariant& value)
|
||
{
|
||
return std::visit([] (const auto& v) -> size_t
|
||
{
|
||
using T = std::decay_t<decltype(v)>;
|
||
if constexpr (std::is_integral_v<T> && !std::is_same_v<T, char> && !std::is_same_v<T, bool>)
|
||
return static_cast<size_t>(v);
|
||
else
|
||
return 0;
|
||
}, value);
|
||
}
|
||
|
||
inline std::string RewriteFormatString(const std::string& real_format, const uns::LogArg* args, size_t count, fmt::dynamic_format_arg_store<fmt::format_context>& store)
|
||
{
|
||
std::string out;
|
||
out.reserve(real_format.size());
|
||
size_t arg_index = 0;
|
||
for (size_t i = 0; i < real_format.size();)
|
||
{
|
||
char c = real_format[i];
|
||
// escaped {{
|
||
if ((c == '{') && ((i + 1) < real_format.size()) && (real_format[i + 1] == '{'))
|
||
{
|
||
out += "{{";
|
||
i += 2;
|
||
continue;
|
||
}
|
||
// escaped }}
|
||
if ((c == '}') && ((i + 1) < real_format.size()) && (real_format[i + 1] == '}'))
|
||
{
|
||
out += "}}";
|
||
i += 2;
|
||
continue;
|
||
}
|
||
|
||
if (c == '{')
|
||
{
|
||
size_t j = i + 1;
|
||
while ((j < real_format.size()) && (real_format[j] != '}'))
|
||
++j;
|
||
if (j >= real_format.size())
|
||
{
|
||
out += '{';
|
||
++i;
|
||
continue;
|
||
}
|
||
|
||
std::string_view inside(real_format.data() + i + 1, j - i - 1);
|
||
if (arg_index < count)
|
||
{
|
||
uns::LogArg arg = args[arg_index];
|
||
// 只处理 time spec
|
||
if (!inside.empty() && (inside[0] == ':'))
|
||
{
|
||
if (std::holds_alternative<uns::TimeCapturer>(arg.value))
|
||
{
|
||
auto& tc = std::get<uns::TimeCapturer>(arg.value);
|
||
tc.format_spec = std::string(inside.substr(1));
|
||
arg.value = tc;
|
||
store.push_back(arg);
|
||
}
|
||
else
|
||
store.push_back(args[arg_index]);
|
||
}
|
||
else if (StartsWith(inside, "siz"))
|
||
{
|
||
auto [u, p] = ParseSizeFormat(inside);
|
||
store.push_back(FormatFileSize(GetUnsignedInteger(arg.value), u, p));
|
||
}
|
||
else
|
||
store.push_back(args[arg_index]);
|
||
}
|
||
|
||
out += "{}";
|
||
++arg_index;
|
||
i = j + 1;
|
||
continue;
|
||
}
|
||
|
||
out += c;
|
||
++i;
|
||
}
|
||
|
||
// debug
|
||
// std::string debug = "[RewriteFormatString] RAW=|" + real_format + "|, OUT=|" + out + "|\n";
|
||
// std::fwrite(debug.c_str(), 1, debug.size(), stdout);
|
||
return out;
|
||
}
|
||
|
||
void ServerLogger::LogImpl(uns::ServerLogLevel level, const std::string& format, const uns::LogArg* args, size_t count)
|
||
{
|
||
std::string formatted;
|
||
try
|
||
{
|
||
if (count == 0)
|
||
formatted = format;
|
||
else
|
||
{
|
||
fmt::dynamic_format_arg_store<fmt::basic_printf_context<char>> store;
|
||
for (size_t i = 0; i < count; ++i)
|
||
{
|
||
std::visit([&] (auto&& val)
|
||
{
|
||
using DeT = std::decay_t<decltype(val)>;
|
||
if constexpr (std::is_arithmetic_v<DeT> || std::is_convertible_v<DeT, fmt::string_view> || std::is_pointer_v<DeT>)
|
||
store.push_back(val);
|
||
else // 【修正】这里必须传入 args[i](即 LogArg 本身) 这样才能正确触发上面我们写好的 fmt::formatter<uns::LogArg> 路由
|
||
store.push_back(fmt::format("{}", args[i]));
|
||
}, args[i].value);
|
||
}
|
||
// 【修正】显式提供模板参数 <char>,彻底解决 std::string 导致的推导失败
|
||
formatted = fmt::vsprintf<char>(format, store);
|
||
}
|
||
}
|
||
catch (const fmt::format_error& e)
|
||
{
|
||
formatted = std::string("<< log format error: ") + e.what() + " >> " + format;
|
||
}
|
||
|
||
std::string logstr = GenerateLogHeader(level);
|
||
logstr += formatted;
|
||
logstr += "\033[0m\n";
|
||
|
||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||
LogQueue.push_back(logstr);
|
||
lock.unlock();
|
||
QueueCV.notify_one();
|
||
}
|
||
|
||
void ServerLogger::LogFImpl(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string& format, const uns::LogArg* args, size_t count)
|
||
{
|
||
std::string formatted;
|
||
std::string real_format = GenerateFileInfo(filename, line_num) + format;
|
||
try
|
||
{
|
||
if (count == 0)
|
||
formatted = real_format;
|
||
else
|
||
{
|
||
fmt::dynamic_format_arg_store<fmt::basic_printf_context<char>> store;
|
||
for (size_t i = 0; i < count; ++i)
|
||
{
|
||
std::visit([&] (auto&& val)
|
||
{
|
||
using DeT = std::decay_t<decltype(val)>;
|
||
if constexpr (std::is_arithmetic_v<DeT> || std::is_convertible_v<DeT, fmt::string_view> || std::is_pointer_v<DeT>)
|
||
store.push_back(val);
|
||
else
|
||
store.push_back(fmt::format("{}", args[i]));
|
||
}, args[i].value);
|
||
}
|
||
// 【修正】显式提供模板参数 <char>
|
||
formatted = fmt::vsprintf<char>(real_format, store);
|
||
}
|
||
}
|
||
catch (const fmt::format_error& e)
|
||
{
|
||
formatted = std::string("<< log format error: ") + e.what() + " >> " + real_format;
|
||
}
|
||
|
||
std::string logstr = GenerateLogHeader(level);
|
||
logstr += formatted;
|
||
logstr += "\033[0m\n";
|
||
|
||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||
LogQueue.push_back(logstr);
|
||
lock.unlock();
|
||
QueueCV.notify_one();
|
||
}
|
||
|
||
void ServerLogger::LogFMTImpl(uns::ServerLogLevel level, const std::string& format, const uns::LogArg* args, size_t count)
|
||
{
|
||
std::string formatted;
|
||
try
|
||
{
|
||
if (count == 0)
|
||
formatted = format;
|
||
else
|
||
{
|
||
// 现代 {} 风格:动态包装擦除后的类型数组
|
||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||
std::string rewrite_format = RewriteFormatString(format, args, count, store);
|
||
formatted = fmt::vformat(rewrite_format, store);
|
||
}
|
||
}
|
||
catch (const fmt::format_error& e)
|
||
{
|
||
formatted = std::string("<< log format error: ") + e.what() + " >> " + format;
|
||
}
|
||
|
||
std::string logstr = GenerateLogHeader(level);
|
||
logstr += formatted;
|
||
logstr += "\033[0m\n";
|
||
|
||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||
LogQueue.push_back(logstr);
|
||
lock.unlock();
|
||
QueueCV.notify_one();
|
||
}
|
||
|
||
void ServerLogger::LogFMT_FImpl(uns::ServerLogLevel level, const std::string& filename, int line_num, const std::string& format, const uns::LogArg* args, size_t count)
|
||
{
|
||
std::string formatted;
|
||
std::string real_format = GenerateFileInfo(filename, line_num) + format;
|
||
try
|
||
{
|
||
if (count == 0)
|
||
formatted = real_format;
|
||
else
|
||
{
|
||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||
std::string rewrite_format = RewriteFormatString(real_format, args, count, store);
|
||
formatted = fmt::vformat(rewrite_format, store);
|
||
}
|
||
}
|
||
catch (const fmt::format_error& e)
|
||
{
|
||
formatted = std::string("<< log format error: ") + e.what() + " >> " + real_format;
|
||
}
|
||
|
||
std::string logstr = GenerateLogHeader(level);
|
||
logstr += formatted;
|
||
logstr += "\033[0m\n";
|
||
|
||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||
LogQueue.push_back(logstr);
|
||
lock.unlock();
|
||
QueueCV.notify_one();
|
||
}
|
||
|
||
ServerLogger::ServerLogger()
|
||
{
|
||
//Default log off
|
||
//InitLogger(uns::llOff);
|
||
CurrentLevel = uns::llOff;
|
||
WorkerRunning = false;
|
||
RotatePeriod = uns::RP_None;
|
||
CurrentFilePeriodStart = 0;
|
||
MaxFileSizeBytes = 50 * 1024 * 1024;
|
||
CurrentOSLIndex = 0;
|
||
return;
|
||
}
|
||
|
||
ServerLogger::ServerLogger(uns::ServerLogLevel log_level, std::string file, uns::LogRotationPeriod rotation, std::size_t max_bytes)
|
||
{
|
||
InitLogger(log_level, file, rotation, max_bytes);
|
||
CurrentLevel = log_level;
|
||
return;
|
||
}
|
||
|
||
ServerLogger::~ServerLogger()
|
||
{
|
||
// 优雅关闭工作线程并刷新
|
||
CloseLog();
|
||
return;
|
||
}
|
||
|
||
bool ServerLogger::InitLogger(uns::ServerLogLevel log_level, const std::string& file, uns::LogRotationPeriod rotation, std::size_t max_bytes)
|
||
{
|
||
WorkerRunning = false;
|
||
RotatePeriod = rotation;
|
||
CurrentFilePeriodStart = 0;
|
||
CurrentLevel = log_level;
|
||
|
||
CurrentOSLIndex = 0;
|
||
MaxFileSizeBytes = max_bytes; // 强制开启大小轮转,默认为 50MiB(可由调用者覆盖)
|
||
LogFileName = file;
|
||
|
||
//初始化轮转
|
||
if (!LogFileName.empty())
|
||
{
|
||
time_t now = time(nullptr);
|
||
//取整点
|
||
CurrentFilePeriodStart = (RotatePeriod == uns::RP_Hourly ? ((now / 3600) * 3600) : (RotatePeriod == uns::RP_Daily ? ((now / 86400) * 86400) : now));
|
||
std::string actual_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex);
|
||
LogStream.open(actual_file.c_str(), std::ios::out | std::ios::app);
|
||
|
||
// 如果当前文件已超过大小限制,生成下一个 OSL 文件
|
||
if (LogStream.is_open())
|
||
{
|
||
// 尝试获取当前位置(文件大小),并在必要时轮转
|
||
std::streampos pos = LogStream.tellp();
|
||
std::size_t filesize = 0;
|
||
if (pos != static_cast<std::streampos>(-1))
|
||
filesize = static_cast<std::size_t>(pos);
|
||
else
|
||
{
|
||
// 备用方案:用 ifstream 直接获取文件大小
|
||
std::ifstream ifs(actual_file.c_str(), std::ios::binary | std::ios::ate);
|
||
if (ifs.is_open())
|
||
{
|
||
filesize = static_cast<std::size_t>(ifs.tellg());
|
||
ifs.close();
|
||
}
|
||
}
|
||
if (filesize >= MaxFileSizeBytes)
|
||
{
|
||
// increase osl index and open new file
|
||
LogStream.close();
|
||
CurrentOSLIndex = 1;
|
||
actual_file = MakeRotatedFileName(LogFileName.empty() ? "UNSC" : LogFileName, now, CurrentOSLIndex);
|
||
LogStream.open(actual_file.c_str(), std::ios::out | std::ios::app);
|
||
}
|
||
}
|
||
}
|
||
|
||
//启动日志线程
|
||
bool started = true;
|
||
if (!WorkerRunning)
|
||
{
|
||
WorkerRunning = true;
|
||
try
|
||
{
|
||
WorkerThread = std::thread(&ServerLogger::WorkerLoop, this);
|
||
}
|
||
catch (...)
|
||
{
|
||
WorkerRunning = false;
|
||
started = false;
|
||
}
|
||
}
|
||
return ((LogStream.is_open() || LogFileName.empty()) && started);
|
||
}
|
||
|
||
void ServerLogger::CloseLog()
|
||
{
|
||
//关闭工作线程
|
||
WorkerRunning = false;
|
||
QueueCV.notify_all();
|
||
if (WorkerThread.joinable())
|
||
WorkerThread.join();
|
||
while (ThreadRunningFlag)
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||
//关闭文件流
|
||
if (LogStream.is_open())
|
||
LogStream.close();
|
||
CurrentLevel = uns::llOff;
|
||
}
|
||
|
||
void ServerLogger::DisableLog()
|
||
{
|
||
CurrentLevel = uns::llOff;
|
||
}
|
||
|
||
void ServerLogger::FlushLogBuffer()
|
||
{
|
||
//触发写入
|
||
QueueCV.notify_all();
|
||
//等待队列清空
|
||
while (true)
|
||
{
|
||
std::unique_lock<std::mutex> lock(QueueMutex);
|
||
if (LogQueue.empty())
|
||
break;
|
||
lock.unlock();
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||
}
|
||
if (LogStream.is_open())
|
||
LogStream.flush();
|
||
}
|
||
|
||
void ServerLogger::EnableLog(uns::ServerLogLevel level)
|
||
{
|
||
CurrentLevel = (level == uns::llOff ? uns::llAll : level);
|
||
}
|
||
|
||
ServerLogger GlobalServerLogger; |