diff --git a/CMakeLists.txt b/CMakeLists.txt index 68decbc..21504ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,14 +4,14 @@ project(jay LANGUAGES CXX ) -# Build type options option(JAY_ENABLE_ASAN "Enable Address Sanitizer" OFF) option(JAY_ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF) option(JAY_ENABLE_TSAN "Enable Thread Sanitizer" OFF) -option(JAY_ENABLE_STRICT "Enable strict compiler warnings" OFF) option(JAY_ENABLE_LTO "Enable Link Time Optimization" OFF) -# Set default build type if not specified +option(JAY_ENABLE_STATIC_ANALYSIS "Enable static analysis during build" OFF) +option(JAY_ENABLE_COVERAGE "Enable coverage reporting" OFF) + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build (Debug/Release)" FORCE) endif() @@ -21,6 +21,7 @@ message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}") set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options( @@ -29,7 +30,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") -Wpedantic ) - if(JAY_ENABLE_STRICT) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_compile_options( -Werror -Wconversion @@ -40,10 +41,9 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") -Woverloaded-virtual -Wsign-conversion -Wnull-dereference + -fno-omit-frame-pointer ) - endif() - if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_compile_options(-O0 -g3 -ggdb) endif() @@ -52,7 +52,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") endif() if(JAY_ENABLE_ASAN) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) endif() @@ -67,7 +67,6 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") endif() endif() -# Link Time Optimization if(JAY_ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT supported OUTPUT error) @@ -79,12 +78,29 @@ if(JAY_ENABLE_LTO) endif() endif() +if(JAY_ENABLE_STATIC_ANALYSIS) + find_program(CLANG_TIDY NAMES clang-tidy) + if(CLANG_TIDY) + set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY}) + endif() +endif() + +if(JAY_ENABLE_COVERAGE) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_compile_options(--coverage) + add_link_options(--coverage) + endif() +endif() + cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0167 NEW) #FindBoost find_package(PkgConfig REQUIRED) -pkg_search_module(GLOOX REQUIRED gloox) - +find_package(Threads REQUIRED) find_package(yaml-cpp REQUIRED) +find_package(Boost REQUIRED COMPONENTS log log_setup thread system) + +pkg_search_module(GLOOX REQUIRED gloox) pkg_check_modules(UUID REQUIRED uuid) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dc4d80..ea412f4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,12 +7,14 @@ set(HEADERS jay.h ) -add_executable(${EXEC_NAME} main.cpp jay.cpp) +add_executable(${EXEC_NAME} main.cpp jay.cpp + log/log.cpp) add_subdirectory(component) add_subdirectory(connection) add_subdirectory(module) add_subdirectory(shared) +add_subdirectory(log) target_sources(${EXEC_NAME} PRIVATE ${SOURCES}) @@ -23,6 +25,11 @@ target_link_libraries(${EXEC_NAME} PRIVATE ${GLOOX_LIBRARIES} yaml-cpp ${UUID_LIBRARIES} + Boost::log + Boost::log_setup + Boost::thread + Boost::system + Threads::Threads ) install(TARGETS ${EXEC_NAME} RUNTIME DESTINATION bin) \ No newline at end of file diff --git a/src/component/config.cpp b/src/component/config.cpp index a3e06ff..fbe4ab5 100644 --- a/src/component/config.cpp +++ b/src/component/config.cpp @@ -24,21 +24,21 @@ std::string Config::getFullJID() const { return getBareJID() + "/" + getResource(); } -Shared::Logger::Level Config::getLogLevel() const { - std::string level = root["logLevel"].as("info"); +Log::Level Config::getLogLevel() const { + auto level = root["logLevel"].as("info"); if (level == "trace") - return Shared::Logger::trace; + return Log::Level::trace; else if (level == "debug") - return Shared::Logger::debug; + return Log::Level::debug; else if (level == "info") - return Shared::Logger::info; + return Log::Level::info; else if (level == "warn" || level == "warning") - return Shared::Logger::warning; + return Log::Level::warning; else if (level == "error") - return Shared::Logger::error; + return Log::Level::error; else - return Shared::Logger::info; + return Log::Level::info; } gloox::TLSPolicy Config::getTLSPolicy() const { diff --git a/src/component/config.h b/src/component/config.h index 0efd1f3..cc2eae3 100644 --- a/src/component/config.h +++ b/src/component/config.h @@ -15,7 +15,7 @@ #include "shared/definitions.h" #include "shared/result.h" -#include "shared/logger.h" +#include "log/logger.h" class Config { public: @@ -36,7 +36,7 @@ public: std::string getPassword() const; std::string getResource() const; std::map getActors() const; - Shared::Logger::Level getLogLevel() const; + Log::Level getLogLevel() const; gloox::TLSPolicy getTLSPolicy() const; std::vector getModules() const; Shared::Responses getResponses() const; diff --git a/src/component/core.cpp b/src/component/core.cpp index b420989..80f9286 100644 --- a/src/component/core.cpp +++ b/src/component/core.cpp @@ -16,7 +16,7 @@ Core::Core(const std::string& configPath): void Core::send(const std::string& jid, const std::string& body) { std::shared_ptr cn = connection.lock(); if (!cn) { - logger.log(Shared::Logger::warning, "Couldn't send a message to " + jid + ", connection is not available", {"Core"}); + logger.log(Log::Level::warning, "Couldn't send a message to " + jid + ", connection is not available", {"Core"}); return; } @@ -55,7 +55,7 @@ void Core::setGroup(const std::string& jid, const std::string& group) { void Core::publish(const std::string& service, const std::string& node, const std::string& title, const std::string& body) { std::shared_ptr cn = connection.lock(); if (!cn) { - logger.log(Shared::Logger::warning, "Couldn't publish to " + node + "@" + service + ", connection is not available", {"Core"}); + logger.log(Log::Level::warning, "Couldn't publish to " + node + "@" + service + ", connection is not available", {"Core"}); return; } @@ -64,7 +64,7 @@ void Core::publish(const std::string& service, const std::string& node, const st void Core::initializeActors() { for (const std::pair& pair : config.getActors()) { - logger.log(Shared::Logger::info, "registering actor " + pair.first + " as " + pair.second, {"Core"}); + logger.log(Log::Level::info, "registering actor " + pair.first + " as " + pair.second, {"Core"}); router.registerActor(pair.first, pair.second); } } diff --git a/src/component/core.h b/src/component/core.h index 46aa93e..e3ab802 100644 --- a/src/component/core.h +++ b/src/component/core.h @@ -6,7 +6,7 @@ #include #include -#include "shared/logger.h" +#include "log/logger.h" #include "config.h" #include "router.h" @@ -23,7 +23,7 @@ public: public: Config config; - Shared::Logger logger; + Log::Logger logger; Router router; private: diff --git a/src/component/loop.cpp b/src/component/loop.cpp index 5531548..a245c87 100644 --- a/src/component/loop.cpp +++ b/src/component/loop.cpp @@ -12,8 +12,8 @@ #include "shared/definitions.h" -Loop::Loop(const Shared::Logger& logger): - Shared::Loggable(logger, {"Loop"}), +Loop::Loop(const Log::Logger& logger): + Log::Loggable(logger, {"Loop"}), wakePipe(), handlers(), handlersToAdd(), @@ -80,16 +80,15 @@ void Loop::stop() { debug("stopping the loop"); wake(); } - -void Loop::addDescriptor(int descriptor, const Callback& handler) { +void Loop::addDescriptor(int descriptor, Callback&& handler) { std::lock_guard lock(mutex); - if (handlers.count(descriptor) != 0) { + if (handlers.contains(descriptor)) { warn("an attempt to add descriptor " + std::to_string(descriptor) + " for the second time"); return; } - if (!handlersToAdd.emplace(descriptor, handler).second) { + if (!handlersToAdd.emplace(descriptor, std::forward(handler)).second) { warn("an attempt to add descriptor " + std::to_string(descriptor) + " for the second time"); return; } @@ -116,13 +115,13 @@ void Loop::removeDescriptor(int descriptor) { wake(); } -void Loop::wake() { +void Loop::wake() const { char w = 'w'; if (write(wakePipe[1], &w, 1) < 0 && errno != EAGAIN) error("failed to wake up event loop: " + std::string(strerror(errno))); } -void Loop::drain(fd_set* readfds) { +void Loop::drain(fd_set* readfds) const { if (!FD_ISSET(wakePipe[0], readfds)) return; @@ -145,7 +144,7 @@ void Loop::drain(fd_set* readfds) { } } -void Loop::runCallbacks(fd_set* readfds) { +void Loop::runCallbacks(fd_set* readfds) const { for (const std::pair& pair : handlers) { if (!FD_ISSET(pair.first, readfds)) continue; @@ -160,7 +159,7 @@ void Loop::runCallbacks(fd_set* readfds) { } } -int Loop::setFDsAndFindMax(fd_set* readfds) { +int Loop::setFDsAndFindMax(fd_set* readfds) const { FD_ZERO(readfds); FD_SET(wakePipe[0], readfds); @@ -188,7 +187,7 @@ void Loop::syncHandlers() { void Loop::registerInstance(Loop* loop) { std::lock_guard lock(instanceMutex); - if (instances.size() == 0) { + if (instances.empty()) { std::signal(SIGINT, signalHandler); std::signal(SIGTERM, signalHandler); } @@ -200,7 +199,7 @@ void Loop::unregisterInstance(Loop* loop) { std::lock_guard lock(instanceMutex); instances.erase(loop); - if (instances.size() == 0) { + if (instances.empty()) { std::signal(SIGINT, SIG_DFL); std::signal(SIGTERM, SIG_DFL); } diff --git a/src/component/loop.h b/src/component/loop.h index 5bd1f5d..e3be8df 100644 --- a/src/component/loop.h +++ b/src/component/loop.h @@ -11,27 +11,32 @@ #include -#include "shared/loggable.h" -#include "shared/logger.h" +#include "log/loggable.h" +#include "log/logger.h" -class Loop : Shared::Loggable { +class Loop : Log::Loggable { public: typedef std::function Callback; typedef std::map Handlers; - explicit Loop(const Shared::Logger& logger); + explicit Loop(const Log::Logger& logger); + Loop(const Loop&) = delete; + Loop& operator=(const Loop&) = delete; + Loop(Loop&&) noexcept = delete; + Loop& operator=(Loop&&) noexcept = delete; ~Loop(); void run(); void stop(); - void addDescriptor(int descriptor, const Callback& handler); + void addDescriptor(int descriptor, Callback&& handler); + void removeDescriptor(int descriptor); private: - void wake(); - void drain(fd_set* readfds); - void runCallbacks(fd_set* readfds); - int setFDsAndFindMax(fd_set* readfds); + void wake() const; + void drain(fd_set* readfds) const; + void runCallbacks(fd_set* readfds) const; + int setFDsAndFindMax(fd_set* readfds) const; void syncHandlers(); private: @@ -39,7 +44,7 @@ private: Handlers handlers; Handlers handlersToAdd; std::set descriptorsToRemove; - std::mutex mutex; + mutable std::mutex mutex; std::atomic running; private: diff --git a/src/component/router.cpp b/src/component/router.cpp index a7459fb..42f8fee 100644 --- a/src/component/router.cpp +++ b/src/component/router.cpp @@ -6,8 +6,8 @@ #include "module/module.h" #include "connection/connection.h" -Router::Router(const Shared::Logger& logger) : - Shared::Loggable(logger, {"Router"}), +Router::Router(const Log::Logger& logger) : + Log::Loggable(logger, {"Router"}), modules(), actors(), defaultGroup("Stranger"), diff --git a/src/component/router.h b/src/component/router.h index 9719753..975920b 100644 --- a/src/component/router.h +++ b/src/component/router.h @@ -11,8 +11,8 @@ #include #include +#include "log/loggable.h" #include "shared/result.h" -#include "shared/loggable.h" #include "shared/utils.h" #include "actor.h" @@ -22,9 +22,9 @@ namespace Module { class Connection; -class Router : private Shared::Loggable { +class Router : private Log::Loggable { public: - Router(const Shared::Logger& logger); + Router(const Log::Logger& logger); void registerModule(const std::shared_ptr& module); void registerActor(const std::string& key, const std::string& group); diff --git a/src/connection/connection.cpp b/src/connection/connection.cpp index 4528535..4402bec 100644 --- a/src/connection/connection.cpp +++ b/src/connection/connection.cpp @@ -6,7 +6,7 @@ #include "gloox/connectiontcpclient.h" Connection::Connection(const std::shared_ptr& core): - Shared::Loggable(core->logger, {"Connection"}), + Log::Loggable(core->logger, {"Connection"}), state(initial), core(core), gloox(), @@ -25,7 +25,7 @@ void Connection::initialize() { gloox->registerConnectionListener(this); gloox->registerMessageHandler(this); - ::gloox::LogLevel level = Shared::Logger::convert(core->config.getLogLevel()); + ::gloox::LogLevel level = Log::convertGloox(core->config.getLogLevel()); gloox->logInstance().registerLogHandler(level, gloox::LogAreaAll, &core->logger); gloox::Disco* disco = gloox->disco(); diff --git a/src/connection/connection.h b/src/connection/connection.h index 6693232..e426e88 100644 --- a/src/connection/connection.h +++ b/src/connection/connection.h @@ -14,12 +14,12 @@ #include #include -#include "shared/loggable.h" +#include "log/loggable.h" #include "shared/utils.h" #include "component/core.h" class Connection: - private Shared::Loggable, + private Log::Loggable, public gloox::ConnectionListener, public gloox::MessageHandler, public gloox::PubSub::ResultHandler diff --git a/src/jay.cpp b/src/jay.cpp index fce86b0..461a597 100644 --- a/src/jay.cpp +++ b/src/jay.cpp @@ -45,7 +45,7 @@ void Jay::run() { initialize(); int fd = connection->connect(); - loop.addDescriptor(fd, std::bind(&Connection::processMessages, connection.get())); + loop.addDescriptor(fd, [conn = connection.get()] { conn->processMessages(); }); loop.run(); diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt new file mode 100644 index 0000000..640afa2 --- /dev/null +++ b/src/log/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES + log.cpp + logger.cpp + loggable.cpp +) + +set(HEADERS + log.h + logger.h + loggable.h +) + +target_sources(${EXEC_NAME} PRIVATE ${SOURCES}) \ No newline at end of file diff --git a/src/log/log.cpp b/src/log/log.cpp new file mode 100644 index 0000000..c6bb71d --- /dev/null +++ b/src/log/log.cpp @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "log.h" + +static constexpr std::string_view gray = "\033[90m"; +static constexpr std::string_view blue = "\033[34m"; +static constexpr std::string_view green = "\033[32m"; +static constexpr std::string_view yellow = "\033[33m"; +static constexpr std::string_view red = "\033[31m"; +static constexpr std::string_view magenta = "\033[35m"; + +gloox::LogLevel Log::convertGloox(const Level level) { + switch (level) { + case Level::trace: + return gloox::LogLevelDebug; + case Level::debug: + return gloox::LogLevelWarning; + case Level::info: + return gloox::LogLevelWarning; + case Level::warning: + return gloox::LogLevelWarning; + case Level::error: + return gloox::LogLevelError; + case Level::fatal: + return gloox::LogLevelError; + } + + return gloox::LogLevelWarning; +} + +Log::Level Log::convertGloox(const gloox::LogLevel level) { + switch (level) { + case gloox::LogLevelDebug: + return Level::trace; + case gloox::LogLevelError: + return Level::error; + case gloox::LogLevelWarning: + return Level::warning; + }; + + return Level::warning; +} + +boost::log::trivial::severity_level Log::convertBoost(const Level level) { + switch (level) { + case Level::trace: + return boost::log::trivial::trace; + case Level::debug: + return boost::log::trivial::debug; + case Level::info: + return boost::log::trivial::info; + case Level::warning: + return boost::log::trivial::warning; + case Level::error: + return boost::log::trivial::error; + case Level::fatal: + return boost::log::trivial::fatal; + } + + return boost::log::trivial::error; +} + +Log::Level Log::convertBoost(const boost::log::trivial::severity_level level) { + switch (level) { + case boost::log::trivial::trace: + return Level::trace; + case boost::log::trivial::debug: + return Level::debug; + case boost::log::trivial::info: + return Level::info; + case boost::log::trivial::warning: + return Level::warning; + case boost::log::trivial::error: + return Level::error; + case boost::log::trivial::fatal: + return Level::fatal; + } + + return Level::error; +} + +std::string_view Log::levelColor(const Level level) { + switch (level) { + case Level::trace: + return gray; + case Level::debug: + return blue; + case Level::info: + return green; + case Level::warning: + return yellow; + case Level::error: + return red; + case Level::fatal: + return magenta; + } + + return red; +} diff --git a/src/log/log.h b/src/log/log.h new file mode 100644 index 0000000..17ade56 --- /dev/null +++ b/src/log/log.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +#include + +namespace Log { +enum class Level { + trace, + debug, + info, + warning, + error, + fatal +}; + +gloox::LogLevel convertGloox(Level level); +Level convertGloox(gloox::LogLevel level); + +boost::log::trivial::severity_level convertBoost(Level level); +Level convertBoost(boost::log::trivial::severity_level level); + +std::string_view levelColor(Level level); + +} \ No newline at end of file diff --git a/src/log/loggable.cpp b/src/log/loggable.cpp new file mode 100644 index 0000000..ee32b0d --- /dev/null +++ b/src/log/loggable.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "loggable.h" + +Log::Loggable::Loggable(const Logger& _logger, const std::vector& _domain) : + logger(_logger), + domain(_domain) +{} + +void Log::Loggable::trace(const std::string& message) const { + logger.log(Level::trace, message, domain); +} + +void Log::Loggable::debug(const std::string& message) const { + logger.log(Level::debug, message, domain); +} + +void Log::Loggable::info(const std::string& message) const { + logger.log(Level::info, message, domain); +} + +void Log::Loggable::warn(const std::string& message) const { + logger.log(Level::warning, message, domain); +} + +void Log::Loggable::error(const std::string& message) const { + logger.log(Level::error, message, domain); +} + +void Log::Loggable::fatal(const std::string& message) const { + logger.log(Level::fatal, message, domain); +} diff --git a/src/shared/loggable.h b/src/log/loggable.h similarity index 97% rename from src/shared/loggable.h rename to src/log/loggable.h index d65adee..fe5edcc 100644 --- a/src/shared/loggable.h +++ b/src/log/loggable.h @@ -8,7 +8,7 @@ #include "logger.h" -namespace Shared { +namespace Log { class Loggable { public: diff --git a/src/log/logger.cpp b/src/log/logger.cpp new file mode 100644 index 0000000..2868ffe --- /dev/null +++ b/src/log/logger.cpp @@ -0,0 +1,191 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "logger.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +static constexpr std::string_view clear = "\033[0m"; +static constexpr std::string_view bold = "\033[1m"; + +struct GlooxTag { + gloox::LogArea bit; + const char* label; + const char* color; +}; + +static constexpr GlooxTag glooxTags[] = { + { gloox::LogAreaClassParser, "Parser", nullptr }, + { gloox::LogAreaClassConnectionTCPBase, "Connection", nullptr }, + { gloox::LogAreaClassClient, "Client", nullptr }, + { gloox::LogAreaClassClientbase, "Client Base", nullptr }, + { gloox::LogAreaClassComponent, "Component", nullptr }, + { gloox::LogAreaClassDns, "DNS", nullptr }, + { gloox::LogAreaClassConnectionHTTPProxy,"HTTP Proxy", nullptr }, + { gloox::LogAreaClassConnectionSOCKS5Proxy, "SOCKS5 Proxy", nullptr }, + { gloox::LogAreaClassConnectionTCPClient,"TCP Client", nullptr }, + { gloox::LogAreaClassConnectionTCPServer,"TCP Server", nullptr }, + { gloox::LogAreaClassS5BManager, "SOCKS5 Bytestream Manager", nullptr }, + { gloox::LogAreaClassSOCKS5Bytestream, "SOCKS5 Bytestream", nullptr }, + { gloox::LogAreaLinkLocalManager, "Link Local Manager", nullptr }, + { gloox::LogAreaXmlIncoming, "XML IN", "\033[1;36m" }, + { gloox::LogAreaXmlOutgoing, "XML OUT", "\033[1;35m" }, + { gloox::LogAreaUser, "USER", nullptr }, +}; + +BOOST_LOG_ATTRIBUTE_KEYWORD(channel_attr, "Channel", std::string) +BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp_attr, "TimeStamp", boost::posix_time::ptime) + +Log::Logger::Logger(Level level): + running(true), + mutex(), + condition(), + thread(), + level(level), + sink(boost::make_shared()), + logger(), + queue(8192) +{ + auto backend = sink->locked_backend(); + boost::shared_ptr const stream(&std::clog, Shared::noDelete); + + backend->add_stream(stream); + backend->auto_flush(true); + + const boost::log::filter severityFilter = boost::log::trivial::severity >= convertBoost(level); + sink->set_filter(severityFilter); + sink->set_formatter(&Logger::formatRecord); + + boost::log::core::get()->add_sink(sink); + + logger.add_attribute("Timestamp", boost::log::attributes::local_clock()); + + thread = std::thread(&Logger::processLogQueue, this); +} + +Log::Logger::~Logger() { + running.store(false, std::memory_order_release); + condition.notify_one(); + + if (thread.joinable()) + thread.join(); + + sink->stop(); + sink->flush(); + boost::log::core::get()->remove_sink(sink); +}; + +void Log::Logger::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) { + const Level lvl = convertGloox(level); + if (lvl < Logger::level) + return; + + LogEntry entry; + entry.level = lvl; + entry.source = LogEntry::GlooxSource(area); + entry.message = message; + + if (!queue.push(std::move(entry))) + std::cerr << "Log queue full, message dropped\n"; + else + condition.notify_one(); +} + +std::vector Log::Logger::convertTags(gloox::LogArea area) { + std::vector tags; + for (const auto& tag : glooxTags) { + if (area & tag.bit) + tags.emplace_back(tag.label); + } + + return tags; +} + +std::string Log::Logger::formatDomain(const std::vector& domain) { + std::ostringstream tagStream; + for (size_t i = 0; i < domain.size(); ++i) { + if (i > 0) + tagStream << ' '; + + tagStream << domain[i]; + } + + return tagStream.str(); +} + +void Log::Logger::processLogQueue() { + LogEntry entry; + std::unique_lock lock(mutex); + + while (running.load(std::memory_order_relaxed)) { + while (queue.pop(entry)) { + lock.unlock(); + logEntry(entry); + lock.lock(); + } + + condition.wait(lock, [this]() { + return !running.load(std::memory_order_relaxed) || !queue.empty(); + }); + } + + + while (queue.pop(entry)) + logEntry(entry); +} + +void Log::Logger::logEntry(const LogEntry& entry) { + std::visit( + [this, &entry](const S& source) { + if constexpr (std::is_same_v, LogEntry::GlooxSource>) { + BOOST_LOG_SCOPED_LOGGER_ATTR(logger, "Channel", + boost::log::attributes::make_constant(formatDomain(convertTags(source.area)))); + BOOST_LOG_SEV(logger, convertBoost(entry.level)) << entry.message; + } else { + BOOST_LOG_SCOPED_LOGGER_ATTR(logger, "Channel", + boost::log::attributes::make_constant(formatDomain(source.domain))); + BOOST_LOG_SEV(logger, convertBoost(entry.level)) << entry.message; + } + }, entry.source); +} + +void Log::Logger::log(Level lvl, const std::string& message, const std::vector& domain) const { + if (lvl < level) + return; + + LogEntry entry; + entry.level = lvl; + entry.source = LogEntry::DirectSource(domain); + entry.message = message; + + if (!queue.push(std::move(entry))) + std::cerr << "Log queue full, message dropped\n"; + else + condition.notify_one(); +} + +void Log::Logger::formatRecord(boost::log::record_view const& rec, boost::log::formatting_ostream& out) { + auto ts = rec[timestamp_attr]; + auto lvl = rec[boost::log::trivial::severity]; + auto msg = rec[boost::log::expressions::smessage]; + + if (ts) + out << boost::posix_time::to_simple_string(ts.get()) << " "; + + out << bold << levelColor(convertBoost(lvl.get())) << "[" << lvl.get() << "]" << clear; + + auto ch = rec[channel_attr].get_ptr(); + if (ch && !ch->empty()) + out << " [" << *ch << "]"; + + out << "\t" << msg.get() << clear; +} \ No newline at end of file diff --git a/src/log/logger.h b/src/log/logger.h new file mode 100644 index 0000000..f762cd7 --- /dev/null +++ b/src/log/logger.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2024 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "log.h" + +namespace Log { + +class Logger: public gloox::LogHandler { +public: + explicit Logger(Level level = Level::info); + ~Logger() override; + + void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) override; + + void log(Level level, const std::string& message, const std::vector& domain = {}) const; +private: + typedef boost::log::sinks::asynchronous_sink Sink; + typedef boost::log::sources::severity_logger BoostLogger; + + struct LogEntry { + struct GlooxSource { + gloox::LogArea area; + }; + + struct DirectSource { + std::vector domain; + }; + + Level level; + std::string message; + std::chrono::system_clock::time_point timestamp; + std::variant source; + }; + + std::atomic running; + std::mutex mutex; + mutable std::condition_variable condition; + std::thread thread; + + Level level; + boost::shared_ptr sink; + mutable BoostLogger logger; + mutable boost::lockfree::spsc_queue queue; + +private: + static void formatRecord(boost::log::record_view const& rec, boost::log::formatting_ostream& out); + static std::vector convertTags(gloox::LogArea area); + static std::string formatDomain(const std::vector& domain); + void processLogQueue(); + void logEntry(const LogEntry& entry); +}; + +} \ No newline at end of file diff --git a/src/module/module.cpp b/src/module/module.cpp index 4118436..36b3a72 100644 --- a/src/module/module.cpp +++ b/src/module/module.cpp @@ -8,7 +8,7 @@ #include "gloox/message.h" Module::Module::Module(const std::shared_ptr& core, const Config::Module& conf, const std::string& name): - Shared::Loggable(core->logger, {"Module", name, conf.alias}), + Log::Loggable(core->logger, {"Module", name, conf.alias}), name(name), alias(conf.alias), core(core), diff --git a/src/module/module.h b/src/module/module.h index aed7de4..494fb3a 100644 --- a/src/module/module.h +++ b/src/module/module.h @@ -12,14 +12,14 @@ #include "shared/definitions.h" #include "shared/result.h" -#include "shared/loggable.h" +#include "log/loggable.h" #include "component/core.h" #include "component/actor.h" #include "component/config.h" namespace Module { -class Module : protected Shared::Loggable { +class Module : protected Log::Loggable { protected: Module(const std::shared_ptr& core, const Config::Module& conf, const std::string& name); diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 303a42d..53efa23 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -1,12 +1,8 @@ set(SOURCES - logger.cpp - loggable.cpp utils.cpp ) set(HEADERS - logger.h - loggable.h definitions.h result.h utils.h diff --git a/src/shared/definitions.h b/src/shared/definitions.h index c612244..36f3815 100644 --- a/src/shared/definitions.h +++ b/src/shared/definitions.h @@ -10,6 +10,11 @@ #define UNUSED(x) (void)(x) namespace Shared { + struct NullDeleter { + void operator()(const void*) const noexcept {} + }; + void inline noDelete(const void*) noexcept {}; + typedef std::vector Strings; typedef std::map Permissions; typedef std::map VC; diff --git a/src/shared/loggable.cpp b/src/shared/loggable.cpp deleted file mode 100644 index 68d4ad6..0000000 --- a/src/shared/loggable.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Yury Gubich -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "loggable.h" - -Shared::Loggable::Loggable(const Logger& _logger, const std::vector& _domain) : - logger(_logger), - domain(_domain) -{} - -void Shared::Loggable::trace(const std::string& message) const { - logger.log(Logger::trace, message, domain); -} - -void Shared::Loggable::debug(const std::string& message) const { - logger.log(Logger::debug, message, domain); -} - -void Shared::Loggable::info(const std::string& message) const { - logger.log(Logger::info, message, domain); -} - -void Shared::Loggable::warn(const std::string& message) const { - logger.log(Logger::warning, message, domain); -} - -void Shared::Loggable::error(const std::string& message) const { - logger.log(Logger::error, message, domain); -} - -void Shared::Loggable::fatal(const std::string& message) const { - logger.log(Logger::fatal, message, domain); -} diff --git a/src/shared/logger.cpp b/src/shared/logger.cpp deleted file mode 100644 index 9a713cc..0000000 --- a/src/shared/logger.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Yury Gubich -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "logger.h" - -#include -#include - -static constexpr std::string_view logLevelMap[] = { - "TRACE", - "DEBUG", - "INFO", - "WARNING", - "ERROR", - "FATAL", -}; - -static constexpr std::string_view colorMap[] = { - "\033[90m", // TRACE (Gray) - "\033[34m", // DEBUG (Blue) - "\033[32m", // INFO (Green) - "\033[33m", // WARNING (Yellow) - "\033[31m", // ERROR (Red) - "\033[35m", // FATAL (Magenta) -}; - -static constexpr std::string_view clear = "\033[0m"; -static constexpr std::string_view bold = "\033[1m"; - -void writeTimestamp() { - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); - std::time_t time_now = std::chrono::system_clock::to_time_t(now); - std::tm local_time = *std::localtime(&time_now); - std::chrono::milliseconds millis = std::chrono::duration_cast(now.time_since_epoch()) % 1000; - - std::cout << "\033[90m" << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S"); - std::cout << "." << std::setfill('0') << std::setw(3) << millis.count(); - std::cout << clear << ' '; -} - -constexpr std::string_view getLogLevel(gloox::LogLevel level) { - return (level >= 0 && level < 3) ? logLevelMap[Shared::Logger::convert(level)] : "UNKNOWN"; -} - -constexpr std::string_view getColor(gloox::LogLevel level) { - return (level >= 0 && level < 3) ? colorMap[Shared::Logger::convert(level)] : ""; -} - -void writeTags(gloox::LogArea area) { - if (area & gloox::LogAreaClassParser) - std::cout << " [Parser]"; - if (area & gloox::LogAreaClassConnectionTCPBase) - std::cout << " [Connection]"; - if (area & gloox::LogAreaClassClient) - std::cout << " [Client]"; - if (area & gloox::LogAreaClassClientbase) - std::cout << " [Client Base]"; - if (area & gloox::LogAreaClassComponent) - std::cout << " [Component]"; - if (area & gloox::LogAreaClassDns) - std::cout << " [DNS]"; - if (area & gloox::LogAreaClassConnectionHTTPProxy) - std::cout << " [HTTP Proxy]"; - if (area & gloox::LogAreaClassConnectionSOCKS5Proxy) - std::cout << " [SOCKS5 Proxy]"; - if (area & gloox::LogAreaClassConnectionTCPClient) - std::cout << " [TCP Client]"; - if (area & gloox::LogAreaClassConnectionTCPServer) - std::cout << " [TCP Server]"; - if (area & gloox::LogAreaClassS5BManager) - std::cout << " [SOCKS5 Bytestream Manager]"; - if (area & gloox::LogAreaClassSOCKS5Bytestream) - std::cout << " [SOCKS5 Bytestream]"; - if (area & gloox::LogAreaLinkLocalManager) - std::cout << " [Link Local Manager]"; - if (area & gloox::LogAreaXmlIncoming) - std::cout << " \033[1;36m[XML IN]"; - if (area & gloox::LogAreaXmlOutgoing) - std::cout << " \033[1;35m[XML OUT]"; - if (area & gloox::LogAreaUser) - std::cout << " [USER]"; - - if (area == gloox::LogAreaClassDns) - std::cout << '\t'; -} - -Shared::Logger::Level Shared::Logger::convert(gloox::LogLevel level) { - switch (level) { - case gloox::LogLevelDebug: - return trace; - case gloox::LogLevelError: - return error; - case gloox::LogLevelWarning: - return warning; - }; - - return warning; -} - -gloox::LogLevel Shared::Logger::convert(Level level) { - switch (level) { - case trace: - return gloox::LogLevelDebug; - case debug: - return gloox::LogLevelWarning; - case info: - return gloox::LogLevelWarning; - case warning: - return gloox::LogLevelWarning; - case error: - return gloox::LogLevelError; - case fatal: - return gloox::LogLevelError; - } - - return gloox::LogLevelWarning; -} - -Shared::Logger::Logger(Level level): - level(level) -{} - -Shared::Logger::~Logger() = default; - -void Shared::Logger::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) { - writeTimestamp(); - std::cout << getColor(level) << bold << '[' << getLogLevel(level) << ']' << clear << bold; - writeTags(area); - std::cout << clear << '\t' << message << clear << std::endl; -} - -void Shared::Logger::log(Level lvl, const std::string& message, const std::vector& domain) const { - if (lvl < level) - return; - - writeTimestamp(); - std::cout << colorMap[lvl] << bold << '[' << logLevelMap[lvl] << ']' << clear; - - if (!domain.empty()) { - std::cout << ' ' << bold << '['; - - bool first = true; - for (const std::string& tag : domain) { - if (first) - first = false; - else - std::cout << ' '; - - std::cout << tag; - } - - std::cout << ']' << clear; - } - std::cout << '\t' << message << clear << std::endl; -} - diff --git a/src/shared/logger.h b/src/shared/logger.h deleted file mode 100644 index fa4ea06..0000000 --- a/src/shared/logger.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Yury Gubich -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include -#include - -namespace Shared { - -class Logger: public gloox::LogHandler { -public: - enum Level { - trace, - debug, - info, - warning, - error, - fatal - }; - -public: - explicit Logger(Level level = info); - ~Logger() override; - - void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) override; - - void log(Level level, const std::string& message, const std::vector& domain = {}) const; - - static gloox::LogLevel convert(Level level); - static Level convert(gloox::LogLevel level); - -private: - Level level; -}; - -} \ No newline at end of file