* Copyright (c) Huawei Technologies Co., Ltd. 2025-2026. All rights reserved.
* MindIE is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "system_log.h"
#include <Python.h>
#include <cxxabi.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>
#include "file_system.h"
#include "safe_envvar.h"
#include "safe_path.h"
#include "string_utils.h"
namespace mindie_llm {
const std::string LLM = "llm";
static constexpr size_t DECIMAL_BASE = 10;
static constexpr size_t BUFFER_SIZE_32 = 32;
static constexpr size_t BUFFER_SIZE_256 = 256;
static constexpr size_t BUFFER_SIZE_512 = 512;
static constexpr size_t BUFFER_SIZE_2048 = 2048;
const std::array<std::string, static_cast<uint8_t>(LogSeverity::__COUNT__) - 1>& GetLogSeverityNameArray() {
static const std::array<std::string, static_cast<uint8_t>(LogSeverity::__COUNT__) - 1> levelNames = {
"debug", "info", "warn", "error", "critical"};
return levelNames;
}
const std::unordered_set<std::string>& GetAllLogSeverity() {
static const std::unordered_set<std::string> values = [] {
std::unordered_set<std::string> s;
for (const auto& name : GetLogSeverityNameArray()) {
s.insert(name);
}
return s;
}();
return values;
}
const std::array<std::string, static_cast<uint8_t>(LogType::__COUNT__)>& GetLogTypeNameArray() {
static const std::array<std::string, static_cast<uint8_t>(LogType::__COUNT__)> typeNames = {"general", "request",
"token", "tokenizer"};
return typeNames;
}
bool String2LogType(const std::string& s, LogType& out) {
const auto& typeNames = GetLogTypeNameArray();
for (uint8_t i = 0; i < typeNames.size(); ++i) {
if (typeNames[i] == s) {
out = static_cast<LogType>(i);
return true;
}
}
return false;
}
static const std::unordered_map<LogType, std::string> logType2StrMap = {{LogType::GENERAL, "mindie-llm"},
{LogType::REQUEST, "mindie-llm-request"},
{LogType::TOKEN, "mindie-llm-token"},
{LogType::TOKENIZER, "mindie-llm-tokenizer"}};
const std::array<std::string, static_cast<uint8_t>(LogComponent::__COUNT__)>& GetComponentNameArray() {
static const std::array<std::string, static_cast<uint8_t>(LogComponent::__COUNT__)> compNames = {"llm", "llmmodels",
"server"};
return compNames;
}
const std::string& Component2String(LogComponent c) {
const auto& compNames = GetComponentNameArray();
return compNames[static_cast<uint8_t>(c)];
}
bool String2Component(const std::string& s, LogComponent& out) {
const auto& compNames = GetComponentNameArray();
for (uint8_t i = 0; i < compNames.size(); ++i) {
if (compNames[i] == s) {
out = static_cast<LogComponent>(i);
return true;
}
}
return false;
}
enum class TimestampFormat { READABLE, TIGHT };
bool String2LogSeverity(const std::string& in, LogSeverity& out) {
std::string key = in;
ToLower(key);
const auto& names = GetLogSeverityNameArray();
for (uint8_t i = 0; i < names.size(); ++i) {
if (names[i] == key) {
out = static_cast<LogSeverity>(i);
return true;
}
}
return false;
}
void AppendCurTimestamp(std::string& out, TimestampFormat format) {
using namespace std::chrono;
auto now = system_clock::now();
auto sec = time_point_cast<seconds>(now);
auto ms = duration_cast<milliseconds>(now - sec).count();
std::time_t t = system_clock::to_time_t(sec);
std::tm tbuf;
localtime_r(&t, &tbuf);
char buf[BUFFER_SIZE_32];
size_t length = 0;
if (format == TimestampFormat::READABLE) {
buf[length++] = '[';
length += std::strftime(buf + length, sizeof(buf) - length, "%Y-%m-%d %H:%M:%S", &tbuf);
buf[length++] = '.';
} else if (format == TimestampFormat::TIGHT) {
length = std::strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", &tbuf);
}
buf[length++] = '0' + (ms / (DECIMAL_BASE * DECIMAL_BASE));
buf[length++] = '0' + (ms / DECIMAL_BASE % DECIMAL_BASE);
buf[length++] = '0' + (ms % DECIMAL_BASE);
if (format == TimestampFormat::READABLE) {
buf[length++] = ']';
}
buf[length] = '\0';
out.append(buf, length);
}
std::string GetTightTimestamp() {
static constexpr size_t tightTimestampLength = 17;
std::string ts;
ts.reserve(tightTimestampLength);
AppendCurTimestamp(ts, TimestampFormat::TIGHT);
return ts;
}
inline void AppendComponent(std::string& out, const std::string& comp) {
out.append(" [");
out.append(comp);
out.push_back(']');
}
inline void AppendInt(std::string& out, uint64_t v, int width = 0) {
char buf[BUFFER_SIZE_32];
char* p = buf + sizeof(buf);
do {
*--p = '0' + (v % DECIMAL_BASE);
v /= DECIMAL_BASE;
} while (v);
int length = buf + sizeof(buf) - p;
for (; length < width; ++length) {
out.push_back('0');
}
out.append(p, buf + sizeof(buf));
}
inline void AppendPid(std::string& out) {
out.append(" [");
AppendInt(out, ::getpid());
out.push_back(']');
}
inline void AppendTid(std::string& out) {
out.append(" [");
AppendInt(out, static_cast<uint64_t>(::syscall(SYS_gettid)));
out.push_back(']');
}
inline std::string LogSeverity2String(LogSeverity level) {
static const std::unordered_map<LogSeverity, std::string> logSeverity2StrMap = {
{LogSeverity::DEBUG, "DEBUG"}, {LogSeverity::INFO, "INFO"}, {LogSeverity::WARN, "WARN"},
{LogSeverity::ERROR, "ERROR"}, {LogSeverity::CRITICAL, "CRITICAL"}, {LogSeverity::AUDIT, "AUDIT"}};
auto it = logSeverity2StrMap.find(level);
if (it != logSeverity2StrMap.end()) {
return it->second;
}
return "INFO";
}
inline void AppendLevel(std::string& out, LogSeverity level) {
out.append(" [");
out.append(LogSeverity2String(level));
out.push_back(']');
}
inline void FilterAndAppend(std::string& out, const char* input, size_t length) {
static constexpr unsigned char kAsciiControlMin = 0x00;
static constexpr unsigned char kAsciiControlMax = 0x1F;
static constexpr unsigned char kAsciiDelete = 0x7F;
static constexpr unsigned char kLineFeed = '\n';
static constexpr unsigned char kCarriageReturn = '\r';
const char* cursor = input;
const char* end = input + length;
while (cursor < end) {
unsigned char ch = static_cast<unsigned char>(*cursor++);
const bool isControlChar = (ch >= kAsciiControlMin && ch <= kAsciiControlMax) || (ch == kAsciiDelete);
const bool isLineBreak = (ch == kLineFeed) || (ch == kCarriageReturn);
if (isControlChar || isLineBreak) {
out.push_back('_');
} else {
out.push_back(static_cast<char>(ch));
}
}
}
void ParseRotateArgs(const std::string& argsStr, uint32_t& outLogFileSize, uint32_t& outLogFileNum) {
std::unordered_map<std::string, std::string> rotateArgs = ParseArgs(argsStr);
if (rotateArgs.find("-fs") != rotateArgs.end()) {
Result r = Str2Int(rotateArgs["-fs"], "logFileSize", outLogFileSize);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
outLogFileSize *= SIZE_1MB;
if (outLogFileSize < SIZE_1MB || outLogFileSize > SIZE_500MB) {
throw std::runtime_error(
"Log file size must be between 1 MB and 500 MB, got: " + std::to_string(outLogFileSize) + " bytes (" +
std::to_string(outLogFileSize / SIZE_1MB) + " MB)");
}
}
if (rotateArgs.find("-r") != rotateArgs.end()) {
Result r = Str2Int(rotateArgs["-r"], "logFileNum", outLogFileNum);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
static constexpr size_t fileNumLimit1 = 1;
static constexpr size_t fileNumLimit64 = 64;
if (outLogFileNum < fileNumLimit1 || outLogFileNum > fileNumLimit64) {
throw std::runtime_error("Log file count must be between " + std::to_string(fileNumLimit1) + " and " +
std::to_string(fileNumLimit64) + ", got: " + std::to_string(outLogFileNum));
}
}
}
inline void AppendFileLine(std::string& out, const char* file, int line) {
out.append(" [");
out.append(GetBasename(file));
out.push_back(':');
AppendInt(out, line);
out.append("] ");
}
static std::string MakeRotateName(const std::string& base, int idx) {
char buf[BUFFER_SIZE_32];
std::snprintf(buf, sizeof(buf), ".%02d.log", idx);
return base + buf;
}
static std::string GetStackTrace(size_t skip) {
void* buffer[BUFFER_SIZE_32];
int nptrs = ::backtrace(buffer, BUFFER_SIZE_32) - 3;
if (nptrs <= 0 || nptrs <= static_cast<int>(skip)) {
return "";
}
std::ostringstream oss;
oss << "\nStack trace:\n";
for (int i = skip; i < nptrs; ++i) {
Dl_info info{};
if (!dladdr(buffer[i], &info)) {
oss << "#" << (i - skip) << " ??\n";
continue;
}
std::string function = "??";
if (info.dli_sname) {
int status = 0;
std::unique_ptr<char, void (*)(void*)> demangled(
abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status), std::free);
if (status == 0 && demangled) {
function = demangled.get();
} else {
function = info.dli_sname;
}
}
oss << "#" << (i - skip) << " " << function << "\n";
}
return oss.str();
}
static std::string GetPythonStackTrace() {
std::string result;
PyGILState_STATE gil = PyGILState_Ensure();
PyObject* traceback = PyImport_ImportModule("traceback");
if (!traceback) {
PyGILState_Release(gil);
return result;
}
PyObject* formatFunc = PyObject_GetAttrString(traceback, "format_stack");
if (!formatFunc) {
Py_DECREF(traceback);
PyGILState_Release(gil);
return result;
}
PyObject* stackList = PyObject_CallObject(formatFunc, nullptr);
if (!stackList) {
Py_DECREF(formatFunc);
Py_DECREF(traceback);
PyGILState_Release(gil);
return result;
}
PyObject* sep = PyUnicode_FromString("");
if (!sep) {
Py_DECREF(stackList);
Py_DECREF(formatFunc);
Py_DECREF(traceback);
PyGILState_Release(gil);
return result;
}
PyObject* joined = PyUnicode_Join(sep, stackList);
if (joined) {
result = PyUnicode_AsUTF8(joined);
Py_DECREF(joined);
}
Py_DECREF(sep);
Py_DECREF(stackList);
Py_DECREF(formatFunc);
Py_DECREF(traceback);
PyGILState_Release(gil);
return result;
}
LogManager& LogManager::GetInstance() {
static LogManager inst;
return inst;
}
LogManager::LogManager() { Init(); }
LogManager::~LogManager() { Stop(); }
void LogManager::Stop() {
if (!isRunning_) {
return;
}
isRunning_ = false;
if (flushThread_.joinable()) {
flushThread_.join();
}
}
void LogManager::Init() {
LoadComponentConfigs();
if (IsAnyComponentToFile()) {
GetLogRotate();
GetLogDirs();
OpenLogFiles();
}
isRunning_ = true;
flushThread_ = std::thread(&LogManager::FlushLoop, this);
pthread_setname_np(flushThread_.native_handle(), "LogFlushThread");
}
void LogManager::LoadComponentConfigs() {
LoadByComponentByEnv<LogSeverity>(
MINDIE_LOG_LEVEL, DEFAULT_MINDIE_LOG_LEVEL, GetAllLogSeverity(),
[](const std::string& s) {
LogSeverity lvl;
if (!String2LogSeverity(s, lvl)) {
return LogSeverity::INFO;
}
return lvl;
},
[](ComponentConfig& c, LogSeverity v) { c.minLevel = v; });
LoadByComponentByEnv<bool>(
MINDIE_LOG_TO_STDOUT, DEFAULT_MINDIE_LOG_TO_STDOUT, {"true", "false", "1", "0"},
[](const std::string& s) { return s == "1" || s == "true"; },
[](ComponentConfig& c, bool v) { c.toStdout = v; });
LoadByComponentByEnv<bool>(
MINDIE_LOG_TO_FILE, DEFAULT_MINDIE_LOG_TO_FILE, {"true", "false", "1", "0"},
[](const std::string& s) { return s == "1" || s == "true"; }, [](ComponentConfig& c, bool v) { c.toFile = v; });
LoadByComponentByEnv<bool>(
MINDIE_LOG_VERBOSE, DEFAULT_MINDIE_LOG_VERBOSE, {"true", "false", "1", "0"},
[](const std::string& s) { return s == "1" || s == "true"; },
[](ComponentConfig& c, bool v) { c.verbose = v; });
}
bool LogManager::IsAnyComponentToFile() const {
for (const auto& c : componentCfgs_) {
if (c.toFile) {
return true;
}
}
return false;
}
ComponentConfig& LogManager::GetComponentConfig(LogComponent comp) { return componentCfgs_[static_cast<size_t>(comp)]; }
bool LogManager::IsPrintLog(LogComponent comp, LogSeverity level) {
if (level != LogSeverity::AUDIT) {
auto& cfg = GetComponentConfig(comp);
return isRunning_ && level >= cfg.minLevel && (cfg.toStdout || cfg.toFile);
} else {
return isRunning_;
}
}
void LogManager::GetLogRotate() {
std::string rotateVal;
Result r = EnvVar::GetInstance().Get(MINDIE_LOG_ROTATE, DEFAULT_MINDIE_LOG_ROTATE, rotateVal);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
std::unordered_map<std::string, std::string> rotateValMap =
ParseKeyValueString(rotateVal, {}, ALL_COMPONENT, ';', ':');
if (rotateValMap.count(LLM)) {
ParseRotateArgs(rotateValMap[LLM], logFileSize_, logFileNum_);
}
if (rotateValMap.count(ALL_COMPONENT)) {
ParseRotateArgs(rotateValMap[ALL_COMPONENT], logFileSize_, logFileNum_);
}
}
void LogManager::GetLogDirs() {
Result r = EnvVar::GetInstance().Get(MINDIE_LOG_PATH, DEFAULT_MINDIE_LOG_PATH, logDir_);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
auto logDirMap = ParseKeyValueString(logDir_, {}, ALL_COMPONENT, ';', ':');
std::string base;
if (logDirMap.count(LLM)) {
base = logDirMap[LLM];
}
if (logDirMap.count(ALL_COMPONENT)) {
base = logDirMap[ALL_COMPONENT];
}
logDir_ = base + "/debug/";
r = MakeDirs(logDir_);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
}
void LogManager::OpenLogFiles() {
for (size_t i = 0; i < static_cast<size_t>(LogType::__COUNT__); ++i) {
LogType type = static_cast<LogType>(i);
CreateLogFilePath(type);
auto& sink = sinks_[i];
sink.ofs.open(sink.filePath, std::ios::app);
if (sink.ofs.is_open()) {
std::error_code ec;
auto sz = fs::file_size(sink.filePath, ec);
sink.curSize = ec ? 0 : static_cast<size_t>(sz);
}
}
}
void LogManager::CreateLogFilePath(LogType type) {
const size_t idx = static_cast<size_t>(type);
auto& sink = sinks_[idx];
sink.basePath =
logDir_ +
Join(std::vector<std::string>{logType2StrMap.at(type), std::to_string(getpid()), GetTightTimestamp()}, "_");
SafePath logBasePath(sink.basePath, PathType::FILE, "a+", PERM_440);
Result r = logBasePath.Check(sink.basePath, false);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
sink.filePath = sink.basePath + ".log";
}
void LogManager::Push(LogComponent comp, LogType type, std::string&& msg) {
if (!isRunning_) {
return;
}
const size_t idx = static_cast<size_t>(type);
if (idx >= buffers_.size()) {
return;
}
std::lock_guard<std::mutex> lock(bufferMutex_[idx]);
buffers_[idx].push_back(MsgPkg{comp, type, std::move(msg)});
}
void LogManager::FlushLoop() {
while (isRunning_) {
std::this_thread::sleep_for(std::chrono::seconds(1));
Writer();
}
Writer();
}
void LogManager::Writer() {
BufferArray local;
for (size_t i = 0; i < local.size(); ++i) {
std::lock_guard<std::mutex> lock(bufferMutex_[i]);
local[i].swap(buffers_[i]);
}
for (size_t i = 0; i < local.size(); ++i) {
auto& msgs = local[i];
if (msgs.empty()) {
continue;
}
auto& sink = sinks_[i];
for (auto& m : msgs) {
const auto& cfg = componentCfgs_[static_cast<size_t>(m.component)];
if (cfg.toStdout) {
struct iovec iov[2] = {{const_cast<char*>(m.msg.data()), m.msg.size()}, {const_cast<char*>("\n"), 1}};
ssize_t ret = ::writev(STDOUT_FILENO, iov, 2);
if (ret == -1) {
perror("writev failed for system log.");
}
}
if (cfg.toFile && sink.ofs.is_open()) {
if (sink.curSize + m.msg.size() + 1 >= GetLogFileSizeCutOff(static_cast<LogType>(i))) {
RotateLogs(static_cast<LogType>(i));
}
sink.ofs << m.msg << '\n';
sink.curSize += m.msg.size() + 1;
}
}
std::cout.flush();
if (sink.ofs.is_open()) {
sink.ofs.flush();
}
}
}
uint32_t LogManager::GetLogFileSizeCutOff(LogType type) const {
if (type != LogType::TOKEN) {
return logFileSize_;
} else {
return SIZE_1MB;
}
}
uint32_t LogManager::GetLogFileNumCutOff(LogType type) const {
if (type != LogType::TOKEN) {
return logFileNum_;
} else {
uint32_t maxFileNumForTokenType = 2;
return maxFileNumForTokenType;
}
}
void LogManager::RotateLogs(LogType type) {
const size_t idx = static_cast<size_t>(type);
auto& sink = sinks_[idx];
sink.ofs.close();
ChangePermission(sink.filePath, PERM_440);
const std::string& base = sink.basePath;
std::error_code ec;
fs::remove(MakeRotateName(base, GetLogFileNumCutOff(type)), ec);
ec.clear();
for (int i = static_cast<int>(GetLogFileNumCutOff(type)) - 1; i >= 1; --i) {
fs::rename(MakeRotateName(base, i), MakeRotateName(base, i + 1), ec);
ec.clear();
}
fs::rename(base + ".log", MakeRotateName(base, 1), ec);
ec.clear();
sink.filePath = base + ".log";
sink.ofs.open(sink.filePath, std::ios::app);
std::error_code ec2;
auto sz = fs::file_size(sink.filePath, ec2);
sink.curSize = ec2 ? 0 : static_cast<size_t>(sz);
}
Logger::Logger(LogComponent comp, LogSeverity level) : component_(comp), level_(level) {}
bool Logger::ShouldLog() const { return LogManager::GetInstance().IsPrintLog(component_, level_); }
void Logger::AssembleAndPush(LogType type, const char* file, size_t line, std::string& stack) {
if (stream_.tellp() == std::streampos(0)) {
return;
}
std::string msg = stream_.str();
const size_t length = std::min(msg.size(), BUFFER_SIZE_2048);
std::string out;
out.reserve(length + BUFFER_SIZE_256);
AppendCurTimestamp(out, TimestampFormat::READABLE);
auto& cfg = LogManager::GetInstance().GetComponentConfig(component_);
if (cfg.verbose) {
AppendPid(out);
AppendTid(out);
AppendComponent(out, Component2String(component_));
}
AppendLevel(out, level_);
AppendFileLine(out, file, line);
FilterAndAppend(out, msg.data(), length);
if (!stack.empty()) {
out.append(stack);
}
LogManager::GetInstance().Push(component_, type, std::move(out));
}
void Logger::Reset() {
stream_.str("");
stream_.clear();
}
LogLine::LogLine(LogComponent comp, LogSeverity level, const char* file, size_t line)
: logger_(GetThreadLogger(comp, level)), enabled_(false), file_(file), line_(line) {
logger_.Reset();
enabled_ = logger_.ShouldLog();
if (enabled_ && (level == LogSeverity::ERROR || level == LogSeverity::CRITICAL)) {
stack_ = BuildStackTrace();
}
}
LogLine::~LogLine() {
if (!enabled_) {
return;
}
logger_.AssembleAndPush(type_, file_, line_, stack_);
logger_.Reset();
}
std::string LogLine::BuildStackTrace() {
static constexpr size_t kSkip = 3;
std::string result;
if (Py_IsInitialized()) {
PyGILState_STATE gil = PyGILState_Ensure();
PyFrameObject* frame = PyEval_GetFrame();
if (frame != nullptr) {
result += GetPythonStackTrace();
}
PyGILState_Release(gil);
}
result += GetStackTrace(kSkip);
return result;
}
Logger& GetThreadLogger(LogComponent comp, LogSeverity level) {
static thread_local std::array<std::array<Logger, static_cast<uint8_t>(LogSeverity::__COUNT__)>,
static_cast<uint8_t>(LogComponent::__COUNT__)>
loggers = [] {
std::array<std::array<Logger, static_cast<uint8_t>(LogSeverity::__COUNT__)>,
static_cast<uint8_t>(LogComponent::__COUNT__)>
arr{};
for (uint8_t c = 0; c < static_cast<uint8_t>(LogComponent::__COUNT__); ++c) {
for (uint8_t l = 0; l < static_cast<uint8_t>(LogSeverity::__COUNT__); ++l) {
arr[c][l] = Logger(static_cast<LogComponent>(c), static_cast<LogSeverity>(l));
}
}
return arr;
}();
return loggers[static_cast<uint8_t>(comp)][static_cast<uint8_t>(level)];
}
DynamicLogManager& DynamicLogManager::GetInstance() {
static DynamicLogManager inst;
return inst;
}
DynamicLogManager::DynamicLogManager() { Init(); }
void DynamicLogManager::Init() {
GetDefaultLogSeverity();
isRunning_ = true;
monitorThread_ = std::thread(&DynamicLogManager::Monitor, this);
pthread_setname_np(monitorThread_.native_handle(), "DynamicLogMonitorThread");
}
DynamicLogManager::~DynamicLogManager() { Stop(); }
void DynamicLogManager::Stop() {
if (!isRunning_) {
return;
}
isRunning_ = false;
if (monitorThread_.joinable()) {
monitorThread_.join();
}
}
void DynamicLogManager::GetDefaultLogSeverity() {
EnvVar::GetInstance().Get(MINDIE_LOG_LEVEL, DEFAULT_MINDIE_LOG_LEVEL, defaultLogSeverity_);
}
void DynamicLogManager::Monitor() {
while (isRunning_) {
try {
GetAndSetLogConfig();
} catch (const std::exception& e) {
std::cout << "DynamicLogManager exception: " << e.what() << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(monitorInterval_));
}
}
void DynamicLogManager::GetAndSetLogConfig() {
std::lock_guard<std::mutex> guard(mtx_);
const std::string configPath = GetConfigPath();
DynamicLogConfig newCfg = LoadLogConfig(configPath);
if (newCfg.logSeverity.empty() && !lastLogSeverity_.empty()) {
ResetToDefaultLogSeverity();
return;
}
DynamicLogConfig lastCfg{lastLogSeverity_, lastValidHours_, lastValidTimeStamp_};
auto diff = DiffConfig(newCfg, lastCfg);
UpdateValidTimeStamp(newCfg);
if (!IsWithinValidRange(newCfg)) {
ResetToDefaultLogSeverity();
return;
}
if (!diff.logSeverityChanged && !diff.validHoursChanged) {
return;
}
ApplyLogSeverity(newCfg.logSeverity);
lastLogSeverity_ = newCfg.logSeverity;
lastValidHours_ = newCfg.validHours;
lastValidTimeStamp_ = newCfg.validTimeStamp;
}
std::string DynamicLogManager::GetConfigPath() const {
std::string mindieLlmHomePath;
Result r = EnvVar::GetInstance().Get(MINDIE_LLM_HOME_PATH, GetDefaultMindIELLMHomePath(), mindieLlmHomePath);
if (r.IsOk()) {
std::string initPyPath = mindieLlmHomePath + "/__init__.py";
if (FileSystem::Exists(initPyPath)) {
return mindieLlmHomePath + "/conf/config.json";
}
}
std::string miesInstallPath;
r = EnvVar::GetInstance().Get(MIES_INSTALL_PATH, "", miesInstallPath);
if (r.IsOk() && !miesInstallPath.empty()) {
return miesInstallPath + "/conf/config.json";
}
throw std::runtime_error(
"Failed to determine config path: neither 'MINDIE_LLM_HOME_PATH' "
"(with __init__.py), nor 'MIES_INSTALL_PATH' is valid.");
}
DynamicLogConfig DynamicLogManager::LoadLogConfig(const std::string& configPath) {
Json configJsonData;
Result r = LoadJson(configPath, configJsonData);
if (!r.IsOk()) {
throw std::runtime_error(r.message());
}
const Json& cfgJson = configJsonData.value(keyLogConfig, Json::object());
std::string logSeverity = GetLogSeverity(cfgJson);
int timeInterval = GetTimeInterval(cfgJson, lastValidHours_);
std::string timeStamp = GetTimeStamp(cfgJson, lastValidTimeStamp_);
return {logSeverity, timeInterval, timeStamp};
}
std::string DynamicLogManager::GetLogSeverity(const Json& logConfig) const {
if (!logConfig.contains(keyLogSeverity) || !logConfig[keyLogSeverity].is_string()) {
return "";
}
return logConfig[keyLogSeverity].get<std::string>();
}
int DynamicLogManager::GetTimeInterval(const Json& logConfig, int lastHours) const {
static constexpr int minValidHours = 1;
static constexpr int maxValidHours = 168;
if (!logConfig.contains(keyTimeInterval) || !logConfig[keyTimeInterval].is_number_integer()) {
return lastHours;
}
int hours = logConfig[keyTimeInterval].get<int>();
if (hours < minValidHours || hours > maxValidHours) {
return lastHours;
}
return hours;
}
std::string DynamicLogManager::GetTimeStamp(const Json& logConfig, const std::string& lastTs) const {
if (!logConfig.contains(keyTimeStamp) || !logConfig[keyTimeStamp].is_string()) {
return lastTs;
}
const std::string ts = logConfig[keyTimeStamp].get<std::string>();
if (!IsValidTimeFormat(ts)) {
return lastTs;
}
if (!ts.empty() && IsGreaterThanNow(ts)) {
return "";
}
return ts;
}
DynamicLogDiff DynamicLogManager::DiffConfig(const DynamicLogConfig& current, const DynamicLogConfig& last) {
return {current.logSeverity != last.logSeverity, current.validHours != last.validHours,
current.validTimeStamp != last.validTimeStamp};
}
bool DynamicLogManager::IsValidTimeFormat(const std::string& timeStr) const {
static constexpr size_t strLen = 19;
static constexpr int maxHour = 23;
static constexpr int maxMinute = 59;
static constexpr int maxSecond = 59;
static constexpr int maxMonth = 11;
static constexpr int maxDay = 31;
if (timeStr.empty()) {
return true;
}
if (timeStr.length() != strLen) {
return false;
}
std::tm tm = {};
std::istringstream iss(timeStr);
iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
if (iss.fail()) {
return false;
}
if (tm.tm_hour < 0 || tm.tm_hour > maxHour || tm.tm_min < 0 || tm.tm_min > maxMinute || tm.tm_sec < 0 ||
tm.tm_sec > maxSecond || tm.tm_mon < 0 || tm.tm_mon > maxMonth || tm.tm_mday < 1 || tm.tm_mday > maxDay) {
return false;
}
std::tm tm_copy = tm;
std::time_t t = std::mktime(&tm_copy);
if (t == -1) {
return false;
}
return tm.tm_year == tm_copy.tm_year && tm.tm_mon == tm_copy.tm_mon && tm.tm_mday == tm_copy.tm_mday &&
tm.tm_hour == tm_copy.tm_hour && tm.tm_min == tm_copy.tm_min && tm.tm_sec == tm_copy.tm_sec;
}
bool DynamicLogManager::ParseTime(const std::string& s, std::time_t& out) const {
std::tm tm{};
tm.tm_isdst = -1;
std::istringstream iss(s);
iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
if (iss.fail()) {
return false;
}
out = std::mktime(&tm);
return out != -1;
}
bool DynamicLogManager::IsGreaterThanNow(const std::string& timeStr) const {
std::time_t t{};
if (!ParseTime(timeStr, t)) {
return false;
}
return t > std::time(nullptr);
}
void DynamicLogManager::ResetToDefaultLogSeverity() {
lastLogSeverity_.clear();
lastValidHours_ = defaultHours_;
lastValidTimeStamp_.clear();
ApplyLogSeverity(defaultLogSeverity_);
}
void DynamicLogManager::ApplyLogSeverity(const std::string& severity) {
LogManager::GetInstance().LoadByComponentByString<LogSeverity>(
severity, GetAllLogSeverity(),
[](const std::string& s) {
LogSeverity lvl;
if (!String2LogSeverity(s, lvl)) {
return LogSeverity::INFO;
}
return lvl;
},
[](ComponentConfig& c, LogSeverity v) { c.minLevel = v; });
}
void DynamicLogManager::UpdateValidTimeStamp(DynamicLogConfig& cfg) {
if (cfg.logSeverity.empty() || !cfg.validTimeStamp.empty()) {
return;
}
std::time_t now = std::time(nullptr);
std::tm tm{};
localtime_r(&now, &tm);
char buf[BUFFER_SIZE_32] = {};
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
cfg.validTimeStamp = buf;
}
bool DynamicLogManager::IsWithinValidRange(const DynamicLogConfig& cfg) const {
std::time_t start{};
if (!ParseTime(cfg.validTimeStamp, start)) {
return false;
}
const std::time_t end = start + cfg.validHours * 3600;
const std::time_t now = std::time(nullptr);
return now >= start && now < end;
}
void InitSystemLog() {
LogManager::GetInstance();
DynamicLogManager::GetInstance();
}
}