add humanus_server (mcp)

main
hkr04 2025-04-08 23:26:53 +08:00
parent 7c797864dd
commit 26c45d966e
15 changed files with 530 additions and 83 deletions

View File

@ -157,7 +157,7 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
memory->add_message(Message::user_message(stuck_prompt));
}
// O(nm) LCS algorithm, could basically handle current LLM context
// O(n * m) LCS algorithm, could basically handle current LLM context
size_t get_lcs_length(const std::string& s1, const std::string& s2) {
std::vector<std::vector<size_t>> dp(s1.size() + 1, std::vector<size_t>(s2.size() + 1));
for (size_t i = 1; i <= s1.size(); i++) {
@ -209,10 +209,19 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
void reset(bool reset_memory = true) {
current_step = 0;
state = AgentState::IDLE;
llm->reset_tokens();
if (reset_memory) {
memory->clear();
}
}
size_t get_prompt_tokens() {
return llm->get_prompt_tokens();
}
size_t get_completion_tokens() {
return llm->get_completion_tokens();
}
};
} // namespace humanus

View File

@ -35,11 +35,13 @@ struct ReActAgent : BaseAgent {
// Execute a single step: think and act.
virtual std::string step() {
bool should_act = think();
logger->info("Prompt tokens: " + std::to_string(llm->total_prompt_tokens()) + ", Completion tokens: " + std::to_string(llm->total_completion_tokens()));
if (!should_act) {
return "Thinking complete - no action needed";
}
return act();
if (state == AgentState::RUNNING) {
return act();
}
return "Agent is not running";
}
};

View File

@ -30,6 +30,10 @@ bool ToolCallAgent::think() {
);
}
if (state != AgentState::RUNNING) {
return false;
}
try {
// Handle different tool_choices modes
if (tool_choice == "none") {
@ -76,6 +80,10 @@ std::string ToolCallAgent::act() {
std::vector<std::string> results;
for (const auto& tool_call : tool_calls) {
if (state != AgentState::RUNNING) {
break;
}
auto result = execute_tool(tool_call);
logger->info(
"🎯 Tool `" + tool_call.function.name + "` completed its mission! Result: " + result.substr(0, 500) + (result.size() > 500 ? "..." : "")
@ -94,6 +102,10 @@ std::string ToolCallAgent::act() {
result_str += result + "\n\n";
}
if (state != AgentState::RUNNING) {
result_str += "Agent is not running, so no more tool calls will be executed.\n\n";
}
return result_str;
}

View File

@ -1,16 +1,8 @@
# examples/CMakeLists.txt
# examples
# examples
file(GLOB EXAMPLE_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
#
foreach(EXAMPLE_DIR ${EXAMPLE_DIRS})
#
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR})
# CMakeLists.txt
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR}/CMakeLists.txt")
#
add_subdirectory(${EXAMPLE_DIR})
message(STATUS "Added example: ${EXAMPLE_DIR}")
endif()

View File

@ -2,5 +2,4 @@ set(target humanus_chat)
add_executable(${target} humanus_chat.cpp)
#
target_link_libraries(${target} PRIVATE humanus)

View File

@ -2,11 +2,4 @@ set(target humanus_cli)
add_executable(${target} main.cpp)
#
target_link_libraries(${target} PRIVATE humanus)
#
set_target_properties(${target}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
target_link_libraries(${target} PRIVATE humanus)

View File

@ -2,11 +2,4 @@ set(target humanus_cli_mcp)
add_executable(${target} humanus_mcp.cpp)
#
target_link_libraries(${target} PRIVATE humanus)
#
set_target_properties(${target}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
target_link_libraries(${target} PRIVATE humanus)

View File

@ -2,11 +2,4 @@ set(target humanus_cli_plan)
add_executable(${target} humanus_plan.cpp)
#
target_link_libraries(${target} PRIVATE humanus)
#
set_target_properties(${target}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
target_link_libraries(${target} PRIVATE humanus)

View File

@ -0,0 +1,5 @@
set(target humanus_server)
add_executable(${target} server.cpp)
target_link_libraries(${target} PRIVATE humanus)

View File

@ -0,0 +1,222 @@
#include "httplib.h"
#include "agent/humanus.h"
#include "logger.h"
#include "mcp_server.h"
#include "mcp_tool.h"
#include <vector>
#include <mutex>
#include <chrono>
#include <thread>
#include <iostream>
#include <sstream>
#include <memory>
#include <atomic>
#include <condition_variable>
#include <random>
#include <unordered_map>
using namespace humanus;
static auto session_sink = SessionSink::get_instance();
class SessionManager {
public:
std::shared_ptr<Humanus> get_agent(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = agents_.find(session_id);
if (it != agents_.end()) {
return it->second;
}
auto agent = std::make_shared<Humanus>();
agents_[session_id] = agent;
return agent;
}
static std::vector<std::string> get_logs_buffer(const std::string& session_id) {
return session_sink->get_buffer(session_id);
}
static std::vector<std::string> get_logs_history(const std::string& session_id) {
return session_sink->get_history(session_id);
}
void set_result(const std::string& session_id, const std::string& result) {
std::lock_guard<std::mutex> lock(mutex_);
results_[session_id] = result;
}
std::string get_result(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = results_.find(session_id);
if (it != results_.end()) {
return it->second;
}
return "";
}
void clear_result(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
results_.erase(session_id);
}
bool has_session(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
return agents_.find(session_id) != agents_.end();
}
void close_session(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
agents_.erase(session_id);
results_.erase(session_id);
session_sink->cleanup_session(session_id);
}
std::vector<std::string> get_all_sessions() {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> sessions;
for (const auto& pair : agents_) {
sessions.push_back(pair.first);
}
return sessions;
}
private:
std::mutex mutex_;
std::unordered_map<std::string, std::shared_ptr<Humanus>> agents_;
std::unordered_map<std::string, std::string> results_;
std::unordered_map<std::string, std::shared_ptr<SessionSink>> session_sinks_;
};
int main(int argc, char** argv) {
mcp::set_log_level(mcp::log_level::warning);
int port = 8896;
if (argc == 2) {
try {
port = std::stoi(argv[1]);
} catch (...) {
std::cerr << "Invalid port number: " << argv[1] << std::endl;
return 1;
}
}
// Create and configure server
mcp::server server("localhost", port, "HumanusServer", "0.0.1");
// Set server capabilities
mcp::json capabilities = {
{"tools", mcp::json::object()}
};
server.set_capabilities(capabilities);
auto session_manager = std::make_shared<SessionManager>();
auto run_tool = mcp::tool_builder("humanus_run")
.with_description("Request to start a new task. Best to give clear and concise prompts.")
.with_string_param("prompt", "The prompt text to process", true)
.build();
server.register_tool(run_tool, [session_manager](const json& args, const std::string& session_id) -> json {
if (!args.contains("prompt")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing `prompt` parameter");
}
std::string prompt = args["prompt"].get<std::string>();
auto agent = session_manager->get_agent(session_id);
if (agent->state != AgentState::IDLE) {
throw mcp::mcp_exception(mcp::error_code::invalid_request, "The agent is busy, please wait for the current task to complete or terminate the current task.");
}
agent->reset();
std::thread([agent, session_manager, prompt, session_id]() {
try {
session_sink->set_session_id(session_id);
logger->info("Processing your request: " + prompt);
auto result = agent->run(prompt);
logger->info("Task completed.");
session_manager->set_result(session_id, result);
} catch (const std::exception& e) {
logger->error("Session {} error: {}", session_id, e.what());
}
}).detach();
return {{
{"type", "text"},
{"text", "Task started, call `humanus_status` to check the status."}
}};
});
auto terminate_tool = mcp::tool_builder("humanus_terminate")
.with_description("Terminate the current task")
.build();
server.register_tool(terminate_tool, [session_manager](const json& args, const std::string& session_id) -> json {
if (!session_manager->has_session(session_id)) {
throw mcp::mcp_exception(mcp::error_code::invalid_request, "Session not found");
}
auto agent = session_manager->get_agent(session_id);
if (agent->state == AgentState::IDLE) {
return {{
{"type", "text"},
{"text", "The agent is idle, no task to terminate."}
}};
}
agent->update_memory("user", "User interrupted the interaction. Consider rescheduling the previous task or switching to a different task according to the user's request.");
agent->state = AgentState::IDLE;
logger->info("Task terminated by user.");
return {{
{"type", "text"},
{"text", "Task terminated."}
}};
});
auto status_tool = mcp::tool_builder("humanus_status")
.with_description("Get the status of the current task.")
.build();
server.register_tool(status_tool, [session_manager](const json& args, const std::string& session_id) -> json {
if (!session_manager->has_session(session_id)) {
throw mcp::mcp_exception(mcp::error_code::invalid_request, "Session not found");
}
auto agent = session_manager->get_agent(session_id);
auto result = session_manager->get_result(session_id);
json status = {
{"state", agent_state_map[agent->state]},
{"current_step", agent->current_step},
{"max_steps", agent->max_steps},
{"prompt_tokens", agent->get_prompt_tokens()},
{"completion_tokens", agent->get_completion_tokens()},
{"logs_buffer", session_sink->get_buffer(session_id)},
{"result", result}
};
return {{
{"type", "text"},
{"text", status.dump(2)}
}};
});
// Start server
std::cout << "Starting Humanus server at http://localhost:" << port << "..." << std::endl;
std::cout << "Press Ctrl+C to stop server" << std::endl;
server.start(true); // Blocking mode
return 0;
}

View File

@ -120,13 +120,18 @@ public:
int max_retries = 3
);
size_t total_prompt_tokens() const {
size_t get_prompt_tokens() const {
return total_prompt_tokens_;
}
size_t total_completion_tokens() const {
size_t get_completion_tokens() const {
return total_completion_tokens_;
}
void reset_tokens() {
total_prompt_tokens_ = 0;
total_completion_tokens_ = 0;
}
};
} // namespace humanus

View File

@ -1,33 +1,180 @@
#ifndef HUMANUSlogger_H
#define HUMANUSlogger_H
#include "config.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/dist_sink.h"
#include <string>
#include <filesystem>
#include "config.h"
#include <mutex>
#include <unordered_set>
namespace humanus {
static spdlog::level::level_enum _print_level = spdlog::level::info;
static spdlog::level::level_enum _logfile_level = spdlog::level::debug;
/**
* @brief Adjust the log level
* @param print_level The console output log level
* @param logfile_level The file record log level
* @param name The log file name prefix
* @return The log record instance
*/
extern std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print_level = spdlog::level::info,
spdlog::level::level_enum logfile_level = spdlog::level::debug,
std::string name = "");
extern std::shared_ptr<spdlog::logger> set_log_level(spdlog::level::level_enum print_level, spdlog::level::level_enum logfile_level);
static std::shared_ptr<spdlog::logger> logger = define_log_level();
class SessionSink : public spdlog::sinks::base_sink<std::mutex> {
private:
inline static std::unordered_map<std::string, std::vector<std::string>> buffers_; // session_id -> buffer
inline static std::unordered_map<std::string, std::vector<std::string>> histories_; // session_id -> history
inline static std::unordered_map<std::string, std::string> sessions_; // thread_id -> session_id
inline static std::mutex mutex_;
SessionSink() = default;
SessionSink(const SessionSink&) = delete;
SessionSink& operator=(const SessionSink&) = delete;
public:
~SessionSink() = default;
static std::shared_ptr<SessionSink> get_instance() {
static SessionSink instance;
static std::shared_ptr<SessionSink> shared_instance(&instance, [](SessionSink*){});
return shared_instance;
}
void sink_it_(const spdlog::details::log_msg& msg) override {
std::lock_guard<std::mutex> lock(mutex_);
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
return;
}
auto session_id = sessions_[get_thread_id()];
auto time_t = std::chrono::system_clock::to_time_t(msg.time);
auto tm = fmt::localtime(time_t);
std::string log_message = fmt::format("[{:%Y-%m-%d %H:%M:%S}] {}", tm, msg.payload);
buffers_[session_id].push_back(log_message);
}
void flush_() override {
std::lock_guard<std::mutex> lock(mutex_);
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
return;
}
auto session_id = sessions_[get_thread_id()];
if (!buffers_[session_id].empty()) {
histories_[session_id].insert(histories_[session_id].end(),
buffers_[session_id].begin(),
buffers_[session_id].end());
buffers_[session_id].clear();
}
}
inline std::string get_thread_id() {
std::stringstream ss;
ss << std::this_thread::get_id();
return ss.str();
}
void set_session_id(std::string session_id) {
if (session_id.empty()) {
throw std::invalid_argument("session_id is empty");
}
std::lock_guard<std::mutex> lock(mutex_);
sessions_[get_thread_id()] = session_id;
}
// Messages in buffer are flushed to the history and cleared from the buffer.
std::vector<std::string> get_buffer(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
if (buffers_.find(session_id) == buffers_.end()) {
throw std::invalid_argument("Invalid session_id: " + session_id);
}
std::vector<std::string> buffer = buffers_[session_id];
if (!buffers_[session_id].empty()) {
histories_[session_id].insert(histories_[session_id].end(),
buffers_[session_id].begin(),
buffers_[session_id].end());
buffers_[session_id].clear();
}
return buffer;
}
std::vector<std::string> get_history(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
if (histories_.find(session_id) == histories_.end()) {
throw std::invalid_argument("Invalid session_id: " + session_id);
}
return histories_[session_id];
}
void clear_buffer() {
std::lock_guard<std::mutex> lock(mutex_);
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
return;
}
auto session_id = sessions_[get_thread_id()];
if (!buffers_[session_id].empty()) {
histories_[session_id].insert(histories_[session_id].end(),
buffers_[session_id].begin(),
buffers_[session_id].end());
buffers_[session_id].clear();
}
}
void clear_history() {
std::lock_guard<std::mutex> lock(mutex_);
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
return;
}
auto session_id = sessions_[get_thread_id()];
histories_[session_id].clear();
}
void cleanup_session(const std::string& session_id) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = sessions_.begin(); it != sessions_.end();) {
if (it->second == session_id) {
it = sessions_.erase(it);
} else {
++it;
}
}
buffers_.erase(session_id);
histories_.erase(session_id);
}
std::vector<std::string> get_active_sessions() {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> result;
std::unordered_set<std::string> unique_sessions;
for (const auto& [thread_id, session_id] : sessions_) {
if (unique_sessions.insert(session_id).second) {
result.push_back(session_id);
}
}
return result;
}
};
static std::shared_ptr<spdlog::logger> logger = set_log_level(_print_level, _logfile_level);
} // namespace humanus

2
mcp

@ -1 +1 @@
Subproject commit 21dc5cb1448923abc2566568403d837f0a9da64a
Subproject commit 0dbcd1e6d5bef3443c9d4b3cff6c6e3adef72264

View File

@ -14,6 +14,7 @@
#include <memory>
#include <stdexcept>
#include <mutex>
#include <unordered_map>
// Check if Python is found
#ifdef PYTHON_FOUND
@ -22,13 +23,16 @@
/**
* @class python_interpreter
* @brief Python interpreter class for executing Python code
* @brief Python interpreter class for executing Python code with session support
*/
class python_interpreter {
private:
// Mutex to ensure thread safety of Python interpreter
mutable std::mutex py_mutex;
bool is_initialized;
// Map to store Python thread states for each session
mutable std::unordered_map<std::string, PyThreadState*> session_states;
public:
/**
@ -40,7 +44,9 @@ public:
Py_Initialize();
if (Py_IsInitialized()) {
is_initialized = true;
PyThreadState *_save = PyEval_SaveThread();
// Create main thread state
PyThreadState* main_thread_state = PyThreadState_Get();
PyThreadState_Swap(NULL);
} else {
std::cerr << "Failed to initialize Python interpreter" << std::endl;
}
@ -57,27 +63,69 @@ public:
#ifdef PYTHON_FOUND
if (is_initialized) {
std::lock_guard<std::mutex> lock(py_mutex);
// Clean up all session states
for (auto& pair : session_states) {
PyThreadState_Swap(pair.second);
PyThreadState_Clear(pair.second);
PyThreadState_Delete(pair.second);
}
session_states.clear();
Py_Finalize();
is_initialized = false;
}
#endif
}
/**
* @brief Get or create a thread state for a session
* @param session_id The session identifier
* @return PyThreadState for the session
*/
#ifdef PYTHON_FOUND
PyThreadState* get_session_state(const std::string& session_id) const {
std::lock_guard<std::mutex> lock(py_mutex);
// Check if session already exists
auto it = session_states.find(session_id);
if (it != session_states.end()) {
return it->second;
}
// Create new thread state for this session
PyThreadState* new_state = Py_NewInterpreter();
if (!new_state) {
throw std::runtime_error("Failed to create new Python interpreter for session" + session_id);
}
// Store and return the new state
session_states[session_id] = new_state;
return new_state;
}
#endif
/**
* @brief Execute Python code
* @brief Execute Python code in the context of a specific session
* @param input JSON object containing Python code
* @param session_id The session identifier
* @return JSON object with execution results
*/
mcp::json forward(const mcp::json& input) const {
mcp::json forward(const mcp::json& input, const std::string& session_id) const {
#ifdef PYTHON_FOUND
if (!is_initialized) {
return mcp::json{{"error", "Python interpreter not properly initialized"}};
}
// Acquire GIL lock
std::lock_guard<std::mutex> lock(py_mutex);
PyGILState_STATE gstate = PyGILState_Ensure();
// Get or create session thread state
PyThreadState* tstate = nullptr;
try {
tstate = get_session_state(session_id);
} catch (const std::exception& e) {
return mcp::json{{"error", e.what()}};
}
PyThreadState* old_state = PyThreadState_Swap(tstate);
mcp::json result_json;
try {
@ -87,13 +135,13 @@ public:
// Get main module and dictionary
PyObject *main_module = PyImport_AddModule("__main__");
if (!main_module) {
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to get Python main module"}};
}
PyObject *main_dict = PyModule_GetDict(main_module);
if (!main_dict) {
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to get Python main module dictionary"}};
}
@ -101,7 +149,7 @@ public:
PyObject *sys_module = PyImport_ImportModule("sys");
if (!sys_module) {
PyErr_Print();
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to import sys module"}};
}
@ -109,7 +157,7 @@ public:
if (!io_module) {
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to import io module"}};
}
@ -119,7 +167,7 @@ public:
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to get StringIO class"}};
}
@ -130,7 +178,7 @@ public:
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to create stdout StringIO object"}};
}
@ -141,7 +189,7 @@ public:
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to create stderr StringIO object"}};
}
@ -161,7 +209,7 @@ public:
Py_DECREF(io_module);
Py_DECREF(sys_module);
PyErr_Print();
PyGILState_Release(gstate);
PyThreadState_Swap(old_state);
return mcp::json{{"error", "Failed to set stdout/stderr redirection"}};
}
@ -237,11 +285,32 @@ public:
result_json["error"] = std::string("Python execution exception: ") + e.what();
}
// Release GIL
PyGILState_Release(gstate);
// Restore previous thread state
PyThreadState_Swap(old_state);
return result_json;
#else
return mcp::json{{"error", "Python interpreter not available"}};
#endif
}
/**
* @brief Clean up a session and remove its thread state
* @param session_id The session identifier to clean up
*/
void cleanup_session(const std::string& session_id) {
#ifdef PYTHON_FOUND
std::lock_guard<std::mutex> lock(py_mutex);
auto it = session_states.find(session_id);
if (it != session_states.end()) {
PyThreadState* old_state = PyThreadState_Swap(it->second);
PyThreadState_Clear(it->second);
PyThreadState_Delete(it->second);
PyThreadState_Swap(old_state);
session_states.erase(it);
}
#endif
}
};
@ -250,14 +319,14 @@ public:
static python_interpreter interpreter;
// Python execution tool handler function
mcp::json python_execute_handler(const mcp::json& args) {
mcp::json python_execute_handler(const mcp::json& args, const std::string& session_id) {
if (!args.contains("code")) {
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'code' parameter");
}
try {
// Use Python interpreter to execute code
mcp::json result = interpreter.forward(args);
// Use Python interpreter to execute code with session context
mcp::json result = interpreter.forward(args, session_id);
return {{
{"type", "text"},
@ -277,4 +346,9 @@ void register_python_execute_tool(mcp::server& server) {
.build();
server.register_tool(python_tool, python_execute_handler);
// Register session cleanup handler
server.register_session_cleanup("python_execute", [](const std::string& session_id) {
interpreter.cleanup_session(session_id);
});
}

View File

@ -6,20 +6,16 @@
namespace humanus {
std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print_level,
spdlog::level::level_enum logfile_level,
std::string name) {
_print_level = print_level;
std::shared_ptr<spdlog::logger> set_log_level(spdlog::level::level_enum print_level, spdlog::level::level_enum logfile_level) {
auto current_date = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(current_date);
std::stringstream ss;
std::tm tm_info = *std::localtime(&in_time_t);
ss << std::put_time(&tm_info, "%Y%m%d");
std::string formatted_date = ss.str(); // YYYYMMDD
ss << std::put_time(&tm_info, "%Y-%m-%d");
std::string formatted_date = ss.str(); // YYYY-MM-DD
std::string log_name = name.empty() ? formatted_date : name + "_" + formatted_date;
std::string log_name = formatted_date;
std::string log_file_path = (PROJECT_ROOT / "logs" / (log_name + ".log")).string();
// Ensure the log directory exists
@ -29,13 +25,18 @@ std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print
std::shared_ptr<spdlog::logger> _logger = std::make_shared<spdlog::logger>(log_name);
auto stderr_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
stderr_sink->set_level(print_level);
stderr_sink->set_level(_print_level);
_logger->sinks().push_back(stderr_sink);
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file_path, false);
file_sink->set_level(logfile_level);
file_sink->set_level(_logfile_level);
_logger->sinks().push_back(file_sink);
auto session_sink = SessionSink::get_instance();
session_sink->set_level(_print_level);
_logger->sinks().push_back(session_sink);
// Reset the log output
return _logger;
}