diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index c90be9f48..48f5a5ead 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -115,6 +115,7 @@ if(DEFINED CMAKE_JS_INC) add_library(${PROJECT_NAME} SHARED addon.cc ${CMAKE_CURRENT_SOURCE_DIR}/utils/cpuid/cpu_info.cc + ${CMAKE_CURRENT_SOURCE_DIR}/utils/file_logger.cc ${CMAKE_JS_SRC} ) @@ -131,6 +132,7 @@ if(DEFINED CMAKE_JS_INC) else() # Official build add_executable(${PROJECT_NAME} main.cc ${CMAKE_CURRENT_SOURCE_DIR}/utils/cpuid/cpu_info.cc + ${CMAKE_CURRENT_SOURCE_DIR}/utils/file_logger.cc ) endif() diff --git a/engine/main.cc b/engine/main.cc index 1f403d869..1450d887c 100644 --- a/engine/main.cc +++ b/engine/main.cc @@ -6,6 +6,7 @@ #include "utils/archive_utils.h" #include "utils/cortex_utils.h" #include "utils/dylib.h" +#include "utils/file_logger.h" #include "utils/file_manager_utils.h" #include "utils/logging_utils.h" @@ -29,16 +30,18 @@ void RunServer() { LOG_INFO << "Host: " << config.apiServerHost << " Port: " << config.apiServerPort << "\n"; // Create logs/ folder and setup log to file - std::filesystem::create_directory(cortex_utils::logs_folder); - trantor::AsyncFileLogger asyncFileLogger; - asyncFileLogger.setFileName(cortex_utils::logs_base_name); + std::filesystem::create_directory(config.logFolderPath + "/" + + cortex_utils::logs_folder); + trantor::FileLogger asyncFileLogger; + asyncFileLogger.setFileName(config.logFolderPath + "/" + + cortex_utils::logs_base_name); + asyncFileLogger.setMaxLines(config.maxLogLines); // Keep last 100000 lines asyncFileLogger.startLogging(); trantor::Logger::setOutputFunction( [&](const char* msg, const uint64_t len) { - asyncFileLogger.output(msg, len); + asyncFileLogger.output_(msg, len); }, [&]() { asyncFileLogger.flush(); }); - asyncFileLogger.setFileSizeLimit(cortex_utils::log_file_size_limit); // Number of cortex.cpp threads // if (argc > 1) { // thread_num = std::atoi(argv[1]); @@ -154,6 +157,18 @@ int main(int argc, char* argv[]) { RunServer(); return 0; } else { + auto config = file_manager_utils::GetCortexConfig(); + trantor::FileLogger asyncFileLogger; + asyncFileLogger.setFileName(config.logFolderPath + "/" + + cortex_utils::logs_cli_base_name); + asyncFileLogger.setMaxLines( + config.maxLogLines); // Keep last 100000 lines + asyncFileLogger.startLogging(); + trantor::Logger::setOutputFunction( + [&](const char* msg, const uint64_t len) { + asyncFileLogger.output_(msg, len); + }, + [&]() { asyncFileLogger.flush(); }); CommandLineParser clp; clp.SetupCommand(argc, argv); return 0; diff --git a/engine/utils/config_yaml_utils.h b/engine/utils/config_yaml_utils.h index 99db396cb..8e3668292 100644 --- a/engine/utils/config_yaml_utils.h +++ b/engine/utils/config_yaml_utils.h @@ -8,7 +8,9 @@ namespace config_yaml_utils { struct CortexConfig { + std::string logFolderPath; std::string dataFolderPath; + int maxLogLines; std::string apiServerHost; std::string apiServerPort; }; @@ -16,6 +18,7 @@ struct CortexConfig { const std::string kCortexFolderName = "cortexcpp"; const std::string kDefaultHost{"127.0.0.1"}; const std::string kDefaultPort{"3928"}; +const int kDefaultMaxLines{100000}; inline void DumpYamlConfig(const CortexConfig& config, const std::string& path) { @@ -27,7 +30,9 @@ inline void DumpYamlConfig(const CortexConfig& config, throw std::runtime_error("Failed to open output file."); } YAML::Node node; + node["logFolderPath"] = config.logFolderPath; node["dataFolderPath"] = config.dataFolderPath; + node["maxLogLines"] = config.maxLogLines; node["apiServerHost"] = config.apiServerHost; node["apiServerPort"] = config.apiServerPort; @@ -48,8 +53,16 @@ inline CortexConfig FromYaml(const std::string& path, try { auto node = YAML::LoadFile(config_file_path.string()); + int max_lines; + if (!node["maxLogLines"]) { + max_lines = kDefaultMaxLines; + } else { + max_lines = node["maxLogLines"].as(); + } CortexConfig config = { + .logFolderPath = node["logFolderPath"].as(), .dataFolderPath = node["dataFolderPath"].as(), + .maxLogLines = max_lines, .apiServerHost = node["apiServerHost"].as(), .apiServerPort = node["apiServerPort"].as(), }; diff --git a/engine/utils/cortex_utils.h b/engine/utils/cortex_utils.h index 32dea9321..9673f0c1a 100644 --- a/engine/utils/cortex_utils.h +++ b/engine/utils/cortex_utils.h @@ -34,8 +34,8 @@ constexpr static auto kTensorrtLlmPath = "/engines/cortex.tensorrt-llm"; inline std::string models_folder = "./models"; inline std::string logs_folder = "./logs"; -inline std::string logs_base_name = "./logs/cortex"; -inline size_t log_file_size_limit = 20000000; // ~20 mb +inline std::string logs_base_name = "./logs/cortex.log"; +inline std::string logs_cli_base_name = "./logs/cortex-cli.log"; inline std::string extractBase64(const std::string& input) { std::regex pattern("base64,(.*)"); diff --git a/engine/utils/file_logger.cc b/engine/utils/file_logger.cc new file mode 100644 index 000000000..f684d457f --- /dev/null +++ b/engine/utils/file_logger.cc @@ -0,0 +1,175 @@ +#include "file_logger.h" +#include +#include +#include + +#ifdef _WIN32 +#include +#define ftruncate _chsize +#else +#include +#endif +#include + +using namespace trantor; + +FileLogger::FileLogger() : AsyncFileLogger() {} + +FileLogger::~FileLogger() = default; + +void FileLogger::output_(const char* msg, const uint64_t len) { + if (!circular_log_file_ptr_) { + circular_log_file_ptr_ = + std::make_unique(fileBaseName_, max_lines_); + } + circular_log_file_ptr_->writeLog(msg, len); +} + +FileLogger::CircularLogFile::CircularLogFile(const std::string& fileName, + uint64_t maxLines) + : max_lines_(maxLines), file_name_(fileName) { + std::lock_guard lock(mutex_); + OpenFile(); + LoadExistingLines(); + TruncateFileIfNeeded(); +} + +FileLogger::CircularLogFile::~CircularLogFile() { + std::lock_guard lock(mutex_); + CloseFile(); +} +void FileLogger::CircularLogFile::writeLog(const char* logLine, + const uint64_t len) { + std::lock_guard lock(mutex_); + if (!fp_) + return; + + std::string logString(logLine, len); + std::istringstream iss(logString); + std::string line; + while (std::getline(iss, line)) { + if (lineBuffer_.size() >= max_lines_) { + lineBuffer_.pop_front(); + } + lineBuffer_.push_back(line); + AppendToFile(line + "\n"); + ++linesWrittenSinceLastTruncate_; + if (linesWrittenSinceLastTruncate_.load() >= TRUNCATE_CHECK_INTERVAL) { + + TruncateFileIfNeeded(); + } + } +} +void FileLogger::CircularLogFile::flush() { + std::lock_guard lock(mutex_); + if (fp_) { + fflush(fp_); + } +} + +void FileLogger::CircularLogFile::TruncateFileIfNeeded() { + // std::cout<<"Truncating file "<< totalLines_ < max_lines_ ? lineBuffer_.size() - max_lines_ : 0; + + for (size_t i = startIndex; i < lineBuffer_.size(); ++i) { + fprintf(tempFile, "%s\n", lineBuffer_[i].c_str()); + } + + fclose(tempFile); + + // Replace the original file with the temporary file + if (std::rename(tempFileName.c_str(), file_name_.c_str()) != 0) { + std::cout << "Error replacing original file with truncated file: " + << strerror(errno) << std::endl; + std::remove(tempFileName.c_str()); // Clean up the temporary file + } + // else { + // totalLines_.store(lineBuffer_.size() > max_lines_ ? max_lines_ + // : lineBuffer_.size()); + // } + + // Reopen the file + OpenFile(); + // LoadExistingLines(); + linesWrittenSinceLastTruncate_.store(0); +} + +void FileLogger::CircularLogFile::OpenFile() { +#ifdef _WIN32 + auto wFileName = utils::toNativePath(file_name_); + fp_ = _wfopen(wFileName.c_str(), L"r+"); +#else + fp_ = fopen(file_name_.c_str(), "r+"); +#endif + + if (!fp_) { +// If file doesn't exist, create it +#ifdef _WIN32 + fp_ = _wfopen(wFileName.c_str(), L"w+"); +#else + fp_ = fopen(file_name_.c_str(), "w+"); +#endif + + if (!fp_) { + std::cerr << "Error opening file: " << strerror(errno) << std::endl; + } + } +} +void FileLogger::CircularLogFile::LoadExistingLines() { + if (!fp_) + return; + + // Move to the beginning of the file + fseek(fp_, 0, SEEK_SET); + + lineBuffer_.clear(); + + std::string line; + char buffer[4096]; + while (fgets(buffer, sizeof(buffer), fp_) != nullptr) { + line = buffer; + if (!line.empty() && line.back() == '\n') { + line.pop_back(); // Remove trailing newline + } + if (lineBuffer_.size() >= max_lines_) { + lineBuffer_.pop_front(); + } + lineBuffer_.push_back(line); + } + + // Move back to the end of the file for appending + fseek(fp_, 0, SEEK_END); +} +void FileLogger::CircularLogFile::AppendToFile(const std::string& line) { + if (fp_) { + fwrite(line.c_str(), 1, line.length(), fp_); + fflush(fp_); + } +} + +void FileLogger::CircularLogFile::CloseFile() { + if (fp_) { + fclose(fp_); + fp_ = nullptr; + } +} \ No newline at end of file diff --git a/engine/utils/file_logger.h b/engine/utils/file_logger.h new file mode 100644 index 000000000..58b719019 --- /dev/null +++ b/engine/utils/file_logger.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace trantor { + +class TRANTOR_EXPORT FileLogger : public AsyncFileLogger { + public: + FileLogger(); + ~FileLogger(); + + /** + * @brief Set the maximum number of lines to keep in the log file. + * + * @param maxLines + */ + void setMaxLines(uint64_t maxLines) { max_lines_ = maxLines; } + + /** + * @brief Set the log file name. + * + * @param fileName The full name of the log file. + */ + void setFileName(const std::string& fileName) { + filePath_ = "./"; + fileBaseName_ = fileName; + fileExtName_ = ""; + } + void output_(const char* msg, const uint64_t len); + + protected: + class CircularLogFile { + public: + CircularLogFile(const std::string& fileName, uint64_t maxLines); + ~CircularLogFile(); + + void writeLog(const char* logLine, const uint64_t len); + void flush(); + uint64_t getLength() const { return lineBuffer_.size(); } + + private: + FILE* fp_{nullptr}; + uint64_t max_lines_; + std::string file_name_; + std::deque lineBuffer_; + std::atomic linesWrittenSinceLastTruncate_{0}; + static const uint64_t TRUNCATE_CHECK_INTERVAL = 1000; + mutable std::mutex mutex_; + + void LoadExistingLines(); + void TruncateFileIfNeeded(); + void AppendToFile(const std::string& line); + void OpenFile(); + void CloseFile(); + }; + std::unique_ptr circular_log_file_ptr_; + uint64_t max_lines_{100000}; // Default to 100000 lines +}; + +} // namespace trantor \ No newline at end of file diff --git a/engine/utils/file_manager_utils.h b/engine/utils/file_manager_utils.h index e0bce0ca6..b4d7ab07a 100644 --- a/engine/utils/file_manager_utils.h +++ b/engine/utils/file_manager_utils.h @@ -135,7 +135,9 @@ inline void CreateConfigFileIfNotExist() { CTL_INF("Default data folder path: " + defaultDataFolderPath.string()); auto config = config_yaml_utils::CortexConfig{ + .logFolderPath = defaultDataFolderPath.string(), .dataFolderPath = defaultDataFolderPath.string(), + .maxLogLines = config_yaml_utils::kDefaultMaxLines, .apiServerHost = config_yaml_utils::kDefaultHost, .apiServerPort = config_yaml_utils::kDefaultPort, }; @@ -169,6 +171,27 @@ inline std::filesystem::path GetCortexDataPath() { return data_folder_path; } +inline std::filesystem::path GetCortexLogPath() { + // TODO: We will need to support user to move the data folder to other place. + // TODO: get the variant of cortex. As discussed, we will have: prod, beta, nightly + // currently we will store cortex data at ~/cortexcpp + auto config = GetCortexConfig(); + std::filesystem::path log_folder_path; + if (!config.logFolderPath.empty()) { + log_folder_path = std::filesystem::path(config.logFolderPath); + } else { + auto home_path = GetHomeDirectoryPath(); + log_folder_path = home_path / config_yaml_utils::kCortexFolderName; + } + + if (!std::filesystem::exists(log_folder_path)) { + CTL_INF("Cortex log folder not found. Create one: " + + log_folder_path.string()); + std::filesystem::create_directory(log_folder_path); + } + return log_folder_path; +} + inline std::filesystem::path GetModelsContainerPath() { auto cortex_path = GetCortexDataPath(); auto models_container_path = cortex_path / "models"; diff --git a/engine/utils/logging_utils.h b/engine/utils/logging_utils.h index 77311c13e..fcaa3f4bb 100644 --- a/engine/utils/logging_utils.h +++ b/engine/utils/logging_utils.h @@ -30,4 +30,3 @@ inline bool log_verbose = false; } else { \ std::cout << msg << std::endl; \ } -