diff --git a/Makefile b/Makefile index 5cfe469..3fd6df7 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ SRCS = $(SRCS_DIR)/main.cpp \ $(SRCS_DIR)/core/Server.cpp \ $(SRCS_DIR)/core/Client.cpp \ $(SRCS_DIR)/core/Channel.cpp \ + $(SRCS_DIR)/core/Logger.cpp \ $(SRCS_DIR)/network/PollSocketManager.cpp \ $(SRCS_DIR)/network/MessageBuffer.cpp \ $(SRCS_DIR)/commands/ACommand.cpp \ diff --git a/incs/core/Config.hpp b/incs/core/Config.hpp index 2a7227c..038a09c 100644 --- a/incs/core/Config.hpp +++ b/incs/core/Config.hpp @@ -10,10 +10,10 @@ class Config private: int m_port; std::string m_passwd; - Config(int port, std::string password); + Config(int port, const std::string& password); public: int getPort() const; const std::string& getPassword() const; static Config checkArgs(int argc, char** argv); -}; \ No newline at end of file +}; diff --git a/incs/core/Logger.hpp b/incs/core/Logger.hpp new file mode 100644 index 0000000..1111882 --- /dev/null +++ b/incs/core/Logger.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +class Logger +{ +public: + enum Level + { + DEBUG = 0, + NOTICE = 1, + INFO = 2, + WARNING = 3, + ERROR = 4, + CRITICAL = 5 + }; + +private: + Level m_minLevel; + std::ostream* m_output; + std::ostringstream m_buffer; + Level m_currentLevel; + bool m_lineStarted; + + Logger(); + ~Logger(); + Logger(const Logger&); + Logger& operator=(const Logger&); + + std::string getLevelString(Level level) const; + std::string getColorCode(Level level) const; + void flush(); + +public: + static Logger& getInstance(); + + void setMinLevel(Level level); + void setOutput(std::ostream& output); + + Logger& log(Level level); + Logger& debug(); + Logger& notice(); + Logger& info(); + Logger& warning(); + Logger& error(); + Logger& critical(); + + template < typename T > Logger& operator<<(const T& value) + { + if (!m_lineStarted) + { + m_buffer << "[ " << getColorCode(m_currentLevel) << getLevelString(m_currentLevel) + << getColorCode((Level)-1) << " ] "; + m_lineStarted = true; + } + m_buffer << value; + return *this; + } + + Logger& operator<<(std::ostream& (*manip)(std::ostream&)); +}; + +#define LOG_DEBUG Logger::getInstance().debug() +#define LOG_NOTICE Logger::getInstance().notice() +#define LOG_INFO Logger::getInstance().info() +#define LOG_WARNING Logger::getInstance().warning() +#define LOG_ERROR Logger::getInstance().error() +#define LOG_CRITICAL Logger::getInstance().critical() diff --git a/incs/core/Server.hpp b/incs/core/Server.hpp index 5669b93..8793d97 100644 --- a/incs/core/Server.hpp +++ b/incs/core/Server.hpp @@ -27,6 +27,15 @@ #include "core/IServer.hpp" #include "network/ISocketManager.hpp" +#define RESET "\033[0m" +#define RED "\033[91m" +#define GREEN "\033[92m" +#define YELLOW "\033[93m" +#define BLUE "\033[94m" +#define MAGENTA "\033[95m" +#define CYAN "\033[96m" +#define WHITE "\033[97m" + class Server : public IServer { private: diff --git a/incs/protocol/Message.hpp b/incs/protocol/Message.hpp index 1b66a91..25598fe 100644 --- a/incs/protocol/Message.hpp +++ b/incs/protocol/Message.hpp @@ -1,8 +1,10 @@ #pragma once #include "commands/CommandType.hpp" +#include #include #include + struct Message { std::string m_prefix; @@ -13,3 +15,5 @@ struct Message Message(); bool isValid() const; }; + +std::ostream& operator<<(std::ostream& stream, const Message& message); diff --git a/srcs/core/Config.cpp b/srcs/core/Config.cpp index 310d545..d844582 100644 --- a/srcs/core/Config.cpp +++ b/srcs/core/Config.cpp @@ -1,35 +1,52 @@ #include "Config.hpp" +#include + +#define PORT_MIN 1024 +#define PORT_MAX 65535 std::string parsePasswd(char* argv) { if (!argv) - throw(std::runtime_error("Password must not be null\n.")); + throw(std::runtime_error("Password must not be null")); std::string passwd = argv; if (passwd.empty() || (*argv == ' ' && strlen(argv) == 1)) - throw(std::runtime_error("Password must not be empty\n.")); + throw(std::runtime_error("Password must not be empty")); return (passwd); } int parsePort(char* argv) { if (!argv || *argv == '\0') - throw(std::runtime_error("Port must not be empty\n.")); + throw(std::runtime_error("Port must not be empty")); errno = 0; char* end = 0; long port = std::strtol(argv, &end, 10); if (errno != 0 || end == argv || *end != '\0') - throw(std::runtime_error("Port must be a valid number\n.")); - if (port < 1 || port > 65535) - throw(std::runtime_error("Port must be between 1 and 65535.")); + { + std::ostringstream oss; + oss << "Port must be a number.\nI know it's crazy, but there's nothing to listen to on " + << argv << " :o"; + throw(std::runtime_error(oss.str())); + } + if (port < PORT_MIN || port > PORT_MAX) + { + std::ostringstream oss; + oss << "Port must be " << PORT_MIN << " and " << PORT_MAX; + throw(std::runtime_error(oss.str())); + } return (static_cast< int >(port)); } Config Config::checkArgs(int argc, char** argv) { if (argc != 3) - throw(std::runtime_error("Number of arguments must be equal to 3\n.")); + { + std::ostringstream oss; + oss << "Usage: " << argv[0] << " "; + throw(std::runtime_error(oss.str())); + } int port = parsePort(argv[1]); std::string passwd = parsePasswd(argv[2]); @@ -48,6 +65,6 @@ const std::string& Config::getPassword() const return this->m_passwd; } -Config::Config(int port, std::string password) : m_port(port), m_passwd(password) +Config::Config(int port, const std::string& password) : m_port(port), m_passwd(password) { -} \ No newline at end of file +} diff --git a/srcs/core/Logger.cpp b/srcs/core/Logger.cpp new file mode 100644 index 0000000..c5dd334 --- /dev/null +++ b/srcs/core/Logger.cpp @@ -0,0 +1,124 @@ +#include "core/Logger.hpp" +#include + +Logger::Logger() + : m_minLevel(INFO), m_output(&std::cout), m_currentLevel(INFO), m_lineStarted(false) +{ +} + +Logger::~Logger() +{ + flush(); +} + +Logger& Logger::getInstance() +{ + static Logger instance; + return (instance); +} + +void Logger::setMinLevel(Level level) +{ + m_minLevel = level; +} + +void Logger::setOutput(std::ostream& output) +{ + m_output = &output; +} + +Logger& Logger::log(Level level) +{ + flush(); + m_currentLevel = level; + m_lineStarted = false; + return (*this); +} + +Logger& Logger::debug() +{ + return log(DEBUG); +} +Logger& Logger::notice() +{ + return log(NOTICE); +} +Logger& Logger::info() +{ + return log(INFO); +} +Logger& Logger::warning() +{ + return log(WARNING); +} +Logger& Logger::error() +{ + return log(ERROR); +} +Logger& Logger::critical() +{ + return log(CRITICAL); +} + +std::string Logger::getLevelString(Level level) const +{ + switch (level) + { + case DEBUG: + return "DEBUG"; + case NOTICE: + return "NOTICE"; + case INFO: + return "INFO"; + case WARNING: + return "WARN"; + case ERROR: + return "ERROR"; + case CRITICAL: + return "CRIT"; + default: + return "?????"; // if you got a better suggestion im all ears + } +} + +std::string Logger::getColorCode(Level level) const +{ + switch (level) + { + case DEBUG: + return "\033[36m"; // cyan + case NOTICE: + return "\033[96m"; // bright cyan + case INFO: + return "\033[32m"; // green + case WARNING: + return "\033[33m"; // yellow + case ERROR: + return "\033[31m"; // red + case CRITICAL: + return "\033[95m"; // bold Red + default: + return "\033[0m"; // reset + } +} + +void Logger::flush() +{ + if (m_lineStarted && m_currentLevel >= m_minLevel) + { + *m_output << m_buffer.str() << "\033[0m" << '\n'; + m_output->flush(); + } + m_buffer.str(""); + m_buffer.clear(); + m_lineStarted = false; +} + +Logger& Logger::operator<<(std::ostream& (*manip)(std::ostream&)) +{ + if (manip == static_cast< std::ostream& (*)(std::ostream&) >(std::endl)) + { + flush(); + } + return *this; +} diff --git a/srcs/core/Server.cpp b/srcs/core/Server.cpp index d3b06f9..07d0552 100644 --- a/srcs/core/Server.cpp +++ b/srcs/core/Server.cpp @@ -1,4 +1,5 @@ #include "core/Server.hpp" +#include "Logger.hpp" #include "core/Channel.hpp" #include "core/Client.hpp" #include "core/IClient.hpp" @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -41,6 +43,8 @@ void Server::deleteChannelIfEmpty(IChannel* channel) if (!channel->isEmpty()) return; m_channels.erase(channel->getName()); + LOG_INFO << "Channel " << channel->getName() << " was empty, so I went ahead and deleted it" + << std::endl; delete channel; } @@ -62,7 +66,8 @@ void Server::checkClientTimeouts() if (last_ping != 0 && (now - last_ping) > PONG_TIMEOUT) { - std::cout << "Client #" << client_fd << " didn't respond to ping. Killing it :D\n"; + LOG_NOTICE << "Client #" << client_fd << " didn't respond to ping. Killing it :D" + << std::endl; std::string error_msg = "ERROR :You didn't respond to my ping and that's mean :( " "Therefore you're gonna die :D\r\n"; send(client_fd, error_msg.c_str(), error_msg.length(), MSG_NOSIGNAL); @@ -71,8 +76,8 @@ void Server::checkClientTimeouts() } else if (last_ping == 0 && (now - last_activity) > PING_TIMEOUT) { - std::cout << "Client #" << client_fd << " has been idle for " << (now - last_activity) - << " seconds. Checking if it's still alive lol\n"; + LOG_NOTICE << "Client #" << client_fd << " has been idle for " << (now - last_activity) + << " seconds. Checking if it's still alive lol" << std::endl; std::ostringstream ping; ping << "PING :" << getServerName() << "\r\n"; client->getBuffer().appendWrite(ping.str()); @@ -95,6 +100,8 @@ IChannel* Server::createChannel(const std::string& name, IClient* creator) m_channels[name] = channel; channel->addMember(creator, ""); + LOG_INFO << "Channel " << channel->getName() << " has been created by " + << creator->getNickname() << " (#" << creator->getFd() << ")" << std::endl; return (channel); } @@ -159,13 +166,15 @@ void Server::onIrcLine(int fd, const std::string& line) if (!msg.isValid()) return; + LOG_DEBUG << "Processing message\n" << msg << std::endl; + ACommand* cmd = CommandFactory::getInstance().createCommand(msg.m_command_type, *this); if (!cmd) { - std::cout << "[DEBUG] Command not found: " << msg.m_command << ". Ignoring...\n"; + LOG_NOTICE << "Command not found: " << msg.m_command << ". Ignoring..." << std::endl; return; } - std::cout << "[DEBUG] Running " << cmd->getName() << "\n"; + LOG_INFO << "Running command: " << cmd->getName() << std::endl; cmd->execute(client, msg); delete cmd; @@ -215,7 +224,7 @@ void Server::disconnectClient(int fd) delete client; m_clients.erase(it); } - std::cout << "Disconnected : " << fd << "\n"; + LOG_INFO << "Disconnected Client #" << fd << std::endl; } void Server::writeClientsData(int fd) @@ -276,7 +285,7 @@ void Server::readClientsData(int fd) } if (client->getBuffer().getReadBufferSize() > 65536) { - std::cout << "Input buffer is too big, disconnecting : " << fd << "\n"; + LOG_NOTICE << "Input buffer is too big, so I'm killing Client #" << fd << std::endl; disconnectClient(fd); return; } @@ -311,7 +320,7 @@ void Server::acceptNewClients() m_sm->addSocket(clientFd, EPOLLIN); m_clients[clientFd] = new Client(clientFd, "unknown", *this); - std::cout << "Client Accepted : " << clientFd << "\n"; + LOG_NOTICE << "New client has joined! (#" << clientFd << ")" << std::endl; } } @@ -373,10 +382,9 @@ static int createListeningSocket(int port) // fd that listens to all interfaces void Server::run() { - std::cout << "Ready to run !\n"; - + LOG_INFO << "Ready to run!" << std::endl; m_listenFd = createListeningSocket(m_cfg.getPort()); // the "door" of the server irc - std::cout << "Listening...\n"; + LOG_INFO << "Now listening on port " << m_cfg.getPort() << std::endl; m_sm = new PollSocketManager(); // THE Manager of the Sockets @@ -428,7 +436,10 @@ void Server::run() } if (g_shutdown) - std::cout << "\nServer shutting down...\n"; + { + std::cout << '\n'; + LOG_INFO << "Server is shutting down..." << std::endl; + } } Server::Server(const Config& cfg) : m_cfg(cfg), m_listenFd(-1), m_sm(0) @@ -437,8 +448,7 @@ Server::Server(const Config& cfg) : m_cfg(cfg), m_listenFd(-1), m_sm(0) Server::~Server() { - std::cout << "Cleaning up my stuff...\n"; - + LOG_NOTICE << "Cleaning up my stuff" << std::endl; for (std::map< int, IClient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++) { if (it->first != m_listenFd) @@ -462,5 +472,5 @@ Server::~Server() delete (m_sm); - std::cout << "All resources freed. I can die in peace! :D\n"; + LOG_INFO << "All resources freed. I can die in peace! :D" << std::endl; } diff --git a/srcs/main.cpp b/srcs/main.cpp index a9c3d93..213e602 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,3 +1,4 @@ +#include "Logger.hpp" #include "core/Server.hpp" #include #include @@ -15,6 +16,8 @@ int main(int argc, char** argv) std::signal(SIGINT, signalHandler); std::signal(SIGTERM, signalHandler); + Logger::getInstance().setMinLevel(Logger::DEBUG); + try { Config cfg = Config::checkArgs(argc, argv); @@ -25,7 +28,9 @@ int main(int argc, char** argv) catch (const std::exception& e) { - std::cerr << e.what() << '\n'; + LOG_CRITICAL << "Something went TERRIBLY wrong: " << e.what(); + Logger::getInstance().log((Logger::Level)153) + << "Bailing out, you are on your own. Good luck" << std::endl; return (1); } } diff --git a/srcs/protocol/Message.cpp b/srcs/protocol/Message.cpp index d08b289..ba3a243 100644 --- a/srcs/protocol/Message.cpp +++ b/srcs/protocol/Message.cpp @@ -8,3 +8,20 @@ bool Message::isValid() const { return (!m_command.empty()); } + +std::ostream& operator<<(std::ostream& stream, const Message& message) +{ + stream << "\tValid: " << (message.isValid() ? "yes" : "no") << "\n"; + stream << "\tPrefix: " << message.m_prefix << "\n"; + stream << "\tCommand: " << message.m_command << "\n"; + stream << "\tParameters:\n"; + if (message.m_params.empty()) + stream << "\t\t"; + else + { + for (size_t i = 0; i < message.m_params.size(); i++) + stream << "\t\t[" << i << "]: " << message.m_params[i] + << (i == message.m_params.size() - 1 ? "" : "\n"); + } + return (stream); +}