diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 7c83cea..1c1eb1d 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -1,5 +1,3 @@ - - add_executable(demo main.cpp) target_link_libraries(demo PRIVATE matador-core @@ -7,3 +5,11 @@ target_link_libraries(demo PRIVATE ${CMAKE_DL_LIBS} ${SQLite3_LIBRARIES} ) + +add_executable(sandbox sandbox.cpp) +target_link_libraries(sandbox PRIVATE + matador-core + matador-orm + ${CMAKE_DL_LIBS} + ${SQLite3_LIBRARIES} +) diff --git a/demo/author.hpp b/demo/author.hpp new file mode 100644 index 0000000..d14ecbf --- /dev/null +++ b/demo/author.hpp @@ -0,0 +1,36 @@ +#ifndef AUTHOR_HPP +#define AUTHOR_HPP + +#include "matador/object/collection.hpp" +#include "matador/object/object_ptr.hpp" + +#include "matador/utils/access.hpp" + +namespace demo { +struct book; + +struct author { + unsigned int id{}; + std::string first_name; + std::string last_name; + std::string date_of_birth; + unsigned short year_of_birth{}; + bool distinguished{false}; + matador::object::collection> books; + + + template + void process( Operator& op ) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::attribute( op, "first_name", first_name, 63 ); + field::attribute( op, "last_name", last_name, 63 ); + field::attribute( op, "date_of_birth", date_of_birth, 31 ); + field::attribute( op, "year_of_birth", year_of_birth ); + field::attribute( op, "distinguished", distinguished ); + field::has_many( op, "books", books, "author_id" ); + } +}; +} + +#endif //AUTHOR_HPP diff --git a/demo/book.hpp b/demo/book.hpp new file mode 100644 index 0000000..a7c3859 --- /dev/null +++ b/demo/book.hpp @@ -0,0 +1,26 @@ +#ifndef BOOK_HPP +#define BOOK_HPP + +#include "matador/object/object_ptr.hpp" +#include "matador/utils/access.hpp" + +namespace demo { +struct author; + +struct book { + unsigned int id{}; + matador::object::object_ptr book_author; + std::string title; + unsigned short published_in{}; + + template + void process( Operator& op ) { + namespace field = matador::access; + field::primary_key( op, "id", id ); + field::attribute( op, "title", title, 511 ); + field::has_one( op, "author_id", book_author, matador::utils::default_foreign_attributes ); + field::attribute( op, "published_in", published_in ); + } +}; +} +#endif //BOOK_HPP diff --git a/demo/sandbox.cpp b/demo/sandbox.cpp new file mode 100644 index 0000000..9a13156 --- /dev/null +++ b/demo/sandbox.cpp @@ -0,0 +1,15 @@ +#include "matador/object/schema.hpp" + +#include "author.hpp" +#include "book.hpp" + +using namespace demo; +using namespace matador; +int main() { + object::schema tree; + + + tree.attach("authors"); + tree.attach("books"); + +} \ No newline at end of file diff --git a/demo/work.cpp b/demo/work.cpp index a8fec36..614335c 100644 --- a/demo/work.cpp +++ b/demo/work.cpp @@ -52,15 +52,15 @@ int main() { object::schema schema("Administration"); auto result = schema.attach("collection_center") - .and_then([&schema] { return schema.attach("internal_user_directories"); }) + .and_then([&schema] { return schema.attach("user_directories"); }) .and_then([&schema] { return schema.attach("ldap_group_schema_settings"); }) .and_then([&schema] { return schema.attach("ldap_import_settings"); }) - .and_then([&schema] { return schema.attach("ldap_user_directories"); } ) .and_then([&schema] { return schema.attach("ldap_user_schema_settings"); }) + .and_then([&schema] { return schema.attach("internal_user_directories"); }) + .and_then([&schema] { return schema.attach("ldap_user_directories"); } ) .and_then([&schema] { return schema.attach("login_histories"); }) .and_then([&schema] { return schema.attach("scenarios"); }) .and_then([&schema] { return schema.attach("users"); }) - .and_then([&schema] { return schema.attach("user_directories"); }) .and_then([&schema] { return schema.attach("user_sessions"); }) .and_then([&schema] { return schema.attach("jobs"); }) .and_then([&schema] { return schema.attach("payloads"); }) diff --git a/include/matador/logger/basic_file_sink.hpp b/include/matador/logger/basic_file_sink.hpp new file mode 100644 index 0000000..010989e --- /dev/null +++ b/include/matador/logger/basic_file_sink.hpp @@ -0,0 +1,49 @@ +#ifndef MATADOR_BASIC_FILE_SINK_HPP +#define MATADOR_BASIC_FILE_SINK_HPP + +#include "matador/logger/log_sink.hpp" + +namespace matador::logger { + +/** + * @brief A base class for the file-stream-based sinks + * + * This class acts like a base class for all + * concrete sinks working with a file stream to write + * the log message. + */ +class basic_file_sink : public log_sink +{ +protected: + basic_file_sink() = default; + + /** + * Creates a basic_file_sink with a given file stream + * + * @param f File stream to write on + */ + explicit basic_file_sink(FILE *f); + +public: + /** + * Writes the log message to the internal + * file stream. + * + * @param message The message to write + * @param size The size of the message + */ + void write(const char *message, size_t size) override; + + /** + * Closes the internal file stream. + */ + void close() override; + +protected: + /// @cond MATADOR_DEV + FILE *stream = nullptr; + /// @endcond +}; + +} +#endif //MATADOR_BASIC_FILE_SINK_HPP diff --git a/include/matador/logger/file_sink.hpp b/include/matador/logger/file_sink.hpp new file mode 100644 index 0000000..2902be2 --- /dev/null +++ b/include/matador/logger/file_sink.hpp @@ -0,0 +1,90 @@ +#ifndef MATADOR_FILE_SINK_HPP +#define MATADOR_FILE_SINK_HPP + +#include "matador/logger/basic_file_sink.hpp" + +#include +#include + +namespace matador::logger { + +/** + * @brief A file sink writing the log message to one file + * + * The log sink writes all log messages to one single + * file identified by a given path. + * + * Note because there is no limit, the file grows infinitely. + */ +class file_sink final : public basic_file_sink +{ +public: + /** + * Creates a file_sink with the given path. + * If the path doesn't exist, it is created. + * + * @param path The log file to write to + */ + explicit file_sink(const std::string &path); + + /** + * Creates a file_sink with the given path. + * If the path doesn't exist, it is created. + * + * @param path The log file to write to + */ + explicit file_sink(const char *path); + + /** + * Destroys the file_sink + */ + ~file_sink() override; + + /** + * Returns the path to the log file. + * + * @return The path to the log file + */ + std::string path() const; + +private: + std::string path_; +}; + +/** + * @brief Log sink writing to stdout + * + * This log sink writes all messages to stdout. + */ +class stdout_sink final : public basic_file_sink +{ +public: + stdout_sink(); + ~stdout_sink() override = default; + + /** + * Do nothing on close + */ + void close() override {} +}; + +/** + * @brief Log sink writing to stderr + * + * This log sink writes all messages to stderr. + */ +class stderr_sink final : public basic_file_sink +{ +public: + stderr_sink(); + ~stderr_sink() override = default; + + /** + * Do nothing on close + */ + void close() override {} +}; + +} + +#endif //MATADOR_FILE_SINK_HPP diff --git a/include/matador/logger/log_domain.hpp b/include/matador/logger/log_domain.hpp new file mode 100644 index 0000000..297be46 --- /dev/null +++ b/include/matador/logger/log_domain.hpp @@ -0,0 +1,118 @@ +#ifndef MATADOR_LOG_DOMAIN_HPP +#define MATADOR_LOG_DOMAIN_HPP + +#include "matador/logger/log_level.hpp" +#include "matador/logger/log_sink.hpp" + +#include +#include +#include +#include + +namespace matador::logger { + +/** + * @brief Connection to a set of log sinks + * + * A log domain is the connection point between + * a set of log sinks and the logger objects + * in the user code. + * + * A domain consists of a unique name and a + * list of sinks + */ +class log_domain final +{ +public: + /** + * The time format for each log line + */ + static constexpr auto TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%f"; + + /** + * Creates a log_domain with the given name + * and the given log range + * + * @param name The name of the log domain + * @param log_range The log range of this domain + */ + log_domain(std::string name, log_level_range log_range); + + /** + * Returns the name of the domain + * + * @return The name of the domain + */ + [[nodiscard]] std::string name() const; + + /** + * Sets the max log level. The default + * max level is LVL_FATAL + * + * @param max_level max log level + */ + void max_log_level(log_level max_level); + + /** + * Returns the max log level + * + * @return The max log level + */ + [[nodiscard]] log_level max_log_level() const; + + /** + * Sets the min log level. Default + * min leven is LVL_INFO + * + * @param min_level min log level + */ + void min_log_level(log_level min_level); + + /** + * Returns the min log level + * + * @return The min log level + */ + [[nodiscard]] log_level min_log_level() const; + + /** + * Add a sink to the domain. + * + * The sink must be packed into a std::shared_ptr + * because it can be shared among other domains + * + * @param sink The sink to add + */ + void add_sink(sink_ptr sink); + + /** + * Logs the given message for the given source and log level + * to this log domain. + * + * @param lvl Log level + * @param source Source of the log message + * @param message Message to log + */ + void log(log_level lvl, const std::string &source, const char *message); + + /** + * Clears the list of log sinks + */ + void clear(); + +private: + void get_time_stamp(char* timestamp_buffer); + +private: + static std::map level_strings; + + std::string name_; + std::list sinks{}; + + log_level_range log_level_range_; + + std::mutex mutex_; +}; + +} +#endif //MATADOR_LOG_DOMAIN_HPP diff --git a/include/matador/logger/log_level.hpp b/include/matador/logger/log_level.hpp new file mode 100644 index 0000000..819f3c7 --- /dev/null +++ b/include/matador/logger/log_level.hpp @@ -0,0 +1,43 @@ +#ifndef MATADOR_LOG_LEVEL_HPP +#define MATADOR_LOG_LEVEL_HPP + +#include + +namespace matador::logger { + +/** + * Represents all available log levels + */ +enum class log_level +{ + LVL_FATAL, /**< If a serious error occurred, use FATAL level */ + LVL_ERROR, /**< On error use ERROR level */ + LVL_WARN, /**< Warnings should use WARN level */ + LVL_INFO, /**< Information should go with INFO level */ + LVL_DEBUG, /**< Debug output should use DEBUG level */ + LVL_TRACE, /**< Trace information should use TRACE level */ + LVL_ALL /**< This level represents all log levels and should be used for logging */ +}; + +/** + * Write log level in a human-readable string + * to a given std::ostream. + * + * @param os std::stream to write to + * @param lvl Log level to write + * @return The std::ostream + */ +std::ostream& operator<<(std::ostream &os, log_level lvl); + +/// @cond MATADOR_DEV + +struct log_level_range +{ + log_level min_level = log_level::LVL_INFO; + log_level max_level = log_level::LVL_FATAL; +}; + +/// @endcond + +} +#endif //MATADOR_LOG_LEVEL_HPP diff --git a/include/matador/logger/log_manager.hpp b/include/matador/logger/log_manager.hpp new file mode 100644 index 0000000..ab4330a --- /dev/null +++ b/include/matador/logger/log_manager.hpp @@ -0,0 +1,303 @@ +#ifndef MATADOR_LOG_MANAGER_HPP +#define MATADOR_LOG_MANAGER_HPP + +#include "matador/utils/singleton.hpp" + +#include "matador/logger/logger.hpp" +#include "matador/logger/log_sink.hpp" +#include "matador/logger/file_sink.hpp" +#include "matador/logger/rotating_file_sink.hpp" + +#include + +namespace matador::logger { + +/** + * @brief Manages all log domains + * + * The log_manager class is a singleton and + * manages all available log_domains + * + * There ist always a default log domain + * with the name "default" + * available for which sinks can be added + * and loggers can be created. + */ +class log_manager final : public utils::singleton +{ +public: + /** + * Creates a logger with the given source name + * for the default log domain + * + * @param source Name of the source + * @return The created logger + */ + [[nodiscard]] logger create_logger(std::string source) const; + + /** + * Creates a logger with the given source name + * for the log domain identified by the given + * log domain name. + * + * If the log domain with the given name doesn't exist, + * the domain is created + * + * @param source Name of the source + * @param domain_name The name of the log domain to execute to + * @return The created logger + */ + logger create_logger(std::string source, const std::string &domain_name); + + /** + * Adds a log sink to the default log_domain + * + * @param sink Sink to add to the default log_domain + */ + void add_sink(sink_ptr sink) const; + + /** + * Adds a log sink to the log_domain with the given name. + * If the log domain doesn't exist, it is automatically created. + * + * @param sink Sink to add + * @param domain_name Name of the log domain + */ + void add_sink(sink_ptr sink, const std::string &domain_name); + + /** + * Clears all sinks from the default log domain + */ + void clear_all_sinks() const; + + /** + * Clears all sinks from the log domain + * with the given name + * + * @param domain_name Domain name to clear all sinks from + */ + void clear_all_sinks(const std::string &domain_name); + + /** + * Remove all log domains but the default log domain. + * Clears all sinks from the default log domain. + */ + void clear(); + + /** + * Sets the max default log level. The default + * max leven is LVL_FATAL. All log domains + * will start with this default max log range + * + * @param max_level max log level + */ + static void max_default_log_level(log_level max_level); + + /** + * Returns the default max log level + * + * @return The max log level + */ + static log_level max_default_log_level(); + + /** + * Sets the default min log level. The default + * min leven is LVL_INFO. All log domains + * will start with this default max log range + * + * @param min_level min log level + */ + static void min_default_log_level(log_level min_level); + + /** + * Returns the default min log level + * + * @return The min log level + */ + static log_level min_default_log_level(); + + /// @cond MATADOR_DEV + std::shared_ptr find_domain(const std::string &name); + void log_default(log_level lvl, const std::string &source, const char *message) const; + /// @endcond + +protected: + /// @cond MATADOR_DEV + log_manager() + { + default_log_domain_ = log_domain_map_.insert(std::make_pair("default", std::make_shared("default", default_log_level_range_))).first->second; + } + /// @endcond + +private: + std::shared_ptr acquire_domain(const std::string &name); + +private: + friend class utils::singleton; + + std::shared_ptr default_log_domain_; + + std::map> log_domain_map_; + + static log_level_range default_log_level_range_; +}; + +/** + * Shortcut to create a file log sink + * with the given path. If the path doesn't + * exist, it is created. + * + * @param logfile Path to the logfile + * @return A shared_ptr to the file_sink + */ +std::shared_ptr create_file_sink(const std::string &logfile); + +/** + * Shortcut to create a stderr log sink. + * + * @return A shared_ptr to the stderr_sink + */ +std::shared_ptr create_stderr_sink(); + +/** + * Shortcut to create a stdout log sink. + * + * @return A shared_ptr to the stdout_sink + */ +std::shared_ptr create_stdout_sink(); + +/** + * Shortcut to create a rotating file log sink + * with the given path, max log files and max + * log file size. If the path doesn't + * exist, it is created. + * + * @param logfile Path to the log file + * @param max_size Max log file size + * @param file_count Max number of log files + * @return A shared_ptr to the rotating_file_sink + */ +std::shared_ptr create_rotating_file_sink(const std::string &logfile, size_t max_size, size_t file_count); + +/** + * Sets the default min log level. + * + * @param min_lvl Default min log level + */ +void default_min_log_level(log_level min_lvl); + +/** + * Sets the default max log level. + * + * @param max_lvl Default max log level + */ +void default_max_log_level(log_level max_lvl); + +/** + * Sets the domain min log level for the + * domain with the given name. + * + * @param name Log domain name + * @param min_lvl Default min log level + */ +void domain_min_log_level(const std::string &name, log_level min_lvl); + +/** + * Sets the default max log level for the + * domain with the given name. + * + * @param name Log domain name + * @param max_lvl Default max log level + */ +void domain_max_log_level(const std::string &name, log_level max_lvl); + +/** + * Adds a log sink to the default log domain + * + * @param sink The log sink to add + */ +void add_log_sink(sink_ptr sink); + +/** + * Adds a log sink to the log domain + * with the given name. If the domain + * doesn't exist, it is created. + * + * @param sink The log sink to add + * @param domain The log domain name to add + */ +void add_log_sink(sink_ptr sink, const std::string &domain); + +/** + * Removes all sinks from the + * default domain + */ +void clear_all_log_sinks(); + +/** + * Removes all sinks from the log domain + * with the given domain name + * + * @param domain Domain name to clear all sinks + */ +void clear_all_log_sinks(const std::string &domain); + +/** + * Creates a logger with the given source name + * connected to the default log domain. + * + * @param source The name of the source + * @return The logger instance + */ +logger create_logger(std::string source); + +/** + * Creates a logger with the given source name + * connected to the log domain with the given + * name. If the domain doesn't exist, it is created + * + * @param source The name of the source + * @param domain The name of the log domain + * @return The logger instance + */ +logger create_logger(std::string source, const std::string &domain); + +/** + * Logs the given message for the given source and log level + * to the default log domain. + * + * @param lvl Log level + * @param source Source of the log message + * @param message Message to log + */ +void log_default(log_level lvl, const std::string &source, const char *message); + +/** + * Log the given message with source and log level + * to the default domain. The message will be created + * from the what-argument and the args while the preprocessed + * message uses the printf style to add the arguments. + * + * @tparam ARGS Type of the arguments + * @param lvl Log level + * @param source Source of the log message + * @param what The printf style message + * @param args The arguments for the message + */ +template +void log(const log_level lvl, const std::string &source, const char *what, ARGS const &... args) +{ + char message_buffer[16384]; + +#ifdef _MSC_VER + sprintf_s(message_buffer, 912, what, args...); +#else + sprintf(message_buffer, what, args...); +#endif + + log_default(lvl, source, message_buffer); +} + +} + +#endif //MATADOR_LOG_MANAGER_HPP diff --git a/include/matador/logger/log_sink.hpp b/include/matador/logger/log_sink.hpp new file mode 100644 index 0000000..708dcb0 --- /dev/null +++ b/include/matador/logger/log_sink.hpp @@ -0,0 +1,47 @@ +#ifndef MATADOR_LOG_SINK_HPP +#define MATADOR_LOG_SINK_HPP + +#include + +namespace matador::logger { + +/** + * @brief Base class for all log sinks + * + * This class must be the base class for all + * log sinks and provides their interface + * + * The main interface is the write() interface + * defining how the log message is written. + * + * The close() interface defines a way to close + * the concrete log sink + */ +class log_sink +{ +public: + /** + * Destroys the log sink + */ + virtual ~log_sink() = default; + + /** + * Writes the given log message with the given size + * to the concrete sink + * + * @param message The message to log + * @param size The size of the message + */ + virtual void write(const char *message, std::size_t size) = 0; + + /** + * Closes the log sink if necessary. + */ + virtual void close() = 0; +}; + +using sink_ptr = std::shared_ptr; /**< Shortcut to the log sink shared pointer */ + +} + +#endif //MATADOR_LOG_SINK_HPP diff --git a/include/matador/logger/logger.hpp b/include/matador/logger/logger.hpp new file mode 100644 index 0000000..04082db --- /dev/null +++ b/include/matador/logger/logger.hpp @@ -0,0 +1,267 @@ +#ifndef MATADOR_LOGGER_HPP +#define MATADOR_LOGGER_HPP + +#include "matador/logger/log_level.hpp" +#include "matador/logger/log_domain.hpp" + +#include +#include +#include + +namespace matador::logger { + +/** + * @brief logger to write log messages to log domains + * + * This class is used to write log messages to a connected + * log domain (@sa log_domain). + * Everywhere a logger is needed, it can be instantiated with + * @code + * matador::create_logger(source name) + * @endcode + * + * The interface provides methods to log to each relevant + * log level (@sa log_level) + * + * The message format syntax is like the printf syntax. + * If the message string contains placeholder (beginning with %) + * an argument is expected to be part of the argument list of the + * calling method. + * + * All log messages are written through the internal + * log_domain object to the sinks. + */ +class logger final +{ +public: + + /** + * Create a logger with a given source name connected + * to the given log_domain + * + * @param source The name of the source + * @param log_domain The log_domain containing the log sinks + */ + logger(std::string source, std::shared_ptr log_domain); + + logger(const logger& l) = delete; + logger(logger&& l) noexcept; + logger& operator=(const logger& l) = delete; + logger& operator=(logger&& l) noexcept; + + /** + * Writes a log message string with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void fatal(const std::string &what, ARGS const &... args) { fatal(what.c_str(), args...); } + + /** + * Writes a log message represented by a char pointer with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void fatal(const char *what, ARGS const &... args) { log(log_level::LVL_FATAL, what, args...); } + + /** + * Writes a log message string with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void error(const std::string &what, ARGS const &... args) { error(what.c_str(), args...); } + + /** + * Writes a log message represented by a char pointer with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void error(const char *what, ARGS const &... args) { log(log_level::LVL_ERROR, what, args...); } + + /** + * Writes a log message string with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void warn(const std::string &what, ARGS const &... args) { warn(what.c_str(), args...); } + + /** + * Writes a log message represented by a char pointer with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void warn(const char *what, ARGS const &... args) { log(log_level::LVL_WARN, what, args...); } + + /** + * Writes a log message string with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void info(const std::string &what, ARGS const &... args) { info(what.c_str(), args...); } + + /** + * Writes a log message represented by a char pointer with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void info(const char *what, ARGS const &... args) { log(log_level::LVL_INFO, what, args...); } + + /** + * Writes a log message string with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void debug(const std::string &what, ARGS const &... args) { debug(what.c_str(), args...); } + + /** + * Writes a log message represented by a char pointer with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void debug(const char *what, ARGS const &... args) { log(log_level::LVL_DEBUG, what, args...); } + + /** + * Writes a log message string with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void trace(const std::string &what, ARGS const &... args) { trace(what.c_str(), args...); } + + /** + * Writes a log message represented by a char pointer with log level LVL_FATAL + * to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void trace(const char *what, ARGS const &... args) { log(log_level::LVL_TRACE, what, args...); } + + /** + * Writes a log message represented by a char pointer + * with the given log level to the connected log_domain. + * + * @tparam ARGS Type of the arguments to be replaced for the message placeholder + * @param lvl The log level + * @param what The message to log + * @param args The arguments to be replaced in the message + */ + template + void log(log_level lvl, const char *what, ARGS const &... args); + + /** + * Writes a log message represented by a char pointer + * with the given log level to the connected log_domain. + * + * @param lvl The log level + * @param what The message to log + */ + void log(log_level lvl, const char *what) const; + + /** + * Returns the name of the source the logger represents + * + * @return Represented log source name + */ + const std::string& source() const; + + /** + * Returns the name of the connected log domain + * + * @return The name of the log domain + */ + std::string domain() const; + +private: + std::string source_; + std::shared_ptr logger_domain_; +}; + +template +void logger::log(log_level lvl, const char *what, ARGS const &... args) +{ + char message_buffer[16384]; + +#ifdef _MSC_VER + sprintf_s(message_buffer, 16384, what, args...); +#else + sprintf(message_buffer, what, args...); +#endif + + logger_domain_->log(lvl, source_, message_buffer); +} + +/* + + template +decltype(auto) myForward(T&& t) +{ + return t; +} + +template<> +decltype(auto) myForward(std::string& t) +{ + return t.c_str(); +} + +template<> +decltype(auto) myForward(std::string&& t) +{ + return t.c_str(); +} + +template +static void log(const char* pszFmt, Args&&... args) +{ + doSomething(pszFmt, myForward(std::forward(args))...); +} + + */ +} + +#endif //MATADOR_LOGGER_HPP diff --git a/include/matador/logger/rotating_file_sink.hpp b/include/matador/logger/rotating_file_sink.hpp new file mode 100644 index 0000000..2be3216 --- /dev/null +++ b/include/matador/logger/rotating_file_sink.hpp @@ -0,0 +1,82 @@ +#ifndef MATADOR_ROTATING_FILE_SINK_HPP +#define MATADOR_ROTATING_FILE_SINK_HPP + +#include "matador/logger/log_sink.hpp" +#include "matador/utils/file.hpp" + +#include + +namespace matador::logger { + +/** + * @brief A rotating log file sink + * + * This log sink provides a possibility to + * rotate several log files if the current + * log file reaches the maximum size. + * + * The user can define the maximum number of log files + * and the maximum size of the current log file + * + * The name of the current log file is defined within the + * given logfile path. Each rotated (moved) log file gets + * an incremented number extension right before the + * file extension, e.g.: + * + * Log file name is 'log.txt' the first rotated log file + * is named 'log-1.txt' and so on until the maximum + * number of log files is reached. Then it starts from + * the beginning. + * Keep in mind that the log file to which is currently + * written to is always named like the file name + * given within the path. + */ +class rotating_file_sink final : public log_sink +{ +public: + /** + * Creates a rotating log file sink within the given path + * with the given maximum number of rotating log files where + * each file size is never greater than the given max file + * size. + * + * @param path Path of the log file + * @param max_size Max log file size + * @param file_count Max log file count + */ + rotating_file_sink(const std::string& path, size_t max_size, size_t file_count); + + /** + * Write the message to the current log file. If the + * actual size exceeds the file size limit, the log files + * are rotated. + * + * @param message Message to write + * @param size The size of the log message + */ + void write(const char *message, size_t size) override; + + /** + * Close all open log files + */ + void close() override; + +private: + std::string calculate_filename(size_t fileno); + + void rotate(); + void prepare(const std::string &path); + +private: + file logfile_; + std::string path_; + std::string base_path_; + std::string extension_; + size_t max_size_ = 0; + size_t current_size_ = 0; + size_t current_file_no_ = 0; + size_t file_count_ = 0; +}; + +} +#endif //MATADOR_ROTATING_FILE_SINK_HPP diff --git a/include/matador/object/schema.hpp b/include/matador/object/schema.hpp index 371136c..15e37f7 100644 --- a/include/matador/object/schema.hpp +++ b/include/matador/object/schema.hpp @@ -1,6 +1,7 @@ #ifndef SCHEMA_HPP #define SCHEMA_HPP +#include "matador/logger/log_manager.hpp" #include "matador/object/many_to_many_relation.hpp" #include "matador/object/primary_key_resolver.hpp" #include "matador/object/error_code.hpp" @@ -10,6 +11,8 @@ #include "matador/utils/result.hpp" #include "matador/utils/error.hpp" +#include "matador/logger/logger.hpp" + #include #include #include @@ -77,9 +80,7 @@ public: static void on_attribute(const char * /*id*/, std::optional &/*val*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} template - void on_belongs_to(const char * /*id*/, ForeignPointerType &/*obj*/, const utils::foreign_attributes &/*attr*/) { - on_foreign_key(); - } + void on_belongs_to(const char *id, ForeignPointerType &obj, const utils::foreign_attributes &attr); template void on_has_one(const char * /*id*/, ForeignPointerType &/*obj*/, const utils::foreign_attributes &/*attr*/); @@ -93,19 +94,16 @@ public: template void on_has_many_to_many(const char *id, ContainerType &collection, const utils::foreign_attributes &attr); -private: - template - void on_foreign_key(); - private: explicit relation_completer(schema_node& node) : node_(node) - , schema_(node.schema_){} - + , schema_(node.schema_) + , log_(logger::create_logger("relation_completer")) {} private: schema_node &node_; schema& schema_; + logger::logger log_; }; @@ -239,6 +237,7 @@ private: t_node_map node_map_; t_type_index_node_map type_index_node_map_; t_type_index_node_map expected_node_map_; + logger::logger log_; }; template @@ -250,7 +249,7 @@ void relation_completer::on_has_many( const char *id, CollectionType&, con return new many_to_many_relation(join_column, "id"); }); - schema_.attach_node(node, typeid(many_to_many_relation)); + // schema_.attach_node(node, typeid(many_to_many_relation)); } template @@ -262,7 +261,7 @@ void relation_completer::on_has_many( const char *id, CollectionType&, con return new many_to_many_relation(join_column, "value"); }); - const auto result = schema_.attach>(id); + const auto result = schema_.attach>(id); if (!result) { // Todo: throw internal exception } @@ -280,15 +279,23 @@ void relation_completer::on_has_many_to_many( const char *id, CollectionTy return new many_to_many_relation(join_column, inverse_join_column); }; - auto node = schema_node::make_relation_node(schema_, id); + // auto node = schema_node::make_relation_node(schema_, id); - schema_.attach_node(node, typeid(relation_type)); + // schema_.attach_node(node, typeid(relation_type)); } } template template void relation_completer::on_has_many_to_many( const char *id, ContainerType &collection, const utils::foreign_attributes &attr ) { + auto result = schema_.find_node(id); + if (!result) { + using relation_type = many_to_many_relation; + // auto creator = [attr] { + // return new relation_type(attr.join_column, attr.inverse_join_column); + // }; + } + } template @@ -311,6 +318,11 @@ void relation_completer::on_has_one(const char * id, ForeignPointerType &/ } } +} + +template +template +void relation_completer::on_belongs_to( const char* id, ForeignPointerType& obj, const utils::foreign_attributes& attr ) { } diff --git a/include/matador/object/schema_node.hpp b/include/matador/object/schema_node.hpp index 670f83a..0efe5af 100644 --- a/include/matador/object/schema_node.hpp +++ b/include/matador/object/schema_node.hpp @@ -37,11 +37,11 @@ public: static std::shared_ptr make_relation_node(object::schema& tree, const std::string& name, CreatorFunc &&creator) { auto node = std::shared_ptr(new schema_node(tree, name)); - auto info = std::make_unique>( - node, - object_definition{attribute_definition_generator::generate(tree)} - ); - node->info_ = std::move(info); + // auto info = std::make_unique>( + // node, + // object_definition{attribute_definition_generator::generate(tree)} + // ); + // node->info_ = std::move(info); return node; } diff --git a/include/matador/utils/file.hpp b/include/matador/utils/file.hpp new file mode 100644 index 0000000..486671d --- /dev/null +++ b/include/matador/utils/file.hpp @@ -0,0 +1,126 @@ +#ifndef MATADOR_FILE_HPP +#define MATADOR_FILE_HPP + +#include + +namespace matador { + +/** + * File class representing file stream + * + * The open methods uses internally fopen() thus + * the open modes are the same: + * + * "r" read: Open file for input operations. The file must exist. + * "w" write: Create an empty file for output operations. If a + * file with the same name already exists, its contents are discarded and + * the file is treated as a new empty file. + * "a" append: Open file for output at the end of a file. Output operations + * always write data at the end of the file, expanding it. Repositioning + * operations (fseek, fsetpos, rewind) are ignored. The file is created + * if it does not exist. + * "r+" read/update: Open a file for update (both for input and output). + * The file must exist. + * "w+" write/update: Create an empty file and open it for update (both for input + * and output). If a file with the same name already exists its contents + * are discarded and the file is treated as a new empty file. + * "a+" append/update: Open a file for update (both for input and output) with + * all output operations writing data at the end of the file. Repositioning + * operations (fseek, fsetpos, rewind) affects the next input operations, but + * output operations move the position back to the end of file. The file is + * created if it does not exist. + */ +class file final +{ +public: + /** + * Creates an uninitialized file. + */ + file() = default; + + /** + * Creates and open a file stream. + * + * @param path Path of the file to create + * @param mode The file mode to open + */ + file(const char *path, const char *mode); + + /** + * Creates and open a file stream. + * + * @param path Path of the file to create + * @param mode The file mode to open + */ + file(const std::string &path, const char *mode); + file(const file&) = delete; + file operator=(const file&) = delete; + ~file(); + + /** + * Opens a file stream with the given path. + * If the file is already open it is closed + * + * @param path Path of the file to create + * @param mode The file mode to open + */ + void open(const char *path, const char *mode); + + /** + * Opens a file stream with the given path. + * If the file is already open it is closed + * + * @param path Path of the file to create + * @param mode The file mode to open + */ + void open(const std::string &path, const char *mode); + + /** + * Closes the file stream if it is open + */ + void close(); + + /** + * Returns the size of the file + * + * @return The size of the file + */ + [[nodiscard]] size_t size() const; + + /** + * Returns the path to the file + * + * @return The path to the file + */ + [[nodiscard]] std::string path() const; + + /** + * Returns the internal file stream pointer + * + * @return The internal file stream pointer + */ + [[nodiscard]] FILE* stream() const; + + /** + * Returns true if file is open. + * + * @return True if file is open + */ + [[nodiscard]] bool is_open() const; + +private: + std::string path_; + FILE *stream_ = nullptr; +}; + +/** + * Reads a given file as text and + * returns its content as string + * + * @param f File to read in + * @return The content of the file as string + */ +std::string read_as_text(const file &f); + +} +#endif //MATADOR_FILE_HPP diff --git a/include/matador/utils/singleton.hpp b/include/matador/utils/singleton.hpp index 91983c4..efe1bc0 100644 --- a/include/matador/utils/singleton.hpp +++ b/include/matador/utils/singleton.hpp @@ -18,7 +18,7 @@ template < typename T > class singleton { public: - typedef T value_type; /**< Shortcut for the singletons type */ + typedef T value_type; /**< Shortcut for the singleton type */ /** * @brief Access the instance of the class. diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index 93e9a65..53ac056 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -1,4 +1,12 @@ add_library(matador-core STATIC + ../../include/matador/logger/basic_file_sink.hpp + ../../include/matador/logger/file_sink.hpp + ../../include/matador/logger/log_domain.hpp + ../../include/matador/logger/log_level.hpp + ../../include/matador/logger/log_manager.hpp + ../../include/matador/logger/log_sink.hpp + ../../include/matador/logger/logger.hpp + ../../include/matador/logger/rotating_file_sink.hpp ../../include/matador/net/event_type.hpp ../../include/matador/net/handler.hpp ../../include/matador/net/os.hpp @@ -36,6 +44,7 @@ add_library(matador-core STATIC ../../include/matador/utils/errors.hpp ../../include/matador/utils/export.hpp ../../include/matador/utils/fetch_type.hpp + ../../include/matador/utils/file.hpp ../../include/matador/utils/field_attributes.hpp ../../include/matador/utils/foreign_attributes.hpp ../../include/matador/utils/identifier.hpp @@ -52,6 +61,13 @@ add_library(matador-core STATIC ../../include/matador/utils/uuid.hpp ../../include/matador/utils/value.hpp ../../include/matador/utils/version.hpp + logger/basic_file_sink.cpp + logger/file_sink.cpp + logger/log_domain.cpp + logger/log_level.cpp + logger/log_manager.cpp + logger/logger.cpp + logger/rotating_file_sink.cpp object/attribute_definition_generator.cpp object/attribute_definition.cpp object/basic_object_info.cpp @@ -66,6 +82,7 @@ add_library(matador-core STATIC utils/error.cpp utils/errors.cpp utils/field_attributes.cpp + utils/file.cpp utils/foreign_attributes.cpp utils/identifier.cpp utils/leader_follower_thread_pool.cpp diff --git a/source/core/logger/basic_file_sink.cpp b/source/core/logger/basic_file_sink.cpp new file mode 100644 index 0000000..068101c --- /dev/null +++ b/source/core/logger/basic_file_sink.cpp @@ -0,0 +1,23 @@ +#include "matador/logger/basic_file_sink.hpp" + +namespace matador::logger { + +basic_file_sink::basic_file_sink(FILE *f) +: stream(f) +{} + +void basic_file_sink::write(const char *message, const size_t size) +{ + fwrite(message, sizeof(char), size, stream); + fflush(stream); +} + +void basic_file_sink::close() +{ + if (stream) { + fclose(stream); + stream = nullptr; + } +} + +} diff --git a/source/core/logger/file_sink.cpp b/source/core/logger/file_sink.cpp new file mode 100644 index 0000000..40b4d2a --- /dev/null +++ b/source/core/logger/file_sink.cpp @@ -0,0 +1,68 @@ +#include "matador/logger/file_sink.hpp" + +#include "matador/utils/os.hpp" +#include "matador/utils/string.hpp" + +#include + +namespace matador::logger { + +file_sink::file_sink(const std::string &path) +: path_(path) +{ + std::string filename(path); + // find last dir delimiter + const char *last = strrchr(path.c_str(), os::DIR_SEPARATOR); + if (last != nullptr) { + path_.assign(path.data(), last-path.data()); + } else { + path_.clear(); + } + + if (last != nullptr) { + filename = (last + 1); + } + // extract base path and extension + if (std::vector result; utils::split(filename, '.', result) != 2) { + throw std::logic_error("split path must consists of two elements"); + } + // get the current path + const auto pwd = os::get_current_dir(); + // make the path + os::mkpath(path_); + // change into the path + os::chdir(path_); + // create the file + stream = os::fopen(filename, "a"); + if (stream == nullptr) { + os::chdir(pwd); + throw std::logic_error("error opening file"); + } + os::chdir(pwd); +} + +file_sink::file_sink(const char *path) + : file_sink(std::string(path)) +{} + +file_sink::~file_sink() +{ + if (stream) { + fclose(stream); + stream = nullptr; + } +} + +std::string file_sink::path() const +{ + return path_; +} + +stdout_sink::stdout_sink() + : basic_file_sink(stdout) +{} + +stderr_sink::stderr_sink() + : basic_file_sink(stderr) +{} +} diff --git a/source/core/logger/log_domain.cpp b/source/core/logger/log_domain.cpp new file mode 100644 index 0000000..0c33d39 --- /dev/null +++ b/source/core/logger/log_domain.cpp @@ -0,0 +1,123 @@ +#include "matador/logger/log_domain.hpp" + +#include +#include + +namespace matador::logger { + +namespace details { + +std::size_t acquire_thread_index(const std::thread::id id) { + static std::size_t next_index = 0; + static std::mutex thread_mutex; + static std::map ids; + std::lock_guard lock(thread_mutex); + if(ids.find(id) == ids.end()) { + ids[id] = next_index++; + } + return ids[id]; +} + +char* gettimestamp(char* const buffer, const size_t size) { + using namespace std::chrono; + + const auto now = system_clock::now(); + const auto now_ms = time_point_cast(now); + + std::time_t t = system_clock::to_time_t(now); + const auto ms = duration_cast(now_ms.time_since_epoch()) % 1000; + + std::tm tm{}; +#ifdef _WIN32 + localtime_s(&tm, &t); +#else + localtime_r(&t, &tm); +#endif + char buf[32]; + std::strftime(buf, 32, "%Y-%m-%d %H:%M:%S", &tm); + + std::snprintf(buffer, size, "%s.%03ld", buf, static_cast(ms.count())); + + return buffer; +} + +} + +std::map log_domain::level_strings = { /* NOLINT */ + { log_level::LVL_DEBUG, "DEBUG" }, + { log_level::LVL_INFO, "INFO" }, + { log_level::LVL_WARN, "WARN" }, + { log_level::LVL_ERROR, "ERROR" }, + { log_level::LVL_TRACE, "TRACE" } +}; + +log_domain::log_domain(std::string name, const log_level_range log_range) +: name_(std::move(name)) +, log_level_range_(log_range) +{} + +std::string log_domain::name() const +{ + return name_; +} + +void log_domain::max_log_level(log_level max_level) +{ + log_level_range_.max_level = max_level; +} + +log_level log_domain::max_log_level() const +{ + return log_level_range_.max_level; +} + +void log_domain::min_log_level(log_level min_level) +{ + log_level_range_.min_level = min_level; +} + +log_level log_domain::min_log_level() const +{ + return log_level_range_.min_level; +} + +void log_domain::add_sink(sink_ptr sink) +{ + sinks.push_back(std::move(sink)); +} + +void log_domain::log(log_level lvl, const std::string &source, const char *message) +{ + if (lvl < log_level_range_.max_level || lvl > log_level_range_.min_level) { + return; + } + + char timestamp[80]; + get_time_stamp(timestamp); + + char buffer[1024]; + +#ifdef _MSC_VER + int ret = sprintf_s(buffer, 1024, "%s [Thread %zu] [%-7s] [%s]: %s\n", timestamp, details::acquire_thread_index(std::this_thread::get_id()), level_strings[lvl].c_str(), source.c_str(), message); +#else + int ret = sprintf(buffer, "%s [Thread %lu] [%-7s] [%s]: %s\n", timestamp, acquire_thread_index(std::this_thread::get_id()), level_strings[lvl].c_str(), source.c_str(), message); +#endif + + std::lock_guard l(mutex_); + for (auto &sink : sinks) { + sink->write(buffer, ret); + } +} + +void log_domain::clear() +{ + sinks.clear(); +} + +void log_domain::get_time_stamp(char* const timestamp_buffer) +{ + std::lock_guard l(mutex_); + details::gettimestamp(timestamp_buffer, 80); +} + +} diff --git a/source/core/logger/log_level.cpp b/source/core/logger/log_level.cpp new file mode 100644 index 0000000..edd760a --- /dev/null +++ b/source/core/logger/log_level.cpp @@ -0,0 +1,38 @@ +#include "matador/logger/log_level.hpp" + +#include + +namespace matador::logger { + +std::ostream& operator<<(std::ostream &os, const log_level lvl) +{ + switch (lvl) { + case log_level::LVL_ERROR: + os << "ERROR"; + break; + case log_level::LVL_FATAL: + os << "FATAL"; + break; + case log_level::LVL_DEBUG: + os << "DEBUG"; + break; + case log_level::LVL_INFO: + os << "INFO"; + break; + case log_level::LVL_TRACE: + os << "TRACE"; + break; + case log_level::LVL_WARN: + os << "WARN"; + break; + case log_level::LVL_ALL: + os << "ALL"; + break; + default: + os << "UNKNOWN"; + break; + } + return os; +} + +} diff --git a/source/core/logger/log_manager.cpp b/source/core/logger/log_manager.cpp new file mode 100644 index 0000000..fac5fb2 --- /dev/null +++ b/source/core/logger/log_manager.cpp @@ -0,0 +1,170 @@ +#include "matador/logger/log_manager.hpp" + +namespace matador::logger { + +log_level_range log_manager::default_log_level_range_ = {}; + +logger log_manager::create_logger(std::string source) const { + return logger(std::move(source), default_log_domain_); +} + +logger log_manager::create_logger(std::string source, const std::string &domain_name) { + return logger(std::move(source), acquire_domain(domain_name)); +} + +void log_manager::add_sink(sink_ptr sink) const { + default_log_domain_->add_sink(std::move(sink)); +} + +void log_manager::add_sink(sink_ptr sink, const std::string &domain_name) +{ + const auto log_domain = acquire_domain(domain_name); + + log_domain->add_sink(std::move(sink)); +} + +void log_manager::clear_all_sinks() const { + default_log_domain_->clear(); +} + +void log_manager::clear_all_sinks(const std::string &domain_name) +{ + if (const auto it = log_domain_map_.find(domain_name); it != log_domain_map_.end()) { + it->second->clear(); + } +} + +void log_manager::clear() +{ + log_domain_map_.clear(); + default_log_domain_->clear(); +} + +void log_manager::max_default_log_level(log_level max_level) +{ + default_log_level_range_.max_level = max_level; +} + +log_level log_manager::max_default_log_level() +{ + return default_log_level_range_.max_level; +} + +void log_manager::min_default_log_level(log_level min_level) +{ + default_log_level_range_.min_level = min_level; +} + +log_level log_manager::min_default_log_level() +{ + return default_log_level_range_.min_level; +} + +std::shared_ptr log_manager::acquire_domain(const std::string &name) +{ + if (name == "default") { + return default_log_domain_; + } + auto it = log_domain_map_.find(name); + if (it == log_domain_map_.end()) { + it = log_domain_map_.insert(std::make_pair(name, std::make_shared(name, default_log_level_range_))).first; + } + return it->second; +} + +std::shared_ptr log_manager::find_domain(const std::string &name) +{ + if (name == "default") { + return default_log_domain_; + } + auto it = log_domain_map_.find(name); + if (it != log_domain_map_.end()) { + return it->second; + } + return std::shared_ptr(); +} + +void log_manager::log_default(const log_level lvl, const std::string &source, const char *message) const { + default_log_domain_->log(lvl, source, message); +} + +std::shared_ptr create_file_sink(const std::string &logfile) +{ + return std::make_shared(logfile); +} + +std::shared_ptr create_stderr_sink() +{ + return std::make_shared(); +} + +std::shared_ptr create_stdout_sink() +{ + return std::make_shared(); +} + +std::shared_ptr create_rotating_file_sink(const std::string &logfile, size_t max_size, size_t file_count) +{ + return std::make_shared(logfile, max_size, file_count); +} + +void default_min_log_level(const log_level min_lvl) +{ + log_manager::min_default_log_level(min_lvl); +} + +void default_max_log_level(const log_level max_lvl) +{ + log_manager::max_default_log_level(max_lvl); +} + +void domain_min_log_level(const std::string &name, const log_level min_lvl) +{ + if (const auto domain = log_manager::instance().find_domain(name)) { + domain->min_log_level(min_lvl); + } +} + +void domain_max_log_level(const std::string &name, const log_level max_lvl) +{ + if (const auto domain = log_manager::instance().find_domain(name)) { + domain->min_log_level(max_lvl); + } +} + +void add_log_sink(sink_ptr sink) +{ + log_manager::instance().add_sink(std::move(sink)); +} + +void add_log_sink(sink_ptr sink, const std::string &domain) +{ + log_manager::instance().add_sink(std::move(sink), domain); +} + +void clear_all_log_sinks() +{ + log_manager::instance().clear_all_sinks(); +} + +void clear_all_log_sinks(const std::string &domain) +{ + log_manager::instance().clear_all_sinks(domain); +} + +logger create_logger(std::string source) +{ + return log_manager::instance().create_logger(std::move(source)); +} + +logger create_logger(std::string source, const std::string &domain) +{ + return log_manager::instance().create_logger(std::move(source), domain); +} + +void log_default(const log_level lvl, const std::string &source, const char *message) +{ + log_manager::instance().log_default(lvl, source, message); +} + +} diff --git a/source/core/logger/logger.cpp b/source/core/logger/logger.cpp new file mode 100644 index 0000000..fb12b9b --- /dev/null +++ b/source/core/logger/logger.cpp @@ -0,0 +1,34 @@ +#include "matador/logger/logger.hpp" + +namespace matador::logger { + +logger::logger(std::string source, std::shared_ptr log_domain) + : source_(std::move(source)) + , logger_domain_(std::move(log_domain)) +{} + +logger::logger(logger&& l) noexcept +: source_(std::move(l.source_)) +, logger_domain_(std::move(l.logger_domain_)) +{} + +logger& logger::operator=(logger&& l) noexcept { + source_ = std::move(l.source_); + logger_domain_ = std::move(l.logger_domain_); + return *this; +} + +const std::string& logger::source() const +{ + return source_; +} + +std::string logger::domain() const +{ + return logger_domain_->name(); +} + +void logger::log(const log_level lvl, const char *what) const { + logger_domain_->log(lvl, source_, what); +} +} \ No newline at end of file diff --git a/source/core/logger/rotating_file_sink.cpp b/source/core/logger/rotating_file_sink.cpp new file mode 100644 index 0000000..2fffd0f --- /dev/null +++ b/source/core/logger/rotating_file_sink.cpp @@ -0,0 +1,110 @@ +#include "matador/logger/rotating_file_sink.hpp" + +#include "matador/utils/string.hpp" +#include "matador/utils/os.hpp" + +#include +#include + +namespace matador::logger { + +rotating_file_sink::rotating_file_sink(const std::string& path, size_t max_size, size_t file_count) + : max_size_(max_size) + , file_count_(file_count) +{ + // split path and file + prepare(path); + + current_size_ = logfile_.size(); +} + +void rotating_file_sink::write(const char *message, size_t size) +{ + current_size_ += size; + if (current_size_ > max_size_) { + current_size_ = size; + rotate(); + } + fwrite(message, sizeof(char), size, logfile_.stream()); + fflush(logfile_.stream()); +} + +void rotating_file_sink::close() +{ + logfile_.close(); +} + +std::string rotating_file_sink::calculate_filename(size_t fileno) +{ + char buffer[1024]; +#ifdef _WIN32 + sprintf_s(buffer, 1024, "%s.%zu.%s", base_path_.c_str(), fileno, extension_.c_str()); +#else + sprintf(buffer, "%s.%zu.%s", base_path_.c_str(), fileno, extension_.c_str()); +#endif + return buffer; +} + +/* + * Rotate log files: + * log_path.log -> log_path.01.log + * log_path.01.log -> log_path.02.log + * log_path.02.log -> log_path.03.log + * ... + * log_path.[n-1].log -> log_path.[n].log + * delete log_path.[n].log + * create new log_path.log + */ +void rotating_file_sink::rotate() +{ + std::string filename; + for (size_t i = file_count_; i != 0; --i) { + filename = calculate_filename(i); + if (!matador::os::exists(filename.c_str())) { + continue; + } + if (i == file_count_) { + matador::os::remove(filename.c_str()); + } else { + matador::os::rename(filename.c_str(), calculate_filename(i+1).c_str()); + } + } + std::string path = logfile_.path(); + logfile_.close(); + matador::os::rename(path.c_str(), filename.c_str()); + logfile_.open(path, "a"); +} + +void rotating_file_sink::prepare(const std::string &path) +{ + std::string filename(path); + // find last dir delimiter + const char *last = strrchr(path.c_str(), os::DIR_SEPARATOR); + if (last != nullptr) { + path_.assign(path.data(), last-path.data()); + } else { + path_.clear(); + } + + if (last != nullptr) { + filename = (last + 1); + } + // extract base path and extension + std::vector result; + if (utils::split(filename, '.', result) != 2) { + throw std::logic_error("split path must consists of two elements"); + } + base_path_.assign(result[0]); + extension_.assign(result[1]); + // get the current path + const auto pwd = os::get_current_dir(); + // make the path + os::mkpath(path_); + // change into the path + os::chdir(path_); + // create the file + logfile_.open(filename, "a"); + // change back to the origin path + os::chdir(pwd); +} +} diff --git a/source/core/object/schema.cpp b/source/core/object/schema.cpp index 7bcab40..55009aa 100644 --- a/source/core/object/schema.cpp +++ b/source/core/object/schema.cpp @@ -9,7 +9,8 @@ utils::error make_error(const error_code ec, const std::string &msg) { schema::schema(std::string name) : name_(std::move(name)) -, root_(schema_node::make_null_node(*this)) { +, root_(schema_node::make_null_node(*this)) +, log_(logger::create_logger("schema")) { root_->first_child_ = std::shared_ptr(new schema_node(*this)); root_->last_child_ = std::shared_ptr(new schema_node(*this)); root_->first_child_->next_sibling_ = root_->last_child_; diff --git a/source/core/utils/file.cpp b/source/core/utils/file.cpp new file mode 100644 index 0000000..b08d43b --- /dev/null +++ b/source/core/utils/file.cpp @@ -0,0 +1,83 @@ +#include "matador/utils/file.hpp" +#include "matador/utils/os.hpp" + +#include + +namespace matador { + +file::file(const char *path, const char *mode) + : file(std::string(path), mode) +{} + +file::file(const std::string &path, const char *mode) +{ + open(path, mode); +} + +file::~file() +{ + close(); +} + +void file::open(const char *path, const char *mode) +{ + open(std::string(path), mode); +} + +void file::open(const std::string &path, const char *mode) +{ + close(); + path_ = path; + stream_ = os::fopen(path, mode); +} + +void file::close() +{ + if (stream_ != nullptr) { + os::fclose(stream_); + stream_ = nullptr; + } +} + +size_t file::size() const +{ + return os::file_size(stream_); +} + +std::string file::path() const +{ + return path_; +} + +FILE *file::stream() const +{ + return stream_; +} + +bool file::is_open() const +{ + return stream_ != nullptr; +} + +std::string read_as_text(const file &f) +{ + if (!f.is_open()) { + throw std::logic_error("file is not open"); + } + +// get file size: + fseek (f.stream() , 0 , SEEK_END); + const size_t size = ftell(f.stream()); + rewind (f.stream()); + + std::string result(size, '\0'); + + if (const auto ret = fread((result.data()), 1, size, f.stream()); ret == 0 && ferror(f.stream())) { + char error_buffer[1024]; + os::strerror(errno, error_buffer, 1024); + throw std::logic_error(error_buffer); + } + + return result; +} +} diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 29c3eb3..f4e0205 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -3,6 +3,7 @@ CPMAddPackage("gh:catchorg/Catch2@3.7.1") list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) add_executable(CoreTests + logger/LoggerTest.cpp utils/BasicTypeToVisitorTest.cpp utils/ConvertTest.cpp utils/DefaultTypeTraitsTest.cpp diff --git a/test/core/logger/LoggerTest.cpp b/test/core/logger/LoggerTest.cpp new file mode 100644 index 0000000..0f6bcb8 --- /dev/null +++ b/test/core/logger/LoggerTest.cpp @@ -0,0 +1,374 @@ +#include + +#include "matador/logger/log_manager.hpp" + +#include "matador/utils/os.hpp" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +#include +#include +#include + +using namespace matador::logger; + +namespace filehelper { + +class std_stream_switcher +{ +public: + explicit std_stream_switcher(FILE *str, const char* redirect) + : stream(str) { + fflush(stream); + fgetpos(stream, &pos); + fd = matador::os::dup(stream); + matador::os::freopen(redirect, "w+", stream); + } + ~std_stream_switcher() { + fflush(stream); +#ifdef _MSC_VER + _dup2(fd, _fileno(stream)); + _close(fd); +#else + dup2(fd, fileno(stream)); + close(fd); +#endif + clearerr(stream); + fsetpos(stream, &pos); + } + +private: + FILE *stream = nullptr; + int fd = 0; + fpos_t pos = {}; +}; + +} + +void validate_log_file_line(const std::string &filename, int line_index, const std::string &level, const std::string &src, const std::string &msg); + +TEST_CASE("Test log file sink", "[logger][log][file_sink]") { + file_sink test("test.txt"); + + REQUIRE(matador::os::exists("test.txt")); + + // UNIT_ASSERT_EQUAL("test.txt", test.path()); + + test.close(); + + if (::remove("test.txt") == -1) { +#ifdef _MSC_VER + char buf[1024]; + strerror_s(buf, 1024, errno); + FAIL(buf); +#else + UNIT_FAIL(strerror(errno)); +#endif + } + + REQUIRE(!matador::os::exists("test.txt")); + + { + auto path = matador::os::build_path("mylog", "test.txt"); + file_sink test2(path); + + REQUIRE("mylog" == test2.path()); + + matador::os::chdir("mylog"); + + REQUIRE(matador::os::exists("test.txt")); + + } + if (::remove("test.txt") == -1) { +#ifdef _MSC_VER + char buf[1024]; + strerror_s(buf, 1024, errno); + FAIL(buf); +#else + UNIT_FAIL(strerror(errno)); +#endif + } + + REQUIRE(!matador::os::exists("test.txt")); + + matador::os::chdir(".."); + + matador::os::rmdir("mylog"); +} + +TEST_CASE("Test log rotating file sink", "[logger][log][rotate_file_sink]") { + const auto dir = matador::os::build_path("my", "log"); + const auto path = matador::os::build_path(dir, "log.txt"); + + const auto logsink = create_rotating_file_sink(path, 30, 3); + + matador::os::chdir(dir); + + REQUIRE(matador::os::is_readable("log.txt")); + REQUIRE(matador::os::is_writable("log.txt")); + + std::string line = "hello world and first line\n"; + + logsink->write(line.c_str(), line.size()); + + matador::os::chdir("my"); + matador::os::chdir("log"); + + REQUIRE(!matador::os::exists("log.1.txt")); + + logsink->write(line.c_str(), line.size()); + + REQUIRE(matador::os::exists("log.1.txt")); + + logsink->close(); + + matador::os::remove("log.txt"); + matador::os::remove("log.1.txt"); + + matador::os::chdir(".."); + matador::os::chdir(".."); + + matador::os::rmpath(matador::os::build_path("my", "log")); +} + +TEST_CASE("Test log level range", "[logger][level][range]") { + REQUIRE(log_level::LVL_INFO == log_manager::min_default_log_level()); + REQUIRE(log_level::LVL_FATAL == log_manager::max_default_log_level()); + + default_min_log_level(log_level::LVL_DEBUG); + default_max_log_level(log_level::LVL_ERROR); + + REQUIRE(log_level::LVL_DEBUG == log_manager::min_default_log_level()); + REQUIRE(log_level::LVL_ERROR == log_manager::max_default_log_level()); + + log_level_range llr; + llr.min_level = log_level::LVL_DEBUG; + llr.max_level = log_level::LVL_TRACE; + log_domain ld("test", llr); + + REQUIRE(log_level::LVL_DEBUG == ld.min_log_level()); + REQUIRE(log_level::LVL_TRACE == ld.max_log_level()); + + ld.min_log_level(log_level::LVL_INFO); + ld.max_log_level(log_level::LVL_ERROR); + + REQUIRE(log_level::LVL_INFO == ld.min_log_level()); + REQUIRE(log_level::LVL_ERROR == ld.max_log_level()); +} + +TEST_CASE("Test basic logger functions", "[logger][basic]") { + auto logger = create_logger("test"); + + REQUIRE("test" == logger.source()); + REQUIRE("default" == logger.domain()); + + auto logsink = create_file_sink("log.txt"); + + REQUIRE(matador::os::is_readable("log.txt")); + REQUIRE(matador::os::is_writable("log.txt")); + + logsink->close(); + + if (::remove("log.txt") == -1) { +#ifdef _MSC_VER + char buf[1024]; + strerror_s(buf, 1024, errno); + FAIL(buf); +#else + FAIL(strerror(errno)); +#endif + } + + REQUIRE(!matador::os::exists("log.txt")); + + logger = create_logger("net", "system"); + + REQUIRE("net" == logger.source()); + REQUIRE("system" == logger.domain()); + + logsink = create_file_sink("net.txt"); + + REQUIRE(matador::os::is_readable("net.txt")); + REQUIRE(matador::os::is_writable("net.txt")); + + logsink->close(); + + matador::os::remove("net.txt"); + + REQUIRE(!matador::os::exists("net.txt")); + + log_manager::instance().clear(); +} + +TEST_CASE("Test logging", "[logger][logging]") { + domain_min_log_level("default", log_level::LVL_FATAL); + domain_max_log_level("default", log_level::LVL_TRACE); + + auto logger = create_logger("test"); + + REQUIRE("test" == logger.source()); + REQUIRE("default" == logger.domain()); + + auto logsink = create_file_sink("log.txt"); + + REQUIRE(matador::os::is_readable("log.txt")); + REQUIRE(matador::os::is_writable("log.txt")); + + add_log_sink(logsink); + + logger.info("information"); + logger.info("information %s", "important"); + logger.warn("warning"); + logger.warn("warning %s", "important"); + logger.debug("debugging"); + logger.debug("debugging %s", "important"); + logger.trace("tracing something"); + logger.trace("tracing something %s", "important"); + logger.error("big error"); + logger.error("big error %s", "important"); + log(log_level::LVL_ERROR, "test", "global log test %d", 4711); + + logsink->close(); + + validate_log_file_line("log.txt", 0, "INFO", "test", "information"); + validate_log_file_line("log.txt", 1, "INFO", "test", "information important"); + validate_log_file_line("log.txt", 2, "WARN", "test", "warning"); + validate_log_file_line("log.txt", 3, "WARN", "test", "warning important"); + validate_log_file_line("log.txt", 4, "DEBUG", "test", "debugging"); + validate_log_file_line("log.txt", 5, "DEBUG", "test", "debugging important"); + validate_log_file_line("log.txt", 6, "TRACE", "test", "tracing something"); + validate_log_file_line("log.txt", 7, "TRACE", "test", "tracing something important"); + validate_log_file_line("log.txt", 8, "ERROR", "test", "big error"); + validate_log_file_line("log.txt", 9, "ERROR", "test", "big error important"); + validate_log_file_line("log.txt", 10, "ERROR", "test", "global log test 4711"); + + matador::os::remove("log.txt"); + + REQUIRE(!matador::os::exists("log.txt")); + + log_manager::instance().clear(); +} + +TEST_CASE("Test log stdout", "[logger][logging][stdout]") { + auto logger = create_logger("test"); + + REQUIRE("test" == logger.source()); + REQUIRE("default" == logger.domain()); + + auto logsink = create_stdout_sink(); + + // Redirect stdout + { + filehelper::std_stream_switcher stdout_switcher(stdout, "stdout.txt"); + add_log_sink(logsink); + logger.info("information"); + } + + logsink->close(); + + validate_log_file_line("stdout.txt", 0, "INFO", "test", "information"); + + matador::os::remove("stdout.txt"); + + REQUIRE(!matador::os::exists("stdout.txt")); + + log_manager::instance().clear(); +} + +TEST_CASE("Test log stderr", "[logger][logging][stderr]") { + auto logger = create_logger("test"); + + REQUIRE("test" == logger.source()); + REQUIRE("default" == logger.domain()); + + auto logsink = create_stderr_sink(); + + // Redirect stdout + { + filehelper::std_stream_switcher stdout_switcher(stderr, "stderr.txt"); + add_log_sink(logsink); + logger.info("information"); + } + + logsink->close(); + + validate_log_file_line("stderr.txt", 0, "INFO", "test", "information"); + + matador::os::remove("stderr.txt"); + + REQUIRE(!matador::os::exists("stderr.txt")); + + log_manager::instance().clear(); +} + +TEST_CASE("Test log levels", "[logger][levels]") { + std::stringstream out; + + out << log_level::LVL_ERROR; + + REQUIRE("ERROR" == out.str()); + + out.str(""); + out.clear(); + out << log_level::LVL_DEBUG; + + REQUIRE("DEBUG" == out.str()); + + out.str(""); + out.clear(); + out << log_level::LVL_INFO; + + REQUIRE("INFO" == out.str()); + + out.str(""); + out.clear(); + out << log_level::LVL_FATAL; + + REQUIRE("FATAL" == out.str()); + + out.str(""); + out.clear(); + out << log_level::LVL_TRACE; + + REQUIRE("TRACE" == out.str()); + + out.str(""); + out.clear(); + out << log_level::LVL_WARN; + + REQUIRE("WARN" == out.str()); + + out.str(""); + out.clear(); + out << log_level::LVL_ALL; + + REQUIRE("ALL" == out.str()); +} + +void validate_log_file_line(const std::string &filename, int line_index, const std::string &level, const std::string &src, const std::string &msg) { + std::ifstream logfile(filename); + + REQUIRE(logfile.good()); + + std::string line; + int line_count = 0; + do { + if (!std::getline(logfile, line).good()) { + FAIL("couldn't find line"); + } + } while (line_count++ < line_index); + + const std::regex logline_regex(R"(^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\.\d{3}.*\[([A-Z]+)\s*\] \[(.*)\]: (.*)$)"); + std::smatch match; + + REQUIRE(std::regex_match(line, match, logline_regex)); + REQUIRE(4 == match.size()); + + REQUIRE(level == match[1].str()); + REQUIRE(src ==match[2].str()); + REQUIRE(msg == match[3].str()); +} \ No newline at end of file