Moved to boost logging
This commit is contained in:
parent
a1ec9f731d
commit
58020fb3ba
27 changed files with 531 additions and 294 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -24,21 +24,21 @@ std::string Config::getFullJID() const {
|
|||
return getBareJID() + "/" + getResource();
|
||||
}
|
||||
|
||||
Shared::Logger::Level Config::getLogLevel() const {
|
||||
std::string level = root["logLevel"].as<std::string>("info");
|
||||
Log::Level Config::getLogLevel() const {
|
||||
auto level = root["logLevel"].as<std::string>("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 {
|
||||
|
|
|
@ -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<std::string, std::string> getActors() const;
|
||||
Shared::Logger::Level getLogLevel() const;
|
||||
Log::Level getLogLevel() const;
|
||||
gloox::TLSPolicy getTLSPolicy() const;
|
||||
std::vector<Module> getModules() const;
|
||||
Shared::Responses getResponses() const;
|
||||
|
|
|
@ -16,7 +16,7 @@ Core::Core(const std::string& configPath):
|
|||
void Core::send(const std::string& jid, const std::string& body) {
|
||||
std::shared_ptr<Connection> 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<Connection> 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<const std::string, std::string>& 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#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:
|
||||
|
|
|
@ -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<Callback>(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<const int, Callback>& 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);
|
||||
}
|
||||
|
|
|
@ -11,27 +11,32 @@
|
|||
|
||||
#include <sys/select.h>
|
||||
|
||||
#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<void()> Callback;
|
||||
typedef std::map<int, Callback> 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<int> descriptorsToRemove;
|
||||
std::mutex mutex;
|
||||
mutable std::mutex mutex;
|
||||
std::atomic<bool> running;
|
||||
|
||||
private:
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
#include <random>
|
||||
#include <exception>
|
||||
|
||||
#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::Module>& module);
|
||||
void registerActor(const std::string& key, const std::string& group);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "gloox/connectiontcpclient.h"
|
||||
|
||||
Connection::Connection(const std::shared_ptr<Core>& 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();
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
#include <gloox/pubsubitem.h>
|
||||
#include <gloox/pubsubresulthandler.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
13
src/log/CMakeLists.txt
Normal file
13
src/log/CMakeLists.txt
Normal file
|
@ -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})
|
100
src/log/log.cpp
Normal file
100
src/log/log.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// 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;
|
||||
}
|
30
src/log/log.h
Normal file
30
src/log/log.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <gloox/gloox.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
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);
|
||||
|
||||
}
|
33
src/log/loggable.cpp
Normal file
33
src/log/loggable.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "loggable.h"
|
||||
|
||||
Log::Loggable::Loggable(const Logger& _logger, const std::vector<std::string>& _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);
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "logger.h"
|
||||
|
||||
namespace Shared {
|
||||
namespace Log {
|
||||
|
||||
class Loggable {
|
||||
public:
|
191
src/log/logger.cpp
Normal file
191
src/log/logger.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <shared/definitions.h>
|
||||
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/attributes/clock.hpp>
|
||||
#include <boost/log/attributes/scoped_attribute.hpp>
|
||||
#include <boost/log/support/date_time.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
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<Sink>()),
|
||||
logger(),
|
||||
queue(8192)
|
||||
{
|
||||
auto backend = sink->locked_backend();
|
||||
boost::shared_ptr<std::ostream> 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<std::string> Log::Logger::convertTags(gloox::LogArea area) {
|
||||
std::vector<std::string> 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<std::string>& 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<std::mutex> 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]<typename S>(const S& source) {
|
||||
if constexpr (std::is_same_v<std::decay_t<S>, 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<std::string>& 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;
|
||||
}
|
70
src/log/logger.h
Normal file
70
src/log/logger.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <variant>
|
||||
|
||||
#include <gloox/loghandler.h>
|
||||
#include <gloox/logsink.h>
|
||||
|
||||
#include <boost/log/sinks/async_frontend.hpp>
|
||||
#include <boost/log/sinks/text_ostream_backend.hpp>
|
||||
#include <boost/lockfree/spsc_queue.hpp>
|
||||
|
||||
#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<std::string>& domain = {}) const;
|
||||
private:
|
||||
typedef boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend> Sink;
|
||||
typedef boost::log::sources::severity_logger<boost::log::trivial::severity_level> BoostLogger;
|
||||
|
||||
struct LogEntry {
|
||||
struct GlooxSource {
|
||||
gloox::LogArea area;
|
||||
};
|
||||
|
||||
struct DirectSource {
|
||||
std::vector<std::string> domain;
|
||||
};
|
||||
|
||||
Level level;
|
||||
std::string message;
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
std::variant<GlooxSource, DirectSource> source;
|
||||
};
|
||||
|
||||
std::atomic<bool> running;
|
||||
std::mutex mutex;
|
||||
mutable std::condition_variable condition;
|
||||
std::thread thread;
|
||||
|
||||
Level level;
|
||||
boost::shared_ptr<Sink> sink;
|
||||
mutable BoostLogger logger;
|
||||
mutable boost::lockfree::spsc_queue<LogEntry> queue;
|
||||
|
||||
private:
|
||||
static void formatRecord(boost::log::record_view const& rec, boost::log::formatting_ostream& out);
|
||||
static std::vector<std::string> convertTags(gloox::LogArea area);
|
||||
static std::string formatDomain(const std::vector<std::string>& domain);
|
||||
void processLogQueue();
|
||||
void logEntry(const LogEntry& entry);
|
||||
};
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
#include "gloox/message.h"
|
||||
|
||||
Module::Module::Module(const std::shared_ptr<Core>& 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),
|
||||
|
|
|
@ -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>& core, const Config::Module& conf, const std::string& name);
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
set(SOURCES
|
||||
logger.cpp
|
||||
loggable.cpp
|
||||
utils.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
logger.h
|
||||
loggable.h
|
||||
definitions.h
|
||||
result.h
|
||||
utils.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<std::string> Strings;
|
||||
typedef std::map<std::string, Strings> Permissions;
|
||||
typedef std::map<std::string, std::string> VC;
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "loggable.h"
|
||||
|
||||
Shared::Loggable::Loggable(const Logger& _logger, const std::vector<std::string>& _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);
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
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<std::chrono::milliseconds>(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<std::string>& 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;
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <gloox/loghandler.h>
|
||||
#include <gloox/logsink.h>
|
||||
|
||||
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<std::string>& domain = {}) const;
|
||||
|
||||
static gloox::LogLevel convert(Level level);
|
||||
static Level convert(gloox::LogLevel level);
|
||||
|
||||
private:
|
||||
Level level;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue