Procházet zdrojové kódy

* Initial commit

* Moved and refactored code from old project cpputils
master
bergmann před 6 roky
revize
7bcce1db5f
37 změnil soubory, kde provedl 2421 přidání a 0 odebrání
  1. +3
    -0
      .gitmodules
  2. +53
    -0
      CMakeLists.txt
  3. +7
    -0
      cmake/cpplogging-config.cmake
  4. +20
    -0
      cmake/cpplogging-var.cmake
  5. +1
    -0
      cmake/modules
  6. +4
    -0
      include/cpplogging/interface.h
  7. +197
    -0
      include/cpplogging/interface/logger.h
  8. +118
    -0
      include/cpplogging/interface/logger.inl
  9. +7
    -0
      include/cpplogging/manager.h
  10. +6
    -0
      include/cpplogging/manager/consumer.h
  11. +82
    -0
      include/cpplogging/manager/consumer/consumer.h
  12. +26
    -0
      include/cpplogging/manager/consumer/consumer.inl
  13. +52
    -0
      include/cpplogging/manager/consumer/consumer_stream.h
  14. +62
    -0
      include/cpplogging/manager/logger_impl.h
  15. +158
    -0
      include/cpplogging/manager/manager.h
  16. +44
    -0
      include/cpplogging/manager/manager.inl
  17. +6
    -0
      include/cpplogging/manager/matcher.h
  18. +45
    -0
      include/cpplogging/manager/matcher/matcher.h
  19. +35
    -0
      include/cpplogging/manager/matcher/matcher_all.h
  20. +36
    -0
      include/cpplogging/manager/matcher/matcher_default_logger.h
  21. +50
    -0
      include/cpplogging/manager/matcher/matcher_regex.h
  22. +70
    -0
      include/cpplogging/manager/rule.h
  23. +50
    -0
      include/cpplogging/manager/rule.inl
  24. +43
    -0
      include/cpplogging/types.h
  25. +112
    -0
      src/CMakeLists.txt
  26. +7
    -0
      src/cpplogging/interface/logger.cpp
  27. +17
    -0
      src/cpplogging/manager/consumer/consumer.cpp
  28. +25
    -0
      src/cpplogging/manager/consumer/consumer_stream.cpp
  29. +681
    -0
      src/cpplogging/manager/consumer/formatting.cpp
  30. +51
    -0
      src/cpplogging/manager/logger_impl.cpp
  31. +140
    -0
      src/cpplogging/manager/manager.cpp
  32. +9
    -0
      src/cpplogging/manager/matcher/matcher.cpp
  33. +9
    -0
      src/cpplogging/manager/matcher/matcher_all.cpp
  34. +11
    -0
      src/cpplogging/manager/matcher/matcher_default_logger.cpp
  35. +14
    -0
      src/cpplogging/manager/matcher/matcher_regex.cpp
  36. +43
    -0
      test/CMakeLists.txt
  37. +127
    -0
      test/cpplogging/cpplogging_tests.cpp

+ 3
- 0
.gitmodules Zobrazit soubor

@@ -0,0 +1,3 @@
[submodule "/home/bergmann/projects/TotoStarOnline/projects/worker/cpplogging/cmake/modules"]
path = /home/bergmann/projects/TotoStarOnline/projects/worker/cpplogging/cmake/modules
url = b3rgmann@git.bergmann89.de:cpp/CmakeModules.git

+ 53
- 0
CMakeLists.txt Zobrazit soubor

@@ -0,0 +1,53 @@
# Initialize CMake ################################################################################

CMake_Minimum_Required ( VERSION 3.12.0 FATAL_ERROR )

# Set CMAKE_BUILD_TYPE
If ( NOT CMAKE_BUILD_TYPE )
Set ( CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build!" FORCE )
EndIf ( NOT CMAKE_BUILD_TYPE )
Set_Property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel )

# Set CMAKE_MODULE_PATH
If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" )
Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/" )
EndIf ( )
If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" )
Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" )
EndIf ( )

# Project #########################################################################################

Include ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpplogging-var.cmake" )
Project ( cpplogging
DESCRIPTION "A simple library"
VERSION "${CPPLOGGING_VERSION}" )
Include ( CTest )
Include ( GNUInstallDirs )

# Subdirectories
Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/src )
Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/test )

# Install
Include ( CMakePackageConfigHelpers )
Write_Basic_Package_Version_File ( "${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config-version.cmake"
VERSION ${CPPLOGGING_VERSION}
COMPATIBILITY AnyNewerVersion )
Configure_File ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpplogging-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config.cmake"
@ONLY )

Set ( ConfigPackageLocation "${CPPLOGGING_INSTALL_DIR_SHARE}/cmake" )
Install ( EXPORT cpplogging
NAMESPACE cpplogging::
DESTINATION ${ConfigPackageLocation} )
Install ( FILES
"${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/cpplogging-config-version.cmake"
DESTINATION
${ConfigPackageLocation}
COMPONENT
Devel )

+ 7
- 0
cmake/cpplogging-config.cmake Zobrazit soubor

@@ -0,0 +1,7 @@
# cpplogging-config.cmake - package configuration file

Get_Filename_Component ( CURRENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH )
Include ( ${CURRENT_DIR}/cpplogging.cmake )

Include ( CMakeFindDependencyMacro )
Find_Dependency ( cppcore )

+ 20
- 0
cmake/cpplogging-var.cmake Zobrazit soubor

@@ -0,0 +1,20 @@
# Version
Set ( CPPLOGGING_VERSION_MAJOR 1 )
Set ( CPPLOGGING_VERSION_MINOR 0 )
Set ( CPPLOGGING_VERSION_PATCH 0 )
Set ( CPPLOGGING_VERSION_BUILD 0 )
Set ( CPPLOGGING_VERSION_SHORT "${CPPLOGGING_VERSION_MAJOR}.${CPPLOGGING_VERSION_MINOR}" )
Set ( CPPLOGGING_VERSION "${CPPLOGGING_VERSION_SHORT}.${CPPLOGGING_VERSION_PATCH}.${CPPLOGGING_VERSION_BUILD}" )
Set ( CPPLOGGING_NAME "cpplogging-${CPPLOGGING_VERSION_SHORT}" )
Set ( CPPLOGGING_OUTPUTNAME "helloworld" )

# Install directories
Set ( CPPLOGGING_INSTALL_DIR_INCLUDE "include/${CPPLOGGING_NAME}" )
Set ( CPPLOGGING_INSTALL_DIR_LIB "lib" )
Set ( CPPLOGGING_INSTALL_DIR_SHARE "share/${CPPLOGGING_NAME}" )

# C Standard
Set ( CMAKE_C_STANDARD 11 )
Set ( CMAKE_CXX_STANDARD 17 )
Set ( CMAKE_C_STANDARD_REQUIRED ON )
Set ( CMAKE_CXX_STANDARD_REQUIRED ON )

+ 1
- 0
cmake/modules

@@ -0,0 +1 @@
Subproject commit 1a32531aef2deeebd5637b1873bc4e976628801c

+ 4
- 0
include/cpplogging/interface.h Zobrazit soubor

@@ -0,0 +1,4 @@
#pragma once

#include "interface/logger.h"
#include "interface/types.h"

+ 197
- 0
include/cpplogging/interface/logger.h Zobrazit soubor

@@ -0,0 +1,197 @@
#pragma once

#include <sstream>
#include <cpplogging/types.h>

#define cpplogging_log(p_logger, p_level) \
if (p_logger.is_enabled(::cpplogging::log_level::p_level)) \
p_logger.log() \
.level(::cpplogging::log_level::p_level) \
.file(__FILE__) \
.line(__LINE__)

namespace cpplogging
{

struct logger;

namespace __impl
{

/**
* @brief Helper class to create a log entry.
*/
struct log_helper
: public std::ostringstream
{
private:
logger& _logger;
log_entry_ptr_s _entry;

private:
/**
* @brief Move constructor.
*/
log_helper(log_helper&&) = delete;

/**
* @brief Copy constructor.
*/
log_helper(const log_helper&) = delete;

public:
/**
* @brief Constructor.
*
* @param[in] p_logger Logger this log helper belongs to.
*/
inline log_helper(logger& p_logger);

/**
* @brief Constructor.
*
* @param[in] p_logger Logger this log helper belongs to.
* @param[in] p_entry Log entry to create.
*/
inline log_helper(logger& p_logger, const log_entry_ptr_s& p_entry);

/**
* @brief Destructor. Will finally write the log entry.
*/
inline ~log_helper();

/**
* @brief Set the log level of the entry,
*/
inline log_helper& level(log_level level);

/**
* @brief Set the sender of the log entry.
*/
inline log_helper& sender(const void * sender);

/**
* @brief Set the filename of the log entry.
*/
inline log_helper& file(const char * file);

/**
* @brief Set the line number of the log entry.
*/
inline log_helper& line(int line);

/**
* @brief Set the message of the log entry.
*/
inline log_helper& message(const std::string& msg);

/**
* @brief Add the passed string to the log message.
*/
inline log_helper& add_message(const std::string& msg);

/**
* @brief Set the message of the entry using format string.
*/
template<typename... T_args>
inline log_helper& format(const std::string& fmt, T_args&&... args);

/**
* @brief Add a message to the entry using format string.
*/
template<typename... T_args>
inline log_helper& add_format(const std::string& fmt, T_args&&... args);

private:
/**
* @brief Format a string.
*
* @tparam T_args Arguments of the format string.
*
* @param[in] fmt Format string.
* @param[in] sz Size of the buffer to use for the formatting.
* @param[in] args Arguments of the format.
*/
template<typename... T_args>
static inline std::string format(const std::string& fmt, size_t sz, T_args&&... args);
};

}

/**
* @brief Class to create log messages.
*/
struct logger
{
public:
friend __impl::log_helper;

private:
std::string _name; //!< name of the logger

private:
/**
* @brief Move constructor.
*/
logger(logger&&) = delete;

/**
* @brief Copy constructor.
*/
logger(const logger&) = delete;

public:
/**
* @brief Constructor.
*/
inline logger(const std::string name);

/**
* @brief Destructor.
*/
virtual ~logger() = default;

/**
* @brief Get the name of the logger.
*/
inline const std::string& name() const;

/**
* @brief Check if the passed log level is enabled.
*
* @param[in] level Level to check.
*
* @retval true The passed log level is enabled.
* @retval false The passed log level is disabled.
*/
virtual bool is_enabled(log_level level) const = 0;

/**
* @brief Write a log entry,
*
* @return Log helper to create log entry.
*/
inline __impl::log_helper log();

protected:
/**
* @brief Write a log entry.
*
* @param[in] entry Log entry to write.
*/
virtual void write_entry(const log_entry_ptr_s& entry) const = 0;

public:
/**
* @brief Get the instance on an logger with the passed name.
*
* @param[in] name Name of the loggger.
*
* @return Instance of the requested logger.
*/
static logger& get(const std::string& name = "");
};

}

#include "logger.inl"

+ 118
- 0
include/cpplogging/interface/logger.inl Zobrazit soubor

@@ -0,0 +1,118 @@
#pragma once

#include "logger.h"

namespace cpplogging
{

namespace __impl
{

/* log_helper */

log_helper
::log_helper(logger& p_logger)
: _logger (p_logger)
, _entry (new log_entry())
{ _entry->name = p_logger.name(); }

log_helper
::log_helper(logger& p_logger, const log_entry_ptr_s& p_entry)
: _logger (p_logger)
, _entry (p_entry)
{ _entry->name = p_logger.name(); }

log_helper
::~log_helper()
{
_entry->message += str();
_logger.write_entry(_entry);
}

log_helper& log_helper
::level(log_level level)
{
_entry->level = level;
return *this;
}

log_helper& log_helper
::sender(const void * sender)
{
_entry->sender = sender;
return *this;
}

log_helper& log_helper
::file(const char * file)
{
_entry->file = file;
return *this;
}

log_helper& log_helper
::line(int line)
{
_entry->line = line;
return *this;
}

log_helper& log_helper
::message(const std::string& msg)
{
_entry->message = msg;
return *this;
}

log_helper& log_helper
::add_message(const std::string& msg)
{
_entry->message += msg;
return *this;
}

template<typename... T_args>
log_helper& log_helper
::format(const std::string& fmt, T_args&&... args)
{
_entry->message = format(fmt, 0x8000, std::forward<T_args>(args)...);
return *this;
}

template<typename... T_args>
log_helper& log_helper
::add_format(const std::string& fmt, T_args&&... args)
{
_entry->message += format(fmt, 0x8000, std::forward<T_args>(args)...);
return *this;
}

template<typename... T_args>
std::string log_helper
::format(const std::string& fmt, size_t sz, T_args&&... args)
{
std::unique_ptr<char, decltype(&free)> buff(static_cast<char*>(malloc(sz)), &free);
auto len = snprintf(buff.get(), sz, fmt.c_str(), std::forward<T_args>(args)...);
if (len < 0)
throw std::runtime_error("unable to format string");
return std::string(buff.get(), len);
}

}

/* logger */

logger
::logger(const std::string name)
: _name(name)
{ }

const std::string& logger
::name() const
{ return _name; }

__impl::log_helper logger
::log()
{ return __impl::log_helper(*this); }

}

+ 7
- 0
include/cpplogging/manager.h Zobrazit soubor

@@ -0,0 +1,7 @@
#pragma once

#include "manager/manager.h"
#include "manager/consumer.h"
#include "manager/logger_impl.h"
#include "manager/matcher.h"
#include "manager/rule.h"

+ 6
- 0
include/cpplogging/manager/consumer.h Zobrazit soubor

@@ -0,0 +1,6 @@
#pragma once

#include "consumer/consumer.h"
#include "consumer/consumer_stream.h"

#include "consumer/consumer.inl"

+ 82
- 0
include/cpplogging/manager/consumer/consumer.h Zobrazit soubor

@@ -0,0 +1,82 @@
#pragma once

#include <memory>
#include <vector>
#include <iostream>

#include <cpplogging/types.h>

namespace cpplogging
{

/**
* @brief Class to consume log entries.
*/
struct consumer
{
private:
using format_type = std::vector<std::string>;

protected:
std::string _name; //!< name of the consumer
format_type _format; //!< parsed logging format

/**
* @brief Get the default log format.
*/
static inline const std::string& default_format();

/**
* @brief Parse the given format and generate a pre-compiled list of strings.
*
* @param[in] format Format to parse.
*
* @return Precompiled format.
*/
static format_type parse_format(const std::string& format);

/**
* @brief Write a log entry to the passed stream using the precompiled format.
*
* @param[in] e Entry to write to stream.
* @param[in] f Precompiled format to use for writing.
* @param[in] os Stream to write entry to.
*/
static void write_formatted(const log_entry& e, const format_type& f, std::ostream& os);

public:
/**
* @brief Constructor.
*
* @param[in] name Name of the consumer.
* @param[in] auto_register Automatically register the consumer.
* @param[in] format Format to use for logging.
*/
consumer(
const std::string& name,
bool auto_register,
const std::string& format = default_format());

/**
* @breif Destructor.
*/
virtual ~consumer();

/**
* @brief Get the name of the logger.
*/
inline const std::string& name() const;

/**
* @brief Consume a log entry.
*
* @param[in] entry Log entry to consume.
*/
virtual void write_entry(const log_entry_ptr_s& entry) const = 0;
};

using consumer_ptr_u = std::unique_ptr<consumer>;

}

#include "consumer.inl"

+ 26
- 0
include/cpplogging/manager/consumer/consumer.inl Zobrazit soubor

@@ -0,0 +1,26 @@
#pragma once

#include "consumer.h"

namespace cpplogging
{

/* consumer */

const std::string& consumer
::default_format()
{
static const std::string value(
"[${runtime:-016.6f}] "
"${level:5S} "
"${sender:016x}@${thread:016x} "
"${filename:-25}:${line:5d}$n"
"${message}");
return value;
}

const std::string& consumer
::name() const
{ return _name; }

}

+ 52
- 0
include/cpplogging/manager/consumer/consumer_stream.h Zobrazit soubor

@@ -0,0 +1,52 @@
#pragma once

#include <mutex>

#include "consumer.h"

namespace cpplogging
{

/**
* @brief Consumer that writes the log entries to a stream,
*/
struct consumer_stream
: public consumer
{
private:
using stream_ptr_u = std::unique_ptr<std::ostream>;

private:
mutable std::mutex _mutex; //!< Mutex to protect the stream.
std::ostream& _stream; //!< Stream to write log entries to.
stream_ptr_u _storage; //!< Pointer to store owned stream.

public:
/**
* @brief Constructor.
*
* @param[in] name Name of the consumer.
* @param[in] stream Stream to write data to.
* @param[in] auto_register Automatically register the consumer.
*/
consumer_stream(const std::string& name, std::ostream& stream, bool auto_register);

/**
* @brief Constructor.
*
* @param[in] name Name of the consumer.
* @param[in] stream Stream to write data to.
* @param[in] auto_register Automatically register the consumer.
*/
consumer_stream(const std::string& name, stream_ptr_u&& stream, bool auto_register);

/**
* @brief Consume a log entry.
*
* @param[in] entry Log entry to consume.
*/
void write_entry(const log_entry_ptr_s& entry) const override;

};

}

+ 62
- 0
include/cpplogging/manager/logger_impl.h Zobrazit soubor

@@ -0,0 +1,62 @@
#pragma once

#include <set>
#include <mutex>

#include "rule.h"
#include "../interface/logger.h"

namespace cpplogging
{

struct logger_impl
: public logger
{
private:
mutable std::mutex _mutex; //!< mutex to protect the object
std::set<rule*> _rules; //!< rules assigned to this logger
log_level _min_level { log_level::unknown }; //!< min log level over all rules
log_level _max_level { log_level::unknown }; //!< max log level over all rules

public:
using logger::logger;

/**
* @brief Add a rule to this logger.
*
* @param[in] rule Rule to add.
*/
void add_rule(rule& rule);

/**
* @brief Remove a rule to this logger.
*
* @param[in] rule Rule to remove.
*/
void remove_rule(rule& rule);

/**
* @brief Check if the passed log level is enabled.
*
* @param[in] level Level to check.
*
* @retval true The passed log level is enabled.
* @retval false The passed log level is disabled.
*/
bool is_enabled(log_level level) const override;

protected:
/**
* @brief Write a log entry.
*
* @param[in] entry Log entry to write.
*/
void write_entry(const log_entry_ptr_s& entry) const override;

/**
* @brief Update the log levels cached in the logger.
*/
void update_log_level();
};

}

+ 158
- 0
include/cpplogging/manager/manager.h Zobrazit soubor

@@ -0,0 +1,158 @@
#pragma once

#include <set>
#include <map>
#include <list>

#include "rule.h"
#include "logger_impl.h"
#include "consumer/consumer.h"

namespace cpplogging
{

/**
* @brief Wrapps the static configuration methods for the logger engine.
*
* The logging engine has two modes: logging and config.
*
* In the config mode, the user can add rules, consumer and matcher.
* In the logging mode the engine is not thread-safe and must be accessed
* from only one thread!
*
* In the logging mode the user can only create new loggers or log messages.
* The logging mode is thread-safe. The user can use as many thread as he want,
* to log messages.
*/
struct manager
{
private:
struct consumer_wrapper
{
consumer& _consumer; //!< consumer to use for operations
consumer_ptr_u _storage; //!< storage to store owner object

/**
* @brief Construtor to create a consumer wrapper by using the consumer reference.
*/
inline consumer_wrapper(consumer& p_consumer);

/**
* @brief Construtor to create a consumer wrapper. The wrapper takes the ownership of the consumer.
*/
inline consumer_wrapper(consumer_ptr_u&& p_consumer);

/**
* @brief Operator to compare the wrapper to a other wrapper
*/
inline bool operator<(const consumer_wrapper& other) const;

/**
* @brief Operator to access the consumer behind the wrapper.
*/
inline consumer* operator->();

/**
* @brief Operator to access the consumer behind the wrapper.
*/
inline const consumer* operator->() const;

/**
* @brief Operator to access the consumer behind the wrapper.
*/
inline consumer& operator*();

/**
* @brief Operator to access the consumer behind the wrapper.
*/
inline const consumer& operator*() const;
};

using logger_impl_ptr_u = std::unique_ptr<logger_impl>;
using logger_map = std::map<std::string, logger_impl_ptr_u>;
using rule_list = std::list<rule>;
using consumer_set = std::set<consumer_wrapper>;

private:
static std::mutex _mutex; //!< mutex to protect the engine
static logger_impl _default; //!< default logger
static logger_map _logger; //!< normal logger instances
static rule_list _rules; //!< rules to combine loggers with consumers
static consumer_set _consumer; //!< consumers registered

public:
/**
* @brief Get the instance of the logger with the requested name.
*
* @param[in] name Name of the logger to get the instance for.
*
* @return Instance of the logger with the requested name.
*/
static logger& get_logger(const std::string& name = "");

/**
* @brief Register a new consumer in the logging framework.
*
* @param[in] consumer Reference of the consumer to register.
*/
static void register_consumer(consumer& consumer);

/**
* @brief Register a new consumer in the logging framework.
*
* @param[in] consumer Consumer to register (the logging framework will own the consumer).
*
* @return Pointer to the consumer.
*/
static const consumer* register_consumer(consumer_ptr_u&& consumer);

/**
* @brief Unregister a consumer from the logging framework.
*
* @param[in] consumer Reference of the consumer to unregister
*/
static void unregister_consumer(consumer& consumer);

/**
* @brief Unregister a consumer from the logging framework.
*
* @param[in] consumer Consumer to unregister from the framework.
*/
static void unregister_consumer(consumer* consumer);

/**
* @brief Define a new rule in the logging framework.
*
* @param[in] logger_matcher Matcher to use for matching loggers.
* @param[in] consumer_matcher Matcher to use for matching consumers.
* @param[in] min_level Minimum log level.
* @param[in] max_level Maximum log level.
*
* @return Return the handle to the rule (for removing).
*/
static rule_handle define_rule(
matcher_ptr_u&& logger_matcher,
matcher_ptr_u&& consumer_matcher,
log_level min_level = log_level::debug,
log_level max_level = log_level::error);

/**
* @brief Remove a rule from the logging framework.
*
* @param[in] handle Handle of the rule to remove.
*/
static void undefine_rule(rule_handle handle);

/**
* @brief Remove all consumers, rules, matchers and loggers.
*/
static void reset();

private:
/**
* @brief Initialize the given logger instance.
*/
static logger_impl& init_logger(logger_impl& logger);
};

}

+ 44
- 0
include/cpplogging/manager/manager.inl Zobrazit soubor

@@ -0,0 +1,44 @@
#pragma once

#include <exception>

#include "manager.h"

namespace cpplogging
{

/* manager::consumer_wrapper */

manager::consumer_wrapper
::consumer_wrapper(consumer& p_consumer)
: _consumer (p_consumer)
, _storage (nullptr)
{ }

manager::consumer_wrapper
::consumer_wrapper(consumer_ptr_u&& p_consumer)
: _consumer (*p_consumer)
, _storage (std::move(p_consumer))
{ }

bool manager::consumer_wrapper
::operator<(const consumer_wrapper& other) const
{ return &_consumer < &other._consumer; }

consumer* manager::consumer_wrapper
::operator->()
{ return &_consumer; }

const consumer* manager::consumer_wrapper
::operator->() const
{ return &_consumer; }

consumer& manager::consumer_wrapper
::operator*()
{ return _consumer; }

const consumer& manager::consumer_wrapper
::operator*() const
{ return _consumer; }

}

+ 6
- 0
include/cpplogging/manager/matcher.h Zobrazit soubor

@@ -0,0 +1,6 @@
#pragma once

#include "matcher/matcher.h"
#include "matcher/matcher_all.h"
#include "matcher/matcher_default_logger.h"
#include "matcher/matcher_regex.h"

+ 45
- 0
include/cpplogging/manager/matcher/matcher.h Zobrazit soubor

@@ -0,0 +1,45 @@
#pragma once

#include <memory>

#include "../consumer/consumer.h"
#include "../../interface/logger.h"

namespace cpplogging
{

/**
* @brief Class to use for matching a rule or a logger.
*/
struct matcher
{
/**
* @brief Destructor.
*/
virtual ~matcher() = default;

/**
* @brief Check if the passed logger match this matcher.
*
* @param[in] logger Logger to check.
*
* @retval true If the logger matches.
* @retval false If the logger does not match.
*/
virtual bool match(const logger& logger) const;

/**
* @brief Check if the passed consumer match this matcher.
*
* @param[in] consumer Consumer to check.
*
* @retval true If the consumer matches.
* @retval false If the consumer does not match.
*/
virtual bool match(const consumer& consumer) const;

};

using matcher_ptr_u = std::unique_ptr<matcher>;

}

+ 35
- 0
include/cpplogging/manager/matcher/matcher_all.h Zobrazit soubor

@@ -0,0 +1,35 @@
#pragma once

#include "matcher.h"

namespace cpplogging
{

/**
* @brief This matcher matches any logger and any consumer.
*/
struct matcher_all
: public matcher
{
/**
* @brief Check if the passed logger match this matcher.
*
* @param[in] logger Logger to check.
*
* @retval true If the logger matches.
* @retval false If the logger does not match.
*/
bool match(const logger& logger) const override;

/**
* @brief Check if the passed consumer match this matcher.
*
* @param[in] consumer Consumer to check.
*
* @retval true If the consumer matches.
* @retval false If the consumer does not match.
*/
bool match(const consumer& consumer) const override;
};

}

+ 36
- 0
include/cpplogging/manager/matcher/matcher_default_logger.h Zobrazit soubor

@@ -0,0 +1,36 @@
#pragma once

#include "matcher.h"

namespace cpplogging
{

/**
* @brief This matcher does only match the default logger.
*/
struct matcher_default_logger
: public matcher
{
private:
logger& _default; //!< the default logger

public:
using matcher::match;

/**
* @brief Constructor.
*/
matcher_default_logger();

/**
* @brief Check if the passed logger match this matcher.
*
* @param[in] logger Logger to check.
*
* @retval true If the logger matches.
* @retval false If the logger does not match.
*/
bool match(const logger& logger) const override;
};

}

+ 50
- 0
include/cpplogging/manager/matcher/matcher_regex.h Zobrazit soubor

@@ -0,0 +1,50 @@
#pragma once

#include <regex>

#include "matcher.h"

namespace cpplogging
{

/**
* @brief Matcher that uses a regular expression to match a logger or a consumer
*/
struct matcher_regex
: public matcher
{
private:
std::regex _regex; //!< regex to use for matching
bool _invert; //!< invert the regular expression

public:
/**
* @brief Constructor.
*
* @param[in] regex Regex to use for matching.
* @param[in] invert Invert the regular expression.
*/
matcher_regex(const std::string regex, bool invert = false);

/**
* @brief Check if the passed logger match this matcher.
*
* @param[in] logger Logger to check.
*
* @retval true If the logger matches.
* @retval false If the logger does not match.
*/
bool match(const logger& logger) const override;

/**
* @brief Check if the passed consumer match this matcher.
*
* @param[in] consumer Consumer to check.
*
* @retval true If the consumer matches.
* @retval false If the consumer does not match.
*/
bool match(const consumer& consumer) const override;
};

}

+ 70
- 0
include/cpplogging/manager/rule.h Zobrazit soubor

@@ -0,0 +1,70 @@
#pragma once

#include <set>
#include <mutex>

#include "matcher/matcher.h"
#include "consumer/consumer.h"

namespace cpplogging
{

struct rule
{
private:
mutable std::mutex _mutex;
std::set<const consumer*> _consumer;

public:
matcher_ptr_u logger_matcher; //!< matcher to use for logger matching
matcher_ptr_u consumer_matcher; //!< matcher to use for consumer matching
log_level min_level; //!< minimum log level
log_level max_level; //!< maximum log level

/**
* @brief Constructor.
*
* @param[in] lm Matcher to use for logger matching.
* @param[in] cm Matcher to use for consumer matching.
* @param[in] min Minimum log level.
* @param[in] max Maximum log level.
*/
inline rule(matcher_ptr_u&& lm, matcher_ptr_u&& cm, log_level min, log_level max);

/**
* @brief CHeck if the passed log level is enabled in this rule.
*
* @param[in] level Level to check.
*
* @retval true If the log level is enabled for this rule.
* @retval false If the log level is not enabled for this rule.
*/
inline bool is_enabled(log_level level) const;

/**
* @brief Add a consumer to this rule.
*
* @param[in] consumer Consumer to add to the rule.
*/
inline void add_consumer(const consumer& consumer);

/**
* @brief Remove a consumer from this rule.
*
* @param[in] consumer Consumer to remove from the rule.
*/
inline void remove_consumer(const consumer& consumer);

/**
* @brief Write the passed log entry to all registered consumers.
*
* @param[in] entry Entry to write to the consumers.
*/
inline void write_entry(const log_entry_ptr_s& entry) const;
};

using rule_handle = void *;

}

#include "rule.inl"

+ 50
- 0
include/cpplogging/manager/rule.inl Zobrazit soubor

@@ -0,0 +1,50 @@
#pragma once

#include "rule.h"

namespace cpplogging
{

/* rule */

rule
::rule(matcher_ptr_u&& lm, matcher_ptr_u&& cm, log_level min, log_level max)
: logger_matcher (std::move(lm))
, consumer_matcher (std::move(cm))
, min_level (min)
, max_level (max)
{ }

bool rule
::is_enabled(log_level level) const
{
return min_level <= level
&& max_level >= level;
}

void rule
::add_consumer(const consumer& consumer)
{
std::lock_guard<std::mutex> lg(_mutex);
_consumer.insert(&consumer);
}

void rule
::remove_consumer(const consumer& consumer)
{
std::lock_guard<std::mutex> lg(_mutex);
_consumer.erase(&consumer);
}

void rule
::write_entry(const log_entry_ptr_s& entry) const
{
std::lock_guard<std::mutex> lg(_mutex);
if (is_enabled(entry->level))
{
for (auto& c : _consumer)
c->write_entry(entry);
}
}

}

+ 43
- 0
include/cpplogging/types.h Zobrazit soubor

@@ -0,0 +1,43 @@
#pragma once

#include <chrono>
#include <thread>
#include <memory>

namespace cpplogging
{

/**
* @brief Log level.
*/
enum class log_level
{
unknown = 0,
debug,
info,
warn,
error,
};

/**
* @brief Log entry that stored all relevant information for a logged message.
*/
struct log_entry
{
using clock_type = std::chrono::steady_clock;
using uptime_type = clock_type::time_point;

log_level level { log_level::debug };
uptime_type uptime { clock_type::now() };
time_t systime { std::time(nullptr) };
const void* sender { nullptr };
std::thread::id thread { std::this_thread::get_id() };
const char* file { nullptr };
int line { 0 };
std::string name;
std::string message;
};

using log_entry_ptr_s = std::shared_ptr<log_entry>;

}

+ 112
- 0
src/CMakeLists.txt Zobrazit soubor

@@ -0,0 +1,112 @@
# Initialize ######################################################################################

Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE )
Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC )
Include ( strip_symbols OPTIONAL RESULT_VARIABLE HAS_STRIP_SYMBOLS )

Option ( CPPLOGGING_INSTALL_HEADER
"Install headers of cpplogging."
ON )
Option ( CPPLOGGING_INSTALL_STATIC
"Install static library of cpplogging."
ON )
Option ( CPPLOGGING_INSTALL_SHARED
"Install shared library of cpplogging."
ON )
Option ( CPPLOGGING_INSTALL_DEBUG
"Install the stripped debug informations of cpplogging."
OFF )
Option ( CPPLOGGING_NO_STRIP
"Do not strip debug symbols from binary."
OFF )

# Object Library ##################################################################################

Set ( CMAKE_POSITION_INDEPENDENT_CODE ON )
Set ( CPPLOGGING_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include )
File ( GLOB_RECURSE CPPLOGGING_HEADER_FILES ${CPPLOGGING_INCLUDE_DIR}/*.h )
File ( GLOB_RECURSE CPPLOGGING_INLINE_FILES ${CPPLOGGING_INCLUDE_DIR}/*.inl )
File ( GLOB_RECURSE CPPLOGGING_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )
Add_Library ( cpplogging-objects
OBJECT
${CPPLOGGING_HEADER_FILES}
${CPPLOGGING_INLINE_FILES}
${CPPLOGGING_SOURCE_FILES} )
Target_Include_Directories ( cpplogging-objects
PUBLIC
$<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}>
$<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> )

# Static Library ##################################################################################

Add_Library ( cpplogging-static STATIC $<TARGET_OBJECTS:cpplogging-objects> )
Set_Target_Properties ( cpplogging-static
PROPERTIES
OUTPUT_NAME "cpplogging"
VERSION ${CPPLOGGING_VERSION} )
Target_Include_Directories ( cpplogging-static
PUBLIC
$<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}>
$<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> )

# Shared Library ##################################################################################

Add_Library ( cpplogging-shared SHARED $<TARGET_OBJECTS:cpplogging-objects> )
Set_Target_Properties ( cpplogging-shared
PROPERTIES
OUTPUT_NAME "cpplogging"
VERSION ${CPPLOGGING_VERSION}
SOVERSION ${CPPLOGGING_VERSION_SHORT} )
Target_Include_Directories ( cpplogging-shared
PUBLIC
$<BUILD_INTERFACE:${CPPLOGGING_INCLUDE_DIR}>
$<INSTALL_INTERFACE:${CPPLOGGING_INSTALL_DIR_INCLUDE}> )

# Optimization ####################################################################################

# pedantic
If ( HAS_PEDANTIC )
Pedantic_Apply_Flags_Target ( cpplogging-objects CXX )
Pedantic_Apply_Flags_Target ( cpplogging-static CXX )
Pedantic_Apply_Flags_Target ( cpplogging-shared CXX )
EndIf ( )

# cotire
If ( HAS_COTIRE )
Cotire ( cpplogging-objects )
Cotire ( cpplogging-static )
Cotire ( cpplogging-shared )
EndIf ( )

# Install #########################################################################################

# Header
If ( CPPLOGGING_INSTALL_HEADER )
Install ( FILES ${CPPLOGGING_INCLUDE_DIR}/cpplogging.h
DESTINATION ${CPPLOGGING_INSTALL_DIR_INCLUDE} )
Install ( DIRECTORY ${CPPLOGGING_INCLUDE_DIR}/cpplogging
DESTINATION ${CPPLOGGING_INSTALL_DIR_INCLUDE} )
EndIf ( )

# Static
If ( CPPLOGGING_INSTALL_STATIC )
Install ( TARGETS cpplogging-static
EXPORT cpplogging
DESTINATION ${CPPLOGGING_INSTALL_DIR_LIB} )
EndIf ( )

# Shared
If ( CPPLOGGING_INSTALL_SHARED )
Install ( TARGETS cpplogging-shared
EXPORT cpplogging
DESTINATION ${CPPLOGGING_INSTALL_DIR_LIB} )
EndIf ( )

# Debug
If ( HAS_STRIP_SYMBOLS AND NOT CPPLOGGING_NO_STRIP )
Strip_Symbols ( cpplogging-shared CPPLOGGING_DBG_FILE )
If ( CPPLOGGING_INSTALL_DEBUG )
Install ( FILES ${CPPLOGGING_DBG_FILE}
DESTINATION ${CPPLOGGING_INSTALL_DIR_LIB} )
EndIf ( )
EndIf ( )

+ 7
- 0
src/cpplogging/interface/logger.cpp Zobrazit soubor

@@ -0,0 +1,7 @@
#include <cpplogging/manager/manager.h>
#include <cpplogging/interface/logger.h>

using namespace ::cpplogging;

logger& logger::get(const std::string& name)
{ return manager::get_logger(name); }

+ 17
- 0
src/cpplogging/manager/consumer/consumer.cpp Zobrazit soubor

@@ -0,0 +1,17 @@
#include <cpplogging/manager/manager.h>
#include <cpplogging/manager/consumer/consumer.h>

using namespace ::cpplogging;

consumer::consumer(const std::string& name, bool auto_register, const std::string& format)
: _name (name)
, _format (parse_format(format))
{
if (auto_register)
manager::register_consumer(*this);
}

consumer::~consumer()
{
manager::unregister_consumer(this);
}

+ 25
- 0
src/cpplogging/manager/consumer/consumer_stream.cpp Zobrazit soubor

@@ -0,0 +1,25 @@
#include <cstring>
#include <iomanip>

#include <cpplogging/manager/manager.h>
#include <cpplogging/manager/consumer/consumer_stream.h>

using namespace ::cpplogging;

consumer_stream::consumer_stream(const std::string& name, std::ostream& stream, bool auto_register)
: consumer (name, auto_register)
, _stream (stream)
, _storage (nullptr)
{ }

consumer_stream::consumer_stream(const std::string& name, stream_ptr_u&& stream, bool auto_register)
: consumer (name, auto_register)
, _stream (*stream)
, _storage (std::move(stream))
{ }

void consumer_stream::write_entry(const log_entry_ptr_s& entry) const
{
std::lock_guard<std::mutex> lk(_mutex);
write_formatted(*entry, _format, _stream);
}

+ 681
- 0
src/cpplogging/manager/consumer/formatting.cpp Zobrazit soubor

@@ -0,0 +1,681 @@
#include <map>
#include <chrono>
#include <cstring>
#include <iomanip>

#include <cpplogging/manager/manager.h>
#include <cpplogging/manager/consumer/consumer.h>

using namespace ::cpplogging;

enum class parser_state
{
is_text, //!< current pos is inside normal text
is_token_begin, //!< current pos is in token begin (except char '$')
is_in_token, //!< current pos is in token (everything after '{')
is_in_format, //!< current pos is in format (everything after ':')
is_in_format_align, //!< current pos is in format align (expect char '-')
is_in_format_zero, //!< current pos is in format align (expect char '0')
is_in_format_dot, //!< current pos is in format dot (expect char '.')
is_in_format_type, //!< current pos is in format type (expect char 's', 'f', x', 'X' or 'd')
};

enum class value_token
: uint8_t
{
unknown = 0, //!< unknown
level, //!< log level
uptime, //!< system uptime
systime, //!< unix timestamp
runtime, //!< runtime of the application
sender, //!< sender of the log message
thread, //!< thread id
filename, //!< filename only
filepath, //!< whole filepath
line, //!< line number
name, //!< name of the logger
message, //!< log message
newline, //!< write new line
};

enum class value_format
: uint8_t
{
unknown = 0,
decimal,
real,
hex_lower,
hex_upper,
string,
string_upper,
};

struct format_item
{
const uint16_t magic { 0 }; //!< magic value to identify the item
value_token token { value_token::unknown }; //!< value to print
value_format format { value_format::unknown }; //!< format to print the value with
uint8_t width { 0 }; //!< with of the value
uint8_t precision { 0 }; //!< precision (for float values)
bool right_align { false }; //!< use right alignment
bool zero_fill { false }; //!< fill with zeros (if not than spaces)

inline void reset()
{ memset(this, 0, sizeof(*this)); }

inline std::string as_string() const
{ return std::string(reinterpret_cast<const char*>(this), sizeof(*this)); }

} __attribute__ ((packed));

static_assert(sizeof(format_item) == 8, "format_item has invalid size!");

struct op_less_invariant_string
{
inline bool operator()(const std::string& lhs, const std::string& rhs) const
{
auto c1 = lhs.c_str();
auto c2 = rhs.c_str();
auto l1 = lhs.size();
auto l2 = rhs.size();
while (l1 > 0 && l2 > 0 && std::tolower(*c1) == std::tolower(*c2))
{
++c1;
++c2;
--l1;
--l2;
}
return l1 > 0 && l2 > 0
? std::tolower(*c1) < std::tolower(*c2)
: l1 < l2;
}
};

struct invariant_string_map
: public std::map<std::string, value_token, op_less_invariant_string>
{
using map::map;

inline value_token get_token(const std::string& token) const
{
auto it = find(token);
return it == end()
? value_token::unknown
: it->second;
}
};

static invariant_string_map value_token_map({
{ "level", value_token::level },
{ "uptime", value_token::uptime },
{ "systime", value_token::systime },
{ "runtime", value_token::runtime },
{ "sender", value_token::sender },
{ "thread", value_token::thread },
{ "filename", value_token::filename },
{ "filepath", value_token::filepath },
{ "line", value_token::line },
{ "name", value_token::name },
{ "message", value_token::message },
{ "newline", value_token::newline },
});

consumer::format_type consumer::parse_format(const std::string& format)
{
consumer::format_type ret;

parser_state state = parser_state::is_text;
const char * s = format.c_str();
const char * e = s + format.size();
const char * c = s;
const char * x;
char * tmp;
unsigned long long int u;
format_item item;

while (c <= e)
{
switch (*c)
{
/* if this is the EOF */
case '\0':
if (s < c)
{
if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0')
ret.back() += std::string(s, static_cast<size_t>(c - s - 1));
else
ret.emplace_back(s, c - s);
}
break;
/* the format string starts with $ */
case '$':
switch (state)
{
/* if we are 'in_text', we switch to 'is_token_begin' */
case parser_state::is_text:
state = parser_state::is_token_begin;
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* after '$' we expect '{' */
case '{':
switch (state)
{
/* if we have seen '$' we are in 'is_token_begin' and switch to 'is_in_token' */
case parser_state::is_token_begin:
if (s + 1 < c)
{
if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0')
ret.back() += std::string(s, static_cast<size_t>(c - s - 1));
else
ret.emplace_back(s, c - s - 1);
}
s = c - 1;
x = c + 1;
state = parser_state::is_in_token;
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* inside the token we have ':' to seperate the format */
case ':':
switch (state)
{
/* if we are in 'is_in_token' we extract the token and switch to 'is_in_format' */
case parser_state::is_in_token:
state = parser_state::is_in_format;
item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x)));
if (item.token == value_token::unknown)
{
state = parser_state::is_text;
item.reset();
}
x = c + 1;
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* inside the format parsing we can have '-' to indicate right align */
case '-':
switch (state)
{
/* if we are in 'is_in_format' we switch to 'is_in_format_align' */
case parser_state::is_in_format:
state = parser_state::is_in_format_align;
item.right_align = true;
x = c + 1;
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* inside the format parsing we can have '0' to indicate zero fill */
case '0':
switch (state)
{
/* if we are in state 'is_in_format' or 'is_in_format_align' we switch to 'is_in_format_zero' */
case parser_state::is_in_format:
case parser_state::is_in_format_align:
state = parser_state::is_in_format_zero;
item.zero_fill = true;
x = c + 1;
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* inside the format parsing we can have '.' to indicate the precision */
case '.':
switch (state)
{
/* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_dot' */
case parser_state::is_in_format:
case parser_state::is_in_format_align:
case parser_state::is_in_format_zero:
u = std::strtoull(x, &tmp, 10);
if (x == tmp || x > c)
{
/* if parsing the with failed, we switch back to 'is_text' */
state = parser_state::is_text;
item.reset();
}
else
{
item.width = static_cast<uint8_t>(u);
x = c + 1;
state = parser_state::is_in_format_dot;
}
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* if 'n' follows '$' we write a newline */
case 'n':
switch (state)
{
case parser_state::is_token_begin:
item.token = value_token::newline;
ret.emplace_back(item.as_string());
item.reset();
state = parser_state::is_text;
s = c + 1;
break;
default:
break;
}
break;

/* inside the format parsing we can have 's', 'f', 'd', 'x' or 'X' to indicate the format */
case 's':
case 'S':
case 'f':
case 'd':
case 'x':
case 'X':
switch (state)
{
/* if we are in state 'is_in_token' everything is fine */
case parser_state::is_in_token:
break;
/* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_type' */
case parser_state::is_in_format:
case parser_state::is_in_format_align:
case parser_state::is_in_format_zero:
case parser_state::is_in_format_dot:
u = std::strtoull(x, &tmp, 10);
if (x != c && (x == tmp || x > c))
{
/* if parsing the with failed, we switch back to 'is_text' */
state = parser_state::is_text;
item.reset();
}
else
{
if (state == parser_state::is_in_format_dot)
item.precision = static_cast<uint8_t>(u);
else
item.width = static_cast<uint8_t>(u);

/* set the type and switch to 'is_in_format_type' */
x = c + 1;
state = parser_state::is_in_format_type;
switch(*c)
{
case 's': item.format = value_format::string; break;
case 'S': item.format = value_format::string_upper; break;
case 'f': item.format = value_format::real; break;
case 'd': item.format = value_format::decimal; break;
case 'x': item.format = value_format::hex_lower; break;
case 'X': item.format = value_format::hex_upper; break;
default: break;
}
}
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* inside the token/format parsing '}' indicates the end */
case '}':
switch (state)
{
/* if we are in 'is_in_token' we extract the token, add the item and switch to 'is_text' */
case parser_state::is_in_token:
state = parser_state::is_text;
item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x)));
if (item.token == value_token::unknown)
{
state = parser_state::is_text;
}
else
{
ret.emplace_back(item.as_string());
}
item.reset();
s = c + 1;
break;
/* if we are in 'is_in_format_type' we extract the token, add the item and switch to 'is_text' */
case parser_state::is_in_format_type:
state = parser_state::is_text;
ret.emplace_back(item.as_string());
item.reset();
s = c + 1;
break;
/* if we are in 'is_in_format', is_in_format_zero' or 'is_in_format_align'
we extract the with, add the item and switch to 'is_text'*/
case parser_state::is_in_format:
case parser_state::is_in_format_zero:
case parser_state::is_in_format_align:
case parser_state::is_in_format_dot:
u = std::strtoull(x, &tmp, 10);
s = c + 1;
if (x != tmp && x <= c)
{
if (state == parser_state::is_in_format_dot)
item.precision = static_cast<uint8_t>(u);
else
item.width = static_cast<uint8_t>(u);
ret.emplace_back(item.as_string());
}
item.reset();
state = parser_state::is_text;
break;
/* in an other case switch back to 'is_text' */
default:
state = parser_state::is_text;
item.reset();
break;
}
break;

/* any non parsing relevant char */
default:
switch (state)
{
/* in case 'is_in_format', 'is_in_format_align' and 'is_in_format_zero'
we need to check if the current char is a digit, if not we switch to 'is_text' */
case parser_state::is_in_format:
case parser_state::is_in_format_align:
case parser_state::is_in_format_zero:
if ( *c != '.'
&& (*c < '0' || *c > '9'))
{
state = parser_state::is_text;
item.reset();
}
break;
/* any other case is fine */
default:
break;
}
break;
}
++c;
}

return ret;
}

static const auto start_time = std::chrono::steady_clock::now();

void consumer::write_formatted(const log_entry& e, const format_type& f, std::ostream& os)
{
using duration_f = std::chrono::duration<double, std::ratio<1>>;
using duration_u = std::chrono::duration<unsigned long long int, std::ratio<1>>;

static const std::string LogLevelUpperDebug("DEBUG", 5);
static const std::string LogLevelUpperInfo ("INFO", 4);
static const std::string LogLevelUpperWarn ("WARN", 4);
static const std::string LogLevelUpperError("ERROR", 5);

static const std::string LogLevelLowerDebug("debug", 5);
static const std::string LogLevelLowerInfo ("info", 4);
static const std::string LogLevelLowerWarn ("warn", 4);
static const std::string LogLevelLowerError("error", 5);

bool has_line_end = false;
for (auto& x : f)
{
/* check if element is empty */
if (x.empty())
{
continue;
}

/* check if element is normal item */
if (*x.c_str() != '\0')
{
os << x;
continue;
}

/* setup the stream format */
const char * s;
auto& i = *reinterpret_cast<const format_item*>(x.c_str());
os << (i.right_align ? std::right : std::left)
<< std::setfill(i.zero_fill ? '0' : ' ')
<< std::setw(i.width)
<< std::setprecision(i.precision);

switch (i.token)
{
/* level */
case value_token::level:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << static_cast<int>(e.level);
break;
case value_format::string_upper:
switch (e.level)
{
case log_level::debug: os << LogLevelUpperDebug; break;
case log_level::info: os << LogLevelUpperInfo; break;
case log_level::warn: os << LogLevelUpperWarn; break;
case log_level::error: os << LogLevelUpperError; break;
default: break;
}
break;
default:
switch (e.level)
{
case log_level::debug: os << LogLevelLowerDebug; break;
case log_level::info: os << LogLevelLowerInfo; break;
case log_level::warn: os << LogLevelLowerWarn; break;
case log_level::error: os << LogLevelLowerError; break;
default: break;
}
break;
}
break;

/* uptime */
case value_token::uptime:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << std::chrono::duration_cast<duration_u>(e.uptime.time_since_epoch()).count();
break;
default:
os << std::fixed
<< std::chrono::duration_cast<duration_f>(e.uptime.time_since_epoch()).count()
<< std::dec;
break;
}
break;

/* runtime */
case value_token::runtime:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << std::chrono::duration_cast<duration_u>(e.uptime - start_time).count();
break;
default:
os << std::fixed
<< std::chrono::duration_cast<duration_f>(e.uptime - start_time).count()
<< std::dec;
break;
}
break;

/* systemtime */
case value_token::systime:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << e.systime;
break;
default:
os << std::fixed
<< e.systime
<< std::dec;
break;
}
break;

/* sender */
case value_token::sender:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << reinterpret_cast<uintptr_t>(e.sender);
break;
case value_format::hex_lower:
os << "0x"
<< std::hex
<< reinterpret_cast<uintptr_t>(e.sender)
<< std::dec;
break;
default:
os << "0x"
<< std::hex
<< std::uppercase
<< reinterpret_cast<uintptr_t>(e.sender)
<< std::nouppercase
<< std::dec;
break;
}
break;

/* thread */
case value_token::thread:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << e.thread;
break;
case value_format::hex_lower:
os << "0x"
<< std::hex
<< e.thread
<< std::dec;
break;
default:
os << "0x"
<< std::hex
<< std::uppercase
<< e.thread
<< std::nouppercase
<< std::dec;
break;
}
break;

/* filename */
case value_token::filename:
has_line_end = false;
s = e.file;
if (s)
{
auto tmp = strrchr(s, '/');
if (tmp)
s = tmp + 1;
}
else
s = "unknown";
os << s;
break;

/* filepath */
case value_token::filepath:
has_line_end = false;
os << (e.file ? e.file : "unknown");
break;

/* line */
case value_token::line:
has_line_end = false;
switch (i.format)
{
case value_format::decimal:
os << e.line;
break;
case value_format::hex_lower:
os << "0x"
<< std::hex
<< e.line
<< std::dec;
break;
default:
os << "0x"
<< std::hex
<< std::uppercase
<< e.line
<< std::nouppercase
<< std::dec;
break;
}
break;

/* name */
case value_token::name:
if (!e.name.empty())
{
os << e.name;
has_line_end = (e.name.back() == '\n');
}
break;

/* message */
case value_token::message:
if (!e.message.empty())
{
os << e.message;
has_line_end = (e.message.back() == '\n');
}
break;

/* newline */
case value_token::newline:
has_line_end = true;
os << std::endl;
break;

default:
break;
}
}

if (!has_line_end)
os << std::endl;
}

+ 51
- 0
src/cpplogging/manager/logger_impl.cpp Zobrazit soubor

@@ -0,0 +1,51 @@
#include <cpplogging/manager/logger_impl.h>

using namespace ::cpplogging;

void logger_impl::add_rule(rule& rule)
{
std::lock_guard<std::mutex> lk(_mutex);
_rules.insert(&rule);
update_log_level();
}

void logger_impl::remove_rule(rule& rule)
{
std::lock_guard<std::mutex> lk(_mutex);
_rules.erase(&rule);
update_log_level();
}

bool logger_impl::is_enabled(log_level level) const
{
return _min_level <= level
&& _max_level >= level;
}

void logger_impl::write_entry(const log_entry_ptr_s& entry) const
{
std::lock_guard<std::mutex> lk(_mutex);

if (!is_enabled(entry->level))
return;

for (auto& r : _rules)
r->write_entry(entry);
}

void logger_impl::update_log_level()
{
_min_level = log_level::unknown;
_max_level = log_level::unknown;

for (auto& r : _rules)
{
if ( _min_level == log_level::unknown
|| _min_level > r->min_level)
_min_level = r->min_level;

if ( _max_level == log_level::unknown
|| _max_level < r->max_level)
_max_level = r->max_level;
}
}

+ 140
- 0
src/cpplogging/manager/manager.cpp Zobrazit soubor

@@ -0,0 +1,140 @@
#include <cpplogging/manager/manager.h>
#include <cpplogging/manager/manager.inl>

using namespace ::cpplogging;

std::mutex manager::_mutex;
logger_impl manager::_default("");
manager::logger_map manager::_logger;
manager::rule_list manager::_rules;
manager::consumer_set manager::_consumer;

logger& manager::get_logger(const std::string& name)
{
std::lock_guard<std::mutex> lg(_mutex);
if (name.empty())
return _default;

auto it = _logger.find(name);
if (it == _logger.end())
{
it = _logger.emplace_hint(it, name, std::make_unique<logger_impl>(name));
manager::init_logger(*it->second);
}

return *it->second;
}

void manager::register_consumer(consumer& consumer)
{
std::lock_guard<std::mutex> lg(_mutex);
auto it = _consumer.emplace(consumer).first;
auto& c = **it;
for (auto& rule : _rules)
{
if (rule.consumer_matcher->match(c))
rule.add_consumer(c);
}
}

const consumer* manager::register_consumer(consumer_ptr_u&& consumer)
{
std::lock_guard<std::mutex> lg(_mutex);
auto it = _consumer.emplace(std::move(consumer)).first;
auto& ret = **it;
for (auto& rule : _rules)
{
if (rule.consumer_matcher->match(ret))
rule.add_consumer(ret);
}
return &ret;
}

void manager::unregister_consumer(consumer& consumer)
{
std::lock_guard<std::mutex> lg(_mutex);
unregister_consumer(&consumer);
}

void manager::unregister_consumer(consumer* consumer)
{
std::lock_guard<std::mutex> lg(_mutex);
auto it = _consumer.find(consumer_wrapper(*consumer));
if (it == _consumer.end())
return;

auto& c = **it;
for (auto& rule : _rules)
rule.remove_consumer(c);
}

rule_handle manager::define_rule(
matcher_ptr_u&& logger_matcher,
matcher_ptr_u&& consumer_matcher,
log_level min_level,
log_level max_level)
{
std::lock_guard<std::mutex> lg(_mutex);

/* create rule */
_rules.emplace_back(std::move(logger_matcher), std::move(consumer_matcher), min_level, max_level);
auto& rule = _rules.back();

/* update consumers */
for (auto& c : _consumer)
{
if (rule.consumer_matcher->match(*c))
rule.add_consumer(*c);
}

/* update default logger */
if (rule.logger_matcher->match(_default))
_default.add_rule(rule);

/* update loggers */
for (auto& l : _logger)
{
if (rule.logger_matcher->match(*l.second))
l.second->add_rule(rule);
}
return &rule;
}

void manager::undefine_rule(rule_handle handle)
{
std::lock_guard<std::mutex> lg(_mutex);

/* find the rule in the list */
auto r = static_cast<rule*>(handle);
auto it = _rules.begin();
while (&*it != r && it != _rules.end())
++it;
if (it == _rules.end())
return;

/* remove the rule from the loggers */
_default.remove_rule(*it);
for (auto& l : _logger)
l.second->remove_rule(*it);

/* remove the rule from the manager */
_rules.erase(it);
}

void manager::reset()
{
std::lock_guard<std::mutex> lg(_mutex);
_logger.clear();
_rules.clear();
_consumer.clear();
}

logger_impl& manager::init_logger(logger_impl& logger)
{
for (auto& rule : _rules)
{
if (rule.logger_matcher->match(logger))
logger.add_rule(rule);
}
return logger;
}

+ 9
- 0
src/cpplogging/manager/matcher/matcher.cpp Zobrazit soubor

@@ -0,0 +1,9 @@
#include <cpplogging/manager/matcher/matcher.h>

using namespace ::cpplogging;

bool matcher::match(const logger& logger) const
{ return false; }

bool matcher::match(const consumer& consumer) const
{ return false; }

+ 9
- 0
src/cpplogging/manager/matcher/matcher_all.cpp Zobrazit soubor

@@ -0,0 +1,9 @@
#include <cpplogging/manager/matcher/matcher_all.h>

using namespace ::cpplogging;

bool matcher_all::match(const logger& logger) const
{ return true; }

bool matcher_all::match(const consumer& consumer) const
{ return true; }

+ 11
- 0
src/cpplogging/manager/matcher/matcher_default_logger.cpp Zobrazit soubor

@@ -0,0 +1,11 @@
#include <cpplogging/manager/manager.h>
#include <cpplogging/manager/matcher/matcher_default_logger.h>

using namespace ::cpplogging;

matcher_default_logger::matcher_default_logger() :
_default(logger::get())
{ }

bool matcher_default_logger::match(const logger& logger) const
{ return &_default == &logger; }

+ 14
- 0
src/cpplogging/manager/matcher/matcher_regex.cpp Zobrazit soubor

@@ -0,0 +1,14 @@
#include <cpplogging/manager/matcher/matcher_regex.h>

using namespace ::cpplogging;

matcher_regex::matcher_regex(const std::string regex, bool invert)
: _regex (regex)
, _invert (invert)
{ }

bool matcher_regex::match(const logger& logger) const
{ return !logger.name().empty() && std::regex_match(logger.name(), _regex) != _invert; }

bool matcher_regex::match(const consumer& consumer) const
{ return !consumer.name().empty() && std::regex_match(consumer.name(), _regex) != _invert; }

+ 43
- 0
test/CMakeLists.txt Zobrazit soubor

@@ -0,0 +1,43 @@
# Initialize ######################################################################################

Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE )
Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC )
Include ( cmake_tests OPTIONAL RESULT_VARIABLE HAS_CMAKE_TESTS )

# Test ############################################################################################

Find_Package ( GTest )
If ( NOT "${GTest_FOUND}" )
Return ( )
EndIf ( )

File ( GLOB_RECURSE CPPLOGGING_TEST_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h )
File ( GLOB_RECURSE CPPLOGGING_TEST_INLINE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.inl )
File ( GLOB_RECURSE CPPLOGGING_TEST_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )

Add_Executable ( cpplogging-test
EXCLUDE_FROM_ALL
${CPPLOGGING_TEST_HEADER_FILES}
${CPPLOGGING_TEST_INLINE_FILES}
${CPPLOGGING_TEST_SOURCE_FILES} )
Target_Link_Libraries ( cpplogging-test
PUBLIC
cpplogging-objects
GMock::Main )

# pedantic
If ( HAS_PEDANTIC )
Pedantic_Apply_Flags_Target ( cpplogging-test ALL )
EndIf ( )

# optimization
If ( HAS_COTIRE )
Cotire ( cpplogging-test )
EndIf ( )

# test
If ( HAS_CMAKE_TESTS )
Add_CMake_Test ( NAME cpplogging TARGET cpplogging-test )
Else ( )
Add_Test ( NAME cpplogging COMMAND cpplogging-test )
EndIf ( )

+ 127
- 0
test/cpplogging/cpplogging_tests.cpp Zobrazit soubor

@@ -0,0 +1,127 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cpplogging/manager.h>

using namespace ::testing;
using namespace ::cpplogging;

struct LoggingReset
{
~LoggingReset()
{ manager::reset(); }
};

struct consumer_mock
: public consumer
{
MOCK_CONST_METHOD1(write_entry, void (const log_entry_ptr_s& entry));

consumer_mock(const std::string& n) :
consumer(n, true)
{ }
};

MATCHER_P5(MatchLogData, level, sender, thread, name, message, "")
{
if (!arg)
return false;

auto& d = *arg;
return d.level == level
&& d.sender == sender
&& d.thread == thread
&& d.name == name
&& d.message == message;
}

TEST(LoggingTests, matcher_all)
{
LoggingReset loggingReset;
consumer_stream c0("TestConsumer1", std::cout, false);
consumer_stream c1("TestConsumer2", std::cout, false);

auto& l0 = logger::get();
auto& l1 = logger::get("TestLogger");

matcher_all matcher;

EXPECT_TRUE(matcher.match(c0));
EXPECT_TRUE(matcher.match(c1));
EXPECT_TRUE(matcher.match(l0));
EXPECT_TRUE(matcher.match(l1));
}

TEST(LoggingTests, matcher_default)
{
LoggingReset loggingReset;
consumer_stream c0("TestConsumer1", std::cout, false);
consumer_stream c1("TestConsumer2", std::cout, false);

auto& l0 = logger::get();
auto& l1 = logger::get("TestLogger");

matcher_default_logger matcher;

EXPECT_FALSE(matcher.match(c0));
EXPECT_FALSE(matcher.match(c1));
EXPECT_TRUE (matcher.match(l0));
EXPECT_FALSE(matcher.match(l1));
}

TEST(LoggingTests, matcher_regex)
{
LoggingReset loggingReset;
consumer_stream c0("TestConsumer1", std::cout, false);
consumer_stream c1("TestConsumer2", std::cout, false);

auto& l0 = logger::get();
auto& l1 = logger::get("TestLogger");

matcher_regex matcher0("TestConsumer1");
matcher_regex matcher1("ASEF", true);

EXPECT_TRUE (matcher0.match(c0));
EXPECT_FALSE(matcher0.match(c1));
EXPECT_FALSE(matcher1.match(l0));
EXPECT_TRUE (matcher1.match(l1));
}

TEST(LoggingTests, log_base)
{
LoggingReset loggingReset;
StrictMock<consumer_mock> c0("consumer0");
StrictMock<consumer_mock> c1("Consumer1");

EXPECT_CALL(c0, write_entry(MatchLogData(
log_level::info,
(void*)0x12,
std::this_thread::get_id(),
std::string("logger0"),
std::string("test1 info"))));

EXPECT_CALL(c0, write_entry(MatchLogData(
log_level::warn,
(void*)0x13,
std::this_thread::get_id(),
std::string("logger0"),
std::string("test1 warn"))));

manager::define_rule(
std::make_unique<matcher_regex>("logger0"),
std::make_unique<matcher_regex>("consumer0"),
log_level::info,
log_level::warn);

auto& l0 = logger::get("logger0");
auto& l1 = logger::get("logger1");

cpplogging_log(l0, debug).sender((void*)0x11).message("test1") << " debug";
cpplogging_log(l0, info ).sender((void*)0x12).message("test1") << " info";
cpplogging_log(l0, warn ).sender((void*)0x13).message("test1") << " warn";
cpplogging_log(l0, error).sender((void*)0x14).message("test1") << " error";

cpplogging_log(l1, debug).sender((void*)0x21).message("test2") << " debug";
cpplogging_log(l1, info ).sender((void*)0x22).message("test2") << " info";
cpplogging_log(l1, warn ).sender((void*)0x23).message("test2") << " warn";
cpplogging_log(l1, error).sender((void*)0x24).message("test2") << " error";
}

Načítá se…
Zrušit
Uložit