diff --git a/include/config.hpp b/include/config.hpp index ecf3e1b..f7b8876 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,9 +1,12 @@ +#ifndef CONFIG_HPP +#define CONFIG_HPP + #include class Config { public: static void create_config(); - static nlohmann::json get_config(); + static nlohmann::json get_config(std::string config_file_path); static void open_config_menu(); private: @@ -23,3 +26,5 @@ class Config { static void remove_run_scripts_script(std::string repo, std::string script_path); static void remove_token(std::string repo, std::string token); }; + +#endif diff --git a/include/endpoints/run-scripts.hpp b/include/endpoints/run-scripts.hpp index ef3e53e..e247aaf 100644 --- a/include/endpoints/run-scripts.hpp +++ b/include/endpoints/run-scripts.hpp @@ -1,5 +1,10 @@ +#ifndef RUN_SCRIPTS_HPP +#define RUN_SCRIPTS_HPP + #include #include #include crow::response run_scripts(const nlohmann::json &, const nlohmann::json &,const crow::request &); + +#endif diff --git a/include/endpoints/update-files.hpp b/include/endpoints/update-files.hpp index e9677d0..7e9825c 100644 --- a/include/endpoints/update-files.hpp +++ b/include/endpoints/update-files.hpp @@ -1,5 +1,10 @@ +#ifndef UPDATE_FILES_HPP +#define UPDATE_FILES_HPP + #include #include #include crow::response update_files(const nlohmann::json &, const nlohmann::json &, const crow::request &); + +#endif diff --git a/include/logger.hpp b/include/logger.hpp new file mode 100644 index 0000000..bce0520 --- /dev/null +++ b/include/logger.hpp @@ -0,0 +1,20 @@ +#ifndef LOGGER_HPP +#define LOGGER_HPP + +#include + +class Logger { + public: + static void init(std::string log_file_path); + static void info(std::string message); + static void warn(std::string message); + static void error(std::string message); + static void fatal(std::string message); + static void success(std::string message); + static void code(std::string message); + private: + static void log(std::string message, std::string level); + static std::ofstream log_file; +}; + +#endif diff --git a/include/routes.hpp b/include/routes.hpp index 680e016..5241e3d 100644 --- a/include/routes.hpp +++ b/include/routes.hpp @@ -1,3 +1,6 @@ +#ifndef ROUTES_HPP +#define ROUTES_HPP + #include #include @@ -8,3 +11,5 @@ class Routes { private: crow::SimpleApp app; }; + +#endif diff --git a/src/config.cpp b/src/config.cpp index dcb110f..3937349 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,6 +3,8 @@ #include #include +#include "logger.hpp" + void Config::create_config() { std::cout << "Creating config file" << std::endl; nlohmann::json config = { @@ -14,8 +16,8 @@ void Config::create_config() { if (!std::filesystem::exists("/services/gh-wh-handler")) { try { std::filesystem::create_directories("/services/gh-wh-handler"); - } catch (std::exception& e) { - std::cerr << "Error creating directory '/services/gh-wh-handler/': " << e.what() << std::endl; + } catch (std::exception &e) { + Logger::error("[Config] Error creating directory '/services/gh-wh-handler/': " + std::string(e.what())); return; } } @@ -24,23 +26,29 @@ void Config::create_config() { config_file << config.dump(2); config_file.close(); } catch (std::exception& e) { - std::cerr << "Error creating config file: " << e.what() << std::endl; + Logger::error("[Config] Error creating config file: " + std::string(e.what())); } } -nlohmann::json Config::get_config() { +nlohmann::json Config::get_config(std::string config_file_path) { + Logger::info("[Config] Loading config file: " + config_file_path); + nlohmann::json config; try { - std::ifstream config_file("/services/gh-wh-handler/config.json"); + std::ifstream config_file(config_file_path); config_file >> config; config_file.close(); } catch (std::exception& e) { - std::cerr << "Error loading config file: " << e.what() << std::endl; + Logger::error("Error loading config file: " + std::string(e.what())); } + Logger::success("[Config] Loaded config file: " + config_file_path); + Logger::info("[Config] Loaded config: "); + Logger::code(config.dump(2)); + return config; } void Config::open_config_menu() { - std::cout << "Not implemented yet" << std::endl; + Logger::warn("[Config] Config menu not implemented yet"); } diff --git a/src/endpoints/run-scripts.cpp b/src/endpoints/run-scripts.cpp index 4fd7ccd..3f6bffb 100644 --- a/src/endpoints/run-scripts.cpp +++ b/src/endpoints/run-scripts.cpp @@ -2,6 +2,10 @@ crow::response run_scripts(const nlohmann::json &run_scripts, const nlohmann::json &tokens, const crow::request &req) { // TODO: Implement run_scripts - return crow::response(501); + nlohmann::json response = { + {"status", 501}, + {"error", "Not Implemented"} + }; + return crow::response(501, response.dump()); } diff --git a/src/endpoints/update-files.cpp b/src/endpoints/update-files.cpp index 6377da0..6af1654 100644 --- a/src/endpoints/update-files.cpp +++ b/src/endpoints/update-files.cpp @@ -1,12 +1,14 @@ #include "endpoints/update-files.hpp" #include +#include "logger.hpp" + crow::response update_files(const nlohmann::json& config_update_files, const nlohmann::json& config_tokens, const crow::request& req) { nlohmann::json payload; try { payload = nlohmann::json::parse(req.body); } catch (nlohmann::json::parse_error& e) { - std::cerr << "Error parsing payload: " << e.what() << std::endl; + Logger::error("[/update-files] Error parsing payload: " + std::string(e.what())); nlohmann::json response = { {"status", 400}, {"error", "Error parsing payload"} @@ -27,7 +29,7 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo is_private = payload["repository"]["private"]; if (is_private) { if (config_tokens.find(repo) == config_tokens.end()) { - printf("No token configured for private repo %s\n", repo.c_str()); + Logger::warn("[/update-files] No token configured for private repo " + repo); nlohmann::json response = { {"status", 403}, {"error", "No token configured for private repo"} @@ -37,7 +39,7 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo token = config_tokens[repo]; } } catch (nlohmann::json::out_of_range& e) { - std::cerr << "Error parsing payload: " << e.what() << std::endl; + Logger::error("[/update-files] Invalid JSON payload: " + std::string(e.what())); nlohmann::json response = { {"status", 400}, {"error", "Invalid JSON payload"} @@ -45,10 +47,10 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo return crow::response(400, response.dump()); } - printf("Received push to %s:%s (private: %s)\n", repo.c_str(), ref.c_str(), is_private ? "true" : "false"); + Logger::info("[/update-files] Received push to " + repo + ":" + ref + " (private: " + (is_private ? "true" : "false") + ")"); if (config_update_files.find(repo) == config_update_files.end()) { - printf("No update-files webhook configuration for repo %s\n", repo.c_str()); + Logger::warn("[/update-files] No update-files webhook configuration for repo " + repo); nlohmann::json response = { {"status", 404}, {"error", "No update-files webhook configuration for repo"} @@ -56,6 +58,8 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo return crow::response(404, response.dump()); } + Logger::info("[/update-files] Found update-files webhook configuration for repo " + repo); + nlohmann::json config; bool found = false; for (auto c_repo = config_update_files.begin(); c_repo != config_update_files.end(); ++c_repo) { @@ -65,7 +69,7 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo found = true; } if (!found) { - printf("No update-files webhook configuration for repo %s:%s\n", repo.c_str(), ref.c_str()); + Logger::warn("[/update-files] No update-files webhook configuration for branch " + ref); nlohmann::json response = { {"status", 404}, {"error", "No update-files webhook configuration for branch" + ref} @@ -73,11 +77,13 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo return crow::response(404, response.dump()); } + Logger::info("[/update-files] Found update-files webhook configuration for branch " + ref); + if (config["files"].empty()) { - printf("No files configured for repo %s:%s\n", repo.c_str(), ref.c_str()); + Logger::warn("[/update-files] No files configured for repo " + repo + ":" + ref); nlohmann::json response = { {"status", 404}, - {"error", "No files configured for branch" + ref} + {"error", "No files configured for repo" + repo + ":" + ref} }; return crow::response(404, response.dump()); } @@ -98,9 +104,23 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo } } + Logger::info("[/update-files] Found " + std::to_string(modified_files.size()) + " files to update for repo " + repo + ":" + ref); + + if (modified_files.empty()) { + Logger::info("[/update-files] No files to update for repo " + repo + ":" + ref); + nlohmann::json response = { + {"status", 200}, + {"message", "No files to update"} + }; + return crow::response(200, response.dump()); + } + + Logger::info("[/update-files] Updating " + std::to_string(modified_files.size()) + " files for repo " + repo + ":" + ref); + nlohmann::json response = { {"status", 200}, {"message", "OK"}, + {"file_count", 0}, {"updated-files", nlohmann::json::array()} }; for (auto &file : modified_files) { @@ -110,33 +130,34 @@ crow::response update_files(const nlohmann::json& config_update_files, const nlo try { std::filesystem::create_directories(local_path.substr(0, local_path.find_last_of('/'))); } catch (const std::exception &e) { - std::cerr << "Failed to create directories for " << local_path << ": " << e.what() << std::endl; + Logger::error("[/update-files] Failed to create directories for " + local_path + ": " + std::string(e.what())); continue; } std::string command = "curl -s https://raw.githubusercontent.com/" + repo + "/" + ref + "/" + remote_path + " -o " + local_path; if (is_private) command += " -H 'Authorization: token " + token + "'"; std::system(command.c_str()); - printf("%s %s\n", file[1] == "added" ? "Created" : "Updated", local_path.c_str()); + Logger::info("[/update-files] " + std::string(file[1] == "added" ? "Created" : "Updated") + " " + local_path); response["file_count"] = response["file_count"].get() + 1; - response["updated"].push_back(remote_path); + response["updated-files"].push_back(remote_path); } + Logger::success("[/update-files] Finished updating " + std::to_string(modified_files.size()) + " files for repo " + repo + ":" + ref); + Logger::info("[/update-files] Running post-update actions for repo " + repo + ":" + ref); + for (auto &c_action : config_update_files[repo]["post-update"]) { std::string action = c_action.get(); - printf("Running post-update action: %s\n", action.c_str()); + Logger::info("[/update-files] Running post-update action: " + action); int return_code = std::system(action.c_str()); - std::ofstream log_file("/var/log/gh-wh-handler.log", std::ios_base::app); - time_t now = time(0); if (return_code == 0) { - printf("Post-update action %s ran successfully\n", action.c_str()); - log_file << ctime(&now) << "Post-update action " << action << " ran successfully\n"; + Logger::success("[/update-files] Post-update action " + action + " ran successfully"); } else { - printf("Post-update action %s failed with return code %d\n", action.c_str(), return_code); - log_file << ctime(&now) << "Post-update action " << action << " failed with return code " << return_code << "\n"; + Logger::error("[/update-files] Post-update action " + action + " failed with return code " + std::to_string(return_code)); } } + Logger::success("[/update-files] Finished running post-update actions for repo " + repo + ":" + ref); + return crow::response(200, response.dump()); } diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 0000000..14ec31e --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,124 @@ +#include "logger.hpp" +#include +#include +#include +#include +#include + +#define COLORS_RESET "\033[0m" +#define COLORS_BOLD "\033[1m" +#define COLORS_DIM "\033[2m" +#define COLORS_UNDERLINED "\033[4m" +#define COLORS_BLINK "\033[5m" +#define COLORS_REVERSE "\033[7m" +#define COLORS_HIDDEN "\033[8m" + +#define COLORS_FG_BLACK "\033[30m" +#define COLORS_FG_RED "\033[31m" +#define COLORS_FG_GREEN "\033[32m" +#define COLORS_FG_YELLOW "\033[33m" +#define COLORS_FG_BLUE "\033[34m" +#define COLORS_FG_MAGENTA "\033[35m" +#define COLORS_FG_CYAN "\033[36m" +#define COLORS_FG_WHITE "\033[37m" + +#define COLORS_BG_BLACK "\033[40m" +#define COLORS_BG_RED "\033[41m" +#define COLORS_BG_GREEN "\033[42m" +#define COLORS_BG_YELLOW "\033[43m" +#define COLORS_BG_BLUE "\033[44m" +#define COLORS_BG_MAGENTA "\033[45m" +#define COLORS_BG_CYAN "\033[46m" +#define COLORS_BG_WHITE "\033[47m" + +std::ofstream Logger::log_file; + +void Logger::init(std::string log_file_path) { + std::cout << "Initializing logger" << std::endl; + std::cout << "Log file: " << log_file_path << std::endl; + Logger::log_file.open(log_file_path, std::ios::app); + if (!Logger::log_file.is_open()) { + std::cerr << "Error opening log file" << std::endl; + } + Logger::success("Logger initialized"); +} + +void Logger::info(std::string message) { + Logger::log(message, "INFO "); +} + +void Logger::warn(std::string message) { + Logger::log(message, "WARN "); +} + +void Logger::error(std::string message) { + Logger::log(message, "ERROR "); +} + +void Logger::fatal(std::string message) { + Logger::log(message, "FATAL "); +} + +void Logger::success(std::string message) { + Logger::log(message, "SUCCESS "); +} + +void Logger::code(std::string message) { + Logger::log(message, "CODE"); +} + +void Logger::log(std::string message, std::string level) { + // Implement logger with terminal colors if terminal supports it + std::string formatted_message = ""; + if (isatty(fileno(stdout))) { + if (level == "INFO ") { + formatted_message += COLORS_FG_GREEN; + } else if (level == "WARN ") { + formatted_message += COLORS_FG_YELLOW; + } else if (level == "ERROR ") { + formatted_message += COLORS_FG_RED; + } else if (level == "FATAL ") { + formatted_message += COLORS_FG_RED; + formatted_message += COLORS_BOLD; + } else if (level == "SUCCESS ") { + formatted_message += COLORS_FG_GREEN; + formatted_message += COLORS_BOLD; + } else if (level == "CODE") { + formatted_message += COLORS_FG_WHITE; + formatted_message += COLORS_DIM; + } + } + + std::time_t now = std::time(nullptr); + std::tm *now_tm = std::localtime(&now); + char time_buffer[80]; + std::strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", now_tm); + formatted_message += "(" + std::string(time_buffer) + ") "; + + if (level == "CODE") { + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + int term_width = w.ws_col; + formatted_message += "\n" + std::string(term_width - 1, '=') + "\n" + message + "\n" + std::string(term_width - 1, '='); + } else { + formatted_message += "[" + level + "] " + message; + } + if (isatty(fileno(stdout))) { + formatted_message += COLORS_RESET; + } + std::cout << formatted_message << std::endl; + if (level == "CODE") { + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + int term_width = w.ws_col; + Logger::log_file << std::string(term_width - 1, '=') << std::endl << message << std::endl << std::string(term_width - 1, '=') << std::endl; + } else { + Logger::log_file << "[" << level << "] " << message << std::endl; + } + + Logger::log_file.flush(); + + if (level == "FATAL") { + std::exit(1); + } +} diff --git a/src/main.cpp b/src/main.cpp index 6bd64b0..521ef65 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include #include +#include "logger.hpp" #include "routes.hpp" #include "config.hpp" @@ -14,25 +15,33 @@ void signal_handler(const int signum) { int main(int argc, char **argv) { // Check for config file argument, exit if it's not there - if (argc < 2 || argc > 2) { - std::cerr << "Usage: " << 0[argv] << " " << std::endl; + if (argc < 2 || argc > 3) { + std::cerr << "Usage: " << 0[argv] << " [--config]" << std::endl; return 1; } - if (std::string(argv[1]) == "config") { + std::time_t now = std::time(nullptr); + std::tm *now_tm = std::localtime(&now); + char time_buffer[80]; + std::strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d_%H-%M-%S", now_tm); + + std::string config_file_path = 1[argv]; + std::string log_file_path = config_file_path.substr(0, config_file_path.find_last_of("/")) + "/gh-wh-handler_" + time_buffer + ".log"; + Logger::init(log_file_path); + + if (argc == 3 && std::string(argv[2]) == "--config") { + Config::open_config_menu(); return 0; } - std::string config_file_path = 1[argv]; - // Check if config file exists if (!std::filesystem::exists(config_file_path)) { - std::cerr << "Config file does not exist, creating..." << std::endl; + Logger::warn("Config file does not exist, creating..."); Config::create_config(); } // Load configuration - nlohmann::json config = Config::get_config(); + nlohmann::json config = Config::get_config(config_file_path); std::signal(SIGINT, signal_handler); diff --git a/src/routes.cpp b/src/routes.cpp index d09bac2..2a2ebb9 100644 --- a/src/routes.cpp +++ b/src/routes.cpp @@ -3,11 +3,15 @@ #include "endpoints/run-scripts.hpp" #include +#include "logger.hpp" + Routes::Routes(nlohmann::json config) { + Logger::info("Partitioning configuration"); const nlohmann::json config_update_files = config["update-files"]; const nlohmann::json config_run_scripts = config["run-scripts"]; const nlohmann::json config_tokens = config["tokens"]; + Logger::info("Registering route \"/\""); CROW_ROUTE(this->app, "/") .methods("POST"_method) .name("Ping") @@ -19,6 +23,7 @@ Routes::Routes(nlohmann::json config) { return crow::response(200, response.dump()); }); + Logger::info("Registering route \"/update-files\""); CROW_ROUTE(this->app, "/update-files") .methods("POST"_method) .name("Update Files") @@ -26,7 +31,7 @@ Routes::Routes(nlohmann::json config) { try { return update_files(config_update_files, config_tokens, req); } catch (const std::exception &e) { - std::cerr << "Unknown error in update_files: " << e.what() << std::endl; + Logger::error("Unknown error in update_files: " + std::string(e.what())); nlohmann::json response = { {"status", 500}, {"error", "Internal server error"} @@ -35,6 +40,7 @@ Routes::Routes(nlohmann::json config) { } }); + Logger::info("Registering route \"/run-scripts\""); CROW_ROUTE(this->app, "/run-scripts") .methods("POST"_method) .name("Run Scripts") @@ -42,7 +48,7 @@ Routes::Routes(nlohmann::json config) { try { return run_scripts(config_run_scripts, config_tokens, req); } catch (const std::exception &e) { - std::cerr << "Unknown error in run_scripts: " << e.what() << std::endl; + Logger::error("Unknown error in run_scripts: " + std::string(e.what())); nlohmann::json response = { {"status", 500}, {"error", "Internal server error"} @@ -51,5 +57,7 @@ Routes::Routes(nlohmann::json config) { } }); + Logger::info("Starting server"); this->app.port(config["port"].get()).multithreaded().run(); + Logger::info("Server stopped"); }