diff --git a/CMakeLists.txt b/CMakeLists.txt index 635027f..5c15843 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,11 @@ target_include_directories(${PROJECT_NAME} PRIVATE find_package(OpenSSL REQUIRED) find_package(Drogon CONFIG REQUIRED) find_package(nlohmann_json REQUIRED) +find_package(spdlog REQUIRED) # Link libraries for main executable target_link_libraries(${PROJECT_NAME} PRIVATE - nlohmann_json::nlohmann_json Drogon::Drogon + nlohmann_json::nlohmann_json Drogon::Drogon spdlog::spdlog /usr/local/lib/libpgcommon.a /usr/local/lib/libpgport.a lzma dl) diff --git a/src/config.cpp b/src/config.cpp index e3f1e6b..c2b8254 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,4 +1,5 @@ #include "config.hpp" +#include "logger.hpp" #include #include #include @@ -11,17 +12,17 @@ namespace simple_object_storage { bool load_config(const std::string& config_path, ServerConfig& config) { try { if (config_path.empty()) { - std::cerr << "Config path is empty" << std::endl; + LOG_ERROR("Config path is empty"); return false; } if (!std::filesystem::exists(config_path)) { - std::cerr << "Config file does not exist: " << config_path << std::endl; + LOG_ERROR("Config file does not exist: {}", config_path); return false; } std::ifstream file(config_path); if (!file.is_open()) { - std::cerr << "Failed to open config file: " << config_path << std::endl; + LOG_ERROR("Failed to open config file: {}", config_path); return false; } @@ -78,11 +79,22 @@ bool load_config(const std::string& config_path, ServerConfig& config) { } } + // Parse logging configuration + if (j.contains("logging")) { + const auto& logging = j["logging"]; + if (logging.contains("log_file_path")) { + config.log_file_path = logging["log_file_path"].get(); + } + if (logging.contains("log_level")) { + config.log_level = logging["log_level"].get(); + } + } + // Line number accuracy improved return true; } catch (const std::exception& e) { - std::cerr << "Error parsing config file: " << e.what() << std::endl; + LOG_ERROR("Error parsing config file: {}", e.what()); return false; } } diff --git a/src/config.hpp b/src/config.hpp index facc65c..9c82793 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -22,6 +22,10 @@ struct ServerConfig { // Rate limiting configuration int auth_rate_limit = 5; // Maximum number of auth attempts int auth_window_seconds = 300; // Time window in seconds (5 minutes) + + // Logging configuration + std::filesystem::path log_file_path = "/data/sos.log"; // Path to log file (empty string disables file logging) + std::string log_level = "info"; // Log level: trace, debug, info, warn, error, critical }; bool load_config(const std::string& config_path, ServerConfig& config); diff --git a/src/logger.hpp b/src/logger.hpp new file mode 100644 index 0000000..c43e495 --- /dev/null +++ b/src/logger.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include + +namespace simple_object_storage { + +class Logger { +public: + static void init(const std::string& log_level = "info", + const std::string& log_file = "", + size_t max_file_size = 10 * 1024 * 1024, + size_t max_files = 3) { + + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::trace); + + std::vector sinks{console_sink}; + + if (!log_file.empty()) { + auto file_sink = std::make_shared( + log_file, max_file_size, max_files); + file_sink->set_level(spdlog::level::trace); + sinks.push_back(file_sink); + } + + auto logger = std::make_shared("sos", sinks.begin(), sinks.end()); + + if (log_level == "trace") { + logger->set_level(spdlog::level::trace); + } else if (log_level == "debug") { + logger->set_level(spdlog::level::debug); + } else if (log_level == "info") { + logger->set_level(spdlog::level::info); + } else if (log_level == "warn" || log_level == "warning") { + logger->set_level(spdlog::level::warn); + } else if (log_level == "error") { + logger->set_level(spdlog::level::err); + } else if (log_level == "critical") { + logger->set_level(spdlog::level::critical); + } else { + logger->set_level(spdlog::level::info); + } + + logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v"); + + spdlog::set_default_logger(logger); + spdlog::flush_every(std::chrono::seconds(3)); + } + + static std::shared_ptr get() { + return spdlog::default_logger(); + } +}; + +#ifdef LOG_TRACE +#undef LOG_TRACE +#endif +#ifdef LOG_DEBUG +#undef LOG_DEBUG +#endif +#ifdef LOG_INFO +#undef LOG_INFO +#endif +#ifdef LOG_WARN +#undef LOG_WARN +#endif +#ifdef LOG_ERROR +#undef LOG_ERROR +#endif + +#define LOG_TRACE(...) SPDLOG_TRACE(__VA_ARGS__) +#define LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__) +#define LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__) +#define LOG_WARN(...) SPDLOG_WARN(__VA_ARGS__) +#define LOG_ERROR(...) SPDLOG_ERROR(__VA_ARGS__) +#define LOG_CRITICAL(...) SPDLOG_CRITICAL(__VA_ARGS__) + +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 574ba39..228d86b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "server.hpp" #include "config.hpp" +#include "logger.hpp" #include #include #include @@ -40,9 +41,9 @@ std::filesystem::path get_config_path() { return path; } } - std::cout << "No config file found. Checked: " << std::endl; + LOG_ERROR("No config file found. Checked:"); for (const auto& path : config_paths) { - std::cout << " " << path << std::endl; + LOG_ERROR(" {}", path.string()); } return std::filesystem::path(); } @@ -52,19 +53,25 @@ bool initialize_server() { if (config_path.empty()) { return false; } - std::cout << "Config file: " << config_path << std::endl; - + ServerConfig config; if (!simple_object_storage::load_config(config_path, config)) { - std::cout << "Config file at " << config_path << " is not valid." << std::endl; + LOG_ERROR("Config file at {} is not valid.", config_path.string()); return false; } - - std::cout << "Starting server at " << config.host << ":" << config.port << std::endl; - std::cout << "Object store path: " << config.object_store_path << std::endl; + + // Reinitialize logger with config settings + Logger::init(config.log_level, config.log_file_path.string()); + + LOG_INFO("Config file: {}", config_path.string()); + LOG_INFO("Starting server at {}:{}", config.host, config.port); + LOG_INFO("Object store path: {}", config.object_store_path.string()); + if (!config.log_file_path.empty()) { + LOG_INFO("Log file: {}", config.log_file_path.string()); + } if (!std::filesystem::exists(config.object_store_path)) { - std::cout << "Object store path does not exist: " << config.object_store_path << std::endl; + LOG_ERROR("Object store path does not exist: {}", config.object_store_path.string()); return false; } @@ -74,7 +81,7 @@ bool initialize_server() { void stop_server() { if (g_server) { - std::cout << "Stopping server..." << std::endl; + LOG_INFO("Stopping server..."); g_server->stop(); if (g_server_thread.joinable()) { g_server_thread.join(); @@ -86,28 +93,30 @@ void stop_server() { void signal_handler(int signal) { switch (signal) { case SIGHUP: - std::cout << "Received SIGHUP signal - reloading configuration" << std::endl; + LOG_INFO("Received SIGHUP signal - reloading configuration"); stop_server(); if (!initialize_server()) { - std::cerr << "Failed to restart server with new configuration" << std::endl; + LOG_ERROR("Failed to restart server with new configuration"); g_running = false; } break; case SIGTERM: case SIGINT: - std::cout << "Received termination signal" << std::endl; + LOG_INFO("Received termination signal"); g_running = false; break; } } int main(int argc, char* argv[]) { + // Initialize logger with defaults (will be reconfigured after loading config) + Logger::init("info", ""); + if (argc > 1) { if (std::string(argv[1]) == "version") { std::cout << dropshell::VERSION << std::endl; return 0; } - std::cout << "Simple Object Storage version: " << dropshell::VERSION << std::endl; } // Set up signal handlers @@ -117,15 +126,15 @@ int main(int argc, char* argv[]) { sa.sa_flags = 0; if (sigaction(SIGHUP, &sa, nullptr) == -1) { - std::cerr << "Failed to set up SIGHUP handler" << std::endl; + LOG_ERROR("Failed to set up SIGHUP handler"); return 1; } if (sigaction(SIGTERM, &sa, nullptr) == -1) { - std::cerr << "Failed to set up SIGTERM handler" << std::endl; + LOG_ERROR("Failed to set up SIGTERM handler"); return 1; } if (sigaction(SIGINT, &sa, nullptr) == -1) { - std::cerr << "Failed to set up SIGINT handler" << std::endl; + LOG_ERROR("Failed to set up SIGINT handler"); return 1; } @@ -133,10 +142,12 @@ int main(int argc, char* argv[]) { if (!initialize_server()) { return 1; } + + LOG_INFO("Simple Object Storage version: {}", dropshell::VERSION); // Start server in main thread if (!g_server->start()) { - std::cerr << "Failed to start server" << std::endl; + LOG_ERROR("Failed to start server"); return 1; } diff --git a/src/put_handler.cpp b/src/put_handler.cpp index c03f024..8af95a1 100644 --- a/src/put_handler.cpp +++ b/src/put_handler.cpp @@ -1,4 +1,5 @@ #include "put_handler.hpp" +#include "logger.hpp" #include "hash.hpp" #include "compress.hpp" #include "string_utils.hpp" @@ -279,7 +280,7 @@ void PutHandler::handle_upload_object(const drogon::HttpRequestPtr& req, std::fu std::filesystem::rename(temp_path, final_path); temp_file_deleter.release(); } catch (const std::filesystem::filesystem_error& e) { - std::cerr << "Error renaming temp file: " << e.what() << std::endl; + LOG_ERROR("Error renaming temp file: {}", e.what()); resp->setStatusCode(drogon::k500InternalServerError); nlohmann::json response = {{"result", "error"}, {"error", "Failed to store object file"}}; resp->setBody(response.dump()); diff --git a/src/server.cpp b/src/server.cpp index bd4f92d..93f02e0 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -10,6 +10,7 @@ #include #include "server.hpp" +#include "logger.hpp" #include "hash.hpp" #include "compress.hpp" #include "string_utils.hpp" // Include the new utility header @@ -34,7 +35,7 @@ bool Server::init_db() { db_ = std::make_unique(db_path); return true; } catch (const std::runtime_error& e) { - std::cerr << "Database initialization error: " << e.what() << std::endl; + LOG_ERROR("Database initialization error: {}", e.what()); return false; } } @@ -114,7 +115,7 @@ Server::Server(const ServerConfig& config) Server::setInstance(this); if (!std::filesystem::exists(config_.object_store_path)) { - std::cerr << "Object store directory does not exist: " << config_.object_store_path << std::endl; + LOG_ERROR("Object store directory does not exist: {}", config_.object_store_path.string()); return; } @@ -146,7 +147,7 @@ Server::~Server() { bool Server::start() { if (!db_) { // Check if DB initialization failed - std::cerr << "Database is not initialized. Cannot start server." << std::endl; + LOG_ERROR("Database is not initialized. Cannot start server."); return false; } setup_routes(); @@ -167,7 +168,7 @@ bool Server::start() { drogon::app().run(); } catch (const std::exception& e) { running_ = false; - std::cerr << "Failed to start server: " << e.what() << std::endl; + LOG_ERROR("Failed to start server: {}", e.what()); return false; } return true; @@ -177,7 +178,7 @@ void Server::stop() { if (running_) { drogon::app().quit(); running_ = false; - std::cout << "Server stopped." << std::endl; + LOG_INFO("Server stopped."); } } @@ -379,7 +380,7 @@ void Server::handle_get_metadata(const drogon::HttpRequestPtr& req, std::functio response = {{"result", "success"}, {"metadata", entry.metadata}}; resp->setBody(response.dump()); } catch (const nlohmann::json::exception& e) { - std::cerr << "Error serializing metadata for hash " << hash_str << ": " << e.what() << std::endl; + LOG_ERROR("Error serializing metadata for hash {}: {}", hash_str, e.what()); resp->setStatusCode(drogon::k500InternalServerError); response = {{"result", "error"}, {"error", "Internal server error: Failed to serialize metadata"}}; resp->setBody(response.dump()); @@ -455,7 +456,7 @@ void Server::handle_delete_object(const drogon::HttpRequestPtr& req, std::functi try { std::filesystem::remove(file_path); } catch (const std::filesystem::filesystem_error& e) { - std::cerr << "Error deleting object file: " << e.what() << std::endl; + LOG_ERROR("Error deleting object file: {}", e.what()); resp->setStatusCode(drogon::k500InternalServerError); nlohmann::json response = {{"result", "error"}, {"error", "Failed to delete object file: " + std::string(e.what())}}; resp->setBody(response.dump()); diff --git a/src/temp_directory.cpp b/src/temp_directory.cpp index 7c1465d..bbe25c9 100644 --- a/src/temp_directory.cpp +++ b/src/temp_directory.cpp @@ -1,4 +1,5 @@ #include "temp_directory.hpp" +#include "logger.hpp" #include #include @@ -42,13 +43,13 @@ TempDirectory::~TempDirectory() { std::error_code ec; // Use error code to avoid exceptions in destructor std::filesystem::remove_all(path_, ec); if (ec) { - std::cerr << "Error removing temporary directory " << path_.string() << ": " << ec.message() << std::endl; + LOG_ERROR("Error removing temporary directory {}: {}", path_.string(), ec.message()); } } } catch (const std::exception& e) { // Catch potential exceptions from exists() though unlikely - std::cerr << "Error during temporary directory cleanup for " << path_.string() << ": " << e.what() << std::endl; + LOG_ERROR("Error during temporary directory cleanup for {}: {}", path_.string(), e.what()); } catch (...) { - std::cerr << "Unknown error during temporary directory cleanup for " << path_.string() << std::endl; + LOG_ERROR("Unknown error during temporary directory cleanup for {}", path_.string()); } } diff --git a/src/utils.cpp b/src/utils.cpp index a844620..c1ddc93 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,4 +1,5 @@ #include "utils.hpp" +#include "logger.hpp" namespace simple_object_storage { @@ -18,7 +19,7 @@ namespace simple_object_storage } catch (const std::filesystem::filesystem_error &e) { - std::cerr << "Error deleting temp file: " << path_ << " - " << e.what() << std::endl; + LOG_ERROR("Error deleting temp file: {} - {}", path_.string(), e.what()); } } } diff --git a/testing/sos_config.json b/testing/sos_config.json index 24b2d14..019d8f1 100644 --- a/testing/sos_config.json +++ b/testing/sos_config.json @@ -8,6 +8,10 @@ "auth_rate_limit": 5, "auth_window_seconds": 2 }, + "logging": { + "log_file_path": "/data/test.log", + "log_level": "info" + }, "port": 7703, "host": "127.0.0.1" } \ No newline at end of file diff --git a/testing/sos_config_docker.json b/testing/sos_config_docker.json index 24b2d14..019d8f1 100644 --- a/testing/sos_config_docker.json +++ b/testing/sos_config_docker.json @@ -8,6 +8,10 @@ "auth_rate_limit": 5, "auth_window_seconds": 2 }, + "logging": { + "log_file_path": "/data/test.log", + "log_level": "info" + }, "port": 7703, "host": "127.0.0.1" } \ No newline at end of file