This commit is contained in:
Mars 2024-06-02 06:03:21 -04:00
parent 6e5045f1f4
commit 693fa17d10
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
266 changed files with 60543 additions and 1000 deletions

View file

@ -0,0 +1,173 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/BackendManager.h"
#include "quill/backend/BackendOptions.h"
#include "quill/backend/SignalHandler.h"
#include "quill/core/Attributes.h"
#include <atomic>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
#include <mutex>
/** Version Info **/
constexpr uint32_t VersionMajor{4};
constexpr uint32_t VersionMinor{2};
constexpr uint32_t VersionPatch{0};
constexpr uint32_t Version{VersionMajor * 10000 + VersionMinor * 100 + VersionPatch};
namespace quill
{
class Backend
{
public:
/**
* Starts the backend thread.
* @param options Backend options to configure the backend behavior.
*/
QUILL_ATTRIBUTE_COLD static void start(BackendOptions const& options = BackendOptions{})
{
std::call_once(detail::BackendManager::instance().get_start_once_flag(),
[options]()
{
// Run the backend worker thread, we wait here until the thread enters the main loop
detail::BackendManager::instance().start_backend_thread(options);
// Setup an exit handler to call stop when the main application exits.
// always call stop on destruction to log everything. std::atexit seems to be
// working better with dll on windows compared to using ~LogManagerSingleton().
std::atexit([]() { detail::BackendManager::instance().stop_backend_thread(); });
});
}
/**
* Starts the backend thread and initialises a signal handler
*
* @param options Backend options to configure the backend behavior.
* @param catchable_signals List of signals that the backend should catch if with_signal_handler
* is enabled.
* @param signal_handler_timeout_seconds This variable defines the timeout duration in seconds for
* the signal handler alarm. It is only available on Linux, as Windows does not support the alarm
* function. The signal handler sets up an alarm to ensure that the process will terminate if it
* does not complete within the specified time frame. This is particularly useful to prevent the
* process from hanging indefinitely in case the signal handler encounters an issue.
*
* @note When using the SignalHandler on Linux/MacOS, ensure that each spawned thread in your
* application has performed one of the following actions:
* i) Logged at least once.
* ii) Called Frontend::preallocate().
* iii) Blocked signals on that thread to prevent the signal handler from running on it.
* This requirement is because the built-in signal handler utilizes a lock-free queue to issue log
* statements and await the log flushing. The queue is constructed on its first use with `new()`.
* Failing to meet any of the above criteria means the queue was never used, and it will be
* constructed inside the signal handler. The `new` operation is not an async signal-safe function
* and may lead to potential issues. However, when the queue is already created, no `new` call is
* made, and the remaining functions invoked internally by the built-in signal handler are async
* safe.
*/
template <typename TFrontendOptions>
QUILL_ATTRIBUTE_COLD static void start_with_signal_handler(
BackendOptions const& options = BackendOptions{},
QUILL_MAYBE_UNUSED std::initializer_list<int> const& catchable_signals =
std::initializer_list<int>{SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV},
uint32_t signal_handler_timeout_seconds = 20u)
{
std::call_once(detail::BackendManager::instance().get_start_once_flag(),
[options, catchable_signals, signal_handler_timeout_seconds]()
{
#if defined(_WIN32)
(void)catchable_signals;
detail::init_exception_handler<TFrontendOptions>();
#else
// We do not want signal handler to run in the backend worker thread
// Block signals in the main thread so when we spawn the backend worker thread it inherits
// the master
sigset_t set, oldset;
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, &oldset);
detail::init_signal_handler<TFrontendOptions>(catchable_signals);
#endif
// Run the backend worker thread, we wait here until the thread enters the main loop
detail::BackendManager::instance().start_backend_thread(options);
detail::SignalHandlerContext::instance().signal_handler_timeout_seconds.store(
signal_handler_timeout_seconds);
// We need to update the signal handler with some backend thread details
detail::SignalHandlerContext::instance().backend_thread_id.store(
detail::BackendManager::instance().get_backend_thread_id());
#if defined(_WIN32)
// nothing to do
#else
// Unblock signals in the main thread so subsequent threads do not inherit the blocked mask
sigprocmask(SIG_SETMASK, &oldset, nullptr);
#endif
// Set up an exit handler to call stop when the main application exits.
// always call stop on destruction to log everything. std::atexit seems to be
// working better with dll on windows compared to using ~LogManagerSingleton().
std::atexit([]() { detail::BackendManager::instance().stop_backend_thread(); });
});
}
/**
* Stops the backend thread.
* @note thread-safe
*/
QUILL_ATTRIBUTE_COLD static void stop() noexcept
{
detail::BackendManager::instance().stop_backend_thread();
}
/**
* Notifies the backend thread to wake up.
* It is possible to use a long backend sleep_duration and then notify the backend to wake up
* from any frontend thread.
*
* @note thread-safe
*/
static void notify() noexcept { detail::BackendManager::instance().notify_backend_thread(); }
/**
* Checks if the backend is currently running.
* @return True if the backend is running, false otherwise.
*/
QUILL_NODISCARD static bool is_running() noexcept
{
return detail::BackendManager::instance().is_backend_thread_running();
}
/**
* Retrieves the ID of the backend thread.
* @return The ID of the backend thread.
*/
QUILL_NODISCARD static uint32_t get_thread_id() noexcept
{
return detail::BackendManager::instance().get_backend_thread_id();
}
/**
* Converts an rdtsc value to epoch time.
* This function uses the same clock as the backend and can be called from any frontend thread.
* It is useful when using a logger with ClockSourceType::Tsc and you want to obtain a timestamp
* synchronized with the log files generated by the backend.
*
* Alternatively you can use the Clock class from backend/Clock.h
* @param rdtsc_value The RDTSC value to convert.
* @return The epoch time corresponding to the RDTSC value.
*/
QUILL_NODISCARD static uint64_t convert_rdtsc_to_epoch_time(uint64_t rdtsc_value) noexcept
{
return detail::BackendManager::instance().convert_rdtsc_to_epoch_time(rdtsc_value);
}
};
} // namespace quill

View file

@ -0,0 +1,94 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/BackendManager.h"
#include "quill/core/Attributes.h"
#include "quill/core/Rdtsc.h"
#include <chrono>
#include <cstdint>
namespace quill
{
/**
* @brief A utility class for accessing the Time Stamp Counter (TSC) clock used by the backend logging thread.
*
* This class provides access to the TSC clock maintained by the backend logging thread,
* allowing for synchronized timestamp retrieval.
*
* Other threads can obtain timestamps synchronized with the TSC clock of the backend logging
* thread, ensuring synchronization with log statement timestamps.
*
* If `ClockSourceType::Tsc` is not used by any Logger, this class reverts to using the system clock
* for providing a timestamp.
*
* @note For more accurate timestamps, consider reducing `rdtsc_resync_interval` in `BackendOptions`.
* @note All methods of the class are thread-safe.
*/
class BackendTscClock
{
public:
class RdtscVal
{
public:
RdtscVal(RdtscVal const& other) = default;
RdtscVal(RdtscVal&& other) noexcept = default;
RdtscVal& operator=(RdtscVal const& other) = default;
RdtscVal& operator=(RdtscVal&& other) noexcept = default;
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint64_t value() const noexcept { return _value; }
private:
RdtscVal() noexcept : _value(detail::rdtsc()) {}
friend BackendTscClock;
uint64_t _value;
};
public:
using duration = std::chrono::nanoseconds;
using rep = duration::rep;
using period = duration::period;
using time_point = std::chrono::time_point<BackendTscClock, duration>;
static constexpr bool is_steady = false;
/**
* Provides the current synchronized timestamp obtained using the TSC clock maintained by the backend logging thread.
* @return A wall clock timestamp in nanoseconds since epoch, synchronized with the backend logging thread's TSC clock.
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static time_point now() noexcept
{
uint64_t const ts = detail::BackendManager::instance().convert_rdtsc_to_epoch_time(detail::rdtsc());
return ts ? time_point{std::chrono::nanoseconds{ts}}
: time_point{std::chrono::nanoseconds{
std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now())
.time_since_epoch()
.count()}};
}
/**
* Returns the current value of the TSC timer maintained by the backend logging thread.
* @return The current value of the TSC timer.
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static RdtscVal rdtsc() noexcept { return RdtscVal{}; }
/**
* Converts a TSC counter value obtained from the backend logging thread's TSC timer to a wall
* clock timestamp.
*
* @param rdtsc The TSC counter value obtained from the backend logging thread's TSC timer.
* @warning This function will return `0` when TimestampClockType::Tsc is not enabled in Config.h.
* @return Time since epoch in nanoseconds.
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static time_point to_time_point(RdtscVal rdtsc) noexcept
{
return time_point{std::chrono::nanoseconds{
detail::BackendManager::instance().convert_rdtsc_to_epoch_time(rdtsc.value())}};
}
};
} // namespace quill

View file

@ -0,0 +1,211 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/Logger.h"
#include "quill/UserClockSource.h"
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/FrontendOptions.h"
#include "quill/core/LoggerManager.h"
#include "quill/core/QuillError.h"
#include "quill/core/SinkManager.h"
#include "quill/core/ThreadContextManager.h"
#include "quill/sinks/Sink.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <memory>
#include <string>
#include <vector>
namespace quill
{
template <typename TFrontendOptions>
class FrontendImpl
{
public:
using frontend_options_t = TFrontendOptions;
using logger_t = LoggerImpl<frontend_options_t>;
/**
* @brief Pre-allocates the thread-local data needed for the current thread.
*
* Although optional, it is recommended to invoke this function during the thread initialization
* phase before the first log message.
*/
QUILL_ATTRIBUTE_COLD static void preallocate()
{
QUILL_MAYBE_UNUSED uint32_t const volatile spsc_queue_capacity =
detail::get_local_thread_context<TFrontendOptions>()
->template get_spsc_queue<TFrontendOptions::queue_type>()
.capacity();
}
/**
* @brief Creates a new sink or retrieves an existing one with the specified name.
*
* @param sink_name The name of the sink.
* @param args The arguments to pass to the sink constructor.
* @return std::shared_ptr<Sink> A shared pointer to the created or retrieved sink.
*/
template <typename TSink, typename... Args>
static std::shared_ptr<Sink> create_or_get_sink(std::string const& sink_name, Args&&... args)
{
return detail::SinkManager::instance().create_or_get_sink<TSink>(sink_name, static_cast<Args&&>(args)...);
}
/**
* @brief Retrieves an existing sink with the specified name.
*
* @param sink_name The name of the sink.
* @return std::shared_ptr<Sink> A shared pointer to the retrieved sink, or nullptr if not found.
*/
QUILL_NODISCARD static std::shared_ptr<Sink> get_sink(std::string const& sink_name)
{
return detail::SinkManager::instance().get_sink(sink_name);
}
/**
* @brief Creates a new logger or retrieves an existing one with the specified name.
*
* @param logger_name The name of the logger.
* @param sink A shared pointer to the sink to associate with the logger.
* @param format_pattern The format pattern for log messages.
* @param time_pattern The time pattern for log timestamps.
* @param timestamp_timezone The timezone for log timestamps.
* @param clock_source The clock source for log timestamps.
* @param user_clock A pointer to a custom user clock.
* @return Logger* A pointer to the created or retrieved logger.
*/
static logger_t* create_or_get_logger(
std::string const& logger_name, std::shared_ptr<Sink> sink,
std::string const& format_pattern =
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) "
"%(message)",
std::string const& time_pattern = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime,
ClockSourceType clock_source = ClockSourceType::Tsc, UserClockSource* user_clock = nullptr)
{
std::vector<std::shared_ptr<Sink>> sinks;
sinks.push_back(static_cast<std::shared_ptr<Sink>&&>(sink));
return _cast_to_logger(detail::LoggerManager::instance().create_or_get_logger<logger_t>(
logger_name, static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks), format_pattern,
time_pattern, timestamp_timezone, clock_source, user_clock));
}
/**
* @brief Creates a new logger or retrieves an existing one with the specified name and multiple sinks.
*
* @param logger_name The name of the logger.
* @param sinks An initializer list of shared pointers to sinks to associate with the logger.
* @param format_pattern The format pattern for log messages.
* @param time_pattern The time pattern for log timestamps.
* @param timestamp_timezone The timezone for log timestamps.
* @param clock_source The clock source for log timestamps.
* @param user_clock A pointer to a custom user clock.
* @return Logger* A pointer to the created or retrieved logger.
*/
static logger_t* create_or_get_logger(
std::string const& logger_name, std::initializer_list<std::shared_ptr<Sink>> sinks,
std::string const& format_pattern =
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) "
"%(message)",
std::string const& time_pattern = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime,
ClockSourceType clock_source = ClockSourceType::Tsc, UserClockSource* user_clock = nullptr)
{
return _cast_to_logger(detail::LoggerManager::instance().create_or_get_logger<logger_t>(
logger_name, std::vector<std::shared_ptr<Sink>>{sinks}, format_pattern, time_pattern,
timestamp_timezone, clock_source, user_clock));
}
/**
* @brief Asynchronously removes the specified logger.
* When a logger is removed, any files associated with its sinks are also closed.
*
* @note Thread-safe
* @param logger A pointer to the logger to remove.
*/
static void remove_logger(detail::LoggerBase* logger)
{
detail::LoggerManager::instance().remove_logger(logger);
}
/**
* @brief Retrieves an existing logger with the specified name.
*
* @param logger_name The name of the logger.
* @return Logger* A pointer to the retrieved logger, or nullptr if not found.
*/
QUILL_NODISCARD static logger_t* get_logger(std::string const& logger_name)
{
detail::LoggerBase* logger = detail::LoggerManager::instance().get_logger(logger_name);
return logger ? _cast_to_logger(logger) : nullptr;
}
/**
* @brief Retrieves a map of all registered valid loggers.
* @note If `remove_logger()` is called from this or another thread, the return value
* of this function will become invalid.
* @return A vector containing all registered loggers.
*/
QUILL_NODISCARD static std::vector<logger_t*> get_all_loggers()
{
std::vector<detail::LoggerBase*> logger_bases = detail::LoggerManager::instance().get_all_loggers();
std::vector<logger_t*> loggers;
for (auto const& logger_base : logger_bases)
{
loggers.push_back(_cast_to_logger(logger_base));
}
return loggers;
}
/**
* Returns the first valid logger that is found. This is useful when you do not want to use the
* std::vector<logger_t*> return value of get_all_loggers.
*
* @return A pointer to the first valid logger, or nullptr if no valid logger is found.
*/
QUILL_NODISCARD static logger_t* get_valid_logger() noexcept
{
detail::LoggerBase* logger = detail::LoggerManager::instance().get_valid_logger();
return logger ? _cast_to_logger(logger) : nullptr;
}
/**
* @brief Counts the number of existing loggers, including any invalidated loggers.
* This function can be useful for verifying if a logger has been removed after calling
* remove_logger() by the backend, as removal occurs asynchronously.
* @return The number of loggers.
*/
QUILL_NODISCARD static size_t get_number_of_loggers() noexcept
{
return detail::LoggerManager::instance().get_number_of_loggers();
}
private:
QUILL_NODISCARD static logger_t* _cast_to_logger(detail::LoggerBase* logger_base)
{
assert(logger_base);
auto* logger = dynamic_cast<logger_t*>(logger_base);
if (QUILL_UNLIKELY(!logger))
{
QUILL_THROW(QuillError{"Failed to cast logger. Invalid logger type."});
}
return logger;
}
};
using Frontend = FrontendImpl<FrontendOptions>;
} // namespace quill

View file

@ -0,0 +1,284 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/LogLevel.h"
#include "quill/core/MacroMetadata.h"
/**
* Allows compile-time filtering of log messages to completely compile out log levels,
* resulting in zero-cost logging.
*
* Macros like LOG_TRACE_L3(..), LOG_TRACE_L2(..) will expand to empty statements,
* reducing branches in compiled code and the number of MacroMetadata constexpr instances.
*
* The default value of -1 enables all log levels.
* Specify a logging level to disable all levels equal to or higher than the specified level.
*
* For example to only log warnings and above you can use:
* add_compile_definitions(-DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
* or
* target_compile_definitions(${TARGET} PRIVATE -DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
**/
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L3 0
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L2 1
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L1 2
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_DEBUG 3
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_INFO 4
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING 5
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_ERROR 6
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_CRITICAL 7
#if !defined(QUILL_COMPILE_ACTIVE_LOG_LEVEL)
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL -1
#endif
#define QUILL_DEFINE_MACRO_METADATA(caller_function, fmt, tags, log_level) \
static constexpr quill::MacroMetadata macro_metadata \
{ \
__FILE__ ":" QUILL_STRINGIFY(__LINE__), caller_function, fmt, tags, log_level, \
quill::MacroMetadata::Event::Log \
}
#define QUILL_LOGGER_CALL(likelyhood, logger, log_level, fmt, ...) \
do \
{ \
if (likelyhood(logger->template should_log_message<log_level>())) \
{ \
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, nullptr, log_level); \
\
logger->log_message(quill::LogLevel::None, &macro_metadata, ##__VA_ARGS__); \
} \
} while (0)
#define QUILL_LOGGER_CALL_WITH_TAGS(likelyhood, logger, log_level, tags, fmt, ...) \
do \
{ \
if (likelyhood(logger->template should_log_message<log_level>())) \
{ \
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, &tags, log_level); \
\
logger->log_message(quill::LogLevel::None, &macro_metadata, ##__VA_ARGS__); \
} \
} while (0)
#define QUILL_LOGGER_CALL_LIMIT(min_interval, likelyhood, logger, log_level, fmt, ...) \
do \
{ \
if (likelyhood(logger->template should_log_message<log_level>())) \
{ \
thread_local std::chrono::time_point<std::chrono::steady_clock> next_log_time; \
auto const now = std::chrono::steady_clock::now(); \
\
if (now < next_log_time) \
{ \
break; \
} \
\
next_log_time = now + min_interval; \
QUILL_LOGGER_CALL(likelyhood, logger, log_level, fmt, ##__VA_ARGS__); \
} \
} while (0)
#define QUILL_BACKTRACE_LOGGER_CALL(logger, fmt, ...) \
do \
{ \
if (QUILL_LIKELY(logger->template should_log_message<quill::LogLevel::Backtrace>())) \
{ \
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, nullptr, quill::LogLevel::Backtrace); \
\
logger->log_message(quill::LogLevel::None, &macro_metadata, ##__VA_ARGS__); \
} \
} while (0)
/**
* Dynamic runtime log level with a tiny overhead
* @Note: Prefer using the compile time log level macros
*/
#define QUILL_DYNAMIC_LOG_CALL(logger, log_level, fmt, ...) \
do \
{ \
if (logger->should_log_message(log_level)) \
{ \
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, nullptr, quill::LogLevel::Dynamic); \
\
logger->log_message(log_level, &macro_metadata, ##__VA_ARGS__); \
} \
} while (0)
#define QUILL_DYNAMIC_LOG(logger, log_level, fmt, ...) \
QUILL_DYNAMIC_LOG_CALL(logger, log_level, fmt, ##__VA_ARGS__)
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L3
#define QUILL_LOG_TRACE_L3(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL3, fmt, ##__VA_ARGS__)
#define QUILL_LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::TraceL3, fmt, ##__VA_ARGS__)
#define QUILL_LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL3, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_TRACE_L3(logger, fmt, ...) (void)0
#define QUILL_LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L2
#define QUILL_LOG_TRACE_L2(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL2, fmt, ##__VA_ARGS__)
#define QUILL_LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::TraceL2, fmt, ##__VA_ARGS__)
#define QUILL_LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL2, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_TRACE_L2(logger, fmt, ...) (void)0
#define QUILL_LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L1
#define QUILL_LOG_TRACE_L1(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL1, fmt, ##__VA_ARGS__)
#define QUILL_LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::TraceL1, fmt, ##__VA_ARGS__)
#define QUILL_LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL1, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_TRACE_L1(logger, fmt, ...) (void)0
#define QUILL_LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_DEBUG
#define QUILL_LOG_DEBUG(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::Debug, fmt, ##__VA_ARGS__)
#define QUILL_LOG_DEBUG_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::Debug, fmt, ##__VA_ARGS__)
#define QUILL_LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::Debug, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_DEBUG(logger, fmt, ...) (void)0
#define QUILL_LOG_DEBUG_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_INFO
#define QUILL_LOG_INFO(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Info, fmt, ##__VA_ARGS__)
#define QUILL_LOG_INFO_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Info, fmt, ##__VA_ARGS__)
#define QUILL_LOG_INFO_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Info, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_INFO(logger, fmt, ...) (void)0
#define QUILL_LOG_INFO_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_INFO_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING
#define QUILL_LOG_WARNING(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Warning, fmt, ##__VA_ARGS__)
#define QUILL_LOG_WARNING_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Warning, fmt, ##__VA_ARGS__)
#define QUILL_LOG_WARNING_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Warning, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_WARNING(logger, fmt, ...) (void)0
#define QUILL_LOG_WARNING_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_WARNING_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_ERROR
#define QUILL_LOG_ERROR(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Error, fmt, ##__VA_ARGS__)
#define QUILL_LOG_ERROR_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Error, fmt, ##__VA_ARGS__)
#define QUILL_LOG_ERROR_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Error, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_ERROR(logger, fmt, ...) (void)0
#define QUILL_LOG_ERROR_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_ERROR_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_CRITICAL
#define QUILL_LOG_CRITICAL(logger, fmt, ...) \
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Critical, fmt, ##__VA_ARGS__)
#define QUILL_LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Critical, fmt, ##__VA_ARGS__)
#define QUILL_LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Critical, tags, fmt, ##__VA_ARGS__)
#else
#define QUILL_LOG_CRITICAL(logger, fmt, ...) (void)0
#define QUILL_LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ...) (void)0
#define QUILL_LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ...) (void)0
#endif
#define QUILL_LOG_BACKTRACE(logger, fmt, ...) \
QUILL_BACKTRACE_LOGGER_CALL(logger, fmt, ##__VA_ARGS__)
#if !defined(QUILL_DISABLE_NON_PREFIXED_MACROS)
#define LOG_TRACE_L3(logger, fmt, ...) QUILL_LOG_TRACE_L3(logger, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L2(logger, fmt, ...) QUILL_LOG_TRACE_L2(logger, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L1(logger, fmt, ...) QUILL_LOG_TRACE_L1(logger, fmt, ##__VA_ARGS__)
#define LOG_DEBUG(logger, fmt, ...) QUILL_LOG_DEBUG(logger, fmt, ##__VA_ARGS__)
#define LOG_INFO(logger, fmt, ...) QUILL_LOG_INFO(logger, fmt, ##__VA_ARGS__)
#define LOG_WARNING(logger, fmt, ...) QUILL_LOG_WARNING(logger, fmt, ##__VA_ARGS__)
#define LOG_ERROR(logger, fmt, ...) QUILL_LOG_ERROR(logger, fmt, ##__VA_ARGS__)
#define LOG_CRITICAL(logger, fmt, ...) QUILL_LOG_CRITICAL(logger, fmt, ##__VA_ARGS__)
#define LOG_BACKTRACE(logger, fmt, ...) QUILL_LOG_BACKTRACE(logger, fmt, ##__VA_ARGS__)
#define LOG_DYNAMIC(logger, log_level, fmt, ...) \
QUILL_DYNAMIC_LOG(logger, log_level, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_DEBUG_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_DEBUG_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_INFO_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_INFO_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_WARNING_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_WARNING_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_ERROR_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_ERROR_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ...) \
QUILL_LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_INFO_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_INFO_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_WARNING_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_WARNING_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_ERROR_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_ERROR_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#define LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ...) \
QUILL_LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
#endif

View file

@ -0,0 +1,320 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/UserClockSource.h"
#include "quill/core/Attributes.h"
#include "quill/core/Codec.h"
#include "quill/core/Common.h"
#include "quill/core/FrontendOptions.h"
#include "quill/core/LogLevel.h"
#include "quill/core/LoggerBase.h"
#include "quill/core/MacroMetadata.h"
#include "quill/core/Rdtsc.h"
#include "quill/core/ThreadContextManager.h"
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <thread>
#include <vector>
namespace quill
{
/** Forward Declarations **/
class Sink;
namespace detail
{
class LoggerManager;
class BackendWorker;
} // namespace detail
/**
* @brief Thread safe logger.
*
* Logger must be obtained from create_or_get_logger(), therefore constructors are private
*/
template <typename TFrontendOptions>
class LoggerImpl : public detail::LoggerBase
{
public:
using frontend_options_t = TFrontendOptions;
/***/
LoggerImpl(LoggerImpl const&) = delete;
LoggerImpl& operator=(LoggerImpl const&) = delete;
~LoggerImpl() override = default;
/**
* Push a log message to the spsc queue to be logged by the backend thread.
* One spsc queue per caller thread. This function is enabled only when all arguments are
* fundamental types.
* This is the fastest way possible to log
* @note This function is thread-safe.
* @param dynamic_log_level dynamic log level
* @param macro_metadata metadata of the log message
* @param fmt_args arguments
*
* @return true if the message is written to the queue, false if it is dropped (when a dropping queue is used)
*/
template <typename... Args>
QUILL_ATTRIBUTE_HOT bool log_message(LogLevel dynamic_log_level,
MacroMetadata const* macro_metadata, Args&&... fmt_args)
{
assert(valid.load(std::memory_order_acquire) && "Invalidated loggers can not log");
// Store the timestamp of the log statement at the start of the call. This gives more accurate
// timestamp especially if the queue is full
uint64_t const current_timestamp = (clock_source == ClockSourceType::Tsc) ? detail::rdtsc()
: (clock_source == ClockSourceType::System)
? static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count())
: user_clock->now();
detail::ThreadContext* const thread_context =
quill::detail::get_local_thread_context<frontend_options_t>();
// Need to reserve additional space as we will be aligning the pointer
size_t total_size = sizeof(current_timestamp) + (sizeof(uintptr_t) * 3) +
detail::calculate_args_size_and_populate_string_lengths(
thread_context->get_conditional_arg_size_cache(), fmt_args...);
if (dynamic_log_level != LogLevel::None)
{
// For the dynamic log level we want to add to the total size to store the dynamic log level
total_size += sizeof(dynamic_log_level);
}
constexpr bool is_unbounded_queue = (frontend_options_t::queue_type == QueueType::UnboundedUnlimited) ||
(frontend_options_t::queue_type == QueueType::UnboundedBlocking) ||
(frontend_options_t::queue_type == QueueType::UnboundedDropping);
std::byte* write_buffer;
if constexpr (is_unbounded_queue)
{
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
static_cast<uint32_t>(total_size), frontend_options_t::queue_type);
}
else
{
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
static_cast<uint32_t>(total_size));
}
if constexpr (frontend_options_t::queue_type == QueueType::UnboundedUnlimited)
{
assert(write_buffer &&
"Unbounded unlimited queue will always allocate and have enough capacity");
}
else if constexpr ((frontend_options_t::queue_type == QueueType::BoundedDropping) ||
(frontend_options_t::queue_type == QueueType::UnboundedDropping))
{
if (QUILL_UNLIKELY(write_buffer == nullptr))
{
// not enough space to push to queue message is dropped
thread_context->increment_failure_counter();
return false;
}
}
else if constexpr ((frontend_options_t::queue_type == QueueType::BoundedBlocking) ||
(frontend_options_t::queue_type == QueueType::UnboundedBlocking))
{
if (QUILL_UNLIKELY(write_buffer == nullptr))
{
thread_context->increment_failure_counter();
do
{
if constexpr (frontend_options_t::blocking_queue_retry_interval_ns > 0)
{
std::this_thread::sleep_for(std::chrono::nanoseconds{frontend_options_t::blocking_queue_retry_interval_ns});
}
// not enough space to push to queue, keep trying
if constexpr (is_unbounded_queue)
{
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
static_cast<uint32_t>(total_size), frontend_options_t::queue_type);
}
else
{
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
static_cast<uint32_t>(total_size));
}
} while (write_buffer == nullptr);
}
}
// we have enough space in this buffer, and we will write to the buffer
#ifndef NDEBUG
std::byte const* const write_begin = write_buffer;
assert(write_begin);
#endif
std::memcpy(write_buffer, &current_timestamp, sizeof(current_timestamp));
write_buffer += sizeof(current_timestamp);
std::memcpy(write_buffer, &macro_metadata, sizeof(uintptr_t));
write_buffer += sizeof(uintptr_t);
detail::LoggerBase* logger_context = this;
std::memcpy(write_buffer, &logger_context, sizeof(uintptr_t));
write_buffer += sizeof(uintptr_t);
detail::FormatArgsDecoder ftf = detail::decode_and_populate_format_args<detail::remove_cvref_t<Args>...>;
std::memcpy(write_buffer, &ftf, sizeof(uintptr_t));
write_buffer += sizeof(uintptr_t);
// encode remaining arguments
detail::encode(write_buffer, thread_context->get_conditional_arg_size_cache(), fmt_args...);
if (dynamic_log_level != LogLevel::None)
{
// write the dynamic log level
// The reason we write it last is that is less likely to break the alignment in the buffer
std::memcpy(write_buffer, &dynamic_log_level, sizeof(dynamic_log_level));
write_buffer += sizeof(dynamic_log_level);
}
#ifndef NDEBUG
assert((write_buffer > write_begin) && "write_buffer must be greater than write_begin");
assert(total_size == (static_cast<uint32_t>(write_buffer - write_begin)) &&
"The committed write bytes must be equal to the total_size requested bytes");
#endif
thread_context->get_spsc_queue<frontend_options_t::queue_type>().finish_write(static_cast<uint32_t>(total_size));
thread_context->get_spsc_queue<frontend_options_t::queue_type>().commit_write();
thread_context->get_conditional_arg_size_cache().clear();
return true;
}
/**
* Init a backtrace for this logger.
* Stores messages logged with LOG_BACKTRACE in a ring buffer messages and displays them later on demand.
* @param max_capacity The max number of messages to store in the backtrace
* @param flush_level If this loggers logs any message higher or equal to this severity level the backtrace will also get flushed.
* Default level is None meaning the user has to call flush_backtrace explicitly
*/
void init_backtrace(uint32_t max_capacity, LogLevel flush_level = LogLevel::None)
{
// we do not care about the other fields, except quill::MacroMetadata::Event::InitBacktrace
static constexpr MacroMetadata macro_metadata{
"", "", "{}", nullptr, LogLevel::Critical, MacroMetadata::Event::InitBacktrace};
// we pass this message to the queue and also pass capacity as arg
// We do not want to drop the message if a dropping queue is used
while (!this->log_message(LogLevel::None, &macro_metadata, max_capacity))
{
std::this_thread::sleep_for(std::chrono::nanoseconds{100});
}
// Also store the desired flush log level
backtrace_flush_level.store(flush_level, std::memory_order_relaxed);
}
/**
* Dump any stored backtrace messages
*/
void flush_backtrace()
{
// we do not care about the other fields, except quill::MacroMetadata::Event::Flush
static constexpr MacroMetadata macro_metadata{
"", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::FlushBacktrace};
// We do not want to drop the message if a dropping queue is used
while (!this->log_message(LogLevel::None, &macro_metadata))
{
std::this_thread::sleep_for(std::chrono::nanoseconds{100});
}
}
/**
* Blocks the calling thread until all log messages up to the current timestamp are flushed.
*
* The backend thread will invoke the write operation on all sinks for all loggers up to the point
* (timestamp) when this function is invoked.
*
* @param sleep_duration_ns The duration in nanoseconds to sleep between retries when the
* blocking queue is full, and between checks for the flush completion. Default is 100 nanoseconds.
*
* @note This function should only be called when the backend worker is running after Backend::start(...)
* @note This function will block the calling thread until the flush message is processed by the backend thread.
* The calling thread can block for up to backend_options.sleep_duration. If you configure a custom
* long sleep duration on the backend thread, e.g., backend_options.sleep_duration = std::chrono::minutes{1},
* then you should ideally avoid calling this function as you can block for long period of times unless
* you use another thread that calls Backend::notify()
*/
void flush_log(uint32_t sleep_duration_ns = 100)
{
static constexpr MacroMetadata macro_metadata{
"", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::Flush};
std::atomic<bool> backend_thread_flushed{false};
std::atomic<bool>* backend_thread_flushed_ptr = &backend_thread_flushed;
// We do not want to drop the message if a dropping queue is used
while (!this->log_message(LogLevel::None, &macro_metadata, reinterpret_cast<uintptr_t>(backend_thread_flushed_ptr)))
{
if (sleep_duration_ns > 0)
{
std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
}
else
{
std::this_thread::yield();
}
}
// The caller thread keeps checking the flag until the backend thread flushes
while (!backend_thread_flushed.load())
{
if (sleep_duration_ns > 0)
{
std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
}
else
{
std::this_thread::yield();
}
}
}
private:
friend class detail::LoggerManager;
friend class detail::BackendWorker;
/***/
LoggerImpl(std::string logger_name, std::vector<std::shared_ptr<Sink>> sinks,
std::string format_pattern, std::string time_pattern, Timezone timestamp_timezone,
ClockSourceType clock_source, UserClockSource* user_clock)
: detail::LoggerBase(static_cast<std::string&&>(logger_name),
static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
static_cast<std::string&&>(format_pattern),
static_cast<std::string&&>(time_pattern), timestamp_timezone, clock_source, user_clock)
{
if (this->user_clock)
{
// if a user clock is provided then set the ClockSourceType to User
this->clock_source = ClockSourceType::User;
}
}
};
using Logger = LoggerImpl<FrontendOptions>;
} // namespace quill

View file

@ -0,0 +1,38 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include <cstdint>
namespace quill
{
/**
* @brief Base class that provides a timestamp for log statements based on a user-provided clock source.
*
* This base class can be derived from to pass a user-generated timestamp to a Logger.
*
* It is particularly useful for simulations or scenarios where time manipulation is necessary,
* such as simulating time in the past and displaying past timestamps in logs.
*
* @note The derived class must be thread-safe if the Logger object is used across multiple threads.
* If a Logger is used within a single thread only, thread safety is not a concern.
*/
class UserClockSource
{
public:
UserClockSource() = default;
virtual ~UserClockSource() = default;
UserClockSource(UserClockSource const&) = delete;
UserClockSource& operator=(UserClockSource const&) = delete;
/**
* Returns time since epoch in nanoseconds
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT virtual uint64_t now() const = 0;
};
} // namespace quill

View file

@ -0,0 +1,88 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include <cstddef>
#include <string>
#include <string_view>
#include <tuple>
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
/**
* Contains useful utilities to assist with logging
*/
namespace quill::utility
{
/**
* @brief Formats the given buffer to hexadecimal representation.
*
* This function converts the contents of the input buffer to a hexadecimal string.
*
* @param buffer Pointer to the input buffer.
* @param size Size of the input buffer.
* @return A string containing the hexadecimal representation of the given buffer.
*/
template <typename T>
QUILL_NODISCARD std::string to_hex(T* buffer, size_t size) noexcept
{
static constexpr char hex_chars[] = "0123456789ABCDEF";
std::string hex_string;
hex_string.reserve(3 * size);
for (size_t i = 0; i < size; ++i)
{
// 00001111 mask
static constexpr uint8_t mask = 0x0Fu;
// add the first four bits
hex_string += hex_chars[(buffer[i] >> 4u) & mask];
// add the remaining bits
hex_string += hex_chars[buffer[i] & mask];
if (i != (size - 1))
{
// add a space delimiter
hex_string += ' ';
}
}
return hex_string;
}
/**
* @brief Helper class for combining quill::Tag objects.
* This class combines multiple quill::Tag objects into a single Tag object.
*/
template <typename... TTags>
class CombinedTags : public Tags
{
public:
constexpr CombinedTags(TTags... tags, std::string_view delim = ", ")
: _tags(std::move(tags)...), _delim(delim)
{
}
void format(std::string& out) const override
{
std::apply([&out, this](const auto&... tags)
{ (((tags.format(out)), out.append(_delim.data())), ...); }, _tags);
if (!out.empty())
{
// erase last delim
out.erase(out.length() - _delim.length(), _delim.length());
}
}
private:
std::tuple<TTags...> _tags;
std::string_view _delim;
};
} // namespace quill::utility

View file

@ -0,0 +1,80 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/BackendOptions.h"
#include "quill/backend/BackendWorker.h"
#include "quill/core/Attributes.h"
#include <cstdint>
#include <mutex>
namespace quill::detail
{
/**
* Provides access to common collection class that are used by both the frontend and the backend
* components of the logging system
* There should only be only active active instance of this class which is achieved by the
* LogSystemManagerSingleton
*/
class BackendManager
{
public:
/***/
static BackendManager& instance() noexcept
{
static BackendManager instance;
return instance;
}
/***/
BackendManager(BackendManager const&) = delete;
BackendManager& operator=(BackendManager const&) = delete;
/***/
QUILL_ATTRIBUTE_COLD void start_backend_thread(BackendOptions const& options)
{
// Start the backend worker
_backend_worker.run(options);
}
/***/
QUILL_ATTRIBUTE_COLD std::once_flag& get_start_once_flag() noexcept { return _start_once_flag; }
/***/
QUILL_ATTRIBUTE_COLD void stop_backend_thread() noexcept { _backend_worker.stop(); }
/***/
QUILL_NODISCARD uint32_t get_backend_thread_id() const noexcept
{
return _backend_worker.get_backend_thread_id();
}
/***/
void notify_backend_thread() noexcept { _backend_worker.notify(); }
/***/
QUILL_NODISCARD bool is_backend_thread_running() const noexcept
{
return _backend_worker.is_running();
}
/***/
QUILL_NODISCARD uint64_t convert_rdtsc_to_epoch_time(uint64_t rdtsc_value) const noexcept
{
return _backend_worker.time_since_epoch(rdtsc_value);
}
private:
/***/
BackendManager() = default;
~BackendManager() = default;
private:
BackendWorker _backend_worker;
std::once_flag _start_once_flag;
};
} // namespace quill::detail

View file

@ -0,0 +1,169 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <limits>
#include <string>
#if defined(__MINGW32__)
#include <iostream>
#endif
namespace quill
{
struct BackendOptions
{
/**
* The name assigned to the backend, visible during thread naming queries (e.g.,
* pthread_getname_np) or in the debugger.
*/
std::string thread_name = "QuillBackend";
/**
* The backend employs "busy-waiting" by spinning around each frontend thread's queue.
* If enabled, the backend will yield when there is no remaining work, potentially
* reducing the OS scheduler priority for the backend.
* This option is effective only when sleep_duration is set to 0.
*/
bool enable_yield_when_idle = false;
/**
* Specifies the duration the backend sleeps if there is no remaining work to process in the queues.
*/
std::chrono::nanoseconds sleep_duration = std::chrono::nanoseconds{500};
/**
* The backend pops all log messages from the frontend queues and buffers them in a
* local ring buffer queue as transit events. The transit_event_buffer is unbounded, starting with
* a customizable initial capacity (in items, not bytes) and will reallocate up to
* transit_events_hard_limit The backend will use a separate transit_event_buffer for each
* frontend thread. The capacity must be a power of two.
*/
uint32_t transit_event_buffer_initial_capacity = 64;
/**
* The backend gives priority to reading messages from the frontend queues of all
* the hot threads and temporarily buffers them.
*
* If a frontend threads continuously push messages to the queue (e.g., logging in a loop),
* no logs can ever be processed.
*
* When the soft limit is reached (default: 800), the backend worker thread will try to process
* a batch of cached transit events all at once
*
* The frontend queues are emptied on each iteration, so the actual popped messages
* can be much greater than the transit_events_soft_limit.
*
* @note This number represents a limit across the messages received from ALL frontend threads.
*/
size_t transit_events_soft_limit = 800;
/**
* The backend gives priority to reading messages from the frontend queues and temporarily
* buffers them.
*
* If a frontend thread continuously push messages to the queue (e.g., logging in a loop),
* no logs can ever be processed.
*
* As the backend buffers messages, it can keep buffering indefinitely if the frontend
* threads keep pushing.
*
* This limit is the maximum size of the backend event buffer. When reached, the backend
* will stop reading the frontend queues until there is space available in the buffer.
*
* @note This number represents a limit PER frontend threads.
*/
size_t transit_events_hard_limit = 100'000;
/**
* The backend iterates through all frontend queues and pops all messages from each queue.
* It then buffers and logs the message with the lowest timestamp among them.
*
* Each frontend queue corresponds to a thread, and when multiple frontend threads are pushing logs
* simultaneously, it is possible to read a timestamp from the last queue in the iteration but
* miss that timestamp when the first queue was read because it was not available at that time.
*
* When this option is enabled, the backend takes a timestamp (`now()`) before reading
* the queues. It uses that timestamp to ensure that each log message's timestamp from the frontend
* queues is less than or equal to the stored `now()` timestamp, guaranteeing ordering by timestamp.
*
* Messages that fail the above check are not logged and remain in the queue.
* They are checked again in the next iteration.
*
* The timestamp check is performed with microsecond precision.
*
* Enabling this option may cause a delay in processing but ensures correct timestamp order.
*
* @note Applicable only when use_transit_buffer = true.
*/
bool enable_strict_log_timestamp_order = true;
/**
* When this option is enabled and the application is terminating, the backend worker thread
* will not exit until all the frontend queues are empty.
*
* However, if there is a thread during application destruction that keeps trying to log
* indefinitely, the backend will be unable to exit because it keeps popping log messages.
*
* When this option is disabled, the backend will try to read the queues once
* and then exit. Reading the queues only once means that some log messages can be dropped,
* especially when strict_log_timestamp_order is set to true.
*/
bool wait_for_queues_to_empty_before_exit = true;
/**
* Pins the backend to the specified CPU.
*
* By default, the backend is not pinned to any CPU unless a value is specified.
* It is recommended to pin the backend to a shared non-critical CPU.
* Use `std::numeric_limits<uint16_t>::max()` as an undefined value to avoid setting CPU affinity.
*/
uint16_t backend_cpu_affinity = (std::numeric_limits<uint16_t>::max)();
/**
* The backend may encounter exceptions that cannot be caught within user threads.
* In such cases, the backend invokes this callback to notify the user.
*
* This function sets up a user error notifier to handle backend errors and notifications,
* such as when the unbounded queue reallocates or when the bounded queue becomes full.
*
* To disable notifications, simply leave the function undefined:
* std::function<void(std::string const&)> backend_error_notifier = {};
*
* It's safe to perform logging operations within this function (e.g., LOG_INFO(...)),
* but avoid calling logger->flush_log(). The function is invoked on the backend thread,
* which should not remain in a waiting state as it waits for itself.
*/
std::function<void(std::string const&)> error_notifier = [](std::string const& error_message)
{
#if !defined(__MINGW32__)
std::fprintf(stderr, "%s\n", error_message.data());
#else
// fprintf crashes on mingw gcc 13 for unknown reason
std::cerr << error_message.data() << "\n";
#endif
};
/**
* This option is only applicable if at least one frontend is using a Logger with
* ClockSourceType::Tsc
*
* When the system clock is used, this option can be ignored.
*
* Controls the frequency at which the backend recalculates and syncs the internal RdtscClock
* with the system time from the system wall clock.
* The TSC clock can drift slightly over time and is not synchronized with NTP server updates.
*
* Smaller values provide more accurate log timestamps at the cost of additional system clock
* calls. Changing this value only affects the performance of the backend worker.
*/
std::chrono::milliseconds rdtsc_resync_interval = std::chrono::milliseconds{500};
};
} // namespace quill

View file

@ -0,0 +1,152 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/QuillError.h"
#include <cstdint>
#include <cstring>
#include <string>
#include <system_error>
#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <windows.h>
#include "quill/core/ThreadUtilities.h" // for s2ws
#elif defined(__APPLE__)
#include <mach/thread_act.h>
#include <mach/thread_policy.h>
#include <pthread.h>
#include <unistd.h>
#elif defined(__CYGWIN__)
#include <sched.h>
#include <unistd.h>
#elif defined(__linux__)
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#elif defined(__NetBSD__)
#include <sched.h>
#include <unistd.h>
#elif defined(__FreeBSD__)
#include <sched.h>
#include <unistd.h>
#elif defined(__DragonFly__)
#include <sched.h>
#include <unistd.h>
#else
#include <sched.h>
#include <unistd.h>
#endif
namespace quill::detail
{
/***/
QUILL_ATTRIBUTE_COLD inline void set_cpu_affinity(uint16_t cpu_id)
{
#if defined(__CYGWIN__)
// setting cpu affinity on cygwin is not supported
#elif defined(_WIN32)
// core number starts from 0
auto const mask = (static_cast<DWORD_PTR>(1) << cpu_id);
auto ret = SetThreadAffinityMask(GetCurrentThread(), mask);
if (ret == 0)
{
auto const last_error = std::error_code(GetLastError(), std::system_category());
QUILL_THROW(
QuillError{std::string{"Failed to set cpu affinity - errno: " + std::to_string(last_error.value()) +
" error: " + last_error.message()}});
}
#elif defined(__APPLE__)
// I don't think that's possible to link a thread with a specific core with Mac OS X
// This may be used to express affinity relationships between threads in the task.
// Threads with the same affinity tag will be scheduled to share an L2 cache if possible.
thread_affinity_policy_data_t policy = {cpu_id};
// Get the mach thread bound to this thread
thread_port_t mach_thread = pthread_mach_thread_np(pthread_self());
thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&policy, 1);
#else
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_id, &cpuset);
auto const err = sched_setaffinity(0, sizeof(cpuset), &cpuset);
if (QUILL_UNLIKELY(err == -1))
{
QUILL_THROW(QuillError{std::string{"Failed to set cpu affinity - errno: " + std::to_string(errno) +
" error: " + strerror(errno)}});
}
#endif
}
/***/
QUILL_ATTRIBUTE_COLD inline void set_thread_name(char const* name)
{
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(QUILL_NO_THREAD_NAME_SUPPORT)
// Disabled on MINGW / Cygwin.
(void)name;
#elif defined(_WIN32)
std::wstring name_ws = s2ws(name);
// Set the thread name
typedef HRESULT(WINAPI * SetThreadDescriptionSignature)(HANDLE, PCWSTR);
HRESULT hr = callRunTimeDynamicLinkedFunction<HRESULT, SetThreadDescriptionSignature>(
"KernelBase.dll", "SetThreadDescription", GetCurrentThread(), name_ws.data());
if (FAILED(hr))
{
QUILL_THROW(QuillError{"Failed to set thread name"});
}
#elif defined(__APPLE__)
// Apple
auto const res = pthread_setname_np(name);
if (res != 0)
{
QUILL_THROW(QuillError{std::string{"Failed to set thread name - errno: " + std::to_string(errno) +
" error: " + strerror(errno)}});
}
#else
// linux
char truncated_name[16];
std::strncpy(truncated_name, name, 15);
truncated_name[15] = '\0';
auto const res = pthread_setname_np(pthread_self(), name);
if (res != 0)
{
QUILL_THROW(QuillError{std::string{"Failed to set thread name - errno: " + std::to_string(errno) +
" error: " + strerror(errno)}});
}
#endif
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_COLD inline uint32_t get_process_id() noexcept
{
#if defined(__CYGWIN__)
// get pid on cygwin not supported
return 0;
#elif defined(_WIN32)
return static_cast<uint32_t>(GetCurrentProcessId());
#else
return static_cast<uint32_t>(getpid());
#endif
}
} // namespace quill::detail

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,198 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/TransitEvent.h"
#include "quill/core/Attributes.h"
#include "quill/core/LoggerBase.h"
#include "quill/core/QuillError.h"
#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
namespace quill::detail
{
/**
* Stores N max messages per logger name in a vector.
* For simplicity this class is used ONLY by the backend worker thread.
* We push to the queue a BacktraceCommand event to communicate this from the frontend caller threads
*/
class BacktraceStorage
{
public:
BacktraceStorage() = default;
~BacktraceStorage()
{
// we want to clear all messages first, calling deallocate on the free list allocator before destructing
_stored_records_map.clear();
}
/**
* Stores an object to a vector that maps to logger_name
* @param transit_event transit_event
*/
void store(TransitEvent transit_event)
{
auto const stored_records_it = _stored_records_map.find(transit_event.logger_base->get_logger_name());
if (QUILL_UNLIKELY(stored_records_it == _stored_records_map.end()))
{
// We have never used backtrace this logger name before, need to call set capacity first
QUILL_THROW(QuillError{
"logger->init_backtrace(...) needs to be called first before using LOG_BACKTRACE(...)."});
}
// we found a stored vector for this logger name and we have to update it
StoredRecordInfo& stored_object_info = stored_records_it->second;
if (stored_object_info.stored_records_collection.size() < stored_object_info.capacity)
{
// We are still growing the vector to max capacity
auto& emplaced = stored_object_info.stored_records_collection.emplace_back(
std::string{transit_event.thread_name}, std::string{transit_event.thread_id}, std::move(transit_event));
// we want to point the transit event objects to ours because they can point to invalid memory
// if the thread is destructed
emplaced.transit_event.thread_name = emplaced.thread_name;
emplaced.transit_event.thread_id = emplaced.thread_id;
}
else
{
// Store the object in the vector
StoredTransitEvent& ste = stored_object_info.stored_records_collection[stored_object_info.index];
ste = StoredTransitEvent{std::string{transit_event.thread_name},
std::string{transit_event.thread_id}, std::move(transit_event)};
// we want to point the transit event objects to ours because they can point to invalid memory
// if the thread is destructed
ste.transit_event.thread_name = ste.thread_name;
ste.transit_event.thread_id = ste.thread_id;
// Update the index wrapping around the vector capacity
if (stored_object_info.index < stored_object_info.capacity - 1)
{
stored_object_info.index += 1;
}
else
{
stored_object_info.index = 0;
}
}
}
/**
* Calls the provided callback on all stored objects. The stored objects are provided
* in order from the first one (oldest record) to the last one (latest record)
*/
void process(std::string const& logger_name, std::function<void(TransitEvent const&)> const& callback)
{
auto const stored_records_it = _stored_records_map.find(logger_name);
if (QUILL_UNLIKELY(stored_records_it == _stored_records_map.end()))
{
// Not found, Nothing to iterate
return;
}
// we found stored messages for this logger
uint32_t index = stored_records_it->second.index;
StoredRecordsCollection& stored_record_collection = stored_records_it->second.stored_records_collection;
// Iterate retrieved records in order from first message using index
for (uint32_t i = 0; i < stored_record_collection.size(); ++i)
{
// Give to the user callback the thread id and the RecordBase pointer
callback(stored_record_collection[index].transit_event);
// We wrap around to iterate all messages
if (index < stored_record_collection.size() - 1)
{
index += 1;
}
else
{
index = 0;
}
}
// finally clean all messages
stored_record_collection.clear();
}
/**
* Insert a new StoredObject with the given capacity
* @note This has to be called to set a capacity, before a call to store
* @param logger_name The logger name to set this capacity
* @param capacity capacity value
*/
void set_capacity(std::string const& logger_name, uint32_t capacity)
{
auto inserted_it = _stored_records_map.emplace(std::string{logger_name}, StoredRecordInfo{capacity});
if (!inserted_it.second)
{
StoredRecordInfo& stored_object_info = inserted_it.first->second;
// We could not insert, the user called set_backtrace_capacity for a
// second time while one was active. In this case we will clear the existing one and
// store the new capacity if the new capacity is different
if (stored_object_info.capacity != capacity)
{
stored_object_info.stored_records_collection.clear();
stored_object_info.capacity = capacity;
}
}
}
/***/
void erase(std::string const& logger_name) { _stored_records_map.erase(logger_name); }
private:
/**
* We use this to take o copy of some objects that are out of space when a thread finishes but
* the transit events are still in the buffer
*/
struct StoredTransitEvent
{
StoredTransitEvent(std::string thread_name, std::string thread_id, TransitEvent transit_event)
: thread_name(std::move(thread_name)),
thread_id(std::move(thread_id)),
transit_event(std::move(transit_event))
{
}
std::string thread_name;
std::string thread_id;
TransitEvent transit_event;
};
using StoredRecordsCollection = std::vector<StoredTransitEvent>;
/**
* We map each logger name to this type
*/
struct StoredRecordInfo
{
explicit StoredRecordInfo(uint32_t capacity) : capacity(capacity)
{
// also reserve the vector
stored_records_collection.reserve(capacity);
}
uint32_t capacity; /** The maximum capacity for this */
uint32_t index{0}; /** Our current index */
StoredRecordsCollection stored_records_collection{}; /** A vector holding stored objects */
};
private:
/** A map where we store a vector of stored records for each logger name. We use the vectors like a ring buffer and loop around */
std::unordered_map<std::string, StoredRecordInfo> _stored_records_map;
};
} // namespace quill::detail

View file

@ -0,0 +1,508 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/TimestampFormatter.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/format.h"
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/FormatBuffer.h"
#include "quill/core/MacroMetadata.h"
#include "quill/core/QuillError.h"
#include <array>
#include <bitset>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
namespace quill
{
class PatternFormatter
{
/** Public classes **/
public:
/**
* Stores the precision of the timestamp
*/
enum class TimestampPrecision : uint8_t
{
None,
MilliSeconds,
MicroSeconds,
NanoSeconds
};
enum Attribute : uint8_t
{
Time = 0,
FileName,
CallerFunction,
LogLevel,
LogLevelId,
LineNumber,
Logger,
FullPath,
ThreadId,
ThreadName,
ProcessId,
SourceLocation,
ShortSourceLocation,
Message,
Tags,
NamedArgs,
ATTR_NR_ITEMS
};
/** Main PatternFormatter class **/
public:
/**
* Constructor for a PatterFormatter with a custom format
* @param format_pattern format_pattern a format string.
*
* The following attribute names can be used with the corresponding placeholder in a %-style format string.
* @note: The same attribute can not be used twice in the same format pattern
*
* %(time) - Human-readable timestamp representing when the log statement was created.
* %(file_name) - Name of the source file where the logging call was issued.
* %(full_path) - Full path of the source file where the logging call was issued.
* %(caller_function) - Name of the function containing the logging call.
* %(log_level) - Textual representation of the logging level for the message.
* %(log_level_id) - Single-letter identifier representing the logging level.
* %(line_number) - Line number in the source file where the logging call was issued.
* %(logger) - Name of the logger used to log the call.
* %(message) - The logged message itself.
* %(thread_id) - ID of the thread in which the logging call was made.
* %(thread_name) - Name of the thread. Must be set before the first log statement on that thread.
* %(process_id) - ID of the process in which the logging call was made.
* %(source_location) - Full source file path and line number as a single string.
* %(short_source_location) - Shortened source file name and line number as a single string.
* %(tags) - Additional custom tags appended to the message when _WITH_TAGS macros are used.
* %(named_args) - Key-value pairs appended to the message. Only applicable when the message has named args; remains empty otherwise.
*
* @param time_format The format of the date. Same as strftime() format with extra specifiers `%Qms` `%Qus` `Qns`
* @param timestamp_timezone The timezone of the timestamp, local_time or gmt_time
*
* @throws on invalid format string
*/
explicit PatternFormatter(
std::string format_pattern =
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) "
"%(logger:<12) %(message)",
std::string const& time_format = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime)
: _format_pattern(std::move(format_pattern)), _timestamp_formatter(time_format, timestamp_timezone)
{
_set_pattern();
}
PatternFormatter(PatternFormatter const& other) = delete;
PatternFormatter(PatternFormatter&& other) noexcept = delete;
PatternFormatter& operator=(PatternFormatter const& other) = delete;
PatternFormatter& operator=(PatternFormatter&& other) noexcept = delete;
/**
* Destructor
*/
~PatternFormatter() = default;
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format(
uint64_t timestamp, std::string_view thread_id, std::string_view thread_name, std::string_view process_id,
std::string_view logger, std::string_view log_level, MacroMetadata const& log_statement_metadata,
std::vector<std::pair<std::string, std::string>> const* named_args, std::string_view log_msg)
{
if (_fmt_format.empty())
{
// nothing to format when the given format is empty. This is useful e.g. in the
// JsonFileSink if we want to skip formatting the main message
return std::string_view{};
}
// clear out existing buffer
_formatted_log_message_buffer.clear();
if (_is_set_in_pattern[Attribute::Time])
{
_set_arg_val<Attribute::Time>(_timestamp_formatter.format_timestamp(std::chrono::nanoseconds{timestamp}));
}
if (_is_set_in_pattern[Attribute::FileName])
{
_set_arg_val<Attribute::FileName>(log_statement_metadata.file_name());
}
if (_is_set_in_pattern[Attribute::CallerFunction])
{
_set_arg_val<Attribute::CallerFunction>(log_statement_metadata.caller_function());
}
if (_is_set_in_pattern[Attribute::LogLevel])
{
_set_arg_val<Attribute::LogLevel>(log_level);
}
if (_is_set_in_pattern[Attribute::LogLevelId])
{
_set_arg_val<Attribute::LogLevelId>(log_statement_metadata.log_level_id());
}
if (_is_set_in_pattern[Attribute::LineNumber])
{
_set_arg_val<Attribute::LineNumber>(log_statement_metadata.line());
}
if (_is_set_in_pattern[Attribute::Logger])
{
_set_arg_val<Attribute::Logger>(logger);
}
if (_is_set_in_pattern[Attribute::FullPath])
{
_set_arg_val<Attribute::FullPath>(log_statement_metadata.full_path());
}
if (_is_set_in_pattern[Attribute::ThreadId])
{
_set_arg_val<Attribute::ThreadId>(thread_id);
}
if (_is_set_in_pattern[Attribute::ThreadName])
{
_set_arg_val<Attribute::ThreadName>(thread_name);
}
if (_is_set_in_pattern[Attribute::ProcessId])
{
_set_arg_val<Attribute::ProcessId>(process_id);
}
if (_is_set_in_pattern[Attribute::SourceLocation])
{
_set_arg_val<Attribute::SourceLocation>(log_statement_metadata.source_location());
}
if (_is_set_in_pattern[Attribute::ShortSourceLocation])
{
_set_arg_val<Attribute::ShortSourceLocation>(log_statement_metadata.short_source_location());
}
if (_is_set_in_pattern[Attribute::NamedArgs])
{
_formatted_named_args_buffer.clear();
if (named_args)
{
for (size_t i = 0; i < named_args->size(); ++i)
{
_formatted_named_args_buffer.append((*named_args)[i].first);
_formatted_named_args_buffer.append(std::string_view{": "});
_formatted_named_args_buffer.append((*named_args)[i].second);
if (i != named_args->size() - 1)
{
_formatted_named_args_buffer.append(std::string_view{", "});
}
}
}
_set_arg_val<Attribute::NamedArgs>(
std::string_view{_formatted_named_args_buffer.data(), _formatted_named_args_buffer.size()});
}
if (_is_set_in_pattern[Attribute::Tags])
{
if (log_statement_metadata.tags())
{
_tags.clear();
log_statement_metadata.tags()->format(_tags);
_set_arg_val<Attribute::Tags>(_tags);
}
else
{
_set_arg_val<Attribute::Tags>(std::string{});
}
}
_set_arg_val<Attribute::Message>(log_msg);
fmtquill::vformat_to(std::back_inserter(_formatted_log_message_buffer), _fmt_format,
fmtquill::basic_format_args(_args.data(), static_cast<int>(_args.size())));
return std::string_view{_formatted_log_message_buffer.data(), _formatted_log_message_buffer.size()};
}
QUILL_ATTRIBUTE_HOT std::string_view format_timestamp(std::chrono::nanoseconds timestamp)
{
return _timestamp_formatter.format_timestamp(timestamp);
}
/***/
QUILL_NODISCARD detail::TimestampFormatter const& timestamp_formatter() const noexcept
{
return _timestamp_formatter;
}
/***/
QUILL_NODISCARD std::string const& format_pattern() const noexcept { return _format_pattern; }
private:
void _set_pattern()
{
// the order we pass the arguments here must match with the order of Attribute enum
using namespace fmtquill::literals;
std::tie(_fmt_format, _order_index) = _generate_fmt_format_string(
_is_set_in_pattern, _format_pattern, "time"_a = "", "file_name"_a = "",
"caller_function"_a = "", "log_level"_a = "", "log_level_id"_a = "", "line_number"_a = "",
"logger"_a = "", "full_path"_a = "", "thread_id"_a = "", "thread_name"_a = "",
"process_id"_a = "", "source_location"_a = "", "short_source_location"_a = "",
"message"_a = "", "tags"_a = "", "named_args"_a = "");
_set_arg<Attribute::Time>(std::string_view("time"));
_set_arg<Attribute::FileName>(std::string_view("file_name"));
_set_arg<Attribute::CallerFunction>("caller_function");
_set_arg<Attribute::LogLevel>(std::string_view("log_level"));
_set_arg<Attribute::LogLevelId>(std::string_view("log_level_id"));
_set_arg<Attribute::LineNumber>("line_number");
_set_arg<Attribute::Logger>(std::string_view("logger"));
_set_arg<Attribute::FullPath>(std::string_view("full_path"));
_set_arg<Attribute::ThreadId>(std::string_view("thread_id"));
_set_arg<Attribute::ThreadName>(std::string_view("thread_name"));
_set_arg<Attribute::ProcessId>(std::string_view("process_id"));
_set_arg<Attribute::SourceLocation>("source_location");
_set_arg<Attribute::ShortSourceLocation>("short_source_location");
_set_arg<Attribute::Message>(std::string_view("message"));
_set_arg<Attribute::Tags>(std::string_view("tags"));
_set_arg<Attribute::NamedArgs>(std::string_view("named_args"));
}
/***/
template <size_t I, typename T>
void _set_arg(T const& arg)
{
_args[_order_index[I]] = fmtquill::detail::make_arg<fmtquill::format_context>(arg);
}
template <size_t I, typename T>
void _set_arg_val(T const& arg)
{
fmtquill::detail::value<fmtquill::format_context>& value_ =
*(reinterpret_cast<fmtquill::detail::value<fmtquill::format_context>*>(
std::addressof(_args[_order_index[I]])));
value_ = fmtquill::detail::arg_mapper<fmtquill::format_context>().map(arg);
}
/***/
PatternFormatter::Attribute static _attribute_from_string(std::string const& attribute_name)
{
// don't make this static as it breaks on windows with atexit when backend worker stops
std::unordered_map<std::string, PatternFormatter::Attribute> const attr_map = {
{"time", PatternFormatter::Attribute::Time},
{"file_name", PatternFormatter::Attribute::FileName},
{"caller_function", PatternFormatter::Attribute::CallerFunction},
{"log_level", PatternFormatter::Attribute::LogLevel},
{"log_level_id", PatternFormatter::Attribute::LogLevelId},
{"line_number", PatternFormatter::Attribute::LineNumber},
{"logger", PatternFormatter::Attribute::Logger},
{"full_path", PatternFormatter::Attribute::FullPath},
{"thread_id", PatternFormatter::Attribute::ThreadId},
{"thread_name", PatternFormatter::Attribute::ThreadName},
{"process_id", PatternFormatter::Attribute::ProcessId},
{"source_location", PatternFormatter::Attribute::SourceLocation},
{"short_source_location", PatternFormatter::Attribute::ShortSourceLocation},
{"message", PatternFormatter::Attribute::Message},
{"tags", PatternFormatter::Attribute::Tags},
{"named_args", PatternFormatter::Attribute::NamedArgs}};
auto const search = attr_map.find(attribute_name);
if (QUILL_UNLIKELY(search == attr_map.cend()))
{
QUILL_THROW(QuillError{
std::string{"Attribute enum value does not exist for attribute with name " + attribute_name}});
}
return search->second;
}
/***/
template <size_t, size_t>
constexpr void _store_named_args(std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS>&)
{
}
/***/
template <size_t Idx, size_t NamedIdx, typename Arg, typename... Args>
constexpr void _store_named_args(
std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS>& named_args_store,
const Arg& arg, const Args&... args)
{
named_args_store[NamedIdx] = {arg.name, Idx};
_store_named_args<Idx + 1, NamedIdx + 1>(named_args_store, args...);
}
/**
* Convert the pattern to fmt format string and also populate the order index array
* e.g. given :
* "%(time) [%(thread_id)] %(file_name):%(line_number) %(log_level:<12) %(logger) - "
*
* is changed to :
* {} [{}] {}:{} {:<12} {} -
*
* with a order index of :
* i: 0 order idx[i] is: 0 - %(time)
* i: 1 order idx[i] is: 2 - %(file_name)
* i: 2 order idx[i] is: 10 - empty
* i: 3 order idx[i] is: 4 - %(log_level)
* i: 4 order idx[i] is: 10 - empty
* i: 5 order idx[i] is: 3 - %(line_number)
* i: 6 order idx[i] is: 5 - %(logger)
* i: 7 order idx[i] is: 10 - empty
* i: 8 order idx[i] is: 1 - %(thread_id)
* i: 9 order idx[i] is: 10 - empty
* i: 10 order idx[i] is: 10 - empty
* @tparam Args Args
* @param is_set_in_pattern is set in pattern
* @param pattern pattern
* @param args args
* @return process_id pattern
*/
template <typename... Args>
QUILL_NODISCARD std::pair<std::string, std::array<size_t, PatternFormatter::Attribute::ATTR_NR_ITEMS>> _generate_fmt_format_string(
std::bitset<PatternFormatter::Attribute::ATTR_NR_ITEMS>& is_set_in_pattern, std::string pattern,
Args const&... args)
{
// Attribute enum and the args we are passing here must be in sync
static_assert(PatternFormatter::Attribute::ATTR_NR_ITEMS == sizeof...(Args));
pattern += "\n";
std::array<size_t, PatternFormatter::Attribute::ATTR_NR_ITEMS> order_index{};
order_index.fill(PatternFormatter::Attribute::ATTR_NR_ITEMS - 1);
std::array<fmtquill::detail::named_arg_info<char>, PatternFormatter::Attribute::ATTR_NR_ITEMS> named_args{};
_store_named_args<0, 0>(named_args, args...);
uint8_t arg_idx = 0;
// we will replace all %(....) with {} to construct a string to pass to fmt library
size_t arg_identifier_pos = pattern.find_first_of('%');
while (arg_identifier_pos != std::string::npos)
{
if (size_t const open_paren_pos = pattern.find_first_of('(', arg_identifier_pos);
open_paren_pos != std::string::npos && (open_paren_pos - arg_identifier_pos) == 1)
{
// if we found '%(' we have a matching pattern and we now need to get the closed paren
size_t const closed_paren_pos = pattern.find_first_of(')', open_paren_pos);
if (closed_paren_pos == std::string::npos)
{
QUILL_THROW(QuillError{"Invalid format pattern"});
}
// We have everything, get the substring, this time including '%( )'
std::string attr = pattern.substr(arg_identifier_pos, (closed_paren_pos + 1) - arg_identifier_pos);
// find any user format specifiers
size_t const pos = attr.find(':');
std::string attr_name;
if (pos != std::string::npos)
{
// we found user format specifiers that we want to keep.
// e.g. %(short_source_location:<32)
// Get only the format specifier
// e.g. :<32
std::string custom_format_specifier = attr.substr(pos);
custom_format_specifier.pop_back(); // remove the ")"
// replace with the pattern with the correct value
std::string value;
value += "{";
value += custom_format_specifier;
value += "}";
// e.g. {:<32}
pattern.replace(arg_identifier_pos, attr.length(), value);
// Get the part that is the named argument
// e.g. short_source_location
attr_name = attr.substr(2, pos - 2);
}
else
{
// Make the replacement.
pattern.replace(arg_identifier_pos, attr.length(), "{}");
// Get the part that is the named argument
// e.g. short_source_location
attr.pop_back(); // remove the ")"
attr_name = attr.substr(2, attr.size());
}
// reorder
int id = -1;
for (size_t i = 0; i < PatternFormatter::Attribute::ATTR_NR_ITEMS; ++i)
{
if (named_args[i].name == attr_name)
{
id = named_args[i].id;
break;
}
}
if (id < 0)
{
QUILL_THROW(QuillError{"Invalid format pattern, attribute with name \"" + attr_name + "\" is invalid"});
}
order_index[static_cast<size_t>(id)] = arg_idx++;
// Also set the value as used in the pattern in our bitset for lazy evaluation
PatternFormatter::Attribute const attr_enum_value = _attribute_from_string(attr_name);
is_set_in_pattern.set(attr_enum_value);
// Look for the next pattern to replace
arg_identifier_pos = pattern.find_first_of('%');
}
else
{
// search for the next '%'
arg_identifier_pos = pattern.find_first_of('%', arg_identifier_pos + 1);
}
}
return std::make_pair(pattern, order_index);
}
private:
std::string _format_pattern;
std::string _fmt_format;
std::string _tags;
/** Each named argument in the format_pattern is mapped in order to this array **/
std::array<size_t, Attribute::ATTR_NR_ITEMS> _order_index{};
std::array<fmtquill::basic_format_arg<fmtquill::format_context>, Attribute::ATTR_NR_ITEMS> _args{};
std::bitset<Attribute::ATTR_NR_ITEMS> _is_set_in_pattern;
/** class responsible for formatting the timestamp */
detail::TimestampFormatter _timestamp_formatter;
/** The buffer where we store each formatted string, also stored as class member to avoid
* re-allocations **/
fmtquill::basic_memory_buffer<char, 512> _formatted_log_message_buffer;
fmtquill::basic_memory_buffer<char, 512> _formatted_named_args_buffer;
};
} // namespace quill

View file

@ -0,0 +1,212 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/Rdtsc.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <ratio>
namespace quill::detail
{
/**
* Converts tsc ticks to nanoseconds since epoch
*/
class RdtscClock
{
/**
* A static class that calculates the rdtsc ticks per second
*/
class RdtscTicks
{
public:
QUILL_NODISCARD static RdtscTicks& instance()
{
static RdtscTicks inst;
return inst;
}
/***/
QUILL_NODISCARD double ns_per_tick() const noexcept { return _ns_per_tick; }
private:
/**
* Constructor
*/
RdtscTicks()
{
// Convert rdtsc to wall time.
// 1. Get real time and rdtsc current count
// 2. Calculate how many rdtsc ticks can occur in one
// calculate _ticks_per_ns as the median over a number of observations.
constexpr std::chrono::milliseconds spin_duration = std::chrono::milliseconds{10};
constexpr int trials = 13;
std::array<double, trials> rates = {{0}};
for (size_t i = 0; i < trials; ++i)
{
auto const beg_ts =
std::chrono::nanoseconds{std::chrono::steady_clock::now().time_since_epoch().count()};
uint64_t const beg_tsc = rdtsc();
std::chrono::nanoseconds elapsed_ns;
uint64_t end_tsc;
do
{
auto const end_ts =
std::chrono::nanoseconds{std::chrono::steady_clock::now().time_since_epoch().count()};
end_tsc = rdtsc();
elapsed_ns = end_ts - beg_ts; // calculates ns between two timespecs
} while (elapsed_ns < spin_duration); // busy spin for 10ms
rates[i] = static_cast<double>(end_tsc - beg_tsc) / static_cast<double>(elapsed_ns.count());
}
std::nth_element(rates.begin(), rates.begin() + trials / 2, rates.end());
double const ticks_per_ns = rates[trials / 2];
_ns_per_tick = 1 / ticks_per_ns;
}
double _ns_per_tick{0};
};
public:
/***/
explicit RdtscClock(std::chrono::nanoseconds resync_interval)
: _resync_interval_ticks(static_cast<std::int64_t>(static_cast<double>(resync_interval.count()) *
RdtscTicks::instance().ns_per_tick())),
_resync_interval_original(_resync_interval_ticks),
_ns_per_tick(RdtscTicks::instance().ns_per_tick())
{
if (!resync(2500))
{
// try to resync again with higher lag
if (!resync(10000))
{
std::fprintf(stderr, "Failed to sync RdtscClock. Timestamps will be incorrect\n");
}
}
}
/***/
uint64_t time_since_epoch(uint64_t rdtsc_value) const noexcept
{
// should only get called by the backend thread
// get the current index, this is only sef called my the thread that is doing the resync
auto const index = _version.load(std::memory_order_relaxed) & (_base.size() - 1);
// get rdtsc current value and compare the diff then add it to base wall time
auto diff = static_cast<int64_t>(rdtsc_value - _base[index].base_tsc);
// we need to sync after we calculated otherwise base_tsc value will be ahead of passed tsc value
if (diff > _resync_interval_ticks)
{
resync(2500);
diff = static_cast<int64_t>(rdtsc_value - _base[index].base_tsc);
}
return static_cast<uint64_t>(_base[index].base_time +
static_cast<int64_t>(static_cast<double>(diff) * _ns_per_tick));
}
/***/
uint64_t time_since_epoch_safe(uint64_t rdtsc_value) const noexcept
{
// thread-safe, can be called by anyone
// this function won't resync as it can be called by anyone and only a single thread resyncs
uint32_t version;
uint64_t wall_ts;
do
{
version = _version.load(std::memory_order_acquire);
auto const index = version & (_base.size() - 1);
if (QUILL_UNLIKELY((_base[index].base_tsc) == 0 && (_base[index].base_time == 0)))
{
return 0;
}
// get rdtsc current value and compare the diff then add it to base wall time
auto const diff = static_cast<int64_t>(rdtsc_value - _base[index].base_tsc);
wall_ts = static_cast<uint64_t>(_base[index].base_time +
static_cast<int64_t>(static_cast<double>(diff) * _ns_per_tick));
} while (version != _version.load(std::memory_order_acquire));
return wall_ts;
}
/***/
bool resync(uint32_t lag) const noexcept
{
// Sometimes we might get an interrupt and might never resync, so we will try again up to max_attempts
constexpr uint8_t max_attempts{4};
for (uint8_t attempt = 0; attempt < max_attempts; ++attempt)
{
uint64_t const beg = rdtsc();
// we force convert to nanoseconds because the precision of system_clock::time-point is not portable across platforms.
int64_t const wall_time =
std::chrono::nanoseconds{std::chrono::system_clock::now().time_since_epoch()}.count();
uint64_t const end = rdtsc();
if (QUILL_LIKELY(end - beg <= lag))
{
// update the next index
auto const index = (_version.load(std::memory_order_relaxed) + 1) & (_base.size() - 1);
_base[index].base_time = wall_time;
_base[index].base_tsc = _fast_average(beg, end);
_version.fetch_add(1, std::memory_order_release);
_resync_interval_ticks = _resync_interval_original;
return true;
}
}
// we failed to return earlier and we never resynced, but we don't really want to keep retrying on each call
// to time_since_epoch() so we do non accurate resync we will increase the resync duration to resync later
_resync_interval_ticks = _resync_interval_ticks * 2;
return false;
}
/***/
double nanoseconds_per_tick() const noexcept { return _ns_per_tick; }
private:
struct BaseTimeTsc
{
BaseTimeTsc() = default;
int64_t base_time{0}; /**< Get the initial base time in nanoseconds from epoch */
uint64_t base_tsc{0}; /**< Get the initial base tsc time */
};
/***/
QUILL_NODISCARD static uint64_t _fast_average(uint64_t x, uint64_t y) noexcept
{
return (x & y) + ((x ^ y) >> 1);
}
private:
mutable int64_t _resync_interval_ticks{0};
int64_t _resync_interval_original{0}; /**< stores the initial interval value as as if we fail to resync we increase the timer */
double _ns_per_tick{0};
alignas(CACHE_LINE_ALIGNED) mutable std::atomic<uint32_t> _version{0};
mutable std::array<BaseTimeTsc, 2> _base{};
};
} // namespace quill::detail

View file

@ -0,0 +1,374 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/Logger.h"
#include "quill/core/Attributes.h"
#include "quill/core/LogLevel.h"
#include "quill/core/LoggerBase.h"
#include "quill/core/LoggerManager.h"
#include "quill/core/MacroMetadata.h"
#include "quill/core/QuillError.h"
#include "quill/core/ThreadUtilities.h"
#include <atomic>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <string>
#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <chrono>
#include <thread>
#include <windows.h>
#else
#include <unistd.h>
#endif
namespace quill
{
namespace detail
{
/***/
class SignalHandlerContext
{
public:
SignalHandlerContext(SignalHandlerContext const&) = delete;
SignalHandlerContext& operator=(SignalHandlerContext const&) = delete;
/***/
static SignalHandlerContext& instance() noexcept
{
static SignalHandlerContext instance;
return instance;
}
std::atomic<int32_t> signal_number{0};
std::atomic<uint32_t> lock{0};
std::atomic<uint32_t> backend_thread_id{0};
std::atomic<uint32_t> signal_handler_timeout_seconds{20};
std::atomic<bool> should_reraise_signal{true};
private:
SignalHandlerContext() = default;
~SignalHandlerContext() = default;
};
#define QUILL_SIGNAL_HANDLER_LOG(logger, log_level, fmt, ...) \
do \
{ \
if (logger->template should_log_message<log_level>()) \
{ \
static constexpr quill::MacroMetadata macro_metadata{ \
"SignalHandler:~", "", fmt, nullptr, log_level, quill::MacroMetadata::Event::Log}; \
\
logger->log_message(quill::LogLevel::None, &macro_metadata, ##__VA_ARGS__); \
} \
} while (0)
/***/
template <typename TFrontendOptions>
void on_signal(int32_t signal_number)
{
// This handler can be entered by multiple threads.
uint32_t const lock = SignalHandlerContext::instance().lock.fetch_add(1);
if (lock != 0)
{
// We only allow the first thread to enter the signal handler
// sleep until a signal is delivered that either terminates the process or causes the
// invocation of a signal-catching function.
#if defined(_WIN32)
std::this_thread::sleep_for(std::chrono::hours{24000});
#else
pause();
#endif
}
#if defined(_WIN32)
// nothing to do, windows do not have alarm
#else
// Store the original signal number for the alarm
SignalHandlerContext::instance().signal_number.store(signal_number);
// Set up an alarm to crash after 20 seconds by redelivering the original signal,
// in case anything else goes wrong
alarm(SignalHandlerContext::instance().signal_handler_timeout_seconds.load());
#endif
// Get the id of this thread in the handler and make sure it is not the backend worker thread
uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
uint32_t const current_thread_id = get_thread_id();
bool const should_reraise_signal = SignalHandlerContext::instance().should_reraise_signal.load();
if ((backend_thread_id == 0) || (current_thread_id == backend_thread_id))
{
// backend worker thread is not running or the signal handler is called in the backend worker thread
if (signal_number == SIGINT || signal_number == SIGTERM)
{
std::exit(EXIT_SUCCESS);
}
else if (should_reraise_signal)
{
// for other signals expect SIGINT and SIGTERM we re-raise
std::signal(signal_number, SIG_DFL);
std::raise(signal_number);
}
}
else
{
// This means signal handler is running on a frontend thread, we can log and flush
LoggerBase* logger_base = detail::LoggerManager::instance().get_valid_logger();
if (logger_base)
{
#if defined(_WIN32)
int32_t const signal_desc = signal_number;
#else
char const* const signal_desc = ::strsignal(signal_number);
#endif
auto logger = reinterpret_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
QUILL_SIGNAL_HANDLER_LOG(logger, quill::LogLevel::Info, "Received signal: {} (signum: {})",
signal_desc, signal_number);
if (signal_number == SIGINT || signal_number == SIGTERM)
{
// For SIGINT and SIGTERM, we are shutting down gracefully
// Pass `0` to avoid calling std::this_thread::sleep_for()
logger->flush_log(0);
std::exit(EXIT_SUCCESS);
}
else if (should_reraise_signal)
{
QUILL_SIGNAL_HANDLER_LOG(logger, quill::LogLevel::Critical,
"Program terminated unexpectedly due to signal: {} (signum: {})",
signal_desc, signal_number);
// This is here in order to flush the above log statement
logger->flush_log(0);
// Reset to the default signal handler and re-raise the signal
std::signal(signal_number, SIG_DFL);
std::raise(signal_number);
}
else
{
logger->flush_log(0);
}
}
}
}
} // namespace detail
/**
* Setups a signal handler to handle fatal signals
*/
#if defined(_WIN32)
namespace detail
{
/***/
char const* get_error_message(DWORD ex_code)
{
switch (ex_code)
{
case EXCEPTION_ACCESS_VIOLATION:
return "EXCEPTION_ACCESS_VIOLATION";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
case EXCEPTION_BREAKPOINT:
return "EXCEPTION_BREAKPOINT";
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "EXCEPTION_DATATYPE_MISALIGNMENT";
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "EXCEPTION_FLT_DENORMAL_OPERAND";
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
case EXCEPTION_FLT_INEXACT_RESULT:
return "EXCEPTION_FLT_INEXACT_RESULT";
case EXCEPTION_FLT_INVALID_OPERATION:
return "EXCEPTION_FLT_INVALID_OPERATION";
case EXCEPTION_FLT_OVERFLOW:
return "EXCEPTION_FLT_OVERFLOW";
case EXCEPTION_FLT_STACK_CHECK:
return "EXCEPTION_FLT_STACK_CHECK";
case EXCEPTION_FLT_UNDERFLOW:
return "EXCEPTION_FLT_UNDERFLOW";
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "EXCEPTION_ILLEGAL_INSTRUCTION";
case EXCEPTION_IN_PAGE_ERROR:
return "EXCEPTION_IN_PAGE_ERROR";
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
case EXCEPTION_INT_OVERFLOW:
return "EXCEPTION_INT_OVERFLOW";
case EXCEPTION_INVALID_DISPOSITION:
return "EXCEPTION_INVALID_DISPOSITION";
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
case EXCEPTION_PRIV_INSTRUCTION:
return "EXCEPTION_PRIV_INSTRUCTION";
case EXCEPTION_SINGLE_STEP:
return "EXCEPTION_SINGLE_STEP";
case EXCEPTION_STACK_OVERFLOW:
return "EXCEPTION_STACK_OVERFLOW";
default:
return "Unrecognized Exception";
}
}
/***/
template <typename TFrontendOptions>
BOOL WINAPI on_console_signal(DWORD signal)
{
uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
uint32_t const current_thread_id = get_thread_id();
// Check if the signal handler is running from a caller thread and if the signal is CTRL+C or CTRL+BREAK
if ((backend_thread_id != 0) && (current_thread_id != backend_thread_id) &&
(signal == CTRL_C_EVENT || signal == CTRL_BREAK_EVENT))
{
// Log the interruption and flush log messages
LoggerBase* logger_base = detail::LoggerManager::instance().get_valid_logger();
if (logger_base)
{
auto logger = reinterpret_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
QUILL_SIGNAL_HANDLER_LOG(logger, quill::LogLevel::Info,
"Program interrupted by Ctrl+C or Ctrl+Break signal");
// Pass `0` to avoid calling std::this_thread::sleep_for()
logger->flush_log(0);
std::exit(EXIT_SUCCESS);
}
}
return TRUE;
}
/***/
template <typename TFrontendOptions>
LONG WINAPI on_exception(EXCEPTION_POINTERS* exception_p)
{
uint32_t const backend_thread_id = SignalHandlerContext::instance().backend_thread_id.load();
uint32_t const current_thread_id = get_thread_id();
// Check if the signal handler is running from a caller thread and if the signal is CTRL+C or CTRL+BREAK
if ((backend_thread_id != 0) && (current_thread_id != backend_thread_id))
{
// Log the interruption and flush log messages
LoggerBase* logger_base = detail::LoggerManager::instance().get_valid_logger();
if (logger_base)
{
auto logger = reinterpret_cast<LoggerImpl<TFrontendOptions>*>(logger_base);
QUILL_SIGNAL_HANDLER_LOG(logger, quill::LogLevel::Info, "Received exception: {} (Code: {})",
get_error_message(exception_p->ExceptionRecord->ExceptionCode),
exception_p->ExceptionRecord->ExceptionCode);
QUILL_SIGNAL_HANDLER_LOG(logger, quill::LogLevel::Critical,
"Program terminated unexpectedly due to exception: {} (Code: {})",
get_error_message(exception_p->ExceptionRecord->ExceptionCode),
exception_p->ExceptionRecord->ExceptionCode);
// Pass `0` to avoid calling std::this_thread::sleep_for()
logger->flush_log(0);
}
}
// FATAL Exception: It doesn't necessarily stop here. we pass on continue search
// If nobody catches it, then it will exit anyhow.
// The risk here is if someone is catching this and returning "EXCEPTION_EXECUTE_HANDLER"
// but won't shut down then the app will be running with quill shutdown.
return EXCEPTION_CONTINUE_SEARCH;
}
/***/
template <typename TFrontendOptions>
void init_exception_handler()
{
SetUnhandledExceptionFilter(on_exception<TFrontendOptions>);
if (!SetConsoleCtrlHandler(on_console_signal<TFrontendOptions>, TRUE))
{
QUILL_THROW(QuillError{"Failed to call SetConsoleCtrlHandler"});
}
}
} // namespace detail
/**
* On windows it has to be called on each thread
* @param catchable_signals the signals we are catching
*/
template <typename TFrontendOptions>
void init_signal_handler(std::initializer_list<int32_t> const& catchable_signals = std::initializer_list<int>{
SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV})
{
for (auto const& catchable_signal : catchable_signals)
{
// setup a signal handler per signal in the array
if (std::signal(catchable_signal, detail::on_signal<TFrontendOptions>) == SIG_ERR)
{
QUILL_THROW(QuillError{"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
}
}
}
#else
namespace detail
{
/***/
void on_alarm(int32_t signal_number)
{
if (SignalHandlerContext::instance().signal_number.load() == 0)
{
// Will only happen if SIGALRM is the first signal we receive
SignalHandlerContext::instance().signal_number = signal_number;
}
// We will raise the original signal back
std::signal(SignalHandlerContext::instance().signal_number, SIG_DFL);
std::raise(SignalHandlerContext::instance().signal_number);
}
template <typename TFrontendOptions>
void init_signal_handler(std::initializer_list<int32_t> const& catchable_signals)
{
for (auto const& catchable_signal : catchable_signals)
{
if (catchable_signal == SIGALRM)
{
QUILL_THROW(QuillError{"SIGALRM can not be part of catchable_signals."});
}
// set up a signal handler per signal in the array
if (std::signal(catchable_signal, on_signal<TFrontendOptions>) == SIG_ERR)
{
QUILL_THROW(QuillError{"Failed to setup signal handler for signal: " + std::to_string(catchable_signal)});
}
}
/* Register the alarm handler */
if (std::signal(SIGALRM, on_alarm) == SIG_ERR)
{
QUILL_THROW(QuillError{"Failed to setup signal handler for signal: SIGALRM"});
}
}
} // namespace detail
#endif
} // namespace quill

View file

@ -0,0 +1,533 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/QuillError.h"
#include "quill/core/TimeUtilities.h"
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace quill::detail
{
/**
* A class that converts a timestamp to a string based on the given format.
* It works exactly the same as strftime() but it caches the string on it's creation
* and only modifies the parts of the string that need to change.
* This is much more efficient than calling strftime as we just need to calculate the different
* between the cached and the requested timestamp and modify our preformatted string.
*
* 1) We take a format string eg "%Y-%m-%dT%H:%M:%SZ".
* 2) We split it into several parts (_initial_parts) where we separate the time modifiers.
* e.g "%Y-%m-%dT", "%H", ":", "%M", ":", "%S", Z".
* 3) We cache the current timestamp and current h/m/s in seconds and on each part above
* we call strftime() and we create a preformatted string.
* 4) While calling stftime in 3) we can manually check for any time modifiers and store
* their index in the final pre-formatted string.
* 5) Next time we want to calculate the timestamp we will just calculate the difference
* in seconds between the current and the cache timestamp.
* Then we add this value to the cached seconds and then convert the seconds
* to hh::mm::ss and replace in our pre-formatted string using the stored indexes.
* 6) We re-calculate the pre-formatted string every midnight and noon.
*/
class StringFromTime
{
public:
/***/
QUILL_ATTRIBUTE_COLD void init(std::string timestamp_format, Timezone timezone)
{
_timestamp_format = std::move(timestamp_format);
_time_zone = timezone;
if (_timestamp_format.find("%X") != std::string::npos)
{
QUILL_THROW(QuillError("`%X` as format modifier is not currently supported in format: " + _timestamp_format));
}
// We first look for some special format modifiers and replace them
_replace_all(_timestamp_format, "%r", "%I:%M:%S %p");
_replace_all(_timestamp_format, "%R", "%H:%M");
_replace_all(_timestamp_format, "%T", "%H:%M:%S");
// Populate the initial parts that we will use to generate a pre-formatted string
_populate_initial_parts(_timestamp_format);
// Get the current timestamp now
time_t timestamp;
std::time(&timestamp);
if (_time_zone == Timezone::LocalTime)
{
// If localtime is used we will recalculate every 1 hour - this is because of the DST changes
// and an easy work-around
// Then round it down to the nearest hour
timestamp = _nearest_hour_timestamp(timestamp);
// Also calculate and store the next hour timestamp
_next_recalculation_timestamp = _next_hour_timestamp(timestamp);
}
else if (_time_zone == Timezone::GmtTime)
{
// otherwise we will only recalculate every noon and midnight. the reason for this is in case
// user is using PM, AM format etc
_next_recalculation_timestamp = _next_noon_or_midnight_timestamp(timestamp, _time_zone);
// we don't need to modify timestamp in the case of UTC
}
// Now populate a pre formatted string for this hour,
// also cache any indexes of the time modifier in the string
_populate_pre_formatted_string_and_cached_indexes(timestamp);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string const& format_timestamp(time_t timestamp)
{
// First we check for the edge case where the given timestamp is back in time. This is when
// the timestamp provided is less than our cached_timestamp. We only expect to format timestamps
// that are incrementing not those back in time. In this case we just fall back to calling strfime
if (timestamp < _cached_timestamp)
{
_fallback_formatted = _safe_strftime(_timestamp_format.data(), timestamp, _time_zone).data();
return _fallback_formatted;
}
// After this point we know that given timestamp is >= to the cache timestamp.
// We check if the given timestamp greater than the _next_recalculation_timestamp to recalculate
if (timestamp >= _next_recalculation_timestamp)
{
// in this case we have to populate our cached string again using strftime
_pre_formatted_ts.clear();
_cached_indexes.clear();
// Now populate a pre-formatted string for the next rec
_populate_pre_formatted_string_and_cached_indexes(timestamp);
if (_time_zone == Timezone::LocalTime)
{
_next_recalculation_timestamp = _next_hour_timestamp(timestamp);
}
else if (_time_zone == Timezone::GmtTime)
{
_next_recalculation_timestamp = _next_noon_or_midnight_timestamp(timestamp + 1, _time_zone);
}
}
if (_cached_indexes.empty())
{
// if we don't have to format any hours minutes or seconds we can just return here
return _pre_formatted_ts;
}
if (_cached_timestamp == timestamp)
{
// This has 2 usages:
// 1. Any timestamps in seconds precision that are the same, we don't need to do anything.
// 2. This checks happens after the _next_recalculation_timestamp calculation. The _next_recalculation_timestamp
// will mutate _cached_timestamp and if they are similar we will just return here anyway
return _pre_formatted_ts;
}
// Get the difference from our cached timestamp
time_t const timestamp_diff = timestamp - _cached_timestamp;
// cache this timestamp
_cached_timestamp = timestamp;
// Add the timestamp_diff to the cached seconds and calculate the new hours minutes seconds.
// Note: cached_seconds could be in gmtime or localtime but we don't care as we are just
// adding the difference.
_cached_seconds += static_cast<uint32_t>(timestamp_diff);
uint32_t cached_seconds = _cached_seconds;
uint32_t const hours = cached_seconds / 3600;
cached_seconds = cached_seconds % 3600;
uint32_t const minutes = cached_seconds / 60;
cached_seconds = cached_seconds % 60;
uint32_t const seconds = cached_seconds;
std::string to_replace;
for (auto const& index : _cached_indexes)
{
// for each cached index we have, replace it when the new value
switch (index.second)
{
case format_type::H:
to_replace = std::to_string(hours);
if (to_replace.size() == 1)
{
to_replace.insert(0, 1, '0');
}
_pre_formatted_ts.replace(index.first, 2, to_replace);
break;
case format_type::M:
to_replace = std::to_string(minutes);
if (to_replace.size() == 1)
{
to_replace.insert(0, 1, '0');
}
_pre_formatted_ts.replace(index.first, 2, to_replace);
break;
case format_type::S:
to_replace = std::to_string(seconds);
if (to_replace.size() == 1)
{
to_replace.insert(0, 1, '0');
}
_pre_formatted_ts.replace(index.first, 2, to_replace);
break;
case format_type::I:
if (hours != 0)
{
to_replace = std::to_string(hours > 12 ? hours - 12 : hours);
}
else
{
// if hours is `00` we need to replace than with '12' instead in this format
to_replace = std::to_string(12);
}
if (to_replace.size() == 1)
{
to_replace.insert(0, 1, '0');
}
_pre_formatted_ts.replace(index.first, 2, to_replace);
break;
case format_type::k:
to_replace = std::to_string(hours);
if (to_replace.size() == 1)
{
to_replace.insert(0, 1, ' ');
}
_pre_formatted_ts.replace(index.first, 2, to_replace);
break;
case format_type::l:
if (hours != 0)
{
to_replace = std::to_string(hours > 12 ? hours - 12 : hours);
}
else
{
// if hours is `00` we need to replace than with '12' instead in this format
to_replace = std::to_string(12);
}
if (to_replace.size() == 1)
{
to_replace.insert(0, 1, ' ');
}
_pre_formatted_ts.replace(index.first, 2, to_replace);
break;
case format_type::s:
to_replace = std::to_string(_cached_timestamp);
_pre_formatted_ts.replace(index.first, 10, to_replace);
break;
default:
abort();
}
}
return _pre_formatted_ts;
}
protected:
enum class format_type : uint8_t
{
H,
M,
S,
I,
k,
l,
s
};
/***/
QUILL_ATTRIBUTE_COLD void _populate_initial_parts(std::string timestamp_format)
{
do
{
// we get part1 and part2 and keep looping on the new modified string without the part1 and
// part2 until we find not %H, %M or %S at all
auto const [part1, part2] = _split_timestamp_format_once(timestamp_format);
if (!part1.empty())
{
_initial_parts.push_back(part1);
}
if (!part2.empty())
{
_initial_parts.push_back(part2);
}
if (part1.empty() && part2.empty())
{
// if both part_1 and part_2 are empty it means we have no more
// format modifiers to add, we push back the remaining timestamp_format string
// and break
if (!timestamp_format.empty())
{
_initial_parts.push_back(timestamp_format);
}
break;
}
} while (true);
}
/***/
void _populate_pre_formatted_string_and_cached_indexes(time_t timestamp)
{
_cached_timestamp = timestamp;
tm time_info{};
if (_time_zone == Timezone::LocalTime)
{
localtime_rs(reinterpret_cast<time_t const*>(std::addressof(_cached_timestamp)), std::addressof(time_info));
}
else if (_time_zone == Timezone::GmtTime)
{
gmtime_rs(reinterpret_cast<time_t const*>(std::addressof(_cached_timestamp)), std::addressof(time_info));
}
// also cache the seconds
_cached_seconds =
static_cast<uint32_t>((time_info.tm_hour * 3600) + (time_info.tm_min * 60) + time_info.tm_sec);
// Now run through all parts and call strftime
for (auto const& format_part : _initial_parts)
{
// We call strftime on each part of the timestamp to format it.
_pre_formatted_ts += _safe_strftime(format_part.data(), _cached_timestamp, _time_zone).data();
// If we formatted and appended to the string a time modifier also store the
// current index in the string
if (format_part == "%H")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::H);
}
else if (format_part == "%M")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::M);
}
else if (format_part == "%S")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::S);
}
else if (format_part == "%I")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::I);
}
else if (format_part == "%k")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::k);
}
else if (format_part == "%l")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 2, format_type::l);
}
else if (format_part == "%s")
{
_cached_indexes.emplace_back(_pre_formatted_ts.size() - 10, format_type::s);
}
}
}
/***/
std::pair<std::string, std::string> static _split_timestamp_format_once(std::string& timestamp_format) noexcept
{
// don't make this static as it breaks on windows with atexit when backend worker stops
std::array<std::string, 7> const modifiers{"%H", "%M", "%S", "%I", "%k", "%l", "%s"};
// if we find any modifier in the timestamp format we store the index where we
// found it.
// We use a map to find the first modifier (with the lowest index) in the given string
// Maps found_index -> modifier value
std::map<size_t, std::string> found_format_modifiers;
for (auto const& modifier : modifiers)
{
if (auto const search = timestamp_format.find(modifier); search != std::string::npos)
{
// Add the index and the modifier string to our map
found_format_modifiers.emplace(search, modifier);
}
}
if (found_format_modifiers.empty())
{
// We didn't find any modifiers in the given string, therefore we return
// both parts as empty
return std::make_pair(std::string{}, std::string{});
}
// we will split the formatted timestamp on the first modifier we found
// Part 1 is the part before the modifier
// Here we check that there is actually a part of before and the format doesn't start with the
// modifier, otherwise we use an empty string
std::string const part_1 = found_format_modifiers.begin()->first > 0
? std::string{timestamp_format.data(), found_format_modifiers.begin()->first}
: "";
// The actual value of the modifier string
std::string const part_2 = found_format_modifiers.begin()->second;
// We modify the given timestamp string to exclude part_1 and part_2.
// part_2 length as the modifier value will always be 2
timestamp_format = std::string{timestamp_format.data() + found_format_modifiers.begin()->first + 2};
return std::make_pair(part_1, part_2);
}
/***/
QUILL_NODISCARD static std::vector<char> _safe_strftime(char const* format_string, time_t timestamp, Timezone timezone)
{
if (format_string[0] == '\0')
{
std::vector<char> res;
res.push_back('\0');
return res;
}
// Convert timestamp to time_info
tm time_info;
if (timezone == Timezone::LocalTime)
{
localtime_rs(reinterpret_cast<time_t const*>(std::addressof(timestamp)), std::addressof(time_info));
}
else if (timezone == Timezone::GmtTime)
{
gmtime_rs(reinterpret_cast<time_t const*>(std::addressof(timestamp)), std::addressof(time_info));
}
// Create a buffer to call strftimex
std::vector<char> buffer;
buffer.resize(32);
size_t res = strftime(&buffer[0], buffer.size(), format_string, std::addressof(time_info));
while (res == 0)
{
// if strftime fails we will reserve more space
buffer.resize(buffer.size() * 2);
res = strftime(&buffer[0], buffer.size(), format_string, std::addressof(time_info));
}
return buffer;
}
/***/
static void _replace_all(std::string& str, std::string const& old_value, std::string const& new_value) noexcept
{
std::string::size_type pos = 0u;
while ((pos = str.find(old_value, pos)) != std::string::npos)
{
str.replace(pos, old_value.length(), new_value);
pos += new_value.length();
}
}
/***/
QUILL_NODISCARD static time_t _nearest_hour_timestamp(time_t timestamp) noexcept
{
time_t const nearest_hour_ts = timestamp - (timestamp % 3600);
return nearest_hour_ts;
}
/***/
QUILL_NODISCARD static time_t _next_hour_timestamp(time_t timestamp) noexcept
{
time_t const next_hour_ts = _nearest_hour_timestamp(timestamp) + 3600;
return next_hour_ts;
}
/***/
QUILL_NODISCARD static time_t _next_noon_or_midnight_timestamp(time_t timestamp, Timezone timezone) noexcept
{
// Get the current date and time now as time_info
tm time_info;
if (timezone == Timezone::GmtTime)
{
gmtime_rs(&timestamp, &time_info);
}
else
{
localtime_rs(&timestamp, &time_info);
}
if (time_info.tm_hour < 12)
{
// we are before noon, so calculate noon
time_info.tm_hour = 11;
time_info.tm_min = 59;
time_info.tm_sec = 59; // we add 1 second later
}
else
{
// we are after noon so we calculate midnight
time_info.tm_hour = 23;
time_info.tm_min = 59;
time_info.tm_sec = 59; // we add 1 second later
}
// convert back to time since epoch
std::chrono::system_clock::time_point const next_midnight = (timezone == Timezone::GmtTime)
? std::chrono::system_clock::from_time_t(detail::timegm(&time_info))
: std::chrono::system_clock::from_time_t(std::mktime(&time_info));
// returns seconds since epoch of the next midnight.
return std::chrono::duration_cast<std::chrono::seconds>(next_midnight.time_since_epoch()).count() + 1;
}
private:
/** Contains the timestamp_format broken down into parts. We call use those parts to
* create a pre-formatted string */
std::vector<std::string> _initial_parts;
/** Contains stored indexes of the _pre_formatted_ts for each format time modifier*/
std::vector<std::pair<size_t, format_type>> _cached_indexes;
/** The format request format of the timestamp. This can be slightly modified in constructor so we store it. */
std::string _timestamp_format;
/** The pre-formatted timestamp string */
std::string _pre_formatted_ts;
/** This is only used only when we fallback to strftime */
std::string _fallback_formatted;
/** The timestamp of the next noon, or midnight, we use this to resync */
time_t _next_recalculation_timestamp{0};
/** The timestamp of value of our pre-formated string */
time_t _cached_timestamp{0};
/** The seconds of hh:mm:ss of the pre-formatted string this is after using gmtime or localtime on cached_timestamp */
uint32_t _cached_seconds{0};
/** gmtime or localtime */
Timezone _time_zone;
};
} // namespace quill::detail

View file

@ -0,0 +1,214 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/StringFromTime.h"
#include "quill/bundled/fmt/format.h"
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/QuillError.h"
#include <array>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <string_view>
#include <utility>
namespace quill::detail
{
/**
* Formats a timestamp given a format string as a pattern. The format pattern uses the
* same format specifiers as strftime() but with the following additional specifiers :
* 1) %Qms - Milliseconds
* 2) %Qus - Microseconds
* 3) %Qns - Nanoseconds
* @note %Qms, %Qus, %Qns specifiers are mutually exclusive
* e.g given : "%I:%M.%Qms%p" the output would be "03:21.343PM"
*/
class TimestampFormatter
{
private:
enum AdditionalSpecifier : uint8_t
{
None,
Qms,
Qus,
Qns
};
public:
/***/
explicit TimestampFormatter(std::string time_format, Timezone timestamp_timezone = Timezone::LocalTime)
: _time_format(std::move(time_format)), _timestamp_timezone(timestamp_timezone)
{
assert((_timestamp_timezone == Timezone::LocalTime || _timestamp_timezone == Timezone::GmtTime) &&
"Invalid timezone type");
// store the beginning of the found specifier
size_t specifier_begin{std::string::npos};
// look for all three special specifiers
if (size_t const search_qms = _time_format.find(specifier_name[AdditionalSpecifier::Qms]);
search_qms != std::string::npos)
{
_additional_format_specifier = AdditionalSpecifier::Qms;
specifier_begin = search_qms;
}
if (size_t const search_qus = _time_format.find(specifier_name[AdditionalSpecifier::Qus]);
search_qus != std::string::npos)
{
if (specifier_begin != std::string::npos)
{
QUILL_THROW(QuillError{"format specifiers %Qms, %Qus and %Qns are mutually exclusive"});
}
_additional_format_specifier = AdditionalSpecifier::Qus;
specifier_begin = search_qus;
}
if (size_t const search_qns = _time_format.find(specifier_name[AdditionalSpecifier::Qns]);
search_qns != std::string::npos)
{
if (specifier_begin != std::string::npos)
{
QUILL_THROW(QuillError{"format specifiers %Qms, %Qus and %Qns are mutually exclusive"});
}
_additional_format_specifier = AdditionalSpecifier::Qns;
specifier_begin = search_qns;
}
if (specifier_begin == std::string::npos)
{
// If no additional specifier was found then we can simply store the whole format string
assert(_additional_format_specifier == AdditionalSpecifier::None);
_format_part_1 = _time_format;
}
else
{
// We now the index where the specifier begins so copy everything until there from beginning
_format_part_1 = _time_format.substr(0, specifier_begin);
// Now copy the remaining format string, ignoring the specifier
size_t const specifier_end = specifier_begin + specifier_length;
_format_part_2 = _time_format.substr(specifier_end, _time_format.length() - specifier_end);
}
// Now init two custom string from time classes with pre-formatted strings
_strftime_part_1.init(_format_part_1, _timestamp_timezone);
_strftime_part_2.init(_format_part_2, _timestamp_timezone);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::string_view format_timestamp(std::chrono::nanoseconds time_since_epoch)
{
int64_t const timestamp_ns = time_since_epoch.count();
// convert timestamp to seconds
int64_t const timestamp_secs = timestamp_ns / 1'000'000'000;
// First always clear our cached string
_formatted_date.clear();
// 1. we always format part 1
_formatted_date += _strftime_part_1.format_timestamp(timestamp_secs);
// 2. We add any special ms/us/ns specifier if any
auto const extracted_ns = static_cast<uint32_t>(timestamp_ns - (timestamp_secs * 1'000'000'000));
if (_additional_format_specifier == AdditionalSpecifier::Qms)
{
uint32_t const extracted_ms = extracted_ns / 1'000'000;
constexpr char const* zeros = "000";
// Add as many zeros as the extracted_fractional_seconds_length
_formatted_date += zeros;
_append_fractional_seconds(extracted_ms);
}
else if (_additional_format_specifier == AdditionalSpecifier::Qus)
{
uint32_t const extracted_us = extracted_ns / 1'000;
constexpr char const* zeros = "000000";
// Add as many zeros as the extracted_fractional_seconds_length
_formatted_date += zeros;
_append_fractional_seconds(extracted_us);
}
else if (_additional_format_specifier == AdditionalSpecifier::Qns)
{
constexpr char const* zeros = "000000000";
// Add as many zeros as the extracted_fractional_seconds_length
_formatted_date += zeros;
_append_fractional_seconds(extracted_ns);
}
// 3. format part 2 after fractional seconds - if any
if (!_format_part_2.empty())
{
_formatted_date += _strftime_part_2.format_timestamp(timestamp_secs);
}
return _formatted_date;
}
/***/
QUILL_NODISCARD std::string const& time_format() const noexcept { return _time_format; }
/***/
QUILL_NODISCARD Timezone timestamp_timezone() const noexcept { return _timestamp_timezone; }
private:
/***/
void _append_fractional_seconds(uint32_t extracted_fractional_seconds)
{
// Format the seconds and add them
fmtquill::format_int const extracted_ms_string{extracted_fractional_seconds};
// _formatted_date.size() - extracted_ms_string.size() is where we want to begin placing the fractional seconds
memcpy(&_formatted_date[_formatted_date.size() - extracted_ms_string.size()],
extracted_ms_string.data(), extracted_ms_string.size());
}
private:
/** Contains the additional specifier name, at the same index as the enum **/
static constexpr std::array<char const*, 4> specifier_name{"", "%Qms", "%Qus", "%Qns"};
/** All special specifiers have same length at the moment **/
static constexpr size_t specifier_length = 4u;
std::string _time_format;
/** As class member to avoid re-allocating **/
std::string _formatted_date;
/** The format string is broken down to two parts. Before and after our additional specifiers */
std::string _format_part_1;
std::string _format_part_2;
/** Strftime cache for both parts of the string */
StringFromTime _strftime_part_1;
StringFromTime _strftime_part_2;
/** Timezone, Local time by default **/
Timezone _timestamp_timezone;
/** fractional seconds */
AdditionalSpecifier _additional_format_specifier{AdditionalSpecifier::None};
};
} // namespace quill::detail

View file

@ -0,0 +1,93 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Codec.h"
#include "quill/core/FormatBuffer.h"
#include "quill/core/LogLevel.h"
#include "quill/core/MacroMetadata.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace quill::detail
{
/** Forward declaration */
class LoggerBase;
/***/
struct TransitEvent
{
/***/
TransitEvent() { formatted_msg.reserve(32); }
/***/
~TransitEvent() = default;
/***/
TransitEvent(TransitEvent const& other) = delete;
TransitEvent& operator=(TransitEvent const& other) = delete;
/***/
TransitEvent(TransitEvent&& other) noexcept
: timestamp(other.timestamp),
macro_metadata(other.macro_metadata),
logger_base(other.logger_base),
format_args_decoder(other.format_args_decoder),
thread_id(other.thread_id),
thread_name(other.thread_name),
formatted_msg(std::move(other.formatted_msg)),
named_args(std::move(other.named_args)),
dynamic_log_level(other.dynamic_log_level),
flush_flag(other.flush_flag)
{
}
/***/
TransitEvent& operator=(TransitEvent&& other) noexcept
{
if (this != &other)
{
timestamp = other.timestamp;
macro_metadata = other.macro_metadata;
logger_base = other.logger_base;
format_args_decoder = other.format_args_decoder;
thread_id = other.thread_id;
thread_name = other.thread_name;
formatted_msg = std::move(other.formatted_msg);
named_args = std::move(other.named_args);
dynamic_log_level = other.dynamic_log_level;
flush_flag = other.flush_flag;
}
return *this;
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT LogLevel log_level() const noexcept
{
return (macro_metadata->log_level() != LogLevel::Dynamic) ? macro_metadata->log_level() : dynamic_log_level;
}
uint64_t timestamp{0};
MacroMetadata const* macro_metadata{nullptr};
detail::LoggerBase* logger_base{nullptr};
detail::FormatArgsDecoder format_args_decoder{nullptr};
std::string_view thread_id;
std::string_view thread_name;
FormatBuffer formatted_msg; /** buffer for message **/
std::unique_ptr<std::vector<std::pair<std::string, std::string>>> named_args; /** A unique ptr to save space as named args feature is not always used */
LogLevel dynamic_log_level{LogLevel::None};
std::atomic<bool>* flush_flag{nullptr}; /** This is only used in the case of Event::Flush **/
};
} // namespace quill::detail

View file

@ -0,0 +1,211 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/backend/TransitEvent.h"
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/MathUtils.h"
#include <cassert>
#include <cstdint>
#include <limits>
#include <vector>
namespace quill::detail
{
template <typename T>
class BoundedTransitEventBufferImpl
{
public:
using integer_type = T;
/***/
explicit BoundedTransitEventBufferImpl(integer_type capacity)
: _capacity(next_power_of_two(capacity)), _mask(static_cast<integer_type>(_capacity - 1u))
{
_storage.resize(_capacity);
}
/***/
BoundedTransitEventBufferImpl(BoundedTransitEventBufferImpl const&) = delete;
BoundedTransitEventBufferImpl& operator=(BoundedTransitEventBufferImpl const&) = delete;
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT TransitEvent* front() noexcept
{
if (_writer_pos == _reader_pos)
{
// empty
return nullptr;
}
return &_storage[static_cast<uint32_t>(_reader_pos & _mask)];
}
/***/
QUILL_ATTRIBUTE_HOT void pop_front() noexcept { ++_reader_pos; }
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT TransitEvent* back() noexcept
{
if (_capacity - size() == 0)
{
// is full
return nullptr;
}
return &_storage[static_cast<uint32_t>(_writer_pos & _mask)];
}
/***/
QUILL_ATTRIBUTE_HOT void push_back() noexcept { ++_writer_pos; }
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT integer_type size() const noexcept
{
return static_cast<integer_type>(_writer_pos - _reader_pos);
}
/***/
QUILL_NODISCARD integer_type capacity() const noexcept
{
return static_cast<integer_type>(_capacity);
}
private:
integer_type const _capacity;
integer_type const _mask;
integer_type _reader_pos{0};
integer_type _writer_pos{0};
std::vector<TransitEvent> _storage;
};
using BoundedTransitEventBuffer = BoundedTransitEventBufferImpl<uint32_t>;
class UnboundedTransitEventBuffer
{
public:
/***/
struct Node
{
/**
* Constructor
* @param transit_buffer_capacity the capacity of the fixed buffer
*/
explicit Node(uint32_t transit_buffer_capacity) : transit_buffer(transit_buffer_capacity) {}
/** members */
Node* next{nullptr};
BoundedTransitEventBuffer transit_buffer;
};
/***/
explicit UnboundedTransitEventBuffer(uint32_t initial_transit_buffer_capacity)
: _writer(new Node(initial_transit_buffer_capacity)), _reader(_writer)
{
}
/***/
UnboundedTransitEventBuffer(UnboundedTransitEventBuffer const&) = delete;
UnboundedTransitEventBuffer& operator=(UnboundedTransitEventBuffer const&) = delete;
/***/
~UnboundedTransitEventBuffer()
{
Node const* reader_node = _reader;
// Look for extra nodes to delete
while (reader_node)
{
auto const to_delete = reader_node;
reader_node = reader_node->next;
delete to_delete;
}
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT TransitEvent* front() noexcept
{
TransitEvent* next_event = _reader->transit_buffer.front();
if (!next_event)
{
// the buffer is empty check if another buffer exists
if (QUILL_UNLIKELY(_reader->next != nullptr))
{
// a new buffer was added by the producer, this happens only when we have allocated a new queue
// switch to the new buffer, existing one is deleted
Node* next_node = _reader->next;
delete _reader;
_reader = next_node;
next_event = _reader->transit_buffer.front();
}
}
return next_event;
}
/***/
QUILL_ATTRIBUTE_HOT void pop_front() noexcept { _reader->transit_buffer.pop_front(); }
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT TransitEvent* back() noexcept
{
// Try to reserve the bounded queue
TransitEvent* write_event = _writer->transit_buffer.back();
if (QUILL_UNLIKELY(write_event == nullptr))
{
// buffer doesn't have enough space
uint64_t capacity = static_cast<uint64_t>(_writer->transit_buffer.capacity()) * 2ull;
uint64_t constexpr max_bounded_queue_capacity =
(std::numeric_limits<BoundedTransitEventBuffer::integer_type>::max() >> 1) + 1;
if (QUILL_UNLIKELY(capacity > max_bounded_queue_capacity))
{
capacity = max_bounded_queue_capacity;
}
auto const new_node = new Node{static_cast<uint32_t>(capacity)};
_writer->next = new_node;
_writer = _writer->next;
write_event = _writer->transit_buffer.back();
}
assert(write_event && "Write event is always true");
return write_event;
}
/***/
QUILL_ATTRIBUTE_HOT void push_back() noexcept { _writer->transit_buffer.push_back(); }
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint32_t size() const noexcept
{
Node const* reader = _reader;
uint32_t size = reader->transit_buffer.size();
while (reader->next)
{
reader = reader->next;
size += reader->transit_buffer.size();
}
return size;
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool empty() noexcept { return front() ? false : true; }
private:
Node* _writer{nullptr};
Node* _reader{nullptr};
};
} // namespace quill::detail

View file

@ -0,0 +1,235 @@
// Formatting library for C++ - dynamic argument lists
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_ARGS_H_
#define FMTQUILL_ARGS_H_
#include <functional> // std::reference_wrapper
#include <memory> // std::unique_ptr
#include <vector>
#include "core.h"
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
template <typename T>
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
return static_cast<const T&>(v);
}
class dynamic_arg_list {
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So storage_node_base is made a fake template.
template <typename = void> struct node {
virtual ~node() = default;
std::unique_ptr<node<>> next;
};
template <typename T> struct typed_node : node<> {
T value;
template <typename Arg>
FMTQUILL_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
template <typename Char>
FMTQUILL_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};
std::unique_ptr<node<>> head_;
public:
template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value;
}
};
} // namespace detail
/**
\rst
A dynamic version of `fmtquill::format_arg_store`.
It's equipped with a storage to potentially temporary objects which lifetimes
could be shorter than the format arguments object.
It can be implicitly converted into `~fmtquill::basic_format_args` for passing
into type-erased formatting functions such as `~fmtquill::vformat`.
\endrst
*/
template <typename Context>
class dynamic_format_arg_store
#if FMTQUILL_GCC_VERSION && FMTQUILL_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
std::is_same<T, basic_string_view<char_type>>::value ||
std::is_same<T, detail::std_string_view<char_type>>::value ||
(mapped_type != detail::type::cstring_type &&
mapped_type != detail::type::string_type &&
mapped_type != detail::type::custom_type))
};
};
template <typename T>
using stored_type = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_;
std::vector<detail::named_arg_info<char_type>> named_info_;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
detail::dynamic_arg_list dynamic_args_;
friend class basic_format_args<Context>;
auto get_types() const -> unsigned long long {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg));
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
constexpr dynamic_format_arg_store() = default;
/**
\rst
Adds an argument into the dynamic store for later passing to a formatting
function.
Note that custom types and string types (but not string views) are copied
into the store dynamically allocating memory if necessary.
**Example**::
fmtquill::dynamic_format_arg_store<fmtquill::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmtquill::vformat("{} and {} and {}", store);
\endrst
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
/**
\rst
Adds a reference to the argument into the dynamic store for later passing to
a formatting function.
**Example**::
fmtquill::dynamic_format_arg_store<fmtquill::format_context> store;
char band[] = "Rolling Stones";
store.push_back(std::cref(band));
band[9] = 'c'; // Changing str affects the output.
std::string result = fmtquill::vformat("{}", store);
// result == "Rolling Scones"
\endrst
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert(
need_copy<T>::value,
"objects of built-in types and string views are always copied");
emplace_arg(arg.get());
}
/**
Adds named argument into the dynamic store for later passing to a formatting
function. ``std::reference_wrapper`` is supported to avoid copying of the
argument. The name is always copied into the store.
*/
template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmtquill::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmtquill::arg(arg_name, arg.value));
}
}
/** Erase all elements from the store */
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list();
}
/**
\rst
Reserves space to store at least *new_cap* arguments including
*new_cap_named* named arguments.
\endrst
*/
void reserve(size_t new_cap, size_t new_cap_named) {
FMTQUILL_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
};
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_ARGS_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,643 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_COLOR_H_
#define FMTQUILL_COLOR_H_
#include "format.h"
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
};
enum class emphasis : uint8_t {
bold = 1,
faint = 1 << 1,
italic = 1 << 2,
underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
};
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
FMTQUILL_CONSTEXPR rgb() : r(0), g(0), b(0) {}
FMTQUILL_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMTQUILL_CONSTEXPR rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMTQUILL_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace detail {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
FMTQUILL_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
FMTQUILL_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMTQUILL_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
}
FMTQUILL_CONSTEXPR color_type(terminal_color term_color) noexcept
: is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
} // namespace detail
/** A text style consisting of foreground and background colors and emphasis. */
class text_style {
public:
FMTQUILL_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), set_background_color(), ems(em) {}
FMTQUILL_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMTQUILL_THROW(format_error("can't OR a terminal color"));
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMTQUILL_THROW(format_error("can't OR a terminal color"));
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMTQUILL_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
-> text_style {
return lhs |= rhs;
}
FMTQUILL_CONSTEXPR auto has_foreground() const noexcept -> bool {
return set_foreground_color;
}
FMTQUILL_CONSTEXPR auto has_background() const noexcept -> bool {
return set_background_color;
}
FMTQUILL_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return static_cast<uint8_t>(ems) != 0;
}
FMTQUILL_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMTQUILL_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMTQUILL_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMTQUILL_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
FMTQUILL_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMTQUILL_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems;
}
private:
FMTQUILL_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMTQUILL_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style;
friend FMTQUILL_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
/** Creates a text style from the foreground (text) color. */
FMTQUILL_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style {
return text_style(true, foreground);
}
/** Creates a text style from the background color. */
FMTQUILL_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style {
return text_style(false, background);
}
FMTQUILL_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
-> text_style {
return text_style(lhs) | rhs;
}
namespace detail {
template <typename Char> struct ansi_color_escape {
FMTQUILL_CONSTEXPR ansi_color_escape(detail::color_type text_color,
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
}
FMTQUILL_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {};
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0;
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[index++] = static_cast<Char>(0);
}
FMTQUILL_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMTQUILL_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMTQUILL_CONSTEXPR_CHAR_TRAITS auto end() const noexcept -> const Char* {
return buffer + std::char_traits<Char>::length(buffer);
}
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u];
static FMTQUILL_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
static FMTQUILL_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
-> bool {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
};
template <typename Char>
FMTQUILL_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMTQUILL_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
template <typename Char>
FMTQUILL_CONSTEXPR auto make_emphasis(emphasis em) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(em);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename T> struct styled_arg : detail::view {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};
template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf);
}
} // namespace detail
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
format_args args) {
// Legacy wide streams are not supported.
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
if (detail::is_utf8()) {
detail::print(f, string_view(buf.begin(), buf.size()));
return;
}
buf.push_back('\0');
int result = std::fputs(buf.data(), f);
if (result < 0)
FMTQUILL_THROW(system_error(errno, FMTQUILL_STRING("cannot write to file")));
}
/**
\rst
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
**Example**::
fmtquill::print(fmtquill::emphasis::bold | fg(fmtquill::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
vprint(f, ts, format_str,
fmtquill::make_format_args<buffer_context<char_t<S>>>(args...));
}
/**
\rst
Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting.
**Example**::
fmtquill::print(fmtquill::emphasis::bold | fg(fmtquill::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_string<S>::value)>
void print(const text_style& ts, const S& format_str, const Args&... args) {
return print(stdout, ts, format_str, args...);
}
template <typename S, typename Char = char_t<S>>
inline auto vformat(
const text_style& ts, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, detail::to_string_view(format_str), args);
return fmtquill::to_string(buf);
}
/**
\rst
Formats arguments and returns the result as a string using ANSI
escape sequences to specify text formatting.
**Example**::
#include <fmt/color.h>
std::string message = fmtquill::format(fmtquill::emphasis::bold | fg(fmtquill::color::red),
"The answer is {}", 42);
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline auto format(const text_style& ts, const S& format_str,
const Args&... args) -> std::basic_string<Char> {
return fmtquill::vformat(ts, detail::to_string_view(format_str),
fmtquill::make_format_args<buffer_context<Char>>(args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMTQUILL_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
auto vformat_to(OutputIt out, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf, out);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmtquill::format_to(std::back_inserter(out),
fmtquill::emphasis::bold | fg(fmtquill::color::red), "{}", 42);
\endrst
*/
template <
typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value &&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, detail::to_string_view(format_str),
fmtquill::make_format_args<buffer_context<char_t<S>>>(args...));
}
template <typename T, typename Char>
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
template <typename FormatContext>
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = std::copy(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = std::copy(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = std::copy(reset_color.begin(), reset_color.end(), out);
}
return out;
}
};
/**
\rst
Returns an argument that will be formatted using ANSI escape sequences,
to be used in a formatting function.
**Example**::
fmtquill::print("Elapsed time: {0:.2f} seconds",
fmtquill::styled(1.23, fmtquill::fg(fmtquill::color::green) |
fmtquill::bg(fmtquill::color::blue)));
\endrst
*/
template <typename T>
FMTQUILL_CONSTEXPR auto styled(const T& value, text_style ts)
-> detail::styled_arg<remove_cvref_t<T>> {
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMTQUILL_END_EXPORT
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_COLOR_H_

View file

@ -0,0 +1,535 @@
// Formatting library for C++ - experimental format string compilation
//
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_COMPILE_H_
#define FMTQUILL_COMPILE_H_
#include "format.h"
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename InputIt>
FMTQUILL_CONSTEXPR inline auto copy_str(InputIt begin, InputIt end,
counting_iterator it) -> counting_iterator {
return it + (end - begin);
}
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/**
\rst
Converts a string literal *s* into a format string that will be parsed at
compile time and converted into efficient formatting code. Requires C++17
``constexpr if`` compiler support.
**Example**::
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmtquill::format(FMTQUILL_COMPILE("{}"), 42);
\endrst
*/
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMTQUILL_COMPILE(s) \
FMTQUILL_STRING_IMPL(s, fmtquill::detail::compiled_string, explicit)
#else
# define FMTQUILL_COMPILE(s) FMTQUILL_STRING(s)
#endif
#if FMTQUILL_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N,
fmtquill::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& {
return value;
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
else
return detail::get<N - 1>(rest...);
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<Args...>(name);
}
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
};
template <int N, typename T>
using get_type = typename get_type_impl<N, T>::type;
template <typename T> struct is_compiled_format : std::false_type {};
template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
}
};
template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
size_t size) {
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
*out++ = value;
return out;
}
};
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
const T& arg = get_arg_checked<T, N>(args...);
if constexpr (std::is_convertible_v<T, basic_string_view<Char>>) {
auto s = basic_string_view<Char>(arg);
return copy_str<Char>(s.begin(), s.end(), out);
}
return write<Char>(out, arg);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
return true;
}
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
FMTQUILL_THROW(format_error("argument with specified name is not found"));
}
return out;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
formatter<T, Char> fmt;
template <typename OutputIt, typename... Args>
constexpr FMTQUILL_INLINE OutputIt format(OutputIt out,
const Args&... args) const {
const auto& vargs =
fmtquill::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
};
template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
return pos;
}
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) {
if constexpr (POS !=
basic_string_view<typename S::char_type>(format_str).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
return tail;
else
return make_concat(head, tail);
} else {
return head;
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmtquill::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
}
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int on_auto() {
FMTQUILL_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S format_str) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
format_str);
} else if constexpr (c != ':') {
FMTQUILL_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
if constexpr (result.end >= str.size() || str[result.end] != '}') {
FMTQUILL_THROW(format_error("expected '}'"));
return 0;
} else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
format_str);
}
}
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size())
FMTQUILL_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing");
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(
format_str);
} else {
constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
format_str);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
if constexpr (arg_index >= 0) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str);
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
}
}
} else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size())
FMTQUILL_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
}
}
template <typename... Args, typename S,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
constexpr auto compile(S format_str) {
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
format_str);
return result;
}
}
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMTQUILL_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMTQUILL_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMTQUILL_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMTQUILL_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
template <typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMTQUILL_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
const auto& first = detail::first(args...);
if constexpr (detail::is_named_arg<
remove_cvref_t<decltype(first)>>::value) {
return fmtquill::to_string(first.value);
} else {
return fmtquill::to_string(first);
}
}
}
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmtquill::format(
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmtquill::format(compiled, std::forward<Args>(args)...);
}
}
template <typename OutputIt, typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMTQUILL_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmtquill::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmtquill::format_to(out, compiled, std::forward<Args>(args)...);
}
}
#endif
template <typename OutputIt, typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmtquill::format_to(std::back_inserter(buf), format_str,
std::forward<Args>(args)...);
return {buf.out(), buf.count()};
}
template <typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMTQUILL_CONSTEXPR20 auto formatted_size(const S& format_str, const Args&... args)
-> size_t {
return fmtquill::format_to(detail::counting_iterator(), format_str, args...)
.count();
}
template <typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& format_str, const Args&... args) {
memory_buffer buffer;
fmtquill::format_to(std::back_inserter(buffer), format_str, args...);
detail::print(f, {buffer.data(), buffer.size()});
}
template <typename S, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& format_str, const Args&... args) {
print(stdout, format_str, args...);
}
#if FMTQUILL_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
}
} // namespace literals
#endif
FMTQUILL_END_EXPORT
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_COMPILE_H_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,455 @@
// Formatting library for C++ - optional OS-specific functionality
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_OS_H_
#define FMTQUILL_OS_H_
#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <system_error> // std::system_error
#include "format.h"
#if defined __APPLE__ || defined(__FreeBSD__)
# if FMTQUILL_HAS_INCLUDE(<xlocale.h>)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
# endif
#endif
#ifndef FMTQUILL_USE_FCNTL
// UWP doesn't provide _pipe.
# if FMTQUILL_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
# endif
# if (FMTQUILL_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMTQUILL_USE_FCNTL 1
# else
# define FMTQUILL_USE_FCNTL 0
# endif
#endif
#ifndef FMTQUILL_POSIX
# if defined(_WIN32) && !defined(__MINGW32__)
// Fix warnings about deprecated symbols.
# define FMTQUILL_POSIX(call) _##call
# else
# define FMTQUILL_POSIX(call) call
# endif
#endif
// Calls to system functions are wrapped in FMTQUILL_SYSTEM for testability.
#ifdef FMTQUILL_SYSTEM
# define FMTQUILL_HAS_SYSTEM
# define FMTQUILL_POSIX_CALL(call) FMTQUILL_SYSTEM(call)
#else
# define FMTQUILL_SYSTEM(call) ::call
# ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMTQUILL_POSIX_CALL(call) ::_##call
# else
# define FMTQUILL_POSIX_CALL(call) ::call
# endif
#endif
// Retries the expression while it evaluates to error_result and errno
// equals to EINTR.
#ifndef _WIN32
# define FMTQUILL_RETRY_VAL(result, expression, error_result) \
do { \
(result) = (expression); \
} while ((result) == (error_result) && errno == EINTR)
#else
# define FMTQUILL_RETRY_VAL(result, expression, error_result) result = (expression)
#endif
#define FMTQUILL_RETRY(result, expression) FMTQUILL_RETRY_VAL(result, expression, -1)
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_BEGIN_EXPORT
/**
\rst
A reference to a null-terminated string. It can be constructed from a C
string or ``std::string``.
You can use one of the following type aliases for common character types:
+---------------+-----------------------------+
| Type | Definition |
+===============+=============================+
| cstring_view | basic_cstring_view<char> |
+---------------+-----------------------------+
| wcstring_view | basic_cstring_view<wchar_t> |
+---------------+-----------------------------+
This class is most useful as a parameter type to allow passing
different types of strings to a function, for example::
template <typename... Args>
std::string format(cstring_view format_str, const Args & ... args);
format("{}", 42);
format(std::string("{}"), 42);
\endrst
*/
template <typename Char> class basic_cstring_view {
private:
const Char* data_;
public:
/** Constructs a string reference object from a C string. */
basic_cstring_view(const Char* s) : data_(s) {}
/**
\rst
Constructs a string reference from an ``std::string`` object.
\endrst
*/
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
/** Returns the pointer to a C string. */
auto c_str() const -> const Char* { return data_; }
};
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
#ifdef _WIN32
FMTQUILL_API const std::error_category& system_category() noexcept;
namespace detail {
FMTQUILL_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
}
FMTQUILL_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args);
/**
\rst
Constructs a :class:`std::system_error` object with the description
of the form
.. parsed-literal::
*<message>*: *<system-message>*
where *<message>* is the formatted message and *<system-message>* is the
system message corresponding to the error code.
*error_code* is a Windows error code as given by ``GetLastError``.
If *error_code* is not a valid error code such as -1, the system message
will look like "error -1".
**Example**::
// This throws a system_error with the description
// cannot open file 'madeup': The system cannot find the file specified.
// or similar (system message may vary).
const char *filename = "madeup";
LPOFSTRUCT of = LPOFSTRUCT();
HFILE file = OpenFile(filename, &of, OF_READ);
if (file == HFILE_ERROR) {
throw fmtquill::windows_error(GetLastError(),
"cannot open file '{}'", filename);
}
\endrst
*/
template <typename... Args>
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmtquill::make_format_args(args...));
}
// Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors.
FMTQUILL_API void report_windows_error(int error_code, const char* message) noexcept;
#else
inline auto system_category() noexcept -> const std::error_category& {
return std::system_category();
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
FILE* file_;
friend class file;
explicit buffered_file(FILE* f) : file_(f) {}
public:
buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any.
FMTQUILL_API ~buffered_file() noexcept;
public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
auto operator=(buffered_file&& other) -> buffered_file& {
close();
file_ = other.file_;
other.file_ = nullptr;
return *this;
}
// Opens a file.
FMTQUILL_API buffered_file(cstring_view filename, cstring_view mode);
// Closes the file.
FMTQUILL_API void close();
// Returns the pointer to a FILE object representing this file.
auto get() const noexcept -> FILE* { return file_; }
FMTQUILL_API auto descriptor() const -> int;
void vprint(string_view format_str, format_args args) {
fmtquill::vprint(file_, format_str, args);
}
template <typename... Args>
inline void print(string_view format_str, const Args&... args) {
vprint(format_str, fmtquill::make_format_args(args...));
}
};
#if FMTQUILL_USE_FCNTL
// A file. Closed file is represented by a file object with descriptor -1.
// Methods that are not declared with noexcept may throw
// fmtquill::system_error in case of failure. Note that some errors such as
// closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler.
class FMTQUILL_API file {
private:
int fd_; // File descriptor.
// Constructs a file object with a given descriptor.
explicit file(int fd) : fd_(fd) {}
public:
// Possible values for the oflag argument to the constructor.
enum {
RDONLY = FMTQUILL_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMTQUILL_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMTQUILL_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMTQUILL_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMTQUILL_POSIX(O_APPEND), // Open in append mode.
TRUNC = FMTQUILL_POSIX(O_TRUNC) // Truncate the content of the file.
};
// Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag);
public:
file(const file&) = delete;
void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw.
auto operator=(file&& other) -> file& {
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
// Destroys the object closing the file it represents if any.
~file() noexcept;
// Returns the file descriptor.
auto descriptor() const noexcept -> int { return fd_; }
// Closes the file.
void close();
// Returns the file size. The size has signed type for consistency with
// stat::st_size.
auto size() const -> long long;
// Attempts to read count bytes from the file into the specified buffer.
auto read(void* buffer, size_t count) -> size_t;
// Attempts to write count bytes from the specified buffer to the file.
auto write(const void* buffer, size_t count) -> size_t;
// Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object.
static auto dup(int fd) -> file;
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd, std::error_code& ec) noexcept;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
// DEPRECATED! Taking files as out parameters is deprecated.
static void pipe(file& read_end, file& write_end);
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
auto fdopen(const char* mode) -> buffered_file;
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
};
// Returns the memory page size.
auto getpagesize() -> long;
namespace detail {
struct buffer_size {
buffer_size() = default;
size_t value = 0;
auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
oflag = new_oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
// Intel has a bug that results in failure to deduce a constructor
// for empty parameter packs.
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
ostream_params(int new_oflag) : oflag(new_oflag) {}
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
# endif
};
class file_buffer final : public buffer<char> {
file file_;
FMTQUILL_API void grow(size_t) override;
public:
FMTQUILL_API file_buffer(cstring_view path, const ostream_params& params);
FMTQUILL_API file_buffer(file_buffer&& other);
FMTQUILL_API ~file_buffer();
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
} // namespace detail
// Added {} below to work around default constructor error known to
// occur in Xcode versions 7.2.1 and 8.2.1.
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */
class FMTQUILL_API ostream {
private:
FMTQUILL_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { buffer_.close(); }
/**
Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(std::back_inserter(buffer_), fmt,
fmtquill::make_format_args(args...));
}
};
/**
\rst
Opens a file for writing. Supported parameters passed in *params*:
* ``<integer>``: Flags passed to `open
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
(``file::WRONLY | file::CREATE | file::TRUNC`` by default)
* ``buffer_size=<integer>``: Output buffer size
**Example**::
auto out = fmtquill::output_file("guide.txt");
out.print("Don't {}", "Panic");
\endrst
*/
template <typename... T>
inline auto output_file(cstring_view path, T... params) -> ostream {
return {path, detail::ostream_params(params...)};
}
#endif // FMTQUILL_USE_FCNTL
FMTQUILL_END_EXPORT
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_OS_H_

View file

@ -0,0 +1,245 @@
// Formatting library for C++ - std::ostream support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_OSTREAM_H_
#define FMTQUILL_OSTREAM_H_
#include <fstream> // std::filebuf
#ifdef _WIN32
# ifdef __GLIBCXX__
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# include <io.h>
#endif
#include "format.h"
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename Streambuf> class formatbuf : public Streambuf {
private:
using char_type = typename Streambuf::char_type;
using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
using int_type = typename Streambuf::int_type;
using traits_type = typename Streambuf::traits_type;
buffer<char_type>& buffer_;
public:
explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
protected:
// The put area is always empty. This makes the implementation simpler and has
// the advantage that the streambuf and the buffer are always in sync and
// sputc never writes into uninitialized memory. A disadvantage is that each
// call to sputc always results in a (virtual) call to overflow. There is no
// disadvantage here for sputn since this always results in a call to xsputn.
auto overflow(int_type ch) -> int_type override {
if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<char_type>(ch));
return ch;
}
auto xsputn(const char_type* s, streamsize count) -> streamsize override {
buffer_.append(s, s + count);
return count;
}
};
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
#if FMTQUILL_MSC_VERSION
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#endif
inline auto write_ostream_unicode(std::ostream& os, fmtquill::string_view data)
-> bool {
FILE* f = nullptr;
#if FMTQUILL_MSC_VERSION
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
#else
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline auto write_ostream_unicode(std::wostream&,
fmtquill::basic_string_view<wchar_t>) -> bool {
return false;
}
// Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do {
unsigned_streamsize n = size <= max_size ? size : max_size;
os.write(buf_data, static_cast<std::streamsize>(n));
buf_data += n;
size -= n;
} while (size != 0);
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMTQUILL_STATIC_THOUSANDS_SEPARATOR)
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view {
const T& value;
};
} // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
void set_debug_format() = delete;
template <typename T, typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value);
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
};
using ostream_formatter = basic_ostream_formatter<char>;
template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename OutputIt>
auto format(detail::streamed_view<T> view,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
return basic_ostream_formatter<Char>::format(view.value, ctx);
}
};
/**
\rst
Returns a view that formats `value` via an ostream ``operator<<``.
**Example**::
fmtquill::print("Current thread id: {}\n",
fmtquill::streamed(std::this_thread::get_id()));
\endrst
*/
template <typename T>
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
namespace detail {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
} // namespace detail
FMTQUILL_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::write_buffer(os, buffer);
}
/**
\rst
Prints formatted data to the stream *os*.
**Example**::
fmtquill::print(cerr, "Don't {}!", "panic");
\endrst
*/
FMTQUILL_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmtquill::make_format_args(args...);
if (detail::is_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
}
FMTQUILL_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmtquill::make_format_args<buffer_context<wchar_t>>(args...));
}
FMTQUILL_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmtquill::print(os, "{}\n", fmtquill::format(fmt, std::forward<T>(args)...));
}
FMTQUILL_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmtquill::format(fmt, std::forward<Args>(args)...));
}
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_OSTREAM_H_

View file

@ -0,0 +1,675 @@
// Formatting library for C++ - legacy printf implementation
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_PRINTF_H_
#define FMTQUILL_PRINTF_H_
#include <algorithm> // std::max
#include <limits> // std::numeric_limits
#include "format.h"
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context {
private:
detail::buffer_appender<Char> out_;
basic_format_args<basic_printf_context> args_;
static_assert(std::is_same<Char, char>::value ||
std::is_same<Char, wchar_t>::value,
"Unsupported code unit type.");
public:
using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(detail::buffer_appender<Char> out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
auto out() -> detail::buffer_appender<Char> { return out_; }
void advance_to(detail::buffer_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
}
FMTQUILL_CONSTEXPR void on_error(const char* message) {
detail::error_handler().on_error(message);
}
};
namespace detail {
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = max_value<int>();
return value <= max;
}
static auto fits_in_int(bool) -> bool { return true; }
};
template <> struct int_checker<true> {
template <typename T> static auto fits_in_int(T value) -> bool {
return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>();
}
static auto fits_in_int(int) -> bool { return true; }
};
struct printf_precision_handler {
template <typename T, FMTQUILL_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
throw_format_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMTQUILL_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> int {
throw_format_error("precision is not integer");
return 0;
}
};
// An argument visitor that returns true iff arg is a zero integer.
struct is_zero_int {
template <typename T, FMTQUILL_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> bool {
return value == 0;
}
template <typename T, FMTQUILL_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> bool {
return false;
}
};
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
template <> struct make_unsigned_or_bool<bool> {
using type = bool;
};
template <typename T, typename Context> class arg_converter {
private:
using char_type = typename Context::char_type;
basic_format_arg<Context>& arg_;
char_type type_;
public:
arg_converter(basic_format_arg<Context>& arg, char_type type)
: arg_(arg), type_(type) {}
void operator()(bool value) {
if (type_ != 's') operator()<bool>(value);
}
template <typename U, FMTQUILL_ENABLE_IF(std::is_integral<U>::value)>
void operator()(U value) {
bool is_signed = type_ == 'd' || type_ == 'i';
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
auto n = static_cast<int>(static_cast<target_type>(value));
arg_ = detail::make_arg<Context>(n);
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else {
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
}
}
template <typename U, FMTQUILL_ENABLE_IF(!std::is_integral<U>::value)>
void operator()(U) {} // No conversion needed for non-integral types.
};
// Converts an integer argument to T for printf, if T is an integral type.
// If T is void, the argument is converted to corresponding signed or unsigned
// type depending on the type specifier: 'd' and 'i' - signed, other -
// unsigned).
template <typename T, typename Context, typename Char>
void convert_arg(basic_format_arg<Context>& arg, Char type) {
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
}
// Converts an integer argument to char for printf.
template <typename Context> class char_converter {
private:
basic_format_arg<Context>& arg_;
public:
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
template <typename T, FMTQUILL_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
auto c = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
}
template <typename T, FMTQUILL_ENABLE_IF(!std::is_integral<T>::value)>
void operator()(T) {} // No conversion needed for non-integral types.
};
// An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise.
template <typename Char> struct get_cstring {
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
auto operator()(const Char* s) -> const Char* { return s; }
};
// Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
private:
format_specs<Char>& specs_;
public:
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
template <typename T, FMTQUILL_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
}
unsigned int_max = max_value<int>();
if (width > int_max) throw_format_error("number is too big");
return static_cast<unsigned>(width);
}
template <typename T, FMTQUILL_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> unsigned {
throw_format_error("width is not integer");
return 0;
}
};
// Workaround for a bug with the XL compiler when initializing
// printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The ``printf`` argument formatter.
template <typename Char>
class printf_arg_formatter : public arg_formatter<Char> {
private:
using base = arg_formatter<Char>;
using context_type = basic_printf_context<Char>;
context_type& context_;
void write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.type = presentation_type::none;
write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
}
public:
printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); }
template <typename T, FMTQUILL_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
base::operator()(value);
return;
}
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
write<Char>(this->out, static_cast<Char>(value), fmt_specs);
}
template <typename T, FMTQUILL_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) {
base::operator()(value);
}
/** Formats a null-terminated C string. */
void operator()(const char* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
/** Formats a null-terminated wide C string. */
void operator()(const wchar_t* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
void operator()(basic_string_view<Char> value) { base::operator()(value); }
/** Formats a pointer. */
void operator()(const void* value) {
if (value)
base::operator()(value);
else
write_null_pointer();
}
/** Formats an argument of a custom (user-defined) type. */
void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({});
handle.format(parse_ctx, context_);
}
};
template <typename Char>
void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
specs.align = align::left;
break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill[0] = '0';
break;
case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space;
break;
case '#':
specs.alt = true;
break;
default:
return;
}
}
}
template <typename Char, typename GetArg>
auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
GetArg get_arg) -> int {
int arg_index = -1;
Char c = *it;
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
int value = parse_nonnegative_int(it, end, -1);
if (it != end && *it == '$') { // value is an argument index
++it;
arg_index = value != -1 ? value : max_value<int>();
} else {
if (c == '0') specs.fill[0] = '0';
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
if (value == -1) throw_format_error("number is too big");
specs.width = value;
return arg_index;
}
}
}
parse_flags(specs, it, end);
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) throw_format_error("number is too big");
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
detail::printf_width_handler<Char>(specs), get_arg(-1)));
}
}
return arg_index;
}
inline auto parse_printf_presentation_type(char c, type t)
-> presentation_type {
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'x':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none;
case 'a':
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
case 'A':
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none;
case 'f':
return in(t, float_set) ? pt::fixed_lower : pt::none;
case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none;
case 'g':
return in(t, float_set) ? pt::general_lower : pt::none;
case 'G':
return in(t, float_set) ? pt::general_upper : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
}
}
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using iterator = buffer_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
auto get_arg = [&](int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index);
};
const Char* start = parse_ctx.begin();
const Char* end = parse_ctx.end();
auto it = start;
while (it != end) {
if (!find<false, Char>(it, end, '%', it)) {
it = end; // find leaves it == nullptr if it doesn't find '%'.
break;
}
Char c = *it++;
if (it != end && *it == c) {
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
start = ++it;
continue;
}
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs<Char>();
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) throw_format_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
specs.precision = parse_nonnegative_int(it, end, 0);
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(printf_precision_handler(), get_arg(-1)));
} else {
specs.precision = 0;
}
}
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) {
// Ignore '0' for non-numeric types or if '-' present.
specs.fill[0] = ' ';
}
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv);
}
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
else
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
}
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
Char t = it != end ? *it : 0;
switch (c) {
case 'h':
if (t == 'h') {
++it;
t = it != end ? *it : 0;
convert_arg<signed char>(arg, t);
} else {
convert_arg<short>(arg, t);
}
break;
case 'l':
if (t == 'l') {
++it;
t = it != end ? *it : 0;
convert_arg<long long>(arg, t);
} else {
convert_arg<long>(arg, t);
}
break;
case 'j':
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L':
// printf produces garbage when 'L' is omitted for long double, no
// need to do the same.
break;
default:
--it;
convert_arg<void>(arg, c);
}
// Parse type.
if (it == end) throw_format_error("invalid format string");
char type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
switch (type) {
case 'i':
case 'u':
type = 'd';
break;
case 'c':
visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
break;
}
}
specs.type = parse_printf_presentation_type(type, arg.type());
if (specs.type == presentation_type::none)
throw_format_error("invalid format specifier");
start = it;
// Format argument.
visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
}
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
} // namespace detail
using printf_context = basic_printf_context<char>;
using wprintf_context = basic_printf_context<wchar_t>;
using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>;
/**
\rst
Constructs an `~fmtquill::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmtquill::printf_args`.
\endrst
*/
template <typename... T>
inline auto make_printf_args(const T&... args)
-> format_arg_store<printf_context, T...> {
return {args...};
}
// DEPRECATED!
template <typename... T>
inline auto make_wprintf_args(const T&... args)
-> format_arg_store<wprintf_context, T...> {
return {args...};
}
template <typename Char>
inline auto vsprintf(
basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
return to_string(buf);
}
/**
\rst
Formats arguments and returns the result as a string.
**Example**::
std::string message = fmtquill::sprintf("The answer is %d", 42);
\endrst
*/
template <typename S, typename... T,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt),
fmtquill::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename Char>
inline auto vfprintf(
std::FILE* f, basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
size_t size = buf.size();
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
}
/**
\rst
Prints formatted data to the file *f*.
**Example**::
fmtquill::fprintf(stderr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt),
fmtquill::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename Char>
FMTQUILL_DEPRECATED inline auto vprintf(
basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int {
return vfprintf(stdout, fmt, args);
}
/**
\rst
Prints formatted data to ``stdout``.
**Example**::
fmtquill::printf("Elapsed time: %.2f seconds", 1.23);
\endrst
*/
template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...));
}
template <typename... T>
FMTQUILL_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_wprintf_args(args...));
}
FMTQUILL_END_EXPORT
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_PRINTF_H_

View file

@ -0,0 +1,738 @@
// Formatting library for C++ - range and tuple support
//
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_RANGES_H_
#define FMTQUILL_RANGES_H_
#include <initializer_list>
#include <tuple>
#include <type_traits>
#include "format.h"
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename Range, typename OutputIt>
auto copy(const Range& range, OutputIt out) -> OutputIt {
for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it;
return out;
}
template <typename OutputIt>
auto copy(const char* str, OutputIt out) -> OutputIt {
while (*str) *out++ = *str++;
return out;
}
template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
// Returns true if T has a std::string-like interface, like std::string_view.
template <typename T> class is_std_string_like {
template <typename U>
static auto check(U* p)
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
template <typename> static void check(...);
public:
static constexpr const bool value =
is_string<T>::value ||
std::is_convertible<T, std_string_view<char>>::value ||
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Char>
struct is_std_string_like<fmtquill::basic_string_view<Char>> : std::true_type {};
template <typename T> class is_map {
template <typename U> static auto check(U*) -> typename U::mapped_type;
template <typename> static void check(...);
public:
#ifdef FMTQUILL_FORMAT_MAP_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
#endif
};
template <typename T> class is_set {
template <typename U> static auto check(U*) -> typename U::key_type;
template <typename> static void check(...);
public:
#ifdef FMTQUILL_FORMAT_SET_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
#endif
};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMTQUILL_MSC_VERSION || FMTQUILL_MSC_VERSION > 1800
# define FMTQUILL_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
template <typename T, typename Enable = void>
struct has_member_fn_begin_end_t : std::false_type {};
template <typename T>
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};
// Member function overload
template <typename T>
auto range_begin(T&& rng) FMTQUILL_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) FMTQUILL_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
// ADL overload. Only participates in overload resolution if member functions
// are not found.
template <typename T>
auto range_begin(T&& rng)
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(begin(static_cast<T&&>(rng)))> {
return begin(static_cast<T&&>(rng));
}
template <typename T>
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(end(static_cast<T&&>(rng)))> {
return end(static_cast<T&&>(rng));
}
template <typename T, typename Enable = void>
struct has_const_begin_end : std::false_type {};
template <typename T, typename Enable = void>
struct has_mutable_begin_end : std::false_type {};
template <typename T>
struct has_const_begin_end<
T,
void_t<
decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};
template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_end(std::declval<T>())),
// the extra int here is because older versions of MSVC don't
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
# undef FMTQUILL_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
// Check for integer_sequence
#if defined(__cpp_lib_integer_sequence) || FMTQUILL_MSC_VERSION >= 1900
template <typename T, T... N>
using integer_sequence = std::integer_sequence<T, N...>;
template <size_t... N> using index_sequence = std::index_sequence<N...>;
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
#else
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMTQUILL_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
};
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
template <typename T, size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
template <size_t N>
using make_index_sequence = make_integer_sequence<size_t, N>;
#endif
template <typename T>
using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ {
public:
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... Is>
static auto check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>) -> std::true_type;
static auto check2(...) -> std::false_type;
template <std::size_t... Is>
static auto check(index_sequence<Is...>) -> decltype(check2(
index_sequence<Is...>{},
integer_sequence<bool,
(is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{}));
public:
static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
template <typename Tuple, typename F, size_t... Is>
FMTQUILL_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get;
// Using a free function get<Is>(Tuple) now.
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
ignore_unused(unused);
}
template <typename Tuple, typename F>
FMTQUILL_CONSTEXPR void for_each(Tuple&& t, F&& f) {
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
std::forward<Tuple>(t), std::forward<F>(f));
}
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
}
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMTQUILL_MSC_VERSION && FMTQUILL_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>()));
};
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
using type = T&;
};
template <typename T>
using range_reference_type = typename range_reference_type_impl<T>::type;
#else
template <typename Range>
using range_reference_type =
decltype(*detail::range_begin(std::declval<Range&>()));
#endif
// We don't use the Range's value_type for anything, but we do need the Range's
// reference type, with cv-ref stripped.
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMTQUILL_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMTQUILL_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
// These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs {
template <typename Formatter> FMTQUILL_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename T>
void operator()(const formatter<T, char_type>& f, const T& v) {
if (i > 0)
ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
}
int i;
FormatContext& ctx;
basic_string_view<char_type> separator;
};
} // namespace detail
template <typename T> struct is_tuple_like {
static constexpr const bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value =
detail::is_tuple_formattable_<T, C>::value;
};
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmtquill::is_tuple_like<Tuple>::value &&
fmtquill::is_tuple_formattable<Tuple, Char>::value>> {
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{};
public:
FMTQUILL_CONSTEXPR formatter() {}
FMTQUILL_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMTQUILL_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it != '}')
FMTQUILL_THROW(format_error("invalid format specifier"));
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
formatters_, value,
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return detail::copy_str<Char>(closing_bracket_, ctx.out());
}
};
template <typename T, typename Char> struct is_range {
static constexpr const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_convertible<T, detail::std_string_view<Char>>::value;
};
namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMTQUILL_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMTQUILL_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element>
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
std::declval<Element>()))>,
Char>;
template <typename R>
using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMTQUILL_MSC_VERSION || FMTQUILL_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename...> struct conjunction : std::true_type {};
template <typename P> struct conjunction<P> : P {};
template <typename P1, typename... Pn>
struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
template <typename T, typename Char, typename Enable = void>
struct range_formatter;
template <typename T, typename Char>
struct range_formatter<
T, Char,
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
is_formattable<T, Char>>::value>> {
private:
detail::range_formatter_type<Char, T> underlying_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '['>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
public:
FMTQUILL_CONSTEXPR range_formatter() {}
FMTQUILL_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
return underlying_;
}
FMTQUILL_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMTQUILL_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it == 'n') {
set_brackets({}, {});
++it;
}
if (it != end && *it != '}') {
if (*it != ':') FMTQUILL_THROW(format_error("invalid format specifier"));
++it;
} else {
detail::maybe_set_debug_format(underlying_, true);
}
ctx.advance_to(it);
return underlying_.parse(ctx);
}
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
detail::range_mapper<buffer_context<Char>> mapper;
auto out = ctx.out();
out = detail::copy_str<Char>(opening_bracket_, out);
int i = 0;
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::copy_str<Char>(separator_, out);
ctx.advance_to(out);
auto&& item = *it;
out = underlying_.format(mapper.map(item), ctx);
++i;
}
out = detail::copy_str<Char>(closing_bracket_, out);
return out;
}
};
enum class range_format { disabled, map, set, sequence, string, debug_string };
namespace detail {
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
std::is_same<uncvref_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence> {};
template <range_format K, typename R, typename Char, typename Enable = void>
struct range_default_formatter;
template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>;
template <range_format K, typename R, typename Char>
struct range_default_formatter<
K, R, Char,
enable_if_t<(K == range_format::sequence || K == range_format::map ||
K == range_format::set)>> {
using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> underlying_;
FMTQUILL_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); }
FMTQUILL_CONSTEXPR void init(range_format_constant<range_format::set>) {
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
}
FMTQUILL_CONSTEXPR void init(range_format_constant<range_format::map>) {
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
underlying_.underlying().set_brackets({}, {});
underlying_.underlying().set_separator(
detail::string_literal<Char, ':', ' '>{});
}
FMTQUILL_CONSTEXPR void init(range_format_constant<range_format::sequence>) {}
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(range_type& range, FormatContext& ctx) const
-> decltype(ctx.out()) {
return underlying_.format(range, ctx);
}
};
} // namespace detail
template <typename T, typename Char, typename Enable = void>
struct range_format_kind
: conditional_t<
is_range<T, Char>::value, detail::range_format_kind_<T>,
std::integral_constant<range_format, range_format::disabled>> {};
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value !=
range_format::disabled>
// Workaround a bug in MSVC 2015 and earlier.
#if !FMTQUILL_MSC_VERSION || FMTQUILL_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>>
: detail::range_default_formatter<range_format_kind<R, Char>::value, R,
Char> {
};
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMTQUILL_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMTQUILL_TUPLE_JOIN_SPECIFIERS
# define FMTQUILL_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
}
private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMTQUILL_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMTQUILL_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
FMTQUILL_THROW(format_error("incompatible format specs for tuple elements"));
}
#endif
return end;
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (N > 1) {
out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
return out;
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMTQUILL_BEGIN_EXPORT
/**
\rst
Returns an object that formats `tuple` with elements separated by `sep`.
**Example**::
std::tuple<int, char> t = {1, 'a'};
fmtquill::print("{}", fmtquill::join(t, ", "));
// Output: "1, a"
\endrst
*/
template <typename... T>
FMTQUILL_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
return {tuple, sep};
}
template <typename... T>
FMTQUILL_CONSTEXPR auto join(const std::tuple<T...>& tuple,
basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
return {tuple, sep};
}
/**
\rst
Returns an object that formats `initializer_list` with elements separated by
`sep`.
**Example**::
fmtquill::print("{}", fmtquill::join({1, 2, 3}, ", "));
// Output: "1, 2, 3"
\endrst
*/
template <typename T>
auto join(std::initializer_list<T> list, string_view sep)
-> join_view<const T*, const T*> {
return join(std::begin(list), std::end(list), sep);
}
FMTQUILL_END_EXPORT
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_RANGES_H_

View file

@ -0,0 +1,537 @@
// Formatting library for C++ - formatters for standard library types
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_STD_H_
#define FMTQUILL_STD_H_
#include <atomic>
#include <bitset>
#include <cstdlib>
#include <exception>
#include <memory>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>
#include "format.h"
#include "ostream.h"
#if FMTQUILL_HAS_INCLUDE(<version>)
# include <version>
#endif
// Checking FMTQUILL_CPLUSPLUS for warning suppression in MSVC.
#if FMTQUILL_CPLUSPLUS >= 201703L
# if FMTQUILL_HAS_INCLUDE(<filesystem>)
# include <filesystem>
# endif
# if FMTQUILL_HAS_INCLUDE(<variant>)
# include <variant>
# endif
# if FMTQUILL_HAS_INCLUDE(<optional>)
# include <optional>
# endif
#endif
#if FMTQUILL_CPLUSPLUS > 201703L && FMTQUILL_HAS_INCLUDE(<source_location>)
# include <source_location>
#endif
// GCC 4 does not support FMTQUILL_HAS_INCLUDE.
#if FMTQUILL_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMTQUILL_HAS_ABI_CXA_DEMANGLE
# endif
#endif
// Check if typeid is available.
#ifndef FMTQUILL_USE_TYPEID
// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI.
# if defined(__GXX_RTTI) || FMTQUILL_HAS_FEATURE(cxx_rtti) || FMTQUILL_MSC_VERSION || \
defined(__INTEL_RTTI__) || defined(__RTTI)
# define FMTQUILL_USE_TYPEID 1
# else
# define FMTQUILL_USE_TYPEID 0
# endif
#endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
#ifndef FMTQUILL_CPP_LIB_FILESYSTEM
# ifdef __cpp_lib_filesystem
# define FMTQUILL_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
# else
# define FMTQUILL_CPP_LIB_FILESYSTEM 0
# endif
#endif
#ifndef FMTQUILL_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant
# define FMTQUILL_CPP_LIB_VARIANT __cpp_lib_variant
# else
# define FMTQUILL_CPP_LIB_VARIANT 0
# endif
#endif
#if FMTQUILL_CPP_LIB_FILESYSTEM
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
return p.string<Char>();
}
template <typename Char, typename PathChar>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
FMTQUILL_ASSERT(valid, "invalid utf16");
} else if constexpr (std::is_same_v<Char, PathChar>) {
write_escaped_string<std::filesystem::path::value_type>(
std::back_inserter(quoted), native);
} else {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
}
} // namespace detail
FMTQUILL_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs<Char> specs_;
detail::arg_ref<Char> width_ref_;
bool debug_ = false;
char path_type_ = 0;
public:
FMTQUILL_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && (*it == 'g')) path_type_ = *it++;
return it;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
auto specs = specs_;
# ifdef _WIN32
auto path_string = !path_type_ ? p.native() : p.generic_wstring();
# else
auto path_string = !path_type_ ? p.native() : p.generic_string();
# endif
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
}
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p, path_string);
return detail::write(ctx.out(),
basic_string_view<Char>(quoted.data(), quoted.size()),
specs);
}
};
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_CPP_LIB_FILESYSTEM
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
private:
// Functor because C++11 doesn't support generic lambdas.
struct writer {
const std::bitset<N>& bs;
template <typename OutputIt>
FMTQUILL_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) {
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out;
}
};
public:
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return write_padded(ctx, writer{bs});
}
};
FMTQUILL_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMTQUILL_END_NAMESPACE
#ifdef __cpp_lib_optional
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMTQUILL_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMTQUILL_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
template <typename ParseContext> FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(const std::optional<T>& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, optional);
ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
}
};
FMTQUILL_END_NAMESPACE
#endif // __cpp_lib_optional
#ifdef __cpp_lib_source_location
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_EXPORT
template <> struct formatter<std::source_location> {
template <typename ParseContext> FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write(out, loc.file_name());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.line());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.column());
out = detail::write(out, ": ");
out = detail::write(out, loc.function_name());
return out;
}
};
FMTQUILL_END_NAMESPACE
#endif
#if FMTQUILL_CPP_LIB_VARIANT
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
template <typename Char, typename OutputIt, typename T>
auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (is_string<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
else if constexpr (std::is_same_v<T, Char>)
return write_escaped_char(out, v);
else
return write<Char>(out, v);
}
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
};
FMTQUILL_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
return detail::write<Char>(ctx.out(), "monostate");
}
};
FMTQUILL_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const Variant& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
FMTQUILL_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
}
FMTQUILL_CATCH(const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_CPP_LIB_VARIANT
FMTQUILL_BEGIN_NAMESPACE
FMTQUILL_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMTQUILL_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMTQUILL_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
FMTQUILL_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char, // DEPRECATED! Mixing code unit types.
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMTQUILL_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = FMTQUILL_USE_TYPEID != 0;
}
return it;
}
template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);
#if FMTQUILL_USE_TYPEID
const std::type_info& ti = typeid(ex);
# ifdef FMTQUILL_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
out = detail::write_bytes(out, demangled_name_view, spec);
# elif FMTQUILL_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
# else
out = detail::write_bytes(out, string_view(ti.name()), spec);
# endif
*out++ = ':';
*out++ = ' ';
return detail::write_bytes(out, string_view(ex.what()), spec);
#endif
}
};
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization.
FMTQUILL_EXPORT
template <typename BitRef, typename Char>
struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
: formatter<bool, Char> {
template <typename FormatContext>
FMTQUILL_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v, ctx);
}
};
FMTQUILL_EXPORT
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
: formatter<T, Char> {
template <typename FormatContext>
auto format(const std::atomic<T>& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<T, Char>::format(v.load(), ctx);
}
};
#ifdef __cpp_lib_atomic_flag_test
FMTQUILL_EXPORT
template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext>
auto format(const std::atomic_flag& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v.test(), ctx);
}
};
#endif // __cpp_lib_atomic_flag_test
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_STD_H_

View file

@ -0,0 +1,259 @@
// Formatting library for C++ - optional wchar_t and exotic character support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMTQUILL_XCHAR_H_
#define FMTQUILL_XCHAR_H_
#include <cwchar>
#include "format.h"
#ifndef FMTQUILL_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMTQUILL_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
loc_value value, const format_specs<wchar_t>& specs,
locale_ref loc) -> bool {
#ifndef FMTQUILL_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
}
} // namespace detail
FMTQUILL_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_context = buffer_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMTQUILL_GCC_VERSION && FMTQUILL_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<detail::char8_type> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
template <typename... T>
constexpr auto make_wformat_args(const T&... args)
-> format_arg_store<wformat_context, T...> {
return {args...};
}
inline namespace literals {
#if FMTQUILL_USE_USER_DEFINED_LITERALS && !FMTQUILL_USE_NONTYPE_TEMPLATE_ARGS
constexpr auto operator""_a(const wchar_t* s, size_t)
-> detail::udl_arg<wchar_t> {
return {s};
}
#endif
} // namespace literals
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
-> join_view<It, Sentinel, wchar_t> {
return {begin, end, sep};
}
template <typename Range>
auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
template <typename T>
auto join(std::initializer_list<T> list, wstring_view sep)
-> join_view<const T*, const T*, wchar_t> {
return join(std::begin(list), std::end(list), sep);
}
template <typename Char, FMTQUILL_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, format_str, args);
return to_string(buf);
}
template <typename... T>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat(fmtquill::wstring_view(fmt), fmtquill::make_wformat_args(args...));
}
// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... T, typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str),
fmtquill::make_format_args<buffer_context<Char>>(args...));
}
template <typename Locale, typename S, typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(
const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), args);
}
template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, T&&... args)
-> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str),
fmtquill::make_format_args<buffer_context<Char>>(args...));
}
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
return vformat_to(out, detail::to_string_view(fmt),
fmtquill::make_format_args<buffer_context<Char>>(args...));
}
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(
OutputIt out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename Locale, typename S, typename... T,
typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(format_str),
fmtquill::make_format_args<buffer_context<Char>>(args...));
}
template <typename OutputIt, typename Char, typename... Args,
FMTQUILL_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, detail::to_string_view(fmt),
fmtquill::make_format_args<buffer_context<Char>>(args...));
}
template <typename S, typename... T, typename Char = char_t<S>,
FMTQUILL_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt),
fmtquill::make_format_args<buffer_context<Char>>(args...));
return buf.count();
}
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
auto buf = wmemory_buffer();
detail::vformat_to(buf, fmt, args);
buf.push_back(L'\0');
if (std::fputws(buf.data(), f) == -1)
FMTQUILL_THROW(system_error(errno, FMTQUILL_STRING("cannot write to file")));
}
inline void vprint(wstring_view fmt, wformat_args args) {
vprint(stdout, fmt, args);
}
template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), fmtquill::make_wformat_args(args...));
}
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), fmtquill::make_wformat_args(args...));
}
template <typename... T>
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return print(f, L"{}\n", fmtquill::format(fmt, std::forward<T>(args)...));
}
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmtquill::format(fmt, std::forward<T>(args)...));
}
/**
Converts *value* to ``std::wstring`` using the default format for type *T*.
*/
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMTQUILL_STRING(L"{}"), value);
}
FMTQUILL_END_EXPORT
FMTQUILL_END_NAMESPACE
#endif // FMTQUILL_XCHAR_H_

View file

@ -0,0 +1,96 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
/**
* C++ language standard detection
*/
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
#define QUILL_HAS_CPP_17
#define QUILL_HAS_CPP_14
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
#define QUILL_HAS_CPP_14
#endif
/**
* __has_attribute
*/
#ifdef __has_attribute
#define QUILL_HAS_ATTRIBUTE(x) __has_attribute(x)
#else
#define QUILL_HAS_ATTRIBUTE(x) 0
#endif
/**
* __has_cpp_attribute
*/
#if defined(__cplusplus) && defined(__has_cpp_attribute)
#define QUILL_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
#else
#define QUILL_HAS_CPP_ATTRIBUTE(x) 0
#endif
#if defined(__has_include) && !defined(__INTELLISENSE__) && \
!(defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1600)
#define QUILL_HAS_INCLUDE(x) __has_include(x)
#else
#define QUILL_HAS_INCLUDE(x) 0
#endif
/**
* Portable no discard warnings
*/
#if QUILL_HAS_CPP_ATTRIBUTE(nodiscard)
#if defined(__clang__) && !defined(QUILL_HAS_CPP_17)
#define QUILL_NODISCARD
#else
#define QUILL_NODISCARD [[nodiscard]]
#endif
#elif QUILL_HAS_CPP_ATTRIBUTE(gnu::warn_unused_result)
#define QUILL_NODISCARD [[gnu::warn_unused_result]]
#else
#define QUILL_NODISCARD
#endif
/**
* Portable maybe_unused
*/
#if QUILL_HAS_CPP_ATTRIBUTE(maybe_unused) && (defined(_HAS_CXX17) && _HAS_CXX17 == 1)
#define QUILL_MAYBE_UNUSED [[maybe_unused]]
#elif QUILL_HAS_ATTRIBUTE(__unused__) || defined(__GNUC__)
#define QUILL_MAYBE_UNUSED __attribute__((__unused__))
#else
#define QUILL_MAYBE_UNUSED
#endif
/**
* Gcc hot/cold attributes
* Tells GCC that a function is hot or cold. GCC can use this information to
* improve static analysis, i.e. a conditional branch to a cold function
* is likely to be not-taken.
*/
#if QUILL_HAS_ATTRIBUTE(hot) || (defined(__GNUC__) && !defined(__clang__))
#define QUILL_ATTRIBUTE_HOT __attribute__((hot))
#else
#define QUILL_ATTRIBUTE_HOT
#endif
#if QUILL_HAS_ATTRIBUTE(cold) || (defined(__GNUC__) && !defined(__clang__))
#define QUILL_ATTRIBUTE_COLD __attribute__((cold))
#else
#define QUILL_ATTRIBUTE_COLD
#endif
/**
* Likely
*/
#if defined(__GNUC__)
#define QUILL_LIKELY(x) (__builtin_expect((x), 1))
#define QUILL_UNLIKELY(x) (__builtin_expect((x), 0))
#else
#define QUILL_LIKELY(x) (x)
#define QUILL_UNLIKELY(x) (x)
#endif

View file

@ -0,0 +1,327 @@
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/MathUtils.h"
#include "quill/core/QuillError.h"
#include <atomic>
#include <cassert>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#if defined(_WIN32)
#include <malloc.h>
#elif defined(__APPLE__)
#include <sys/mman.h>
#include <unistd.h>
#elif defined(__CYGWIN__)
#include <sys/mman.h>
#include <unistd.h>
#elif defined(__linux__)
#include <sys/mman.h>
#elif defined(__NetBSD__)
#include <lwp.h>
#include <sys/mman.h>
#include <unistd.h>
#elif defined(__FreeBSD__)
#include <sys/mman.h>
#include <sys/thr.h>
#include <unistd.h>
#elif defined(__DragonFly__)
#include <sys/lwp.h>
#include <sys/mman.h>
#include <unistd.h>
#else
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#if defined(QUILL_X86ARCH)
#if defined(_WIN32)
#include <intrin.h>
#else
#if __has_include(<x86gprintrin.h>)
#if defined(__GNUC__) && __GNUC__ > 10
#include <emmintrin.h>
#include <x86gprintrin.h>
#elif defined(__clang_major__)
// clang needs immintrin for _mm_clflushopt
#include <immintrin.h>
#endif
#else
#include <immintrin.h>
#include <x86intrin.h>
#endif
#endif
#endif
namespace quill::detail
{
/**
* A bounded single producer single consumer ring buffer.
*/
template <typename T>
class BoundedSPSCQueueImpl
{
public:
using integer_type = T;
QUILL_ATTRIBUTE_HOT explicit BoundedSPSCQueueImpl(integer_type capacity, bool huges_pages_enabled = false,
integer_type reader_store_percent = 5)
: _capacity(next_power_of_two(capacity)),
_mask(_capacity - 1),
_bytes_per_batch(static_cast<integer_type>(_capacity * static_cast<double>(reader_store_percent) / 100.0)),
_storage(static_cast<std::byte*>(_alloc_aligned(2ull * static_cast<uint64_t>(_capacity),
CACHE_LINE_ALIGNED, huges_pages_enabled))),
_huge_pages_enabled(huges_pages_enabled)
{
std::memset(_storage, 0, 2ull * static_cast<uint64_t>(_capacity));
_atomic_writer_pos.store(0);
_atomic_reader_pos.store(0);
#if defined(QUILL_X86ARCH)
// remove log memory from cache
for (uint64_t i = 0; i < (2ull * static_cast<uint64_t>(_capacity)); i += CACHE_LINE_SIZE)
{
_mm_clflush(_storage + i);
}
// load cache lines into memory
if (_capacity < 1024)
{
QUILL_THROW(QuillError{"Capacity must be at least 1024"});
}
uint64_t const cache_lines = (_capacity >= 2048) ? 32 : 16;
for (uint64_t i = 0; i < cache_lines; ++i)
{
_mm_prefetch(reinterpret_cast<char const*>(_storage + (CACHE_LINE_SIZE * i)), _MM_HINT_T0);
}
#endif
}
~BoundedSPSCQueueImpl() { _free_aligned(_storage); }
/**
* Deleted
*/
BoundedSPSCQueueImpl(BoundedSPSCQueueImpl const&) = delete;
BoundedSPSCQueueImpl& operator=(BoundedSPSCQueueImpl const&) = delete;
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::byte* prepare_write(integer_type n) noexcept
{
if ((_capacity - static_cast<integer_type>(_writer_pos - _reader_pos_cache)) < n)
{
// not enough space, we need to load reader and re-check
_reader_pos_cache = _atomic_reader_pos.load(std::memory_order_acquire);
if ((_capacity - static_cast<integer_type>(_writer_pos - _reader_pos_cache)) < n)
{
return nullptr;
}
}
return _storage + (_writer_pos & _mask);
}
QUILL_ATTRIBUTE_HOT void finish_write(integer_type n) noexcept { _writer_pos += n; }
QUILL_ATTRIBUTE_HOT void commit_write() noexcept
{
// set the atomic flag so the reader can see write
_atomic_writer_pos.store(_writer_pos, std::memory_order_release);
#if defined(QUILL_X86ARCH)
// flush writen cache lines
_flush_cachelines(_last_flushed_writer_pos, _writer_pos);
// prefetch a future cache line
_mm_prefetch(reinterpret_cast<char const*>(_storage + (_writer_pos & _mask) + (CACHE_LINE_SIZE * 10)),
_MM_HINT_T0);
#endif
}
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::byte* prepare_read() noexcept
{
if (_writer_pos_cache == _reader_pos)
{
_writer_pos_cache = _atomic_writer_pos.load(std::memory_order_acquire);
if (_writer_pos_cache == _reader_pos)
{
return nullptr;
}
}
return _storage + (_reader_pos & _mask);
}
QUILL_ATTRIBUTE_HOT void finish_read(integer_type n) noexcept { _reader_pos += n; }
QUILL_ATTRIBUTE_HOT void commit_read() noexcept
{
if (static_cast<integer_type>(_reader_pos - _atomic_reader_pos.load(std::memory_order_relaxed)) >= _bytes_per_batch)
{
_atomic_reader_pos.store(_reader_pos, std::memory_order_release);
#if defined(QUILL_X86ARCH)
_flush_cachelines(_last_flushed_reader_pos, _reader_pos);
#endif
}
}
/**
* Only meant to be called by the reader
* @return true if the queue is empty
*/
QUILL_NODISCARD bool empty() const noexcept
{
return _reader_pos == _atomic_writer_pos.load(std::memory_order_relaxed);
}
QUILL_NODISCARD integer_type capacity() const noexcept
{
return static_cast<integer_type>(_capacity);
}
QUILL_NODISCARD bool huge_pages_enabled() const noexcept { return _huge_pages_enabled; }
private:
#if defined(QUILL_X86ARCH)
QUILL_ATTRIBUTE_HOT void _flush_cachelines(integer_type& last, integer_type offset)
{
integer_type last_diff = last - (last & CACHELINE_MASK);
integer_type const cur_diff = offset - (offset & CACHELINE_MASK);
while (cur_diff > last_diff)
{
_mm_clflushopt(_storage + (last_diff & _mask));
last_diff += CACHE_LINE_SIZE;
last = last_diff;
}
}
#endif
/**
* align a pointer to the given alignment
* @param pointer a pointer the object
* @return an aligned pointer for the given object
*/
QUILL_NODISCARD static std::byte* _align_pointer(void* pointer, size_t alignment) noexcept
{
assert(is_power_of_two(alignment) && "alignment must be a power of two");
return reinterpret_cast<std::byte*>((reinterpret_cast<uintptr_t>(pointer) + (alignment - 1ul)) &
~(alignment - 1ul));
}
/**
* Aligned alloc
* @param size number of bytes to allocate. An integral multiple of alignment
* @param alignment specifies the alignment. Must be a valid alignment supported by the implementation.
* @param huges_pages_enabled allocate huge pages, only supported on linux
* @return On success, returns the pointer to the beginning of newly allocated memory.
* To avoid a memory leak, the returned pointer must be deallocated with _free_aligned().
* @throws std::system_error on failure
*/
QUILL_NODISCARD static void* _alloc_aligned(size_t size, size_t alignment, bool huges_pages_enabled)
{
#if defined(_WIN32)
void* p = _aligned_malloc(size, alignment);
if (!p)
{
QUILL_THROW(QuillError{std::string{"_alloc_aligned failed with error message errno: "} +
std::to_string(errno)});
}
return p;
#else
// Calculate the total size including the metadata and alignment
constexpr size_t metadata_size{2u * sizeof(size_t)};
size_t const total_size{size + metadata_size + alignment};
// Allocate the memory
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
#if defined(__linux__)
if (huges_pages_enabled)
{
flags |= MAP_HUGETLB;
}
#endif
void* mem = ::mmap(nullptr, total_size, PROT_READ | PROT_WRITE, flags, -1, 0);
if (mem == MAP_FAILED)
{
QUILL_THROW(QuillError{std::string{"mmap failed. errno: "} + std::to_string(errno) +
" error: " + strerror(errno)});
}
// Calculate the aligned address after the metadata
std::byte* aligned_address = _align_pointer(static_cast<std::byte*>(mem) + metadata_size, alignment);
// Calculate the offset from the original memory location
auto const offset = static_cast<size_t>(aligned_address - static_cast<std::byte*>(mem));
// Store the size and offset information in the metadata
std::memcpy(aligned_address - sizeof(size_t), &total_size, sizeof(total_size));
std::memcpy(aligned_address - (2u * sizeof(size_t)), &offset, sizeof(offset));
return aligned_address;
#endif
}
/**
* Free aligned memory allocated with _alloc_aligned
* @param ptr address to aligned memory
*/
void static _free_aligned(void* ptr) noexcept
{
#if defined(_WIN32)
_aligned_free(ptr);
#else
// Retrieve the size and offset information from the metadata
size_t offset;
std::memcpy(&offset, static_cast<std::byte*>(ptr) - (2u * sizeof(size_t)), sizeof(offset));
size_t total_size;
std::memcpy(&total_size, static_cast<std::byte*>(ptr) - sizeof(size_t), sizeof(total_size));
// Calculate the original memory block address
void* mem = static_cast<std::byte*>(ptr) - offset;
::munmap(mem, total_size);
#endif
}
private:
static constexpr integer_type CACHELINE_MASK{CACHE_LINE_SIZE - 1};
integer_type const _capacity;
integer_type const _mask;
integer_type const _bytes_per_batch;
std::byte* const _storage{nullptr};
bool const _huge_pages_enabled;
alignas(CACHE_LINE_ALIGNED) std::atomic<integer_type> _atomic_writer_pos{0};
alignas(CACHE_LINE_ALIGNED) integer_type _writer_pos{0};
integer_type _reader_pos_cache{0};
integer_type _last_flushed_writer_pos{0};
alignas(CACHE_LINE_ALIGNED) std::atomic<integer_type> _atomic_reader_pos{0};
alignas(CACHE_LINE_ALIGNED) integer_type _reader_pos{0};
integer_type _writer_pos_cache{0};
integer_type _last_flushed_reader_pos{0};
};
using BoundedSPSCQueue = BoundedSPSCQueueImpl<uint32_t>;
} // namespace quill::detail

View file

@ -0,0 +1,435 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#ifndef __STDC_WANT_LIB_EXT1__
#define __STDC_WANT_LIB_EXT1__ 1
#endif
#include "quill/bundled/fmt/core.h"
#include "quill/core/Attributes.h"
#include "quill/core/DynamicFormatArgStore.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <memory>
#include <windows.h>
#endif
namespace quill::detail
{
/**
* C++14 implementation of C++20's remove_cvref
*/
template <class T>
struct remove_cvref
{
typedef std::remove_cv_t<std::remove_reference_t<T>> type;
};
template <class T>
using remove_cvref_t = typename remove_cvref<T>::type;
template <class>
constexpr bool always_false_v = false;
constexpr auto strnlen =
#if defined(__STDC_LIB_EXT1__) || defined(_MSC_VER)
::strnlen_s
#else
::strnlen
#endif
;
#if defined(_WIN32)
/**
* @brief Convert a wide Unicode string to a UTF-8 encoded string.
*
* @param input_string_data Pointer to the wide string data.
* @param input_string_length Length of the wide string.
* @return The UTF-8 encoded string.
*
* @remarks If the input string is empty or the conversion fails, an empty string is returned.
*/
inline std::string utf8_encode(std::byte const* data, size_t wide_str_len)
{
// Check if the input is empty
if (wide_str_len == 0)
{
return std::string{};
}
// Create a unique_ptr to hold the buffer and one for the null terminator
std::unique_ptr<wchar_t[]> wide_buffer{new wchar_t[wide_str_len + 1]};
// Because we are using a std::byte* buffer and the data are coming from there we will cast
// back the data to char* and then copy them to a wide string buffer before accessing them
std::memcpy(wide_buffer.get(), data, wide_str_len * sizeof(wchar_t));
wide_buffer[wide_str_len] = L'\0';
// Calculate the size needed for the UTF-8 string
int const size_needed = WideCharToMultiByte(
CP_UTF8, 0, wide_buffer.get(), static_cast<int>(wide_str_len), nullptr, 0, nullptr, nullptr);
// Check for conversion failure
if (size_needed == 0)
{
return std::string{};
}
// Create a buffer to hold the UTF-8 string
std::string ret_val(static_cast<size_t>(size_needed), 0);
// Convert the wide string to UTF-8
if (WideCharToMultiByte(CP_UTF8, 0, wide_buffer.get(), static_cast<int>(wide_str_len),
&ret_val[0], size_needed, nullptr, nullptr) == 0)
{
// conversion failure
return std::string{};
}
return ret_val;
}
inline std::string utf8_encode(std::wstring_view str)
{
return utf8_encode(reinterpret_cast<std::byte const*>(str.data()), str.size());
}
#endif
/** typename = void for specializations with enable_if **/
template <typename Arg, typename = void>
struct ArgSizeCalculator
{
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static size_t calculate(QUILL_MAYBE_UNUSED std::vector<size_t>& conditional_arg_size_cache,
QUILL_MAYBE_UNUSED Arg const& arg) noexcept
{
if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>>)
{
return sizeof(Arg);
}
else if constexpr (std::conjunction_v<std::is_array<Arg>, std::is_same<remove_cvref_t<std::remove_extent_t<Arg>>, char>>)
{
size_t constexpr N = std::extent_v<Arg>;
conditional_arg_size_cache.push_back(static_cast<size_t>(strnlen(arg, N) + 1u));
return conditional_arg_size_cache.back();
}
else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>>)
{
// include one extra for the zero termination
conditional_arg_size_cache.push_back(static_cast<size_t>(strlen(arg) + 1u));
return conditional_arg_size_cache.back();
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
{
// for std::string we also need to store the size in order to correctly retrieve it
// the reason for this is that if we create e.g:
// std::string msg = fmtquill::format("{} {} {} {} {}", (char)0, (char)0, (char)0, (char)0,
// "sssssssssssssssssssssss"); then strlen(msg.data()) = 0 but msg.size() = 31
return sizeof(size_t) + arg.length();
}
#if defined(_WIN32)
else if constexpr (std::disjunction_v<std::is_same<Arg, wchar_t*>, std::is_same<Arg, wchar_t const*>,
std::is_same<Arg, std::wstring>, std::is_same<Arg, std::wstring_view>>)
{
// Calculate the size of the string in bytes
size_t len;
if constexpr (std::disjunction_v<std::is_same<Arg, wchar_t*>, std::is_same<Arg, wchar_t const*>>)
{
len = wcslen(arg);
}
else
{
len = arg.size();
}
conditional_arg_size_cache.push_back(len);
// also include the size of the string in the buffer as a separate variable
// we can retrieve it when we decode. We do not store the null terminator in the buffer
return static_cast<size_t>(sizeof(size_t) + (len * sizeof(wchar_t)));
}
#endif
else
{
static_assert(always_false_v<Arg>, "Unsupported type");
}
}
};
/**
* @brief Calculates the total size required to encode the provided arguments
* @param conditional_arg_size_cache Storage to avoid repeating calculations eg. cache strlen
* @param args The arguments to be encoded.
* @return The total size required to encode the arguments.
*/
template <typename... Args>
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t calculate_args_size_and_populate_string_lengths(
QUILL_MAYBE_UNUSED std::vector<size_t>& conditional_arg_size_cache, Args const&... args) noexcept
{
// Do not use fold expression with '+ ...' as we need a guaranteed sequence for the args here
size_t total_sum{0};
((total_sum += ArgSizeCalculator<remove_cvref_t<Args>>::calculate(conditional_arg_size_cache, args)), ...);
return total_sum;
}
/** typename = void for specializations with enable_if **/
template <typename Arg, typename = void>
struct Encoder
{
QUILL_ATTRIBUTE_HOT static void encode(std::byte*& buffer,
QUILL_MAYBE_UNUSED std::vector<size_t> const& conditional_arg_size_cache,
QUILL_MAYBE_UNUSED uint32_t& conditional_arg_size_cache_index,
Arg const& arg) noexcept
{
if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>>)
{
std::memcpy(buffer, &arg, sizeof(Arg));
buffer += sizeof(Arg);
}
else if constexpr (std::conjunction_v<std::is_array<Arg>, std::is_same<remove_cvref_t<std::remove_extent_t<Arg>>, char>>)
{
size_t constexpr N = std::extent_v<Arg>;
size_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
if (QUILL_UNLIKELY(len > N))
{
// no '\0' in c array
assert(len == N + 1);
std::memcpy(buffer, arg, N);
buffer[len - 1] = std::byte{'\0'};
}
else
{
std::memcpy(buffer, arg, len);
}
buffer += len;
}
else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>>)
{
// null terminator is included in the len for c style strings
size_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
std::memcpy(buffer, arg, len);
buffer += len;
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
{
// for std::string we store the size first, in order to correctly retrieve it
// Copy the length first and then the actual string
size_t const len = arg.length();
std::memcpy(buffer, &len, sizeof(len));
if (len != 0)
{
// copy the string, no need to zero terminate it as we got the length
std::memcpy(buffer + sizeof(len), arg.data(), arg.length());
}
buffer += sizeof(len) + len;
}
#if defined(_WIN32)
else if constexpr (std::disjunction_v<std::is_same<Arg, wchar_t*>, std::is_same<Arg, wchar_t const*>,
std::is_same<Arg, std::wstring>, std::is_same<Arg, std::wstring_view>>)
{
// The wide string size in bytes
size_t const len = conditional_arg_size_cache[conditional_arg_size_cache_index++];
std::memcpy(buffer, &len, sizeof(len));
buffer += sizeof(len);
if (len != 0)
{
// copy the string, no need to zero terminate it as we got the length and e.g a wstring_view
// might not always be zero terminated
size_t const size_in_bytes = len * sizeof(wchar_t);
if constexpr (std::disjunction_v<std::is_same<Arg, wchar_t*>, std::is_same<Arg, wchar_t const*>>)
{
std::memcpy(buffer, arg, size_in_bytes);
}
else
{
std::memcpy(buffer, arg.data(), size_in_bytes);
}
buffer += size_in_bytes;
}
}
#endif
else
{
static_assert(always_false_v<Arg>, "Unsupported type");
}
}
};
/**
* @brief Encoders multiple arguments into a buffer.
* @param buffer Pointer to the buffer for encoding.
* @param conditional_arg_size_cache Storage to avoid repeating calculations eg. cache strlen
* @param args The arguments to be encoded.
*/
template <typename... Args>
QUILL_ATTRIBUTE_HOT void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
Args const&... args) noexcept
{
QUILL_MAYBE_UNUSED uint32_t conditional_arg_size_cache_index{0};
(Encoder<remove_cvref_t<Args>>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, args),
...);
}
/** typename = void for specializations with enable_if **/
template <typename Arg, typename = void>
struct Decoder
{
static auto decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if constexpr (std::disjunction_v<std::is_arithmetic<Arg>, std::is_enum<Arg>>)
{
Arg arg;
std::memcpy(&arg, buffer, sizeof(Arg));
buffer += sizeof(Arg);
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
else if constexpr (std::disjunction_v<std::is_same<Arg, char*>, std::is_same<Arg, char const*>,
std::conjunction<std::is_array<Arg>, std::is_same<remove_cvref_t<std::remove_extent_t<Arg>>, char>>>)
{
// c strings or char array
char const* str = reinterpret_cast<char const*>(buffer);
size_t const len = strlen(str);
buffer += len + 1; // for c_strings we add +1 to the length as we also want to copy the null terminated char
if (args_store)
{
// pass the std::string_view to args_store to avoid the dynamic allocation
args_store->push_back(std::string_view{str, len});
}
return str;
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
{
// for std::string we first need to retrieve the length
size_t len;
std::memcpy(&len, buffer, sizeof(len));
// retrieve the rest of the string
char const* str = reinterpret_cast<char const*>(buffer + sizeof(size_t));
std::string_view v{str, len};
buffer += sizeof(len) + v.length();
if (args_store)
{
args_store->push_back(v);
}
return v;
}
#if defined(_WIN32)
else if constexpr (std::disjunction_v<std::is_same<Arg, wchar_t*>, std::is_same<Arg, wchar_t const*>,
std::is_same<Arg, std::wstring>, std::is_same<Arg, std::wstring_view>>)
{
// we first need to retrieve the length
size_t len;
std::memcpy(&len, buffer, sizeof(len));
buffer += sizeof(len);
std::wstring_view wstr{reinterpret_cast<wchar_t const*>(buffer), len};
if (args_store)
{
std::string str = utf8_encode(buffer, len);
args_store->push_back(static_cast<std::string&&>(str));
}
size_t size_bytes = len * sizeof(wchar_t);
buffer += size_bytes;
return wstr;
}
#endif
else
{
static_assert(always_false_v<Arg>, "Unsupported type");
}
}
};
template <typename... Args>
void decode(std::byte*& buffer, QUILL_MAYBE_UNUSED DynamicFormatArgStore* args_store) noexcept
{
(Decoder<Args>::decode(buffer, args_store), ...);
}
/**
* Decoder functions
*/
using FormatArgsDecoder = void (*)(std::byte*& data, DynamicFormatArgStore& args_store);
template <typename... Args>
void decode_and_populate_format_args(std::byte*& buffer, DynamicFormatArgStore& args_store)
{
args_store.clear();
decode<Args...>(buffer, &args_store);
}
/** Codec helpers for user defined types convenience **/
/***/
template <typename... TMembers>
size_t calculate_total_size(std::vector<size_t>& conditional_arg_size_cache, TMembers const&... members)
{
size_t total_size{0};
((total_size += ArgSizeCalculator<remove_cvref_t<TMembers>>::calculate(conditional_arg_size_cache, members)), ...);
return total_size;
}
/***/
template <typename... TMembers>
void encode_members(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, TMembers const&... members)
{
((Encoder<remove_cvref_t<TMembers>>::encode(buffer, conditional_arg_size_cache,
conditional_arg_size_cache_index, members)),
...);
}
/***/
template <typename T, typename... TMembers>
void decode_and_assign_members(std::byte*& buffer, DynamicFormatArgStore* args_store, T& arg, TMembers&... members)
{
((members = Decoder<remove_cvref_t<TMembers>>::decode(buffer, nullptr)), ...);
if (args_store)
{
args_store->push_back(arg);
}
}
} // namespace quill::detail

View file

@ -0,0 +1,89 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
/**
* Convert number to string
*/
#define QUILL_AS_STR(x) #x
#define QUILL_STRINGIFY(x) QUILL_AS_STR(x)
namespace quill
{
namespace detail
{
/**
* Cache line size
*/
static constexpr size_t CACHE_LINE_SIZE{64u};
static constexpr size_t CACHE_LINE_ALIGNED{2 * CACHE_LINE_SIZE};
} // namespace detail
/**
* Enum to select a queue type
*/
enum class QueueType
{
UnboundedBlocking,
UnboundedDropping,
UnboundedUnlimited,
BoundedBlocking,
BoundedDropping
};
/**
* Enum to select a timezone
*/
enum class Timezone : uint8_t
{
LocalTime,
GmtTime
};
/**
* Enum for the used clock type
*/
enum class ClockSourceType : uint8_t
{
Tsc,
System,
User
};
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#elif defined(_WIN32)
#pragma warning(push)
#pragma warning(disable : 4265) // Disable warning about non-virtual destructor in MSVC
#endif
/**
* Tags class that can be used for _WITH_TAGS log macros
*/
class Tags
{
public:
constexpr Tags() = default;
virtual void format(std::string&) const = 0;
};
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#elif defined(_WIN32)
#pragma warning(pop)
#endif
} // namespace quill

View file

@ -0,0 +1,122 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "quill/bundled/fmt/core.h"
#include "quill/core/Attributes.h"
namespace quill::detail
{
class DynamicArgList
{
struct Node
{
virtual ~Node() = default;
std::unique_ptr<Node> next;
};
template <typename T>
struct TypedNode : Node
{
template <typename Arg>
explicit TypedNode(Arg const& arg) : value(arg)
{
}
T value;
};
std::unique_ptr<Node> _head;
public:
template <typename T, typename Arg>
T const& push(Arg const& arg)
{
auto new_node = std::unique_ptr<TypedNode<T>>(new TypedNode<T>(arg));
T& value = new_node->value;
new_node->next = std::move(_head);
_head = std::move(new_node);
return value;
}
};
/**
* Similar to fmt::dynamic_arg_store but better suited to our needs
* e.g does not include <functional> and requires less space
*/
class DynamicFormatArgStore
{
private:
// Storage of basic_format_arg must be contiguous.
std::vector<fmtquill::basic_format_arg<fmtquill::format_context>> _data;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
DynamicArgList _dynamic_arg_list;
template <typename T>
void emplace_arg(T const& arg)
{
_data.emplace_back(fmtquill::detail::make_arg<fmtquill::format_context>(arg));
}
public:
DynamicFormatArgStore() = default;
QUILL_NODISCARD unsigned long long get_types() const
{
return fmtquill::detail::is_unpacked_bit | _data.size();
}
QUILL_NODISCARD fmtquill::basic_format_arg<fmtquill::format_context> const* data() const
{
return _data.data();
}
/**
Adds an argument for later passing to a formatting function.
**Example**::
fmtquill::dynamic_format_arg_store<fmtquill::format_fmtquill::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmtquill::vformat("{} and {} and {}", store);
*/
template <typename T>
void push_back(T const& arg)
{
constexpr auto mapped_type = fmtquill::detail::mapped_type_constant<T, fmtquill::format_context>::value;
using stored_type = std::conditional_t<std::is_convertible_v<T, std::string>, std::string, T>;
if constexpr (!(std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string_view> ||
(mapped_type != fmtquill::detail::type::cstring_type &&
mapped_type != fmtquill::detail::type::string_type &&
mapped_type != fmtquill::detail::type::custom_type)))
{
emplace_arg(_dynamic_arg_list.push<stored_type>(arg));
}
else
{
emplace_arg(arg);
}
}
/** Erase all elements from the store */
void clear()
{
_data.clear();
_dynamic_arg_list = DynamicArgList{};
}
};
} // namespace quill::detail

View file

@ -0,0 +1,63 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#if !defined(QUILL_HAS_FILESYSTEM) && !defined(QUILL_HAS_EXPERIMENTAL_FILESYSTEM)
#if defined(__cpp_lib_filesystem)
#define QUILL_HAS_FILESYSTEM 1
#elif defined(__cpp_lib_experimental_filesystem)
#define QUILL_HAS_EXPERIMENTAL_FILESYSTEM 1
#elif !defined(__has_include)
#define QUILL_HAS_EXPERIMENTAL_FILESYSTEM 1
#elif __has_include(<filesystem>)
#define QUILL_HAS_FILESYSTEM 1
#elif __has_include(<experimental/filesystem>)
#define QUILL_HAS_EXPERIMENTAL_FILESYSTEM 1
#endif
// std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/
#if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8
#undef QUILL_HAS_FILESYSTEM
#undef QUILL_HAS_EXPERIMENTAL_FILESYSTEM
#endif
// no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8
#undef QUILL_HAS_FILESYSTEM
// #undef QUILL_HAS_EXPERIMENTAL_FILESYSTEM
#endif
// no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support
#if defined(__clang_major__) && __clang_major__ < 7
#undef QUILL_HAS_FILESYSTEM
#undef QUILL_HAS_EXPERIMENTAL_FILESYSTEM
#endif
#endif
#ifndef QUILL_HAS_EXPERIMENTAL_FILESYSTEM
#define QUILL_HAS_EXPERIMENTAL_FILESYSTEM 0
#endif
#ifndef QUILL_HAS_FILESYSTEM
#define QUILL_HAS_FILESYSTEM 0
#endif
#if QUILL_HAS_EXPERIMENTAL_FILESYSTEM
#include <experimental/filesystem>
namespace quill
{
namespace fs = std::experimental::filesystem;
} // namespace quill
#elif QUILL_HAS_FILESYSTEM
#include <filesystem>
namespace quill
{
namespace fs = std::filesystem;
} // namespace quill
#endif

View file

@ -0,0 +1,110 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/bundled/fmt/core.h"
#include <cstddef>
#include <memory>
#include <string>
namespace quill::detail
{
/**
* This is a replication of fmt::basic_memory_buffer with only dynamic storage
*/
class FormatBuffer final : public fmtquill::detail::buffer<char>
{
public:
using value_type = char;
using const_reference = char const&;
FormatBuffer() { this->set(nullptr, 0); }
~FormatBuffer() noexcept { _deallocate(); }
FormatBuffer(FormatBuffer&& other) noexcept { _move(other); }
FormatBuffer& operator=(FormatBuffer&& other) noexcept
{
FMTQUILL_ASSERT(this != &other, "");
_deallocate();
_move(other);
return *this;
}
void resize(size_t count) { this->try_resize(count); }
void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
void append(std::string const& str) { append(str.data(), str.data() + str.size()); }
void append(char const* begin, char const* end)
{
while (begin != end)
{
auto count = fmtquill::detail::to_unsigned(end - begin);
this->try_reserve(size_ + count);
auto free_cap = capacity_ - size_;
if (free_cap < count)
{
count = free_cap;
}
std::uninitialized_copy_n(begin, count, ptr_ + size_);
size_ += count;
begin += count;
}
}
private:
// Move data from other to this buffer.
void _move(FormatBuffer& other)
{
this->set(other.data(), other.capacity());
this->resize(other.size());
// Set pointer to the inline array so that delete is not called
// when deallocating.
other.set(nullptr, 0);
other.clear();
}
// Deallocate memory allocated by the buffer.
void _deallocate()
{
if (this->data())
{
delete[] this->data();
}
}
protected:
void grow(size_t size) override
{
size_t const old_capacity = this->capacity();
size_t new_capacity = old_capacity * 2;
if (size > new_capacity)
{
new_capacity = size;
}
char* old_data = this->data();
char* new_data = new char[new_capacity];
// The following code doesn't throw, so the raw pointer above doesn't leak.
std::uninitialized_copy_n(old_data, this->size(), new_data);
this->set(new_data, new_capacity);
if (old_data)
{
delete[] old_data;
}
}
};
} // namespace quill

View file

@ -0,0 +1,44 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Common.h"
#include <cstdint>
namespace quill
{
struct FrontendOptions
{
/**
* Each frontend thread has its own queue, which can be configured with various options:
* - UnboundedBlocking: Starts with initial_queue_capacity and reallocates up to 2GB, then blocks.
* - UnboundedDropping: Starts with initial_queue_capacity and reallocates up to 2GB, then drops log messages.
* - UnboundedUnlimited: Starts with initial_queue_capacity and reallocates up to 2GB; subsequent queues are reallocated as needed. Never blocks or drops.
* - BoundedBlocking: Starts with initial_queue_capacity and never reallocates; blocks when the limit is reached.
* - BoundedDropping: Starts with initial_queue_capacity and never reallocates; drops log messages when the limit is reached.
*
* By default, the library uses an UnboundedBlocking queue, which starts with initial_queue_capacity.
*/
static constexpr quill::QueueType queue_type = quill::QueueType::UnboundedBlocking;
/**
* Initial capacity of the queue. Used for UnboundedBlocking, UnboundedDropping, and
* UnboundedUnlimited. Also serves as the capacity for BoundedBlocking and BoundedDropping.
*/
static constexpr uint32_t initial_queue_capacity = 131'072;
/**
* Interval for retrying when using BoundedBlocking or UnboundedBlocking.
* Applicable only when using BoundedBlocking or UnboundedBlocking.
*/
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
/**
* Enables huge pages on the frontend queues to reduce TLB misses. Available only for Linux.
*/
static constexpr bool huge_pages_enabled = false;
};
} // namespace quill

View file

@ -0,0 +1,156 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/QuillError.h"
#include <cctype>
#include <cstdint>
#include <string>
#include <string_view>
namespace quill
{
/**
* Log level enum
*/
enum class LogLevel : uint8_t
{
TraceL3,
TraceL2,
TraceL1,
Debug,
Info,
Warning,
Error,
Critical,
Backtrace, /**< This is only used for backtrace logging. Should not be set by the user. */
None,
Dynamic /**< This is only used for dynamic logging. Should not be set by the user. */
};
namespace detail
{
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline std::string_view _get_log_level_string(
LogLevel log_level, std::string_view const* log_levels_map, uint32_t log_levels_map_size)
{
auto const log_lvl = static_cast<uint32_t>(log_level);
if (QUILL_UNLIKELY(log_lvl >= log_levels_map_size))
{
std::string const error_msg = "Invalid get_log_level value \"" + std::to_string(log_lvl) + "\"";
QUILL_THROW(QuillError{error_msg});
}
return log_levels_map[log_lvl];
}
} // namespace detail
/**
* Converts a LogLevel enum to string
* @param log_level LogLevel
* @return the corresponding string value
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline std::string_view loglevel_to_string(LogLevel log_level)
{
static constexpr std::string_view log_levels_map[] = {
"TRACE_L3", "TRACE_L2", "TRACE_L1", "DEBUG", "INFO", "WARNING",
"ERROR", "CRITICAL", "BACKTRACE", "NONE", "DYNAMIC"};
static constexpr uint32_t log_levels_map_size = sizeof(log_levels_map) / sizeof(log_levels_map[0]);
return detail::_get_log_level_string(log_level, log_levels_map, log_levels_map_size);
}
/**
* Converts a LogLevel enum to string id
* @param log_level LogLevel
* @return the corresponding string id
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline std::string_view loglevel_to_string_id(LogLevel log_level)
{
static constexpr std::string_view log_levels_map[] = {"T3", "T2", "T1", "D", "I", "W",
"E", "C", "BT", "N", "DN"};
static constexpr uint32_t log_levels_map_size = sizeof(log_levels_map) / sizeof(log_levels_map[0]);
return detail::_get_log_level_string(log_level, log_levels_map, log_levels_map_size);
}
/**
* Converts a string to a LogLevel enum value
* @param log_level the log level string to convert
* "tracel3", "tracel2", "tracel1", "debug", "info", "warning", "error", "backtrace", "none"
* @return the corresponding LogLevel enum value
*/
QUILL_NODISCARD LogLevel inline loglevel_from_string(std::string log_level)
{
// Convert to lowercase
for (char& c : log_level)
{
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
if (log_level == "tracel3" || log_level == "trace_l3")
{
return LogLevel::TraceL3;
}
if (log_level == "tracel2" || log_level == "trace_l2")
{
return LogLevel::TraceL2;
}
if (log_level == "tracel1" || log_level == "trace_l1")
{
return LogLevel::TraceL1;
}
if (log_level == "debug")
{
return LogLevel::Debug;
}
if (log_level == "info")
{
return LogLevel::Info;
}
if (log_level == "warning")
{
return LogLevel::Warning;
}
if (log_level == "error")
{
return LogLevel::Error;
}
if (log_level == "critical")
{
return LogLevel::Critical;
}
if (log_level == "backtrace")
{
return LogLevel::Backtrace;
}
if (log_level == "none")
{
return LogLevel::None;
}
if (log_level == "dynamic")
{
return LogLevel::Dynamic;
}
std::string const error_msg = "LogLevel enum value does not exist for \"" + log_level + "\"";
QUILL_THROW(QuillError{error_msg});
}
} // namespace quill

View file

@ -0,0 +1,140 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/LogLevel.h"
#include <atomic>
#include <cassert>
#include <memory>
#include <string>
#include <vector>
namespace quill
{
/** Forward Declarations **/
class Sink;
class PatternFormatter;
class UserClockSource;
namespace detail
{
class BackendWorker;
/***/
class LoggerBase
{
public:
/***/
LoggerBase(std::string logger_name, std::vector<std::shared_ptr<Sink>> sinks,
std::string format_pattern, std::string time_pattern, Timezone timezone,
ClockSourceType clock_source, UserClockSource* user_clock)
: format_pattern(static_cast<std::string&&>(format_pattern)),
time_pattern(static_cast<std::string&&>(time_pattern)),
logger_name(static_cast<std::string&&>(logger_name)),
user_clock(user_clock),
timezone(timezone),
clock_source(clock_source)
{
#ifndef NDEBUG
for (auto const& sink : sinks)
{
assert(sink && "sink pointer is nullptr");
}
#endif
this->sinks = static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks);
}
/***/
LoggerBase(LoggerBase const&) = delete;
LoggerBase& operator=(LoggerBase const&) = delete;
/***/
virtual ~LoggerBase() = default;
/**
* Returns the name of the logger.
* @return A constant reference to the logger's name.
*/
QUILL_NODISCARD std::string const& get_logger_name() const noexcept { return logger_name; }
/**
* This function sets the logger's validity flag to false, indicating that the logger is no longer valid.
*/
void mark_invalid() { valid.store(false, std::memory_order_release); }
/**
* @brief Checks if the logger is valid.
* @return True if the logger is valid, false otherwise.
*/
QUILL_NODISCARD bool is_valid_logger() const noexcept
{
return valid.load(std::memory_order_acquire);
}
/**
* @return The log level of the logger
*/
QUILL_NODISCARD LogLevel get_log_level() const noexcept
{
return log_level.load(std::memory_order_relaxed);
}
/**
* Set the log level of the logger
* @param new_log_level The new log level
*/
void set_log_level(LogLevel new_log_level)
{
if (QUILL_UNLIKELY(new_log_level == LogLevel::Backtrace))
{
QUILL_THROW(QuillError{"LogLevel::Backtrace is only used internally. Please don't use it."});
}
log_level.store(new_log_level, std::memory_order_relaxed);
}
/**
* Checks if the given log_statement_level can be logged by this logger
* @return bool if a message can be logged based on the current log level
*/
template <LogLevel log_statement_level>
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool should_log_message() const noexcept
{
return log_statement_level >= get_log_level();
}
/**
* Checks if the given log_statement_level can be logged by this logger
* @param log_statement_level The log level of the log statement to be logged
* @return bool if a message can be logged based on the current log level
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool should_log_message(LogLevel log_statement_level) const noexcept
{
return log_statement_level >= get_log_level();
}
protected:
friend class detail::BackendWorker;
std::shared_ptr<PatternFormatter> pattern_formatter; /* The backend thread will set this once, we never access it on the frontend */
std::string format_pattern; /* Set by the frontend and accessed by the backend to initialise PatternFormatter */
std::string time_pattern; /* Set by the frontend and accessed by the backend to initialise PatternFormatter */
std::string logger_name; /* Set by the frontend, accessed by the frontend AND backend */
std::vector<std::shared_ptr<Sink>> sinks; /* Set by the frontend and accessed by the backend */
UserClockSource* user_clock{nullptr}; /* A non owned pointer to a custom timestamp clock, valid only when provided. used by frontend only */
Timezone timezone; /* Set by the frontend and accessed by the backend to initialise PatternFormatter */
ClockSourceType clock_source; /* Set by the frontend and accessed by the frontend AND backend */
std::atomic<LogLevel> log_level{LogLevel::Info}; /* used by frontend only */
std::atomic<LogLevel> backtrace_flush_level{LogLevel::None}; /** Updated by the frontend at any time, accessed by the backend */
std::atomic<bool> valid{true}; /* Updated by the frontend at any time, accessed by the backend */
};
} // namespace detail
} // namespace quill

View file

@ -0,0 +1,223 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/LoggerBase.h"
#include <algorithm>
#include <atomic>
#include <cassert>
#include <initializer_list>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
namespace quill
{
/** Forward Declarations **/
class Sink;
class UserClockSource;
namespace detail
{
class LoggerManager
{
public:
LoggerManager(LoggerManager const&) = delete;
LoggerManager& operator=(LoggerManager const&) = delete;
/***/
static LoggerManager& instance() noexcept
{
static LoggerManager instance;
return instance;
}
/***/
QUILL_NODISCARD LoggerBase* get_logger(std::string const& logger_name) const
{
std::lock_guard<std::recursive_mutex> const lock{_mutex};
LoggerBase* logger = _find_logger(logger_name);
return logger && logger->is_valid_logger() ? logger : nullptr;
}
/***/
QUILL_NODISCARD std::vector<LoggerBase*> get_all_loggers() const
{
std::lock_guard<std::recursive_mutex> const lock{_mutex};
std::vector<LoggerBase*> loggers;
for (auto const& elem : _loggers)
{
// we can not add invalidated loggers as they can be removed at any time
if (elem->is_valid_logger())
{
loggers.push_back(elem.get());
}
}
return loggers;
}
/***/
QUILL_NODISCARD LoggerBase* get_valid_logger() const
{
// Retrieves any valid logger without the need for constructing a vector
std::lock_guard<std::recursive_mutex> const lock{_mutex};
for (auto const& elem : _loggers)
{
// we can not add invalidated loggers as they can be removed at any time
if (elem->is_valid_logger())
{
return elem.get();
}
}
return nullptr;
}
/***/
QUILL_NODISCARD size_t get_number_of_loggers() const noexcept
{
std::lock_guard<std::recursive_mutex> const lock{_mutex};
return _loggers.size();
}
/**
* For backend use only
*/
template <typename TCallback>
void for_each_logger(TCallback cb) const
{
std::lock_guard<std::recursive_mutex> const lock{_mutex};
for (auto const& elem : _loggers)
{
// Here we do not check for valid_logger() like in get_all_loggers() because this
// function is only called by the backend
cb(elem.get());
}
}
/***/
template <typename TLogger>
LoggerBase* create_or_get_logger(std::string const& logger_name, std::vector<std::shared_ptr<Sink>> sinks,
std::string const& format_pattern,
std::string const& time_pattern, Timezone timestamp_timezone,
ClockSourceType clock_source, UserClockSource* user_clock)
{
std::lock_guard<std::recursive_mutex> const lock{_mutex};
LoggerBase* logger_ptr = _find_logger(logger_name);
if (!logger_ptr)
{
// If logger pointer is null, create a new logger instance.
std::unique_ptr<LoggerBase> new_logger{new TLogger{logger_name, static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
format_pattern, time_pattern, timestamp_timezone, clock_source, user_clock}};
_insert_logger(static_cast<std::unique_ptr<LoggerBase>&&>(new_logger));
// Although we could directly return .get() from the new_logger here,
// we retain this portion of code for additional safety in case of potential re-lookup of
// the logger. This section is not performance-critical.
logger_ptr = _find_logger(logger_name);
}
assert(logger_ptr);
assert(logger_ptr->is_valid_logger());
return logger_ptr;
}
/***/
void remove_logger(LoggerBase* logger)
{
logger->mark_invalid();
_has_invalidated_loggers.store(true, std::memory_order_release);
}
/***/
template <typename TCheckQueuesEmpty>
QUILL_NODISCARD std::vector<std::string> cleanup_invalidated_loggers(TCheckQueuesEmpty check_queues_empty)
{
std::vector<std::string> removed_loggers;
if (_has_invalidated_loggers.load(std::memory_order_acquire))
{
_has_invalidated_loggers.store(false, std::memory_order_release);
std::lock_guard<std::recursive_mutex> const lock{_mutex};
for (auto it = _loggers.begin(); it != _loggers.end();)
{
if (!it->get()->is_valid_logger())
{
// invalid logger, check if the logger has any pending records in the queue
if (!check_queues_empty())
{
// we have pending records in the queue, we can not remove the logger yet
++it;
_has_invalidated_loggers.store(true, std::memory_order_release);
}
else
{
removed_loggers.push_back(it->get()->get_logger_name());
it = _loggers.erase(it);
}
}
else
{
++it;
}
}
}
return removed_loggers;
}
/***/
QUILL_NODISCARD bool has_invalidated_loggers() const noexcept
{
return _has_invalidated_loggers.load(std::memory_order_acquire);
}
private:
LoggerManager() = default;
~LoggerManager() = default;
/***/
void _insert_logger(std::unique_ptr<LoggerBase> logger)
{
auto search_it = std::lower_bound(_loggers.begin(), _loggers.end(), logger->get_logger_name(),
[](std::unique_ptr<LoggerBase> const& a, std::string const& b)
{ return a->get_logger_name() < b; });
_loggers.insert(search_it, static_cast<std::unique_ptr<LoggerBase>&&>(logger));
}
/***/
QUILL_NODISCARD LoggerBase* _find_logger(std::string const& target) const noexcept
{
auto search_it = std::lower_bound(_loggers.begin(), _loggers.end(), target,
[](std::unique_ptr<LoggerBase> const& a, std::string const& b)
{ return a->get_logger_name() < b; });
return (search_it != std::end(_loggers) && search_it->get()->get_logger_name() == target)
? search_it->get()
: nullptr;
}
private:
std::vector<std::unique_ptr<LoggerBase>> _loggers;
mutable std::recursive_mutex _mutex;
std::atomic<bool> _has_invalidated_loggers{false};
};
} // namespace detail
} // namespace quill

View file

@ -0,0 +1,220 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/LogLevel.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <string_view>
namespace quill
{
/**
* Captures and stores information about a logging event in compile time
*/
class MacroMetadata
{
public:
enum Event : uint8_t
{
Log,
InitBacktrace,
FlushBacktrace,
Flush
};
constexpr MacroMetadata(char const* source_location, char const* caller_function, char const* message_format, Tags const* tags, LogLevel log_level, Event event) noexcept
: _source_location(source_location),
_caller_function(caller_function),
_message_format(message_format),
_tags(tags),
_colon_separator_pos(_calc_colon_separator_pos()),
_file_name_pos(_calc_file_name_pos()),
_log_level(log_level),
_event(event)
{
_set_named_args_flag(_contains_named_args(message_format));
}
QUILL_NODISCARD char const* source_location() const noexcept { return _source_location; }
QUILL_NODISCARD char const* caller_function() const noexcept { return _caller_function; }
QUILL_NODISCARD char const* message_format() const noexcept
{ return _message_format; }
QUILL_NODISCARD char const* line() const noexcept
{
return _source_location + _colon_separator_pos + 1;
}
QUILL_NODISCARD std::string_view full_path() const noexcept
{
return std::string_view{_source_location, _colon_separator_pos};
}
QUILL_NODISCARD std::string_view file_name() const noexcept
{
return std::string_view{_source_location + _file_name_pos,
static_cast<size_t>(_colon_separator_pos - _file_name_pos)};
}
QUILL_NODISCARD char const* short_source_location() const noexcept
{
return _source_location + _file_name_pos;
}
QUILL_NODISCARD LogLevel log_level() const noexcept { return _log_level; }
QUILL_NODISCARD std::string_view log_level_string() const noexcept
{
return quill::loglevel_to_string(_log_level);
}
QUILL_NODISCARD std::string_view log_level_id() const noexcept
{
return quill::loglevel_to_string_id(_log_level);
}
QUILL_NODISCARD Tags const* tags() const noexcept { return _tags; }
QUILL_NODISCARD bool has_named_args() const noexcept {
return _format_flags & NAMED_ARGS_FLAG;
}
QUILL_NODISCARD Event event() const noexcept { return _event; }
private:
/***/
QUILL_NODISCARD constexpr uint16_t _calc_file_name_pos() const noexcept
{
char const* source_location = _source_location;
char const* file = source_location;
while (*source_location)
{
char cur = *source_location++;
if (cur == '/' || cur == PATH_PREFERRED_SEPARATOR)
{
file = source_location;
}
}
return static_cast<uint16_t>(file - _source_location);
}
/***/
QUILL_NODISCARD constexpr uint16_t _calc_colon_separator_pos() const noexcept
{
std::string_view source_loc{_source_location};
auto const separator_index = source_loc.rfind(':');
return static_cast<uint16_t>(separator_index);
}
/***/
constexpr void _set_named_args_flag(bool value) noexcept
{
if (value)
{
_format_flags |= NAMED_ARGS_FLAG;
}
else
{
_format_flags &= static_cast<uint8_t>(~NAMED_ARGS_FLAG);
}
}
/***/
constexpr bool _contains_named_args(std::string_view fmt) noexcept
{
uint32_t pos{0};
bool found_named_arg{false};
// Iterates the format string and checks if any characters are contained inside `{}`
while (pos < fmt.length())
{
if (fmt[pos] == '{')
{
++pos; // consume {
if (pos >= fmt.length())
{
break;
}
// first character after the {
auto const fc = fmt[pos];
if (fc == '{')
{
// this means first '{' was escaped, so we ignore both of them
++pos;
continue;
}
// else look for the next '}'
uint32_t char_cnt{0};
while (pos < fmt.length())
{
if (fmt[pos] == '}')
{
++pos; // consume }
if (pos >= fmt.length())
{
break;
}
if (fmt[pos] == '}')
{
// this means first '}', was escaped ignore it
++pos;
++char_cnt;
continue;
}
// we found '{' match, we can break
break;
}
++pos;
++char_cnt;
}
if ((char_cnt != 0) && ((fc >= 'a' && fc <= 'z') || (fc >= 'A' && fc <= 'Z')))
{
found_named_arg = true;
}
}
++pos;
}
return found_named_arg;
}
private:
// define our own PATH_PREFERRED_SEPARATOR to not include <filesystem>
#if defined(_WIN32) && !defined(__CYGWIN__)
static constexpr wchar_t PATH_PREFERRED_SEPARATOR = L'\\';
#else
static constexpr char PATH_PREFERRED_SEPARATOR = '/';
#endif
static constexpr uint8_t NAMED_ARGS_FLAG = 0x01;
char const* _source_location;
char const* _caller_function;
char const* _message_format;
Tags const* _tags;
uint16_t _colon_separator_pos;
uint16_t _file_name_pos;
LogLevel _log_level;
Event _event;
uint8_t _format_flags{0};
};
static_assert(sizeof(MacroMetadata) <= detail::CACHE_LINE_SIZE,
"Size of MacroMetadata exceeds the cache line size");
} // namespace quill

View file

@ -0,0 +1,57 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include <cassert>
#include <cstdint>
#include <limits>
namespace quill::detail
{
/**
* Check if a number is a power of 2
* @param number the number to check against
* @return true if the number is power of 2
* false otherwise
*/
QUILL_NODISCARD constexpr bool is_power_of_two(uint64_t number) noexcept
{
return (number != 0) && ((number & (number - 1)) == 0);
}
/**
* Round up to the next power of 2
* @param n input
* @return the next power of 2
*/
template <typename T>
QUILL_NODISCARD T next_power_of_two(T n)
{
constexpr T max_power_of_2 = (std::numeric_limits<T>::max() >> 1) + 1;
if (n >= max_power_of_2)
{
return max_power_of_2;
}
if (is_power_of_two(static_cast<uint64_t>(n)))
{
return n;
}
T result = 1;
while (result < n)
{
result <<= 1;
}
assert(is_power_of_two(static_cast<uint64_t>(result)) && "result is not a power of 2");
return result;
}
} // namespace quill::detail

View file

@ -0,0 +1,55 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include <exception>
#include <string>
/**
* Require check
*/
#define QUILL_REQUIRE(expression, error) \
do \
{ \
if (QUILL_UNLIKELY(!(expression))) \
{ \
printf("Quill fatal error: %s (%s:%d)\n", error, __FILE__, __LINE__); \
std::abort(); \
} \
} while (0)
#if defined(QUILL_NO_EXCEPTIONS)
#define QUILL_TRY if (true)
#define QUILL_THROW(ex) QUILL_REQUIRE(false, ex.what())
#define QUILL_CATCH(x) if (false)
#define QUILL_CATCH_ALL() if (false)
#else
#define QUILL_TRY try
#define QUILL_THROW(ex) throw(ex)
#define QUILL_CATCH(x) catch (x)
#define QUILL_CATCH_ALL() catch (...)
#endif
namespace quill
{
/**
* custom exception
*/
class QuillError : public std::exception
{
public:
explicit QuillError(std::string s) : _error(static_cast<std::string&&>(s)) {}
explicit QuillError(char const* s) : _error(s) {}
QUILL_NODISCARD char const* what() const noexcept override { return _error.data(); }
private:
std::string _error;
};
} // namespace quill

View file

@ -0,0 +1,92 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include <cstdint>
#if defined(__aarch64__)
#include <chrono>
#include <cstdint>
#elif defined(__ARM_ARCH)
#include <chrono>
#include <cstdint>
#elif (defined(_M_ARM) || defined(_M_ARM64))
#include <chrono>
#include <cstdint>
#elif (defined(__PPC64__))
#include <chrono>
#include <cstdint>
#else
// assume x86-64 ..
#if defined(_WIN32)
#include <intrin.h>
#elif __has_include(<x86gprintrin.h>) && !defined(__INTEL_COMPILER)
#include <x86gprintrin.h>
#else
#include <x86intrin.h>
#endif
#endif
namespace quill::detail
{
#if defined(__aarch64__)
// arm64
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline uint64_t rdtsc() noexcept
{
// System timer of ARMv8 runs at a different frequency than the CPU's.
// The frequency is fixed, typically in the range 1-50MHz. It can be
// read at CNTFRQ special register. We assume the OS has set up the virtual timer properly.
int64_t virtual_timer_value;
__asm__ volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value));
return static_cast<uint64_t>(virtual_timer_value);
}
#elif defined(__ARM_ARCH)
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline uint64_t rdtsc() noexcept
{
#if (__ARM_ARCH >= 6)
// V6 is the earliest arch that has a standard cyclecount
uint32_t pmccntr;
uint32_t pmuseren;
uint32_t pmcntenset;
__asm__ volatile("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren));
if (pmuseren & 1)
{
__asm__ volatile("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset));
if (pmcntenset & 0x80000000ul)
{
__asm__ volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr));
return (static_cast<uint64_t>(pmccntr)) * 64u;
}
}
#endif
// soft failover
return static_cast<uint64_t>(std::chrono::system_clock::now().time_since_epoch().count());
}
#elif (defined(_M_ARM) || defined(_M_ARM64))
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline uint64_t rdtsc() noexcept
{
// soft failover
return static_cast<uint64_t>(std::chrono::system_clock::now().time_since_epoch().count());
}
#elif (defined(__PPC64__))
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline uint64_t rdtsc() noexcept
{
// soft failover
return static_cast<uint64_t>(std::chrono::system_clock::now().time_since_epoch().count());
}
#else
/**
* Get the TSC counter
* @return rdtsc timestamp
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT inline uint64_t rdtsc() noexcept { return __rdtsc(); }
#endif
} // namespace quill::detail

View file

@ -0,0 +1,152 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/QuillError.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <type_traits>
#include <vector>
namespace quill
{
/** Forward declarations **/
class FileSink;
class Sink;
namespace detail
{
class SinkManager
{
private:
struct SinkInfo
{
explicit SinkInfo() = default;
SinkInfo(std::string sid, std::weak_ptr<Sink> sptr)
: sink_id(static_cast<std::string&&>(sid)), sink_ptr(static_cast<std::weak_ptr<Sink>&&>(sptr)){};
std::string sink_id;
std::weak_ptr<Sink> sink_ptr;
};
public:
SinkManager(SinkManager const&) = delete;
SinkManager& operator=(SinkManager const&) = delete;
/***/
static SinkManager& instance() noexcept
{
static SinkManager instance;
return instance;
}
/***/
QUILL_NODISCARD std::shared_ptr<Sink> get_sink(std::string const& sink_name) const
{
// The sinks are used by the backend thread, so after their creation we want to avoid mutating their member variables.
std::lock_guard<std::mutex> const lock{_mutex};
std::shared_ptr<Sink> sink = _find_sink(sink_name);
if (QUILL_UNLIKELY(!sink))
{
QUILL_THROW(QuillError{"Sink with name \"" + sink_name + "\" does not exist"});
}
return sink;
}
/***/
template <typename TSink, typename... Args>
std::shared_ptr<Sink> create_or_get_sink(std::string const& sink_name, Args&&... args)
{
// The sinks are used by the backend thread, so after their creation we want to avoid mutating their member variables.
std::lock_guard<std::mutex> const lock{_mutex};
std::shared_ptr<Sink> sink = _find_sink(sink_name);
if (!sink)
{
if constexpr (std::disjunction_v<std::is_same<FileSink, TSink>, std::is_base_of<FileSink, TSink>>)
{
sink = std::make_shared<TSink>(sink_name, static_cast<Args&&>(args)...);
}
else
{
sink = std::make_shared<TSink>(static_cast<Args&&>(args)...);
}
_insert_sink(sink_name, sink);
}
return sink;
}
/***/
uint32_t cleanup_unused_sinks()
{
// this needs to take a lock each time. The backend logging thread should be carefully call
// it only when needed
std::lock_guard<std::mutex> const lock{_mutex};
uint32_t cnt{0};
for (auto it = _sinks.begin(); it != _sinks.end();)
{
if (it->sink_ptr.expired())
{
it = _sinks.erase(it);
++cnt;
}
else
{
++it;
}
}
return cnt;
}
private:
SinkManager() = default;
~SinkManager() = default;
/***/
void _insert_sink(std::string const& sink_name, std::shared_ptr<Sink> const& sink)
{
auto search_it =
std::lower_bound(_sinks.begin(), _sinks.end(), sink_name,
[](SinkInfo const& elem, std::string const& b) { return elem.sink_id < b; });
_sinks.insert(search_it, SinkInfo{sink_name, sink});
}
/***/
QUILL_NODISCARD std::shared_ptr<Sink> _find_sink(std::string const& target) const noexcept
{
std::shared_ptr<Sink> sink;
auto search_it =
std::lower_bound(_sinks.begin(), _sinks.end(), target,
[](SinkInfo const& elem, std::string const& b) { return elem.sink_id < b; });
if (search_it != std::end(_sinks) && search_it->sink_id == target)
{
sink = search_it->sink_ptr.lock();
}
return sink;
}
private:
std::vector<SinkInfo> _sinks;
mutable std::mutex _mutex;
};
} // namespace detail
} // namespace quill

View file

@ -0,0 +1,386 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/BoundedSPSCQueue.h"
#include "quill/core/Common.h"
#include "quill/core/ThreadUtilities.h"
#include "quill/core/UnboundedSPSCQueue.h"
#include <atomic>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <mutex>
#include <new>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
namespace quill::detail
{
/** Forward Declarations **/
class UnboundedTransitEventBuffer;
class BackendWorker;
class ThreadContext
{
private:
union SpscQueueUnion
{
UnboundedSPSCQueue unbounded_spsc_queue;
BoundedSPSCQueue bounded_spsc_queue;
SpscQueueUnion() {}
~SpscQueueUnion() {}
};
public:
/***/
ThreadContext(QueueType queue_type, uint32_t initial_spsc_queue_capacity, bool huges_pages_enabled)
: _queue_type(queue_type)
{
if (has_unbounded_queue_type())
{
new (&_spsc_queue_union.unbounded_spsc_queue)
UnboundedSPSCQueue{initial_spsc_queue_capacity, huges_pages_enabled};
}
else if (has_bounded_queue_type())
{
new (&_spsc_queue_union.bounded_spsc_queue)
BoundedSPSCQueue{initial_spsc_queue_capacity, huges_pages_enabled};
}
_conditional_arg_size_cache.reserve(8);
}
/***/
ThreadContext(ThreadContext const&) = delete;
ThreadContext& operator=(ThreadContext const&) = delete;
/***/
~ThreadContext()
{
if (has_unbounded_queue_type())
{
_spsc_queue_union.unbounded_spsc_queue.~UnboundedSPSCQueue();
}
else if (has_bounded_queue_type())
{
_spsc_queue_union.bounded_spsc_queue.~BoundedSPSCQueue();
}
}
/***/
template <QueueType queue_type>
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT
std::conditional_t<(queue_type == QueueType::UnboundedBlocking) || (queue_type == QueueType::UnboundedUnlimited) ||
(queue_type == QueueType::UnboundedDropping),
UnboundedSPSCQueue, BoundedSPSCQueue>&
get_spsc_queue() noexcept
{
assert((_queue_type == queue_type) && "ThreadContext queue_type mismatch");
if constexpr ((queue_type == QueueType::UnboundedBlocking) ||
(queue_type == QueueType::UnboundedUnlimited) || (queue_type == QueueType::UnboundedDropping))
{
return _spsc_queue_union.unbounded_spsc_queue;
}
else
{
return _spsc_queue_union.bounded_spsc_queue;
}
}
/***/
template <QueueType queue_type>
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT
std::conditional_t<(queue_type == QueueType::UnboundedBlocking) || (queue_type == QueueType::UnboundedUnlimited) ||
(queue_type == QueueType::UnboundedDropping),
UnboundedSPSCQueue, BoundedSPSCQueue> const&
get_spsc_queue() const noexcept
{
assert((_queue_type == queue_type) && "ThreadContext queue_type mismatch");
if constexpr ((queue_type == QueueType::UnboundedBlocking) ||
(queue_type == QueueType::UnboundedUnlimited) || (queue_type == QueueType::UnboundedDropping))
{
return _spsc_queue_union.unbounded_spsc_queue;
}
else
{
return _spsc_queue_union.bounded_spsc_queue;
}
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::vector<size_t>& get_conditional_arg_size_cache() noexcept
{
return _conditional_arg_size_cache;
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool has_bounded_queue_type() const noexcept
{
return (_queue_type == QueueType::BoundedBlocking) || (_queue_type == QueueType::BoundedDropping);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool has_unbounded_queue_type() const noexcept
{
return (_queue_type == QueueType::UnboundedBlocking) ||
(_queue_type == QueueType::UnboundedDropping) || (_queue_type == QueueType::UnboundedUnlimited);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool has_dropping_queue() const noexcept
{
return (_queue_type == QueueType::UnboundedDropping) || (_queue_type == QueueType::BoundedDropping);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool has_blocking_queue() const noexcept
{
return (_queue_type == QueueType::UnboundedBlocking) || (_queue_type == QueueType::BoundedBlocking);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT SpscQueueUnion const& get_spsc_queue_union() const noexcept
{
return _spsc_queue_union;
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT SpscQueueUnion& get_spsc_queue_union() noexcept
{
return _spsc_queue_union;
}
/***/
QUILL_NODISCARD std::string_view thread_id() const noexcept { return _thread_id; }
/***/
QUILL_NODISCARD std::string_view thread_name() const noexcept { return _thread_name; }
/***/
void mark_invalid() noexcept { _valid.store(false, std::memory_order_relaxed); }
/***/
QUILL_NODISCARD bool is_valid_context() const noexcept
{
return _valid.load(std::memory_order_relaxed);
}
/***/
void increment_failure_counter() noexcept
{
_failure_counter.fetch_add(1, std::memory_order_relaxed);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT size_t get_and_reset_failure_counter() noexcept
{
if (QUILL_LIKELY(_failure_counter.load(std::memory_order_relaxed) == 0))
{
return 0;
}
return _failure_counter.exchange(0, std::memory_order_relaxed);
}
private:
friend class detail::BackendWorker;
SpscQueueUnion _spsc_queue_union; /** queue for this thread */
std::vector<size_t> _conditional_arg_size_cache; /** used for storing sizes when necessary, such as when calling strn or when a loop is required for a specific type **/
std::string _thread_id = std::to_string(get_thread_id()); /**< cache this thread pid */
std::string _thread_name = get_thread_name(); /**< cache this thread name */
std::shared_ptr<UnboundedTransitEventBuffer> _transit_event_buffer; /** backend thread buffer. this could be unique_ptr but it is shared_ptr because of the forward declaration */
QueueType _queue_type;
std::atomic<bool> _valid{true}; /**< is this context valid, set by the frontend, read by the backend thread */
alignas(CACHE_LINE_ALIGNED) std::atomic<size_t> _failure_counter{0};
};
class ThreadContextManager
{
public:
/***/
static ThreadContextManager& instance() noexcept
{
static ThreadContextManager instance;
return instance;
}
/***/
ThreadContextManager(ThreadContextManager const&) = delete;
ThreadContextManager& operator=(ThreadContextManager const&) = delete;
/***/
template <typename TCallback>
void for_each_thread_context(TCallback cb)
{
std::lock_guard<std::mutex> const lock{_mutex};
for (auto const& elem : _thread_contexts)
{
cb(elem.get());
}
}
/***/
void register_thread_context(std::shared_ptr<ThreadContext> const& thread_context)
{
_mutex.lock();
_thread_contexts.push_back(thread_context);
_mutex.unlock();
_new_thread_context_flag.store(true, std::memory_order_release);
}
/***/
void add_invalid_thread_context() noexcept
{
_invalid_thread_context_count.fetch_add(1, std::memory_order_relaxed);
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool has_invalid_thread_context() const noexcept
{
// Here we do relaxed because if the value is not zero we will look inside ThreadContext invalid
// flag that is also a relaxed atomic, and then we will look into the SPSC queue size that is
// also atomic Even if we don't read everything in order we will check again in the next circle
return _invalid_thread_context_count.load(std::memory_order_relaxed) != 0;
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT bool new_thread_context_flag() noexcept
{
// Again relaxed memory model as in case it is false we will acquire the lock
if (_new_thread_context_flag.load(std::memory_order_relaxed))
{
// if the variable was updated to true, set it to false,
// There should not be any race condition here as this is the only place _changed is set to
// false, and we will return true anyway
_new_thread_context_flag.store(false, std::memory_order_relaxed);
return true;
}
return false;
}
/***/
void remove_shared_invalidated_thread_context(ThreadContext const* thread_context)
{
std::lock_guard<std::mutex> const lock{_mutex};
// We could use std::find_if, but since this header is included in Logger.h, which is essential
// for logging purposes, we aim to minimize the number of includes in that path.
// Therefore, we implement our own find_if loop here.
auto thread_context_it = _thread_contexts.end();
for (auto it = _thread_contexts.begin(); it != _thread_contexts.end(); ++it)
{
if (it->get() == thread_context)
{
thread_context_it = it;
break;
}
}
assert(thread_context_it != _thread_contexts.end() &&
"Attempting to remove a non existent thread context");
assert(!thread_context_it->get()->is_valid_context() &&
"Attempting to remove a valid thread context");
#ifndef NDEBUG
assert(thread_context->has_unbounded_queue_type() || thread_context->has_bounded_queue_type());
if (thread_context->has_unbounded_queue_type())
{
assert(thread_context->get_spsc_queue_union().unbounded_spsc_queue.empty() &&
"Attempting to remove a thread context with a non empty queue");
}
else if (thread_context->has_bounded_queue_type())
{
assert(thread_context->get_spsc_queue_union().bounded_spsc_queue.empty() &&
"Attempting to remove a thread context with a non empty queue");
}
#endif
_thread_contexts.erase(thread_context_it);
// Decrement the counter since we found something to
_invalid_thread_context_count.fetch_sub(1, std::memory_order_relaxed);
}
private:
ThreadContextManager() = default;
~ThreadContextManager() = default;
private:
std::mutex _mutex; /**< Protect access when register contexts or removing contexts */
std::vector<std::shared_ptr<ThreadContext>> _thread_contexts; /**< The registered contexts */
alignas(CACHE_LINE_ALIGNED) std::atomic<bool> _new_thread_context_flag{false};
alignas(CACHE_LINE_ALIGNED) std::atomic<uint8_t> _invalid_thread_context_count{0};
};
class ScopedThreadContext
{
public:
/***/
ScopedThreadContext(QueueType queue_type, uint32_t spsc_queue_capacity, bool huge_pages_enabled)
: _thread_context(std::make_shared<ThreadContext>(queue_type, spsc_queue_capacity, huge_pages_enabled))
{
ThreadContextManager::instance().register_thread_context(_thread_context);
}
/***/
ScopedThreadContext(ScopedThreadContext const&) = delete;
ScopedThreadContext& operator=(ScopedThreadContext const&) = delete;
/***/
~ScopedThreadContext() noexcept
{
// This destructor will get called when the thread that created this wrapper stops
// we will only invalidate the thread context
// The backend thread will empty an invalidated ThreadContext and then remove_file it from
// the ThreadContextCollection
// There is only exception for the thread who owns the ThreadContextCollection the
// main thread. The thread context of the main thread can get deleted before getting invalidated
_thread_context->mark_invalid();
// Notify the backend thread that one context has been removed
ThreadContextManager::instance().add_invalid_thread_context();
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT ThreadContext*
get_thread_context() const noexcept
{
assert(_thread_context && "_thread_context can not be null");
return _thread_context.get();
}
private:
/**<
* This could be unique_ptr but the thread context of main thread that owns
* ThreadContextCollection can be destructed last even after the logger singleton destruction
* so we use shared_ptr */
std::shared_ptr<ThreadContext> _thread_context;
};
/***/
template <typename TFrontendOptions>
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT extern ThreadContext* get_local_thread_context() noexcept
{
thread_local ScopedThreadContext scoped_thread_context{
TFrontendOptions::queue_type, TFrontendOptions::initial_queue_capacity, TFrontendOptions::huge_pages_enabled};
return scoped_thread_context.get_thread_context();
}
} // namespace quill::detail

View file

@ -0,0 +1,197 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/QuillError.h"
#include <cstdint>
#include <cstring>
#include <string>
#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <windows.h>
#include <codecvt>
#include <locale>
#elif defined(__APPLE__)
#include <mach/thread_act.h>
#include <mach/thread_policy.h>
#include <pthread.h>
#elif defined(__linux__)
#include <pthread.h>
#include <sys/syscall.h>
#include <unistd.h>
#elif defined(__NetBSD__)
#include <lwp.h>
#include <unistd.h>
#elif defined(__FreeBSD__)
#include <sys/thr.h>
#include <unistd.h>
#elif defined(__DragonFly__)
#include <sys/lwp.h>
#include <unistd.h>
#endif
namespace quill::detail
{
#if defined(_WIN32)
/**
* Convert a string to wstring
* @param str input string
* @return the value of input string as wide string
*/
QUILL_NODISCARD inline std::wstring s2ws(std::string const& str) noexcept
{
#pragma warning(push)
#pragma warning(disable : 4996)
using convert_t = std::codecvt_utf8_utf16<wchar_t>;
std::wstring_convert<convert_t, wchar_t> converter;
return converter.from_bytes(str);
#pragma warning(pop)
}
/**
* wstring to string
* @param wstr input wide string
* @return the value of input wide string as string
*/
QUILL_NODISCARD inline std::string ws2s(std::wstring const& wstr) noexcept
{
#pragma warning(push)
#pragma warning(disable : 4996)
using convert_t = std::codecvt_utf8_utf16<wchar_t>;
std::wstring_convert<convert_t, wchar_t> converter;
return converter.to_bytes(wstr);
#pragma warning(pop)
}
/***/
template <typename ReturnT, typename Signature, typename... Args>
ReturnT callRunTimeDynamicLinkedFunction(std::string const& dll_name,
std::string const& function_name, Args... args)
{
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreaddescription
// Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607: e.g. GetThreadDescription is only available by Run Time Dynamic Linking in KernelBase.dll.
#ifdef UNICODE
HINSTANCE const hinstLibrary = LoadLibraryW(s2ws(dll_name).c_str());
#else
HINSTANCE const hinstLibrary = LoadLibraryA(dll_name.c_str());
#endif
if (QUILL_UNLIKELY(hinstLibrary == nullptr))
{
QUILL_THROW(QuillError{std::string{"Failed to load library " + dll_name}});
}
auto const callable = reinterpret_cast<Signature>(GetProcAddress(hinstLibrary, function_name.c_str()));
if (QUILL_UNLIKELY(callable == nullptr))
{
FreeLibrary(hinstLibrary);
QUILL_THROW(QuillError{std::string{"Failed to call " + function_name + " " + dll_name}});
}
ReturnT const hr = callable(static_cast<Args&&>(args)...);
BOOL const fFreeResult = FreeLibrary(hinstLibrary);
if (QUILL_UNLIKELY(!fFreeResult))
{
QUILL_THROW(QuillError{std::string{"Failed to free library " + dll_name}});
}
return hr;
}
#endif
/**
* Returns the name of the thread. By default, each thread is unnamed.
* If set_thread_name has not been used to set the name of the specified thread,
* a null string is retrieved into name.
* @return the thread name
*/
QUILL_NODISCARD inline std::string get_thread_name()
{
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(QUILL_NO_THREAD_NAME_SUPPORT)
// Disabled on MINGW / Cygwin.
return std::string{"ThreadNameDisabled"};
#elif defined(_WIN32)
PWSTR data = nullptr;
typedef HRESULT(WINAPI * GetThreadDescriptionSignature)(HANDLE, PWSTR*);
HRESULT hr = callRunTimeDynamicLinkedFunction<HRESULT, GetThreadDescriptionSignature>(
"KernelBase.dll", "GetThreadDescription", GetCurrentThread(), &data);
if (FAILED(hr))
{
QUILL_THROW(QuillError{"Failed to get thread name"});
}
if (QUILL_UNLIKELY(data == nullptr))
{
QUILL_THROW(QuillError{"Failed to get thread name. Invalid data."});
}
const std::wstring tname{&data[0], wcslen(&data[0])};
LocalFree(data);
return ws2s(tname);
#else
// Apple, linux
char thread_name[16] = {'\0'};
auto res = pthread_getname_np(pthread_self(), &thread_name[0], 16);
if (res != 0)
{
QUILL_THROW(QuillError{"Failed to get thread name. error: " + std::to_string(res)});
}
return std::string{&thread_name[0], strlen(&thread_name[0])};
#endif
}
/**
* Returns the os assigned ID of the thread
* @return the thread ID of the calling thread
*/
QUILL_NODISCARD inline uint32_t get_thread_id() noexcept
{
#if defined(__CYGWIN__)
// get thread id on cygwin not supported
return 0;
#elif defined(_WIN32)
return static_cast<uint32_t>(GetCurrentThreadId());
#elif defined(__linux__)
return static_cast<uint32_t>(::syscall(SYS_gettid));
#elif defined(__APPLE__)
uint64_t tid64;
pthread_threadid_np(nullptr, &tid64);
return static_cast<uint32_t>(tid64);
#elif defined(__NetBSD__)
return static_cast<uint32_t>(_lwp_self());
#elif defined(__FreeBSD__)
long lwpid;
thr_self(&lwpid);
return static_cast<uint32_t>(lwpid);
#elif defined(__DragonFly__)
return static_cast<uint32_t>(lwp_gettid());
#else
return reinterpret_cast<uintptr_t>(pthread_self()); // (Ab)use pthread_self as a last resort option
#endif
}
} // namespace quill::detail

View file

@ -0,0 +1,104 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/QuillError.h"
#include <cerrno>
#include <cstring>
#include <ctime>
#include <string>
// Forward declaration
struct tm;
namespace quill::detail
{
/**
* Portable gmtime_r or _s per operating system
* @param timer to a time_t object to convert
* @param buf to a struct tm object to store the result
* @return copy of the buf pointer, or throws on error
* @throws std::system_error
*/
inline tm* gmtime_rs(time_t const* timer, tm* buf)
{
#if defined(_WIN32)
errno_t const res = gmtime_s(buf, timer);
if (res)
{
QUILL_THROW(QuillError{
std::string{"failed to call gmtime_rs, with error message errno: " + std::to_string(res)}});
}
return buf;
#else
tm* res = gmtime_r(timer, buf);
if (QUILL_UNLIKELY(!res))
{
QUILL_THROW(QuillError{std::string{"failed to call gmtime_rs, with error message errno: " +
std::to_string(errno) + " error: " + strerror(errno)}});
}
return res;
#endif
}
/**
* Portable localtime_r or _s per operating system
* @param timer to a time_t object to convert
* @param buf to a struct tm object to store the result
* @return copy of the buf pointer, or throws on error
* @throws std::system_error
*/
inline tm* localtime_rs(time_t const* timer, tm* buf)
{
#if defined(_WIN32)
auto const res = localtime_s(buf, timer);
if (res)
{
QUILL_THROW(QuillError{
std::string{"failed to call gmtime_rs, with error message errno: " + std::to_string(res)}});
}
return buf;
#else
tm* res = localtime_r(timer, buf);
if (QUILL_UNLIKELY(!res))
{
QUILL_THROW(QuillError{std::string{"failed to call localtime_rs, with error message errno: " +
std::to_string(errno) + " error: " + strerror(errno)}});
}
return res;
#endif
}
/**
* inverses of gmtime
* @param tm struct tm to convert
* @throws on invalid input
*/
inline time_t timegm(tm* tm)
{
#if defined(_WIN32)
time_t const ret_val = _mkgmtime(tm);
if (QUILL_UNLIKELY(ret_val == -1))
{
QUILL_THROW(QuillError{"_mkgmtime failed."});
}
return ret_val;
#else
time_t const ret_val = ::timegm(tm);
if (QUILL_UNLIKELY(ret_val == (time_t)-1))
{
QUILL_THROW(QuillError{"timegm failed."});
}
return ret_val;
#endif
}
} // namespace quill::detail

View file

@ -0,0 +1,261 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/BoundedSPSCQueue.h"
#include "quill/core/Common.h"
#include "quill/core/QuillError.h"
#include <atomic>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
namespace quill::detail
{
/**
* A singe-producer single-consumer FIFO circular buffer
*
* The buffer allows producing and consuming objects
*
* Production is wait free.
*
* When the internal circular buffer becomes full a new one will be created and the production
* will continue in the new buffer.
*
* Consumption is wait free. If not data is available a special value is returned. If a new
* buffer is created from the producer the consumer first consumes everything in the old
* buffer and then moves to the new buffer.
*/
class UnboundedSPSCQueue
{
private:
/**
* A node has a buffer and a pointer to the next node
*/
struct Node
{
/**
* Constructor
* @param bounded_queue_capacity the capacity of the fixed buffer
* @param huge_pages_enabled enables huge pages
*/
explicit Node(uint32_t bounded_queue_capacity, bool huge_pages_enabled)
: bounded_queue(bounded_queue_capacity, huge_pages_enabled)
{
}
/** members */
std::atomic<Node*> next{nullptr};
BoundedSPSCQueue bounded_queue;
};
public:
struct ReadResult
{
explicit ReadResult(std::byte* read_position) : read_pos(read_position) {}
std::byte* read_pos;
uint32_t previous_capacity;
uint32_t new_capacity;
bool allocation{false};
};
/**
* Constructor
*/
explicit UnboundedSPSCQueue(uint32_t initial_bounded_queue_capacity, bool huges_pages_enabled = false)
: _producer(new Node(initial_bounded_queue_capacity, huges_pages_enabled)), _consumer(_producer)
{
}
/**
* Deleted
*/
UnboundedSPSCQueue(UnboundedSPSCQueue const&) = delete;
UnboundedSPSCQueue& operator=(UnboundedSPSCQueue const&) = delete;
/**
* Destructor
*/
~UnboundedSPSCQueue()
{
// Get the current consumer node
Node const* current_node = _consumer;
// Look for extra nodes to delete
while (current_node != nullptr)
{
auto const to_delete = current_node;
current_node = current_node->next;
delete to_delete;
}
}
/**
* Reserve contiguous space for the producer without
* making it visible to the consumer.
* @return a valid point to the buffer
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT std::byte* prepare_write(uint32_t nbytes, QueueType queue_type)
{
// Try to reserve the bounded queue
std::byte* write_pos = _producer->bounded_queue.prepare_write(nbytes);
if (QUILL_LIKELY(write_pos != nullptr))
{
return write_pos;
}
// Then it means the queue doesn't have enough size
uint64_t capacity = static_cast<uint64_t>(_producer->bounded_queue.capacity()) * 2ull;
while (capacity < (nbytes + 1))
{
capacity = capacity * 2ull;
}
// bounded queue max power of 2 capacity since uint32_t type is used to hold the value 2147483648 bytes
uint64_t constexpr max_bounded_queue_capacity =
(std::numeric_limits<BoundedSPSCQueue::integer_type>::max() >> 1) + 1;
if (QUILL_UNLIKELY(capacity > max_bounded_queue_capacity))
{
if ((nbytes + 1) > max_bounded_queue_capacity)
{
QUILL_THROW(QuillError{
"logging single messages greater than 2 GB is not supported. nbytes: " + std::to_string(nbytes) +
" capacity: " + std::to_string(capacity) +
" max_bounded_queue_capacity: " + std::to_string(max_bounded_queue_capacity)});
}
if ((queue_type == QueueType::UnboundedBlocking) || (queue_type == QueueType::UnboundedDropping))
{
// we reached the unbounded queue limit of 2147483648 bytes (~2GB) we won't be allocating
// anymore and instead return nullptr to block or drop
return nullptr;
}
// else the UnboundedNonBlocking queue has no limits and will keep allocating additional 2GB queues
capacity = max_bounded_queue_capacity;
}
// commit previous write to the old queue before switching
_producer->bounded_queue.commit_write();
// We failed to reserve because the queue was full, create a new node with a new queue
auto const next_node =
new Node{static_cast<uint32_t>(capacity), _producer->bounded_queue.huge_pages_enabled()};
// store the new node pointer as next in the current node
_producer->next.store(next_node, std::memory_order_release);
// producer is now using the next node
_producer = next_node;
// reserve again, this time we know we will always succeed, cast to void* to ignore
write_pos = _producer->bounded_queue.prepare_write(nbytes);
assert(write_pos && "Already reserved a queue with that capacity");
return write_pos;
}
/**
* Complement to reserve producer space that makes nbytes starting
* from the return of reserve producer space visible to the consumer.
*/
QUILL_ATTRIBUTE_HOT void finish_write(uint32_t nbytes)
{
_producer->bounded_queue.finish_write(nbytes);
}
/**
* Commit the write to notify the consumer bytes are ready to read
*/
QUILL_ATTRIBUTE_HOT void commit_write() { _producer->bounded_queue.commit_write(); }
/**
* Prepare to read from the buffer
* @error_notifier a callback used for notifications to the user
* @return first: pointer to buffer or nullptr, second: a pair of new_capacity, previous_capacity if an allocation
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT ReadResult prepare_read()
{
ReadResult read_result = ReadResult{_consumer->bounded_queue.prepare_read()};
if (!read_result.read_pos)
{
// the buffer is empty check if another buffer exists
Node* const next_node = _consumer->next.load(std::memory_order_acquire);
if (QUILL_UNLIKELY(next_node != nullptr))
{
// a new buffer was added by the producer, this happens only when we have allocated a new queue
// try the existing buffer once more
read_result.read_pos = _consumer->bounded_queue.prepare_read();
if (!read_result.read_pos)
{
// commit the previous reads before deleting the queue
_consumer->bounded_queue.commit_read();
// switch to the new buffer, existing one is deleted
auto const previous_capacity = _consumer->bounded_queue.capacity();
delete _consumer;
_consumer = next_node;
read_result.read_pos = _consumer->bounded_queue.prepare_read();
// we switched to a new here, so we store the capacity info to return it
read_result.allocation = true;
read_result.new_capacity = _consumer->bounded_queue.capacity();
read_result.previous_capacity = previous_capacity;
}
}
}
return read_result;
}
/**
* Consumes the next nbytes in the buffer and frees it back
* for the producer to reuse.
*/
QUILL_ATTRIBUTE_HOT void finish_read(uint32_t nbytes)
{
_consumer->bounded_queue.finish_read(nbytes);
}
/**
* Commit the read to indicate that the bytes are read and are now free to be reused
*/
QUILL_ATTRIBUTE_HOT void commit_read() { _consumer->bounded_queue.commit_read(); }
/**
* Return the current buffer's capacity
* @return capacity
*/
QUILL_NODISCARD uint32_t capacity() const noexcept { return _consumer->bounded_queue.capacity(); }
/**
* checks if the queue is empty
* @return true if empty, false otherwise
*/
QUILL_NODISCARD bool empty() const noexcept
{
return _consumer->bounded_queue.empty() && (_consumer->next.load(std::memory_order_relaxed) == nullptr);
}
private:
/** Modified by either the producer or consumer but never both */
alignas(CACHE_LINE_ALIGNED) Node* _producer{nullptr};
alignas(CACHE_LINE_ALIGNED) Node* _consumer{nullptr};
};
} // namespace quill::detail

View file

@ -0,0 +1,69 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/LogLevel.h"
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
namespace quill
{
/** Forward Declaration **/
class MacroMetadata;
/**
* Base filter class.
* Filters can be added to Sinks
*/
class Filter
{
public:
/**
* Constructor
* @param filter_name unique filter name
*/
explicit Filter(std::string filter_name) : _filter_name(std::move(filter_name)) {}
/**
* Destructor
*/
virtual ~Filter() = default;
/**
* @brief Filters a log message.
*
* @param log_metadata Pointer to the macro metadata.
* @param log_timestamp Timestamp of the log event.
* @param thread_id ID of the thread.
* @param thread_name Name of the thread.
* @param logger_name Name of the logger.
* @param log_level Log level of the message.
* @param log_message The log message.
*
* @return true if the log message should be written to the file, false otherwise
*/
QUILL_NODISCARD virtual bool filter(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::string_view log_message) noexcept = 0;
/**
* Gets the name of the filter. Only useful if an existing filter is needed to be looked up
* @return the name of the filter
*/
QUILL_NODISCARD virtual std::string const& get_filter_name() const noexcept
{
return _filter_name;
}
private:
std::string _filter_name;
};
} // namespace quill

View file

@ -0,0 +1,457 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/LogLevel.h"
#include "quill/sinks/StreamSink.h"
#include <array>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <io.h>
#include <windows.h>
#else
#include <cstdlib>
#include <unistd.h>
#endif
namespace quill
{
/** Forward Declaration **/
class MacroMetadata;
#if defined(_WIN32)
/**
* Represents console colours
*/
class ConsoleColours
{
private:
// define our own to avoid including windows.h in the header..
using WORD = unsigned short;
public:
ConsoleColours() { _colours.fill(white); }
~ConsoleColours() = default;
/**
* Sets some default colours for terminal
*/
void set_default_colours() noexcept
{
set_colour(LogLevel::TraceL3, white);
set_colour(LogLevel::TraceL2, white);
set_colour(LogLevel::TraceL1, white);
set_colour(LogLevel::Debug, cyan);
set_colour(LogLevel::Info, green);
set_colour(LogLevel::Warning, yellow | bold);
set_colour(LogLevel::Error, red | bold);
set_colour(LogLevel::Critical, on_red | bold | white); // white bold on red background
set_colour(LogLevel::Backtrace, magenta);
}
/**
* Sets a custom colour per log level
* @param log_level the log level
* @param colour the colour
*/
void set_colour(LogLevel log_level, WORD colour) noexcept
{
auto const log_lvl = static_cast<uint32_t>(log_level);
_colours[log_lvl] = colour;
_using_colours = true;
}
/**
* @return true if we are in terminal and have also enabled colours
*/
QUILL_NODISCARD bool can_use_colours() const noexcept
{
return _can_use_colours && _using_colours;
}
/**
* @return true if we have setup colours
*/
QUILL_NODISCARD bool using_colours() const noexcept { return _using_colours; }
/**
* The colour for the given log level
* @param log_level the message log level
* @return the configured colour for this log level
*/
QUILL_NODISCARD WORD colour_code(LogLevel log_level) const noexcept
{
auto const log_lvl = static_cast<uint32_t>(log_level);
return _colours[log_lvl];
}
static inline WORD const bold = FOREGROUND_INTENSITY;
static inline WORD const black = 0;
static inline WORD const red = FOREGROUND_RED;
static inline WORD const green = FOREGROUND_GREEN;
static inline WORD const yellow = FOREGROUND_RED | FOREGROUND_GREEN;
static inline WORD const blue = FOREGROUND_BLUE;
static inline WORD const magenta = FOREGROUND_RED | FOREGROUND_BLUE;
static inline WORD const cyan = FOREGROUND_GREEN | FOREGROUND_BLUE;
static inline WORD const white = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
static inline WORD const on_red = BACKGROUND_RED;
static inline WORD const on_green = BACKGROUND_GREEN;
static inline WORD const on_yellow = BACKGROUND_RED | BACKGROUND_GREEN;
static inline WORD const on_blue = BACKGROUND_BLUE;
static inline WORD const on_magenta = BACKGROUND_RED | BACKGROUND_BLUE;
static inline WORD const on_cyan = BACKGROUND_GREEN | BACKGROUND_BLUE;
static inline WORD const on_white = BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE;
private:
friend class ConsoleSink;
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_in_terminal(FILE* file) noexcept
{
bool const is_atty = _isatty(_fileno(file)) != 0;
// ::GetConsoleMode() should return 0 if file is redirected or does not point to the actual console
DWORD console_mode;
bool const is_console =
GetConsoleMode(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(file))), &console_mode) != 0;
return is_atty && is_console;
}
void _set_can_use_colours(FILE* file) noexcept { _can_use_colours = _is_in_terminal(file); }
private:
std::array<WORD, 10> _colours = {0}; /**< Colours per log level */
bool _using_colours{false};
bool _can_use_colours{false};
};
#else
/**
* Represents console colours
*/
class ConsoleColours
{
public:
ConsoleColours() { _colours.fill(white); }
~ConsoleColours() = default;
/**
* Sets some default colours for terminal
*/
void set_default_colours() noexcept
{
set_colour(LogLevel::TraceL3, white);
set_colour(LogLevel::TraceL2, white);
set_colour(LogLevel::TraceL1, white);
set_colour(LogLevel::Debug, cyan);
set_colour(LogLevel::Info, green);
set_colour(LogLevel::Warning, yellow_bold);
set_colour(LogLevel::Error, red_bold);
set_colour(LogLevel::Critical, bold_on_red);
set_colour(LogLevel::Backtrace, magenta);
}
/**
* Sets a custom colour per log level
* @param log_level the log level
* @param colour the colour
*/
void set_colour(LogLevel log_level, std::string const& colour) noexcept
{
auto const log_lvl = static_cast<uint32_t>(log_level);
_colours[log_lvl] = colour;
_using_colours = true;
}
/**
* @return true if we are in terminal and have also enabled colours
*/
QUILL_NODISCARD bool can_use_colours() const noexcept
{
return _can_use_colours && _using_colours;
}
/**
* @return true if we have setup colours
*/
QUILL_NODISCARD bool using_colours() const noexcept { return _using_colours; }
/**
* The colour for the given log level
* @param log_level the message log level
* @return the configured colour for this log level
*/
QUILL_NODISCARD std::string const& colour_code(LogLevel log_level) const noexcept
{
auto const log_lvl = static_cast<uint32_t>(log_level);
return _colours[log_lvl];
}
// Formatting codes
static inline std::string const reset{"\033[0m"};
static inline std::string const bold{"\033[1m"};
static inline std::string const dark{"\033[2m"};
static inline std::string const underline{"\033[4m"};
static inline std::string const blink{"\033[5m"};
static inline std::string const reverse{"\033[7m"};
static inline std::string const concealed{"\033[8m"};
static inline std::string const clear_line{"\033[K"};
// Foreground colors
static inline std::string const black{"\033[30m"};
static inline std::string const red{"\033[31m"};
static inline std::string const green{"\033[32m"};
static inline std::string const yellow{"\033[33m"};
static inline std::string const blue{"\033[34m"};
static inline std::string const magenta{"\033[35m"};
static inline std::string const cyan{"\033[36m"};
static inline std::string const white{"\033[37m"};
/// Background colors
static inline std::string const on_black{"\033[40m"};
static inline std::string const on_red{"\033[41m"};
static inline std::string const on_green{"\033[42m"};
static inline std::string const on_yellow{"\033[43m"};
static inline std::string const on_blue{"\033[44m"};
static inline std::string const on_magenta{"\033[45m"};
static inline std::string const on_cyan{"\033[46m"};
static inline std::string const on_white{"\033[47m"};
/// Bold colors
static inline std::string const yellow_bold{"\033[33m\033[1m"};
static inline std::string const red_bold{"\033[31m\033[1m"};
static inline std::string const bold_on_red{"\033[1m\033[41m"};
private:
friend class ConsoleSink;
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_colour_terminal() noexcept
{
// Get term from env
auto* env_p = std::getenv("TERM");
if (env_p == nullptr)
{
return false;
}
static constexpr const char* terms[] = {"ansi", "color", "console", "cygwin", "gnome",
"konsole", "kterm", "linux", "msys", "putty",
"rxvt", "screen", "vt100", "xterm", "tmux"};
// Loop through each term and check if it's found in env_p
for (const char* term : terms)
{
if (std::strstr(env_p, term) != nullptr)
{
// term found
return true;
}
}
// none of the terms are found
return false;
}
/***/
QUILL_NODISCARD QUILL_ATTRIBUTE_COLD static bool _is_in_terminal(FILE* file) noexcept
{
return ::isatty(fileno(file)) != 0;
}
void _set_can_use_colours(FILE* file) noexcept
{
_can_use_colours = _is_in_terminal(file) && _is_colour_terminal();
}
private:
std::array<std::string, 10> _colours; /**< Colours per log level */
bool _using_colours{false};
bool _can_use_colours{false};
};
#endif
/***/
class ConsoleSink : public StreamSink
{
public:
/**
* @brief Constructor
* @param console_colours console colours instance
* @param stream stream name can only be "stdout" or "stderr"
*/
explicit ConsoleSink(ConsoleColours const& console_colours, std::string const& stream = "stdout")
: StreamSink{stream, nullptr}, _console_colours(console_colours)
{
assert((stream == "stdout") || (stream == "stderr"));
// In this ctor we take a full copy of console_colours and in our instance we modify it
_console_colours._set_can_use_colours(_file);
}
/**
* @brief Constructor
* @param enable_colours enable or disable console colours
* @param stream stream name can only be "stdout" or "stderr"
*/
explicit ConsoleSink(bool enable_colours = true, std::string const& stream = "stdout")
: StreamSink{stream, nullptr}
{
assert((stream == "stdout") || (stream == "stderr"));
if (enable_colours)
{
_console_colours._set_can_use_colours(_file);
_console_colours.set_default_colours();
}
}
~ConsoleSink() override = default;
/**
* @brief Write a formatted log message to the stream
* @param log_metadata log metadata
* @param log_timestamp log timestamp
* @param thread_id thread id
* @param thread_name thread name
* @param logger_name logger name
* @param log_level log level
* @param named_args vector of key-value pairs of named args
* @param log_message log message
*/
QUILL_ATTRIBUTE_HOT void write_log_message(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::vector<std::pair<std::string, std::string>> const* named_args,
std::string_view log_message) override
{
#if defined(_WIN32)
if (_console_colours.using_colours())
{
WORD const colour_code = _console_colours.colour_code(log_level);
// Set foreground colour and store the original attributes
WORD const orig_attribs = _set_foreground_colour(colour_code);
auto out_handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(_file)));
// Write to console
bool const write_to_console = WriteConsoleA(
out_handle, log_message.data(), static_cast<DWORD>(log_message.size()), nullptr, nullptr);
if (QUILL_UNLIKELY(!write_to_console))
{
auto const error = std::error_code(GetLastError(), std::system_category());
QUILL_THROW(QuillError{std::string{"WriteConsoleA failed. error: "} + error.message() +
std::string{" errno: "} + std::to_string(error.value())});
}
// reset to orig colors
bool const set_text_attr = SetConsoleTextAttribute(out_handle, orig_attribs);
if (QUILL_UNLIKELY(!set_text_attr))
{
auto const error = std::error_code(GetLastError(), std::system_category());
QUILL_THROW(QuillError{std::string{"SetConsoleTextAttribute failed. error: "} + error.message() +
std::string{" errno: "} + std::to_string(error.value())});
}
}
else
{
// Write record to file
StreamSink::write_log_message(log_metadata, log_timestamp, thread_id, thread_name,
logger_name, log_level, named_args, log_message);
}
#else
if (_console_colours.can_use_colours())
{
// Write colour code
std::string const& colour_code = _console_colours.colour_code(log_level);
safe_fwrite(colour_code.data(), sizeof(char), colour_code.size(), _file);
}
// Write record to file
StreamSink::write_log_message(log_metadata, log_timestamp, thread_id, thread_name, logger_name,
log_level, named_args, log_message);
if (_console_colours.can_use_colours())
{
safe_fwrite(ConsoleColours::reset.data(), sizeof(char), ConsoleColours::reset.size(), _file);
}
#endif
}
/**
* Used internally to enable the console colours on "stdout" sink
*/
QUILL_ATTRIBUTE_COLD void enable_console_colours() noexcept
{
_console_colours.set_default_colours();
}
private:
#if defined(_WIN32)
QUILL_NODISCARD ConsoleColours::WORD _set_foreground_colour(ConsoleColours::WORD attributes)
{
CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info;
auto const out_handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(_file)));
bool const screen_buffer_info = GetConsoleScreenBufferInfo(out_handle, &orig_buffer_info);
if (QUILL_UNLIKELY(!screen_buffer_info))
{
auto const error = std::error_code(GetLastError(), std::system_category());
QUILL_THROW(QuillError{std::string{"GetConsoleScreenBufferInfo failed. error: "} +
error.message() + std::string{" errno: "} + std::to_string(error.value())});
}
WORD back_color = orig_buffer_info.wAttributes;
// retrieve the current background color
back_color &=
static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY));
// keep the background color unchanged
bool const console_text_attr = SetConsoleTextAttribute(out_handle, attributes | back_color);
if (QUILL_UNLIKELY(!console_text_attr))
{
auto const error = std::error_code(GetLastError(), std::system_category());
QUILL_THROW(QuillError{std::string{"SetConsoleTextAttribute failed. error: "} + error.message() +
std::string{" errno: "} + std::to_string(error.value())});
}
return orig_buffer_info.wAttributes; // return orig attribs
}
#endif
protected:
// protected in case someone wants to derive from this class and create a custom one, e.g. for json logging to stdout
ConsoleColours _console_colours;
};
} // namespace quill

View file

@ -0,0 +1,370 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/Filesystem.h"
#include "quill/core/QuillError.h"
#include "quill/core/TimeUtilities.h"
#include "quill/sinks/StreamSink.h"
#include <cassert>
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <string>
#include <system_error>
#include <utility>
#if defined(_WIN32)
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
// Mingw already defines this, so no need to redefine
#define NOMINMAX
#endif
#include <io.h>
#include <windows.h>
#elif defined(__APPLE__)
#include <unistd.h>
#elif defined(__CYGWIN__)
#include <unistd.h>
#elif defined(__linux__)
#include <unistd.h>
#elif defined(__NetBSD__)
#include <unistd.h>
#elif defined(__FreeBSD__)
#include <unistd.h>
#elif defined(__DragonFly__)
#include <unistd.h>
#else
#include <unistd.h>
#endif
namespace quill
{
enum class FilenameAppendOption : uint8_t
{
StartDateTime,
StartDate,
None
};
/**
* The FileSinkConfig class holds the configuration options for the FileSink
*/
class FileSinkConfig
{
public:
/**
* @brief Sets the append type for the file name.
* Possible append types are: StartDate, StartDateTime or None.
* When this option is set, the file name will be appended with the start date or date and time
* timestamp of when the process started.
*
* For example:
* application.log -> application_20230101.log (StartDate)
* application.log -> application_20230101_121020.log (StartDateTime)
*
* @param value The append type to set. Valid options are Date and DateAndTime.
*/
QUILL_ATTRIBUTE_COLD void set_filename_append_option(FilenameAppendOption value)
{
_filename_append_option = value;
}
/**
* @brief Sets the timezone to use for time-based operations e.g. when appending the date to the
* get_filename or when setting the logging pattern.
* Valid options for the timezone are 'LocalTime' or 'GmtTime'
* The default value is 'LocalTime'
* @param timezone The timezone to use for time-based operations.
*/
QUILL_ATTRIBUTE_COLD void set_timezone(Timezone timezone) { _time_zone = timezone; }
/**
* @brief Sets whether fsync should be performed when flushing.
* The default value is false.
* @param value True to perform fsync, false otherwise.
*/
QUILL_ATTRIBUTE_COLD void set_do_fsync(bool value) { _fsync_enabled = value; }
/**
* @brief Sets the open mode for the file.
* Valid options for the open mode are 'a' or 'w'. The default value is 'a'.
* @param open_mode open mode for the file.
*/
QUILL_ATTRIBUTE_COLD void set_open_mode(char open_mode) { _open_mode = open_mode; }
/** Getters **/
QUILL_NODISCARD bool fsync_enabled() const noexcept { return _fsync_enabled; }
QUILL_NODISCARD Timezone timezone() const noexcept { return _time_zone; }
QUILL_NODISCARD FilenameAppendOption append_to_filename_option() const noexcept
{
return _filename_append_option;
}
QUILL_NODISCARD std::string const& open_mode() const noexcept { return _open_mode; }
private:
std::string _open_mode{'w'};
Timezone _time_zone{Timezone::LocalTime};
FilenameAppendOption _filename_append_option{FilenameAppendOption::None};
bool _fsync_enabled{false};
};
/**
* FileSink
* Writes the log messages to a file
*/
class FileSink : public StreamSink
{
public:
/**
* Construct a FileSink object.
* This constructor will always attempt to open the given file.
* @param filename Path to the file to be opened.
* @param config Configuration for the FileSink.
* @param file_event_notifier Notifies on file events.
* @param do_fopen If false, the file will not be opened.
*/
explicit FileSink(fs::path const& filename, FileSinkConfig const& config = FileSinkConfig{},
FileEventNotifier file_event_notifier = FileEventNotifier{}, bool do_fopen = true)
: StreamSink(_get_updated_filename_with_appended_datetime(
filename, config.append_to_filename_option(), config.timezone()),
nullptr, std::move(file_event_notifier)),
_config(config)
{
if (do_fopen)
{
open_file(_filename, _config.open_mode());
}
}
~FileSink() override { close_file(); }
/**
* Flushes the stream and optionally fsyncs it.
*/
QUILL_ATTRIBUTE_HOT void flush_sink() override
{
if (!_write_occurred || !_file)
{
// Check here because StreamSink::flush() will set _write_occurred to false
return;
}
StreamSink::flush_sink();
if (_config.fsync_enabled())
{
fsync_file(_file);
}
if (!fs::exists(_filename))
{
// after flushing the file we can check if the file still exists. If not we reopen it.
// This can happen if a user deletes a file while the application is running
close_file();
// now reopen the file for writing again, it will be a new file
open_file(_filename, "w");
}
}
protected:
/**
* Format a datetime string.
* @param timestamp_ns Timestamp in nanoseconds.
* @param timezone Timezone to use.
* @param with_time Include time in the string if true.
* @return Formatted datetime string.
*/
QUILL_NODISCARD static std::string format_datetime_string(uint64_t timestamp_ns, Timezone timezone, bool with_time)
{
// convert to seconds
auto const time_now = static_cast<time_t>(timestamp_ns / 1000000000);
tm now_tm;
if (timezone == Timezone::GmtTime)
{
detail::gmtime_rs(&time_now, &now_tm);
}
else
{
detail::localtime_rs(&time_now, &now_tm);
}
// Construct the string
char buffer[32];
if (with_time)
{
std::snprintf(buffer, sizeof(buffer), "%04d%02d%02d_%02d%02d%02d", now_tm.tm_year + 1900,
now_tm.tm_mon + 1, now_tm.tm_mday, now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);
}
else
{
std::snprintf(buffer, sizeof(buffer), "%04d%02d%02d", now_tm.tm_year + 1900,
now_tm.tm_mon + 1, now_tm.tm_mday);
}
return std::string{buffer};
}
/**
* Extract stem and extension from a filename.
* @param filename Path to the file.
* @return Pair containing stem and extension.
*/
QUILL_NODISCARD static std::pair<std::string, std::string> extract_stem_and_extension(fs::path const& filename) noexcept
{
// filename and extension
return std::make_pair((filename.parent_path() / filename.stem()).string(), filename.extension().string());
}
/**
* Append date and/or time to a filename.
* @param filename Path to the file.
* @param with_time Include time in the filename if true.
* @param timezone Timezone to use.
* @param timestamp Timestamp to use.
* @return Updated filename.
*/
QUILL_NODISCARD static fs::path append_datetime_to_filename(
fs::path const& filename, bool with_time = false, Timezone timezone = Timezone::LocalTime,
std::chrono::system_clock::time_point timestamp = {}) noexcept
{
// Get base file and extension
auto const [stem, ext] = extract_stem_and_extension(filename);
// Get the time now as tm from user or default to now
std::chrono::system_clock::time_point const ts_now =
(timestamp == std::chrono::system_clock::time_point{}) ? std::chrono::system_clock::now() : timestamp;
uint64_t const timestamp_ns = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(ts_now.time_since_epoch()).count());
// Construct a filename
return stem + "_" + format_datetime_string(timestamp_ns, timezone, with_time) + ext;
}
/**
* Open a file.
* @param filename Path to the file.
* @param mode File open mode.
*/
void open_file(fs::path const& filename, std::string const& mode)
{
if (_file_event_notifier.before_open)
{
_file_event_notifier.before_open(filename);
}
if (!filename.parent_path().empty())
{
std::error_code ec;
fs::create_directories(filename.parent_path(), ec);
if (ec)
{
QUILL_THROW(QuillError{std::string{"create directories failed path: "} +
filename.parent_path().string() + " error: " + ec.message()});
}
}
_file = fopen(filename.string().data(), mode.data());
if (!_file)
{
QUILL_THROW(QuillError{std::string{"fopen failed failed path: "} + filename.string() + " mode: " + mode +
" errno: " + std::to_string(errno) + " error: " + strerror(errno)});
}
assert(_file && "open_file always returns a valid pointer or throws");
if (_file_event_notifier.after_open)
{
_file_event_notifier.after_open(filename, _file);
}
}
/**
* Close the file.
*/
void close_file()
{
if (!_file)
{
return;
}
if (_file_event_notifier.before_close)
{
_file_event_notifier.before_close(_filename, _file);
}
fclose(_file);
_file = nullptr;
if (_file_event_notifier.after_close)
{
_file_event_notifier.after_close(_filename);
}
}
/**
* Fsync the file descriptor.
* @param fd File descriptor.
* @return True if successful, false otherwise.
*/
static bool fsync_file(FILE* fd) noexcept
{
#ifdef _WIN32
return FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fd)))) != 0;
#else
return ::fsync(fileno(fd)) == 0;
#endif
}
private:
/**
* Get the filename with appended date and/or time.
* @param filename Path to the file.
* @param append_to_filename_option Append option.
* @param timezone Timezone to use.
* @return Updated filename.
*/
QUILL_NODISCARD quill::fs::path _get_updated_filename_with_appended_datetime(
quill::fs::path const& filename, quill::FilenameAppendOption append_to_filename_option, quill::Timezone timezone)
{
if ((append_to_filename_option == quill::FilenameAppendOption::None) || (filename == "/dev/null"))
{
return filename;
}
if (append_to_filename_option == quill::FilenameAppendOption::StartDate)
{
return append_datetime_to_filename(filename, false, timezone);
}
if (append_to_filename_option == quill::FilenameAppendOption::StartDateTime)
{
return append_datetime_to_filename(filename, true, timezone);
}
return quill::fs::path{};
}
private:
FileSinkConfig _config;
};
} // namespace quill

View file

@ -0,0 +1,76 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/bundled/fmt/core.h"
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/FormatBuffer.h"
#include "quill/core/LogLevel.h"
#include "quill/core/MacroMetadata.h"
#include "quill/sinks/StreamSink.h"
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace quill
{
class JsonConsoleSink : public StreamSink
{
public:
JsonConsoleSink() : StreamSink("stdout", nullptr) {}
~JsonConsoleSink() override = default;
/**
* @brief Logs a formatted log message to the sink.
* @note Accessor for backend processing.
* @param log_metadata Pointer to the macro metadata.
* @param log_timestamp Timestamp of the log event.
* @param thread_id ID of the thread.
* @param thread_name Name of the thread.
* @param logger_name Name of the logger.
* @param log_level Log level of the message.
* @param named_args Vector of key-value pairs of named args
*/
QUILL_ATTRIBUTE_HOT void write_log_message(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::vector<std::pair<std::string, std::string>> const* named_args,
std::string_view) override
{
_json_message.clear();
_json_message.append(fmtquill::format(
R"({{"timestamp":"{}","file_name":"{}","line":"{}","thread_id":"{}","logger":"{}","log_level":"{}","message":"{}")",
std::to_string(log_timestamp), log_metadata->file_name(), log_metadata->line(), thread_id,
logger_name, loglevel_to_string(log_level), log_metadata->message_format()));
if (named_args)
{
for (auto const& [key, value] : *named_args)
{
_json_message.append(",\"");
_json_message.append(key);
_json_message.append("\":\"");
_json_message.append(value);
_json_message.append("\"");
}
}
_json_message.append("}\n");
StreamSink::write_log_message(log_metadata, log_timestamp, thread_id, thread_name, logger_name,
log_level, named_args,
std::string_view{_json_message.data(), _json_message.size()});
}
private:
detail::FormatBuffer _json_message;
};
} // namespace quill

View file

@ -0,0 +1,91 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/bundled/fmt/core.h"
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/Filesystem.h"
#include "quill/core/FormatBuffer.h"
#include "quill/core/LogLevel.h"
#include "quill/core/MacroMetadata.h"
#include "quill/sinks/FileSink.h"
#include "quill/sinks/StreamSink.h"
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace quill
{
/**
* The JsonFileSinkConfig class holds the configuration options for the JsonFileSink
*/
class JsonFileSinkConfig : public FileSinkConfig
{
public:
JsonFileSinkConfig() = default;
};
class JsonFileSink : public FileSink
{
public:
JsonFileSink(fs::path const& filename, JsonFileSinkConfig const& config, FileEventNotifier file_event_notifier)
: FileSink(filename, static_cast<FileSinkConfig const&>(config), std::move(file_event_notifier))
{
}
~JsonFileSink() override = default;
/**
* @brief Logs a formatted log message to the sink.
* @note Accessor for backend processing.
* @param log_metadata Pointer to the macro metadata.
* @param log_timestamp Timestamp of the log event.
* @param thread_id ID of the thread.
* @param thread_name Name of the thread.
* @param logger_name Name of the logger.
* @param log_level Log level of the message.
* @param named_args Vector of key-value pairs of named args
*/
QUILL_ATTRIBUTE_HOT void write_log_message(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::vector<std::pair<std::string, std::string>> const* named_args,
std::string_view) override
{
_json_message.clear();
_json_message.append(fmtquill::format(
R"({{"timestamp":"{}","file_name":"{}","line":"{}","thread_id":"{}","logger":"{}","log_level":"{}","message":"{}")",
std::to_string(log_timestamp), log_metadata->file_name(), log_metadata->line(), thread_id,
logger_name, loglevel_to_string(log_level), log_metadata->message_format()));
if (named_args)
{
for (auto const& [key, value] : *named_args)
{
_json_message.append(",\"");
_json_message.append(key);
_json_message.append("\":\"");
_json_message.append(value);
_json_message.append("\"");
}
}
_json_message.append("}\n");
StreamSink::write_log_message(log_metadata, log_timestamp, thread_id, thread_name, logger_name,
log_level, named_args,
std::string_view{_json_message.data(), _json_message.size()});
}
private:
detail::FormatBuffer _json_message;
};
} // namespace quill

View file

@ -0,0 +1,36 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/LogLevel.h"
#include "quill/sinks/Sink.h"
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace quill
{
/** Forward Declaration **/
class MacroMetadata;
class NullSink : public Sink
{
public:
QUILL_ATTRIBUTE_HOT void write_log_message(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::vector<std::pair<std::string, std::string>> const* named_args,
std::string_view log_message) override
{
}
QUILL_ATTRIBUTE_HOT void flush_sink() override {}
};
} // namespace quill

View file

@ -0,0 +1,796 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Common.h"
#include "quill/core/Filesystem.h"
#include "quill/core/LogLevel.h"
#include "quill/core/QuillError.h"
#include "quill/core/TimeUtilities.h"
#include "quill/sinks/FileSink.h"
#include "quill/sinks/StreamSink.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <ctime>
#include <deque>
#include <limits>
#include <ratio>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
namespace quill
{
/** Forward Declaration **/
class MacroMetadata;
/**
* @brief The configuration options for the RotatingFileSink
*/
class RotatingFileSinkConfig : public FileSinkConfig
{
public:
/**
* @brief The frequency of log file rotation
*/
enum class RotationFrequency : uint8_t
{
Disabled,
Daily,
Hourly,
Minutely
};
/**
* @brief The naming scheme for rotated log files
*/
enum class RotationNamingScheme : uint8_t
{
Index,
Date,
DateAndTime
};
/**
* @brief Constructs a new RotatingFileSinkConfig object.
*/
RotatingFileSinkConfig() : _daily_rotation_time{_disabled_daily_rotation_time()} {}
/**
* @brief Sets the maximum file size for rotation.
* @param value The maximum file size in bytes per file
*/
QUILL_ATTRIBUTE_COLD void set_rotation_max_file_size(size_t value)
{
if (value < 512)
{
QUILL_THROW(QuillError{"rotation_max_file_size must be greater than or equal to 512 bytes"});
}
_rotation_max_file_size = value;
}
/**
* @brief Sets the frequency and interval of file rotation.
* @param frequency The frequency of file rotation ('M' for minutes, 'H' for hours)
* @param interval The rotation interval
*/
QUILL_ATTRIBUTE_COLD void set_rotation_frequency_and_interval(char frequency, uint32_t interval)
{
if (frequency == 'M' || frequency == 'm')
{
_rotation_frequency = RotationFrequency::Minutely;
}
else if (frequency == 'H' || frequency == 'h')
{
_rotation_frequency = RotationFrequency::Hourly;
}
else
{
QUILL_THROW(QuillError{
"Invalid frequency. Valid values are 'M' or 'm' for minutes or 'H' or 'h' for hours"});
}
if (interval == 0)
{
QUILL_THROW(QuillError{"interval must be set to a value greater than 0"});
}
_rotation_interval = interval;
_daily_rotation_time = _disabled_daily_rotation_time();
}
/**
* @brief Sets the time of day for daily log file rotation.
* @param daily_rotation_time_str The time of day for rotation (format: "HH:MM")
*/
QUILL_ATTRIBUTE_COLD void set_rotation_time_daily(std::string const& daily_rotation_time_str)
{
_rotation_frequency = RotationFrequency::Daily;
_rotation_interval = 0;
_daily_rotation_time = _parse_daily_rotation_time(daily_rotation_time_str);
}
/**
* @brief Sets the maximum number of log files to keep.
* @param value The maximum number of log files
*/
QUILL_ATTRIBUTE_COLD void set_max_backup_files(uint32_t value) { _max_backup_files = value; }
/**
* @brief Sets whether the oldest rolled logs should be overwritten when the maximum backup count
* is reached. If set to false, the oldest logs will not be overwritten when the maximum backup
* count is reached, and log file rotation will stop. The default value is true.
* @param value True to overwrite the oldest logs, false otherwise.
*/
QUILL_ATTRIBUTE_COLD void set_overwrite_rolled_files(bool value)
{
_overwrite_rolled_files = value;
}
/**
* @brief Sets whether previous rotated log files should be removed on process start up.
* @note This option works only when using the mode="w"
* This is useful to avoid conflicting file names when the process restarts and
* FilenameAppend::DateTime was not set. The default value is true.
* @param value True to remove old log files, false otherwise.
*/
QUILL_ATTRIBUTE_COLD void set_remove_old_files(bool value) { _remove_old_files = value; }
/**
* @brief Sets the naming scheme for the rotated files.
* The default value is 'Index'.
* @param value The naming scheme to set.
*/
QUILL_ATTRIBUTE_COLD void set_rotation_naming_scheme(RotationNamingScheme value)
{
_rotation_naming_scheme = value;
}
/** Getter methods **/
QUILL_NODISCARD size_t rotation_max_file_size() const noexcept { return _rotation_max_file_size; }
QUILL_NODISCARD uint32_t max_backup_files() const noexcept { return _max_backup_files; }
QUILL_NODISCARD bool overwrite_rolled_files() const noexcept { return _overwrite_rolled_files; }
QUILL_NODISCARD bool remove_old_files() const noexcept { return _remove_old_files; }
QUILL_NODISCARD RotationFrequency rotation_frequency() const noexcept
{
return _rotation_frequency;
}
QUILL_NODISCARD uint32_t rotation_interval() const noexcept { return _rotation_interval; }
QUILL_NODISCARD std::pair<std::chrono::hours, std::chrono::minutes> daily_rotation_time() const noexcept
{
return _daily_rotation_time;
}
QUILL_NODISCARD RotationNamingScheme rotation_naming_scheme() const noexcept
{
return _rotation_naming_scheme;
}
private:
/***/
std::pair<std::chrono::hours, std::chrono::minutes> _disabled_daily_rotation_time() noexcept
{
return std::make_pair(std::chrono::hours{std::numeric_limits<std::chrono::hours::rep>::max()},
std::chrono::minutes{std::numeric_limits<std::chrono::hours::rep>::max()});
}
/***/
std::pair<std::chrono::hours, std::chrono::minutes> _parse_daily_rotation_time(std::string const& daily_rotation_time_str)
{
std::vector<std::string> tokens;
std::string token;
size_t start = 0, end = 0;
while ((end = daily_rotation_time_str.find(':', start)) != std::string::npos)
{
token = daily_rotation_time_str.substr(start, end - start);
tokens.push_back(token);
start = end + 1;
}
// Add the last token (or the only token if there's no delimiter)
token = daily_rotation_time_str.substr(start);
tokens.push_back(token);
if (tokens.size() != 2)
{
QUILL_THROW(
QuillError{"Invalid daily_rotation_time_str value format. The format should be `HH:MM`."});
}
for (auto const& parsed_token : tokens)
{
if (parsed_token.size() != 2)
{
QUILL_THROW(QuillError{
"Invalid daily_rotation_time_str value format. Each component of the time (HH and MM) "
"should be two digits."});
}
}
auto const daily_rotation_time_str_tp = std::make_pair(
std::chrono::hours{std::stoi(tokens[0])}, std::chrono::minutes{std::stoi(tokens[1])});
if ((daily_rotation_time_str_tp.first > std::chrono::hours{23}) ||
(daily_rotation_time_str_tp.second > std::chrono::minutes{59}))
{
QUILL_THROW(
QuillError("Invalid rotation values. The hour value should be between 00 and 23, and the "
"minute value should be between 00 and 59."));
}
return daily_rotation_time_str_tp;
}
private:
std::pair<std::chrono::hours, std::chrono::minutes> _daily_rotation_time;
size_t _rotation_max_file_size{0}; // 0 means disabled
uint32_t _max_backup_files{std::numeric_limits<uint32_t>::max()}; // max means disabled
uint32_t _rotation_interval{0}; // 0 means disabled
RotationFrequency _rotation_frequency{RotationFrequency::Disabled};
RotationNamingScheme _rotation_naming_scheme{RotationNamingScheme::Index};
bool _overwrite_rolled_files{true};
bool _remove_old_files{true};
};
/**
* @brief The RotatingFileSink class
*/
class RotatingFileSink : public FileSink
{
public:
/**
* @brief Constructor.
*
* Creates a new instance of the RotatingFileSink class.
*
* @param filename The base file name to be used for logs.
* @param config The sink configuration.
* @param file_event_notifier file event notifier
* @param start_time start time
*/
RotatingFileSink(fs::path const& filename, RotatingFileSinkConfig const& config,
FileEventNotifier file_event_notifier = FileEventNotifier{},
std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now())
: FileSink(filename, static_cast<FileSinkConfig const&>(config), std::move(file_event_notifier), false),
_config(config)
{
uint64_t const today_timestamp_ns = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count());
_clean_and_recover_files(filename, _config.open_mode(), today_timestamp_ns);
if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled)
{
// Calculate next rotation time
_next_rotation_time = _calculate_initial_rotation_tp(
static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count()),
config);
}
// Open file for logging
open_file(_filename, _config.open_mode());
_open_file_timestamp = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count());
_created_files.emplace_front(_filename, 0, std::string{});
if (!is_null())
{
_file_size = _get_file_size(_filename);
}
}
~RotatingFileSink() override = default;
/**
* @brief Writes a formatted log message to the stream
* @param log_metadata The metadata of the log message
* @param log_timestamp The timestamp of the log message
* @param thread_id The ID of the thread that generated the log message
* @param thread_name The name of the thread that generated the log message
* @param logger_name The name of the logger
* @param log_level The log level of the message
* @param named_args Structured key-value pairs associated with the log message
* @param log_message The log message to write
*/
QUILL_ATTRIBUTE_HOT void write_log_message(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::vector<std::pair<std::string, std::string>> const* named_args,
std::string_view log_message) override
{
if (is_null())
{
StreamSink::write_log_message(log_metadata, log_timestamp, thread_id, thread_name,
logger_name, log_level, named_args, log_message);
return;
}
bool time_rotation = false;
if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled)
{
// Check if we need to rotate based on time
time_rotation = _time_rotation(log_timestamp);
}
if (!time_rotation && _config.rotation_max_file_size() != 0)
{
// Check if we need to rotate based on size
_size_rotation(log_message.size(), log_timestamp);
}
// write to file
StreamSink::write_log_message(log_metadata, log_timestamp, thread_id, thread_name, logger_name,
log_level, named_args, log_message);
_file_size += log_message.size();
}
private:
/***/
QUILL_NODISCARD bool _time_rotation(uint64_t record_timestamp_ns)
{
if (record_timestamp_ns >= _next_rotation_time)
{
_rotate_files(record_timestamp_ns);
_next_rotation_time = _calculate_rotation_tp(record_timestamp_ns, _config);
return true;
}
return false;
}
/***/
void _size_rotation(size_t log_msg_size, uint64_t record_timestamp_ns)
{
// Calculate the new size of the file
if (_file_size + log_msg_size > _config.rotation_max_file_size())
{
_rotate_files(record_timestamp_ns);
}
}
/***/
void _rotate_files(uint64_t record_timestamp_ns)
{
if ((_created_files.size() > _config.max_backup_files()) && !_config.overwrite_rolled_files())
{
// We have reached the max number of backup files, and we are not allowed to overwrite the
// oldest file. We will stop rotating
return;
}
// We need to flush and also fsync before actually getting the size of the file
FileSink::flush_sink();
FileSink::fsync_file(_file);
if (_get_file_size(_filename) <= 0)
{
// Also check the file size is > 0 to better deal with full disk
return;
}
close_file();
// datetime_suffix will be empty if we are using the default naming scheme
std::string datetime_suffix;
if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date)
{
datetime_suffix = format_datetime_string(_open_file_timestamp, _config.timezone(), false);
}
else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::DateAndTime)
{
datetime_suffix = format_datetime_string(_open_file_timestamp, _config.timezone(), true);
}
// We need to rotate the files and rename them with an index
for (auto it = _created_files.rbegin(); it != _created_files.rend(); ++it)
{
// Create each existing filename on disk with the existing index.
// when the index is 0 we want to rename the latest file
fs::path existing_file;
fs::path renamed_file;
existing_file = _get_filename(it->base_filename, it->index, it->date_time);
// increment the index if needed and rename the file
uint32_t index_to_use = it->index;
if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index ||
it->date_time == datetime_suffix)
{
// we are rotating and incrementing the index, or we have another file with the same date_time suffix
index_to_use += 1;
renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix);
it->index = index_to_use;
it->date_time = datetime_suffix;
_rename_file(existing_file, renamed_file);
}
else if (it->date_time.empty())
{
// we are renaming the latest file
index_to_use = it->index;
renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix);
it->index = index_to_use;
it->date_time = datetime_suffix;
_rename_file(existing_file, renamed_file);
}
}
// Check if we have too many files in the queue remove_file the oldest one
if (_created_files.size() > _config.max_backup_files())
{
// remove_file that file from the system and also pop it from the queue
fs::path const removed_file = _get_filename(
_created_files.back().base_filename, _created_files.back().index, _created_files.back().date_time);
_remove_file(removed_file);
_created_files.pop_back();
}
// add the current file back to the list with index 0
_created_files.emplace_front(_filename, 0, std::string{});
// Open file for logging
open_file(_filename, "w");
_open_file_timestamp = record_timestamp_ns;
_file_size = 0;
}
/***/
void _clean_and_recover_files(fs::path const& filename, std::string const& open_mode, uint64_t today_timestamp_ns)
{
if ((_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Index) &&
(_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Date))
{
// clean and recover is only supported for index and date naming scheme, when using
// DateAndTime there are no collisions in the filenames
return;
}
// if we are starting in "w" mode, then we also should clean all previous log files of the previous run
if (_config.remove_old_files() && (open_mode == "w"))
{
for (const auto& entry : fs::directory_iterator(fs::current_path() / filename.parent_path()))
{
if (entry.path().extension().string() != filename.extension().string())
{
// we only check for the files of the same extension to remove
continue;
}
// is_directory() does not exist in std::experimental::filesystem
if (entry.path().filename().string().find(filename.stem().string() + ".") != 0)
{
// expect to find filename.stem().string() exactly at the start of the filename
continue;
}
if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index)
{
fs::remove(entry);
}
else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date)
{
// Find the first dot in the filename
// stem will be something like `logfile.1`
if (size_t const pos = entry.path().stem().string().find_last_of('.'); pos != std::string::npos)
{
// Get the today's date, we won't remove the files of the previous dates as they won't collide
std::string const today_date =
format_datetime_string(today_timestamp_ns, _config.timezone(), false);
if (std::string const index_or_date =
entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length());
(index_or_date.length() >= 8) && (index_or_date == today_date))
{
// assume it is a date, no need to find the index
if (index_or_date == today_date)
{
fs::remove(entry);
}
}
else
{
// assume it is an index
// Find the second last dot to get the date
std::string const filename_with_date = entry.path().filename().string().substr(0, pos);
if (size_t const second_last = filename_with_date.find_last_of('.'); second_last != std::string::npos)
{
if (std::string const date_part =
filename_with_date.substr(second_last + 1, filename_with_date.length());
date_part == today_date)
{
fs::remove(entry);
}
}
}
}
}
}
}
else if (open_mode == "a")
{
// we need to recover the index from the existing files
for (const auto& entry : fs::directory_iterator(fs::current_path() / filename.parent_path()))
{
// is_directory() does not exist in std::experimental::filesystem
if (entry.path().extension().string() != filename.extension().string())
{
// we only check for the files of the same extension to remove
continue;
}
// is_directory() does not exist in std::experimental::filesystem
if (entry.path().filename().string().find(filename.stem().string() + ".") != 0)
{
// expect to find filename.stem().string() exactly at the start of the filename
continue;
}
std::string const extension = entry.path().extension().string(); // e.g. ".log"
// stem will be something like `logfile.1`
if (size_t const pos = entry.path().stem().string().find_last_of('.'); pos != std::string::npos)
{
if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index)
{
std::string const index =
entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length());
std::string const current_filename = entry.path().filename().string().substr(0, pos) + extension;
fs::path current_file = entry.path().parent_path();
current_file.append(current_filename);
// Attempt to convert the index to a number
QUILL_TRY
{
_created_files.emplace_front(current_file, static_cast<uint32_t>(std::stoul(index)),
std::string{});
}
QUILL_CATCH_ALL() { continue; }
}
else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date)
{
// Get the today's date, we won't remove the files of the previous dates as they won't collide
std::string const today_date =
format_datetime_string(today_timestamp_ns, _config.timezone(), false);
if (std::string const index_or_date =
entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length());
(index_or_date.length() >= 8) && (index_or_date == today_date))
{
// assume it is a date, no need to find the index
std::string const current_filename = entry.path().filename().string().substr(0, pos) + extension;
fs::path current_file = entry.path().parent_path();
current_file.append(current_filename);
_created_files.emplace_front(current_file, 0, index_or_date);
}
else
{
// assume it is an index
// Find the second last dot to get the date
std::string const filename_with_date = entry.path().filename().string().substr(0, pos);
if (size_t const second_last = filename_with_date.find_last_of('.'); second_last != std::string::npos)
{
if (std::string const date_part =
filename_with_date.substr(second_last + 1, filename_with_date.length());
date_part == today_date)
{
std::string const current_filename = filename_with_date.substr(0, second_last) + extension;
fs::path current_file = entry.path().parent_path();
current_file.append(current_filename);
// Attempt to convert the index to a number
QUILL_TRY
{
_created_files.emplace_front(
current_file, static_cast<uint32_t>(std::stoul(index_or_date)), date_part);
}
QUILL_CATCH_ALL() { continue; }
}
}
}
}
}
}
// finally we need to sort the deque
std::sort(_created_files.begin(), _created_files.end(),
[](FileInfo const& a, FileInfo const& b) { return a.index < b.index; });
}
}
/***/
QUILL_NODISCARD static size_t _get_file_size(fs::path const& filename)
{
return static_cast<size_t>(fs::file_size(filename));
}
/***/
static bool _remove_file(fs::path const& filename) noexcept
{
std::error_code ec;
fs::remove(filename, ec);
if (ec)
{
return false;
}
return true;
}
/***/
bool static _rename_file(fs::path const& previous_file, fs::path const& new_file) noexcept
{
std::error_code ec;
fs::rename(previous_file, new_file, ec);
if (ec)
{
return false;
}
return true;
}
/***/
QUILL_NODISCARD static fs::path _append_index_to_filename(fs::path const& filename, uint32_t index) noexcept
{
if (index == 0u)
{
return filename;
}
// Get base file and extension
auto const [stem, ext] = extract_stem_and_extension(filename);
return fs::path{stem + "." + std::to_string(index) + ext};
}
/***/
QUILL_NODISCARD static fs::path _append_string_to_filename(fs::path const& filename, std::string const& text) noexcept
{
if (text.empty())
{
return filename;
}
// Get base file and extension
auto const [stem, ext] = extract_stem_and_extension(filename);
return fs::path{stem + "." + text + ext};
}
/***/
static uint64_t _calculate_initial_rotation_tp(uint64_t start_time_ns, RotatingFileSinkConfig const& config)
{
time_t const time_now = static_cast<time_t>(start_time_ns) / 1000000000;
tm date;
// here we do this because of `daily_rotation_time_str` that might have specified the time in UTC
if (config.timezone() == Timezone::GmtTime)
{
detail::gmtime_rs(&time_now, &date);
}
else
{
detail::localtime_rs(&time_now, &date);
}
// update to the desired date
if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely)
{
date.tm_min += 1;
date.tm_sec = 0;
}
else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly)
{
date.tm_hour += 1;
date.tm_min = 0;
date.tm_sec = 0;
}
else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily)
{
date.tm_hour = static_cast<decltype(date.tm_hour)>(config.daily_rotation_time().first.count());
date.tm_min = static_cast<decltype(date.tm_min)>(config.daily_rotation_time().second.count());
date.tm_sec = 0;
}
else
{
QUILL_THROW(QuillError{"Invalid rotation frequency"});
}
// convert back to timestamp
time_t const rotation_time =
(config.timezone() == Timezone::GmtTime) ? detail::timegm(&date) : std::mktime(&date);
uint64_t const rotation_time_seconds = (rotation_time > time_now)
? static_cast<uint64_t>(rotation_time)
: static_cast<uint64_t>(rotation_time + std::chrono::seconds{std::chrono::hours{24}}.count());
return static_cast<uint64_t>(
std::chrono::nanoseconds{std::chrono::seconds{rotation_time_seconds}}.count());
}
/***/
static uint64_t _calculate_rotation_tp(uint64_t rotation_timestamp_ns, RotatingFileSinkConfig const& config)
{
if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely)
{
return rotation_timestamp_ns +
static_cast<uint64_t>(
std::chrono::nanoseconds{std::chrono::minutes{config.rotation_interval()}}.count());
}
if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly)
{
return rotation_timestamp_ns +
static_cast<uint64_t>(
std::chrono::nanoseconds{std::chrono::hours{config.rotation_interval()}}.count());
}
if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily)
{
return rotation_timestamp_ns + std::chrono::nanoseconds{std::chrono::hours{24}}.count();
}
QUILL_THROW(QuillError{"Invalid rotation frequency"});
}
/***/
static fs::path _get_filename(fs::path filename, uint32_t index, std::string const& date_time)
{
if (!date_time.empty())
{
filename = _append_string_to_filename(filename, date_time);
}
if (index > 0)
{
filename = _append_index_to_filename(filename, index);
}
return filename;
}
private:
struct FileInfo
{
FileInfo(fs::path base_filename, uint32_t index, std::string date_time)
: base_filename{std::move(base_filename)}, date_time{std::move(date_time)}, index{index}
{
}
fs::path base_filename;
std::string date_time;
uint32_t index;
};
FileEventNotifier _file_event_notifier;
std::deque<FileInfo> _created_files; /**< We store in a queue the filenames we created, first: index, second: date/datetime, third: base_filename */
uint64_t _next_rotation_time; /**< The next rotation time point */
uint64_t _open_file_timestamp{0}; /**< The timestamp of the currently open file */
size_t _file_size{0}; /**< The current file size */
RotatingFileSinkConfig _config;
};
} // namespace quill

View file

@ -0,0 +1,192 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/LogLevel.h"
#include "quill/core/QuillError.h"
#include "quill/filters/Filter.h"
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace quill
{
/** Forward Declaration **/
class MacroMetadata;
/**
* Base class for sinks
*/
class Sink
{
public:
/**
* Constructor
* Uses the default pattern formatter
*/
Sink() = default;
/**
* Destructor
*/
virtual ~Sink() = default;
Sink(Sink const&) = delete;
Sink& operator=(Sink const&) = delete;
/**
* @brief Logs a formatted log message to the sink.
* @note Accessor for backend processing.
* @param log_metadata Pointer to the macro metadata.
* @param log_timestamp Timestamp of the log event.
* @param thread_id ID of the thread.
* @param thread_name Name of the thread.
* @param logger_name Name of the logger.
* @param log_level Log level of the message.
* @param named_args Vector of key-value pairs of named args
* @param log_message The log message.
*/
QUILL_ATTRIBUTE_HOT virtual void write_log_message(
MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id,
std::string_view thread_name, std::string_view logger_name, LogLevel log_level,
std::vector<std::pair<std::string, std::string>> const* named_args, std::string_view log_message) = 0;
/**
* @brief Flushes the sink, synchronizing the associated sink with its controlled output sequence.
*/
QUILL_ATTRIBUTE_HOT virtual void flush_sink() = 0;
/**
* @brief Executes periodic tasks by the backend thread, providing an opportunity for the user
* to perform custom tasks. For example, batch committing to a database, or any other
* desired periodic operations.
* @note It is recommended to avoid heavy operations within this function as it may affect performance of the backend thread.
*/
QUILL_ATTRIBUTE_HOT virtual void run_periodic_tasks() noexcept {}
/**
* @brief Sets a log level filter on the sink.
* @note Thread safe.
* @param log_level The log level severity.
*/
void set_log_level_filter(LogLevel log_level)
{
_log_level.store(log_level, std::memory_order_relaxed);
}
/**
* @brief Returns the current log level filter set on the sink.
* @note Thread safe.
* @return The current log level filter.
*/
QUILL_NODISCARD LogLevel get_log_level_filter() const noexcept
{
return _log_level.load(std::memory_order_relaxed);
}
/**
* @brief Adds a new filter to the sink.
* @note Thread safe.
* @param filter Unique pointer to the filter instance.
*/
void add_filter(std::unique_ptr<Filter> filter)
{
// Lock and add this filter to our global collection
std::lock_guard<std::recursive_mutex> const lock{_global_filters_lock};
// Check if the same filter already exists
auto const search_filter_it =
std::find_if(_global_filters.cbegin(), _global_filters.cend(), [&filter](std::unique_ptr<Filter> const& elem_filter)
{ return elem_filter->get_filter_name() == filter->get_filter_name(); });
if (QUILL_UNLIKELY(search_filter_it != _global_filters.cend()))
{
QUILL_THROW(QuillError{"Filter with the same name already exists"});
}
_global_filters.push_back(std::move(filter));
// Indicate a new filter was added - here relaxed is okay as the spinlock will do acq-rel on destruction
_new_filter.store(true, std::memory_order_relaxed);
}
/**
* @brief Applies all registered filters to the log record.
* @note Called internally by the backend worker thread.
* @param log_metadata Pointer to the macro metadata.
* @param log_timestamp Timestamp of the log event.
* @param thread_id ID of the thread.
* @param thread_name Name of the thread.
* @param logger_name Name of the logger.
* @param log_level Log level of the message.
* @param log_message The log message.
* @return True if the log record passes all filters, false otherwise.
*/
QUILL_NODISCARD bool apply_all_filters(MacroMetadata const* log_metadata, uint64_t log_timestamp,
std::string_view thread_id, std::string_view thread_name,
std::string_view logger_name, LogLevel log_level,
std::string_view log_message)
{
if (log_level < _log_level.load(std::memory_order_relaxed))
{
return false;
}
// Update our local collection of the filters
if (QUILL_UNLIKELY(_new_filter.load(std::memory_order_relaxed)))
{
// if there is a new filter we have to update
_local_filters.clear();
std::lock_guard<std::recursive_mutex> const lock{_global_filters_lock};
for (auto const& filter : _global_filters)
{
_local_filters.push_back(filter.get());
}
// all filters loaded so change to false
_new_filter.store(false, std::memory_order_relaxed);
}
if (_local_filters.empty())
{
return true;
}
else
{
return std::all_of(_local_filters.begin(), _local_filters.end(),
[log_metadata, log_timestamp, thread_id, thread_name, logger_name,
log_level, log_message](Filter* filter_elem)
{
return filter_elem->filter(log_metadata, log_timestamp, thread_id,
thread_name, logger_name, log_level, log_message);
});
}
}
private:
/** Local Filters for this sink **/
std::vector<Filter*> _local_filters;
/** Global filter for this sink **/
std::vector<std::unique_ptr<Filter>> _global_filters;
std::recursive_mutex _global_filters_lock;
std::atomic<LogLevel> _log_level{LogLevel::TraceL3};
/** Indicator that a new filter was added **/
std::atomic<bool> _new_filter{false};
};
} // namespace quill

View file

@ -0,0 +1,195 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Attributes.h"
#include "quill/core/Filesystem.h"
#include "quill/core/LogLevel.h"
#include "quill/core/QuillError.h"
#include "quill/sinks/Sink.h"
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <functional>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
namespace quill
{
/** Forward Declaration **/
class MacroMetadata;
/**
* @brief Notifies on file events by calling the appropriate callback, the callback is executed on
* the backend worker thread
*/
struct FileEventNotifier
{
std::function<void(fs::path const& filename)> before_open;
std::function<void(fs::path const& filename, FILE* f)> after_open;
std::function<void(fs::path const& filename, FILE* f)> before_close;
std::function<void(fs::path const& filename)> after_close;
std::function<std::string(std::string_view message)> before_write;
};
/**
* @brief StreamSink class for handling log messages
*/
class StreamSink : public Sink
{
public:
/**
* @brief Constructor for StreamSink
* @param stream The stream type (stdout, stderr, or file)
* @param file File pointer for file-based stream
* @param file_event_notifier Notifies on file events
* @throws QuillError if an invalid parameter is provided
*/
explicit StreamSink(fs::path stream, FILE* file = nullptr,
FileEventNotifier file_event_notifier = FileEventNotifier{})
: _filename(std::move(stream)), _file(file), _file_event_notifier(std::move(file_event_notifier))
{
// reserve stdout and stderr as filenames
if (_filename == std::string{"stdout"})
{
_file = stdout;
}
else if (_filename == std::string{"stderr"})
{
_file = stderr;
}
else if (_filename == std::string{"/dev/null"})
{
_is_null = true;
}
else
{
// first attempt to create any non-existing directories
std::error_code ec;
fs::path parent_path;
if (!_filename.parent_path().empty())
{
parent_path = _filename.parent_path();
fs::create_directories(parent_path, ec);
if (ec)
{
// use .string() to also support experimental fs
QUILL_THROW(QuillError{std::string{"cannot create directories for path "} +
parent_path.string() + std::string{" - error: "} + ec.message()});
}
}
else
{
parent_path = fs::current_path();
}
// convert the parent path to an absolute path
fs::path const canonical_path = fs::canonical(parent_path, ec);
if (ec)
{
// use .string() to also support experimental fs
QUILL_THROW(QuillError{std::string{"cannot create canonical path for path "} +
parent_path.string() + std::string{" - error: "} + ec.message()});
}
// finally replace the given filename's parent_path with the equivalent canonical path
_filename = canonical_path / _filename.filename();
}
}
~StreamSink() override = default;
/**
* @brief Writes a formatted log message to the stream
* @param log_message The log message to write
*/
QUILL_ATTRIBUTE_HOT void write_log_message(MacroMetadata const* /* log_metadata */,
uint64_t /* log_timestamp */, std::string_view /* thread_id */,
std::string_view /* thread_name */,
std::string_view /* logger_name */, LogLevel /* log_level */,
std::vector<std::pair<std::string, std::string>> const* /* named_args */,
std::string_view log_message) override
{
if (QUILL_UNLIKELY(!_file))
{
// FileSink::flush() tries to re-open a deleted file and if it fails _file can be null
return;
}
if (_file_event_notifier.before_write)
{
std::string const user_log_message = _file_event_notifier.before_write(log_message);
safe_fwrite(user_log_message.data(), sizeof(char), user_log_message.size(), _file);
}
else
{
safe_fwrite(log_message.data(), sizeof(char), log_message.size(), _file);
}
_write_occurred = true;
}
/**
* Flushes the stream
*/
QUILL_ATTRIBUTE_HOT void flush_sink() override
{
if (!_write_occurred || !_file)
{
return;
}
_write_occurred = false;
fflush(_file);
}
/**
* @brief Returns the name of the file
* @return The name of the file
*/
QUILL_NODISCARD virtual fs::path const& get_filename() const noexcept { return _filename; }
/**
* @brief Checks if the stream is null
* @return True if the stream is null, false otherwise
*/
QUILL_NODISCARD bool is_null() const noexcept { return _is_null; }
protected:
/**
* @brief Writes data safely to the stream
* @param ptr Pointer to the data to be written
* @param size Size of each element to be written
* @param count Number of elements to write
* @param stream The stream to write to
*/
QUILL_ATTRIBUTE_HOT static void safe_fwrite(void const* ptr, size_t size, size_t count, FILE* stream)
{
size_t const written = std::fwrite(ptr, size, count, stream);
if (QUILL_UNLIKELY(written < count))
{
QUILL_THROW(QuillError{std::string{"fwrite failed errno: "} + std::to_string(errno) +
" error: " + strerror(errno)});
}
}
protected:
fs::path _filename;
FILE* _file{nullptr};
FileEventNotifier _file_event_notifier;
bool _is_null{false};
bool _write_occurred{false};
};
} // namespace quill

View file

@ -0,0 +1,124 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <vector>
#if defined(_WIN32)
#include <string>
#endif
namespace quill::detail
{
/***/
template <typename T, size_t N>
struct ArgSizeCalculator<std::array<T, N>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, std::array<T, N> const& arg) noexcept
{
if constexpr (std::disjunction_v<std::is_arithmetic<T>, std::is_enum<T>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
return sizeof(T) * N;
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
size_t total_size{0};
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<T>::calculate(conditional_arg_size_cache, elem);
}
return total_size;
}
}
};
/***/
template <typename T, size_t N>
struct Encoder<std::array<T, N>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::array<T, N> const& arg) noexcept
{
for (auto const& elem : arg)
{
Encoder<T>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <typename T, size_t N>
#if defined(_WIN32)
struct Decoder<std::array<T, N>,
std::enable_if_t<std::negation_v<std::disjunction<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
#else
struct Decoder<std::array<T, N>>
#endif
{
static std::array<T, N> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::array<T, N> arg;
for (size_t i = 0; i < N; ++i)
{
arg[i] = Decoder<T>::decode(buffer, nullptr);
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <typename T, size_t N>
struct Decoder<std::array<T, N>,
std::enable_if_t<std::disjunction_v<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
std::vector<std::string> encoded_values;
encoded_values.reserve(N);
for (size_t i = 0; i < N; ++i)
{
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,136 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <deque>
#include <type_traits>
#include <vector>
#if defined(_WIN32)
#include <string>
#endif
namespace quill::detail
{
/***/
template <typename T, typename Allocator>
struct ArgSizeCalculator<std::deque<T, Allocator>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, std::deque<T, Allocator> const& arg) noexcept
{
// We need to store the size of the deque in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::disjunction_v<std::is_arithmetic<T>, std::is_enum<T>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += sizeof(T) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<T>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <typename T, typename Allocator>
struct Encoder<std::deque<T, Allocator>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::deque<T, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<T>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <typename T, typename Allocator>
#if defined(_WIN32)
struct Decoder<std::deque<T, Allocator>,
std::enable_if_t<std::negation_v<std::disjunction<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
#else
struct Decoder<std::deque<T, Allocator>>
#endif
{
static std::deque<T, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::deque<T, Allocator> arg;
// Read the size of the deque
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
arg.resize(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg[i] = Decoder<T>::decode(buffer, nullptr);
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <typename T, typename Allocator>
struct Decoder<std::deque<T, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
std::vector<std::string> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,90 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/core/Filesystem.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/std.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <type_traits>
namespace quill::detail
{
/***/
template <>
struct ArgSizeCalculator<fs::path>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, fs::path const& arg) noexcept
{
if constexpr (std::is_same_v<fs::path::string_type, std::string>)
{
return ArgSizeCalculator<std::string>::calculate(conditional_arg_size_cache, arg.string());
}
#if defined(_WIN32)
else if constexpr (std::is_same_v<fs::path::string_type, std::wstring>)
{
return ArgSizeCalculator<std::wstring>::calculate(conditional_arg_size_cache, arg.wstring());
}
#endif
}
};
/***/
template <>
struct Encoder<fs::path>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, fs::path const& arg) noexcept
{
if constexpr (std::is_same_v<fs::path::string_type, std::string>)
{
Encoder<std::string>::encode(buffer, conditional_arg_size_cache,
conditional_arg_size_cache_index, arg.string());
}
#if defined(_WIN32)
else if constexpr (std::is_same_v<fs::path::string_type, std::wstring>)
{
Encoder<std::wstring>::encode(buffer, conditional_arg_size_cache,
conditional_arg_size_cache_index, arg.wstring());
}
#endif
}
};
/***/
template <>
struct Decoder<fs::path>
{
static fs::path decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
fs::path arg;
if constexpr (std::is_same_v<fs::path::string_type, std::string>)
{
arg = Decoder<std::string_view>::decode(buffer, nullptr);
}
#if defined(_WIN32)
else if constexpr (std::is_same_v<fs::path::string_type, std::wstring>)
{
arg = Decoder<std::wstring_view>::decode(buffer, nullptr);
}
#endif
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
} // namespace quill::detail

View file

@ -0,0 +1,146 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <forward_list>
#include <vector>
#if defined(_WIN32)
#include <string>
#endif
namespace quill::detail
{
/***/
template <typename T, typename Allocator>
struct ArgSizeCalculator<std::forward_list<T, Allocator>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
std::forward_list<T, Allocator> const& arg) noexcept
{
// We need to store the size of the forward_list in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
// Cache the number of elements of the forward list to avoid iterating again when
// encoding. This information is inserted first to maintain sequence in the cache.
// It will be replaced with the actual count after calculating the size of elements.
size_t number_of_elements{0};
conditional_arg_size_cache.push_back(number_of_elements);
size_t const index = conditional_arg_size_cache.size() - 1u;
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<T>::calculate(conditional_arg_size_cache, elem);
++number_of_elements;
}
conditional_arg_size_cache[index] = number_of_elements;
return total_size;
}
};
/***/
template <typename T, typename Allocator>
struct Encoder<std::forward_list<T, Allocator>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::forward_list<T, Allocator> const& arg) noexcept
{
// First encode the number of elements of the forward list
size_t const elems_num = conditional_arg_size_cache[conditional_arg_size_cache_index++];
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elems_num);
for (auto const& elem : arg)
{
Encoder<T>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <typename T, typename Allocator>
#if defined(_WIN32)
struct Decoder<std::forward_list<T, Allocator>,
std::enable_if_t<std::negation_v<std::disjunction<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
#else
struct Decoder<std::forward_list<T, Allocator>>
#endif
{
static std::forward_list<T, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::forward_list<T, Allocator> arg;
// Read the size
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
if (number_of_elements > 0)
{
arg.emplace_front(Decoder<T>::decode(buffer, nullptr));
}
for (size_t i = 1; i < number_of_elements; ++i)
{
auto it = arg.before_begin();
for (auto curr = arg.begin(); curr != arg.end(); ++it, ++curr)
{
// iterate
}
// Insert after the last element
arg.emplace_after(it, Decoder<T>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <typename T, typename Allocator>
struct Decoder<std::forward_list<T, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
std::vector<std::string> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,135 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <list>
#include <type_traits>
#include <vector>
#if defined(_WIN32)
#include <string>
#endif
namespace quill::detail
{
/***/
template <typename T, typename Allocator>
struct ArgSizeCalculator<std::list<T, Allocator>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, std::list<T, Allocator> const& arg) noexcept
{
// We need to store the size of the list in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::disjunction_v<std::is_arithmetic<T>, std::is_enum<T>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += sizeof(T) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<T>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <typename T, typename Allocator>
struct Encoder<std::list<T, Allocator>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::list<T, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<T>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <typename T, typename Allocator>
#if defined(_WIN32)
struct Decoder<std::list<T, Allocator>,
std::enable_if_t<std::negation_v<std::disjunction<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
#else
struct Decoder<std::list<T, Allocator>>
#endif
{
static std::list<T, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::list<T, Allocator> arg;
// Read the size of the list
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg.emplace_back(Decoder<T>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <typename T, typename Allocator>
struct Decoder<std::list<T, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
std::vector<std::string> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,195 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/std/Pair.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <map>
#include <type_traits>
#include <utility>
#include <vector>
namespace quill::detail
{
/***/
template <template <typename...> class MapType, typename Key, typename T, typename Compare, typename Allocator>
struct ArgSizeCalculator<
MapType<Key, T, Compare, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<MapType<Key, T, Compare, Allocator>, std::map<Key, T, Compare, Allocator>>,
std::is_same<MapType<Key, T, Compare, Allocator>, std::multimap<Key, T, Compare, Allocator>>>>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
MapType<Key, T, Compare, Allocator> const& arg) noexcept
{
// We need to store the size of the set in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::conjunction_v<std::disjunction<std::is_arithmetic<Key>, std::is_enum<Key>>,
std::disjunction<std::is_arithmetic<T>, std::is_enum<T>>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += (sizeof(Key) + sizeof(T)) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<std::pair<Key, T>>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <template <typename...> class MapType, typename Key, typename T, typename Compare, typename Allocator>
struct Encoder<MapType<Key, T, Compare, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<MapType<Key, T, Compare, Allocator>, std::map<Key, T, Compare, Allocator>>,
std::is_same<MapType<Key, T, Compare, Allocator>, std::multimap<Key, T, Compare, Allocator>>>>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index,
MapType<Key, T, Compare, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<std::pair<Key, T>>::encode(buffer, conditional_arg_size_cache,
conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <template <typename...> class MapType, typename Key, typename T, typename Compare, typename Allocator>
#if defined(_WIN32)
struct Decoder<
MapType<Key, T, Compare, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<MapType<Key, T, Compare, Allocator>, std::map<Key, T, Compare, Allocator>>,
std::is_same<MapType<Key, T, Compare, Allocator>, std::multimap<Key, T, Compare, Allocator>>>,
std::negation<std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>,
std::is_same<Key, std::wstring_view>, std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>>
#else
struct Decoder<MapType<Key, T, Compare, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<MapType<Key, T, Compare, Allocator>, std::map<Key, T, Compare, Allocator>>,
std::is_same<MapType<Key, T, Compare, Allocator>, std::multimap<Key, T, Compare, Allocator>>>>>
#endif
{
static MapType<Key, T, Compare, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
MapType<Key, T, Compare, Allocator> arg;
// Read the size of the set
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg.insert(Decoder<std::pair<Key, T>>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <template <typename...> class MapType, typename Key, typename T, typename Compare, typename Allocator>
struct Decoder<
MapType<Key, T, Compare, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<MapType<Key, T, Compare, Allocator>, std::map<Key, T, Compare, Allocator>>,
std::is_same<MapType<Key, T, Compare, Allocator>, std::multimap<Key, T, Compare, Allocator>>>,
std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>, std::is_same<Key, std::wstring_view>,
std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>, std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
{
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
constexpr bool wide_key_t = std::is_same_v<Key, wchar_t*> || std::is_same_v<Key, wchar_t const*> ||
std::is_same_v<Key, std::wstring> || std::is_same_v<Key, std::wstring_view>;
constexpr bool wide_value_t = std::is_same_v<T, wchar_t*> || std::is_same_v<T, wchar_t const*> ||
std::is_same_v<T, std::wstring> || std::is_same_v<T, std::wstring_view>;
if constexpr (wide_key_t && !wide_value_t)
{
std::vector<std::pair<std::string, T>> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::pair<std::string, T> elem;
std::wstring_view v = Decoder<Key>::decode(buffer, nullptr);
elem.first = utf8_encode(v);
elem.second = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(elem);
}
args_store->push_back(encoded_values);
}
else if constexpr (!wide_key_t && wide_value_t)
{
std::vector<std::pair<Key, std::string>> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::pair<Key, std::string> elem;
elem.first = Decoder<Key>::decode(buffer, nullptr);
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
elem.second = utf8_encode(v);
encoded_values.emplace_back(elem);
}
args_store->push_back(encoded_values);
}
else
{
std::vector<std::pair<std::string, std::string>> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::pair<std::string, std::string> elem;
std::wstring_view v1 = Decoder<Key>::decode(buffer, nullptr);
elem.first = utf8_encode(v1);
std::wstring_view v2 = Decoder<T>::decode(buffer, nullptr);
elem.second = utf8_encode(v2);
encoded_values.emplace_back(elem);
}
args_store->push_back(encoded_values);
}
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,119 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/std.h"
#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
#if defined(_WIN32)
#include <string>
#endif
namespace quill::detail
{
/***/
template <typename T>
struct ArgSizeCalculator<std::optional<T>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, std::optional<T> const& arg) noexcept
{
// We need to store the size of the vector in the buffer, so we reserve space for it.
// We add sizeof(bool) bytes to accommodate the size information.
size_t total_size{sizeof(bool)};
if (arg.has_value())
{
total_size += ArgSizeCalculator<T>::calculate(conditional_arg_size_cache, *arg);
}
return total_size;
}
};
/***/
template <typename T>
struct Encoder<std::optional<T>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::optional<T> const& arg) noexcept
{
Encoder<bool>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, arg.has_value());
if (arg.has_value())
{
Encoder<T>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, *arg);
}
}
};
/***/
template <typename T>
#if defined(_WIN32)
struct Decoder<std::optional<T>,
std::enable_if_t<std::negation_v<std::disjunction<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
#else
struct Decoder<std::optional<T>>
#endif
{
static std::optional<T> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::optional<T> arg{std::nullopt};
bool const has_value = Decoder<bool>::decode(buffer, nullptr);
if (has_value)
{
arg = Decoder<T>::decode(buffer, nullptr);
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <typename T>
struct Decoder<std::optional<T>,
std::enable_if_t<std::disjunction_v<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
std::string encoded_value{"none"};
bool const has_value = Decoder<bool>::decode(buffer, nullptr);
if (has_value)
{
std::wstring_view arg = Decoder<T>::decode(buffer, nullptr);
encoded_value = "optional(\"";
encoded_value += utf8_encode(arg);
encoded_value += "\")";
}
args_store->push_back(encoded_value);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,124 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <utility>
namespace quill::detail
{
/***/
template <typename T1, typename T2>
struct ArgSizeCalculator<std::pair<T1, T2>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, std::pair<T1, T2> const& arg) noexcept
{
return ArgSizeCalculator<T1>::calculate(conditional_arg_size_cache, arg.first) +
ArgSizeCalculator<T2>::calculate(conditional_arg_size_cache, arg.second);
}
};
/***/
template <typename T1, typename T2>
struct Encoder<std::pair<T1, T2>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::pair<T1, T2> const& arg) noexcept
{
Encoder<T1>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, arg.first);
Encoder<T2>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, arg.second);
}
};
/***/
template <typename T1, typename T2>
#if defined(_WIN32)
struct Decoder<
std::pair<T1, T2>,
std::enable_if_t<std::negation_v<std::disjunction<
std::is_same<T1, wchar_t*>, std::is_same<T1, wchar_t const*>, std::is_same<T1, std::wstring>, std::is_same<T1, std::wstring_view>,
std::is_same<T2, wchar_t*>, std::is_same<T2, wchar_t const*>, std::is_same<T2, std::wstring>, std::is_same<T2, std::wstring_view>>>>>
#else
struct Decoder<std::pair<T1, T2>>
#endif
{
static std::pair<T1, T2> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::pair<T1, T2> arg;
arg.first = Decoder<T1>::decode(buffer, nullptr);
arg.second = Decoder<T2>::decode(buffer, nullptr);
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
template <typename T1, typename T2>
struct Decoder<
std::pair<T1, T2>,
std::enable_if_t<std::disjunction_v<std::is_same<T1, wchar_t*>, std::is_same<T1, wchar_t const*>, std::is_same<T1, std::wstring>,
std::is_same<T1, std::wstring_view>, std::is_same<T2, wchar_t*>, std::is_same<T2, wchar_t const*>,
std::is_same<T2, std::wstring>, std::is_same<T2, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
constexpr bool wide_t1 = std::is_same_v<T1, wchar_t*> || std::is_same_v<T1, wchar_t const*> ||
std::is_same_v<T1, std::wstring> || std::is_same_v<T1, std::wstring_view>;
constexpr bool wide_t2 = std::is_same_v<T2, wchar_t*> || std::is_same_v<T2, wchar_t const*> ||
std::is_same_v<T2, std::wstring> || std::is_same_v<T2, std::wstring_view>;
if (args_store)
{
if constexpr (wide_t1 && !wide_t2)
{
std::pair<std::string, T2> arg;
std::wstring_view v = Decoder<T1>::decode(buffer, nullptr);
arg.first = utf8_encode(v);
arg.second = Decoder<T2>::decode(buffer, nullptr);
args_store->push_back(arg);
}
else if constexpr (!wide_t1 && wide_t2)
{
std::pair<T1, std::string> arg;
arg.first = Decoder<T1>::decode(buffer, nullptr);
std::wstring_view v = Decoder<T2>::decode(buffer, nullptr);
arg.second = utf8_encode(v);
args_store->push_back(arg);
}
else
{
std::wstring_view v1 = Decoder<T1>::decode(buffer, nullptr);
std::wstring_view v2 = Decoder<T2>::decode(buffer, nullptr);
args_store->push_back(std::pair<std::string, std::string>{utf8_encode(v1), utf8_encode(v2)});
}
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,146 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <set>
#include <type_traits>
#include <vector>
namespace quill::detail
{
/***/
template <template <typename...> class SetType, typename Key, typename Compare, typename Allocator>
struct ArgSizeCalculator<
SetType<Key, Compare, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<SetType<Key, Compare, Allocator>, std::set<Key, Compare, Allocator>>,
std::is_same<SetType<Key, Compare, Allocator>, std::multiset<Key, Compare, Allocator>>>>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
SetType<Key, Compare, Allocator> const& arg) noexcept
{
// We need to store the size of the set in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::disjunction_v<std::is_arithmetic<Key>, std::is_enum<Key>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += sizeof(Key) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<Key>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <template <typename...> class SetType, typename Key, typename Compare, typename Allocator>
struct Encoder<SetType<Key, Compare, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<SetType<Key, Compare, Allocator>, std::set<Key, Compare, Allocator>>,
std::is_same<SetType<Key, Compare, Allocator>, std::multiset<Key, Compare, Allocator>>>>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index,
SetType<Key, Compare, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<Key>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <template <typename...> class SetType, typename Key, typename Compare, typename Allocator>
#if defined(_WIN32)
struct Decoder<
SetType<Key, Compare, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<SetType<Key, Compare, Allocator>, std::set<Key, Compare, Allocator>>,
std::is_same<SetType<Key, Compare, Allocator>, std::multiset<Key, Compare, Allocator>>>,
std::negation<std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>, std::is_same<Key, std::wstring_view>>>>>>
#else
struct Decoder<SetType<Key, Compare, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<SetType<Key, Compare, Allocator>, std::set<Key, Compare, Allocator>>,
std::is_same<SetType<Key, Compare, Allocator>, std::multiset<Key, Compare, Allocator>>>>>
#endif
{
static SetType<Key, Compare, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
SetType<Key, Compare, Allocator> arg;
// Read the size of the set
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg.emplace(Decoder<Key>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <template <typename...> class SetType, typename Key, typename Compare, typename Allocator>
struct Decoder<
SetType<Key, Compare, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<SetType<Key, Compare, Allocator>, std::set<Key, Compare, Allocator>>,
std::is_same<SetType<Key, Compare, Allocator>, std::multiset<Key, Compare, Allocator>>>,
std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>, std::is_same<Key, std::wstring_view>>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
std::vector<std::string> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::wstring_view v = Decoder<Key>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,78 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <tuple>
#include <vector>
namespace quill::detail
{
/***/
template <typename... Types>
struct ArgSizeCalculator<std::tuple<Types...>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, std::tuple<Types...> const& arg) noexcept
{
size_t total_size{0};
std::apply(
[&total_size, &conditional_arg_size_cache](auto const&... elems)
{
((total_size += ArgSizeCalculator<std::decay_t<decltype(elems)>>::calculate(conditional_arg_size_cache, elems)),
...);
},
arg);
return total_size;
}
};
/***/
template <typename... Types>
struct Encoder<std::tuple<Types...>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::tuple<Types...> const& arg) noexcept
{
std::apply(
[&conditional_arg_size_cache, &conditional_arg_size_cache_index, &buffer](auto const&... elems)
{
((Encoder<std::decay_t<decltype(elems)>>::encode(buffer, conditional_arg_size_cache,
conditional_arg_size_cache_index, elems)),
...);
},
arg);
}
};
/***/
template <typename... Types>
struct Decoder<std::tuple<Types...>>
{
static auto decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::tuple<Types...> arg{};
std::apply([&buffer](auto&... elems)
{ ((elems = Decoder<std::decay_t<decltype(elems)>>::decode(buffer, nullptr)), ...); }, arg);
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
} // namespace quill::detail

View file

@ -0,0 +1,203 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/std/Pair.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
namespace quill::detail
{
/***/
template <template <typename...> class UnorderedMapType, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
struct ArgSizeCalculator<
UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::disjunction_v<
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_map<Key, T, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>>>>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
UnorderedMapType<Key, T, Hash, KeyEqual, Allocator> const& arg) noexcept
{
// We need to store the size of the set in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::conjunction_v<std::disjunction<std::is_arithmetic<Key>, std::is_enum<Key>>,
std::disjunction<std::is_arithmetic<T>, std::is_enum<T>>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += (sizeof(Key) + sizeof(T)) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<std::pair<Key, T>>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <template <typename...> class UnorderedMapType, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
struct Encoder<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::disjunction_v<
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_map<Key, T, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>>>>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index,
UnorderedMapType<Key, T, Hash, KeyEqual, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<std::pair<Key, T>>::encode(buffer, conditional_arg_size_cache,
conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <template <typename...> class UnorderedMapType, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
#if defined(_WIN32)
struct Decoder<
UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_map<Key, T, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>>>,
std::negation<std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>,
std::is_same<Key, std::wstring_view>, std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>>
#else
struct Decoder<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::disjunction_v<
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_map<Key, T, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>>>>>
#endif
{
static UnorderedMapType<Key, T, Hash, KeyEqual, Allocator> decode(std::byte*& buffer,
DynamicFormatArgStore* args_store)
{
UnorderedMapType<Key, T, Hash, KeyEqual, Allocator> arg;
// Read the size of the set
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
arg.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg.insert(Decoder<std::pair<Key, T>>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <template <typename...> class UnorderedMapType, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
struct Decoder<
UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_map<Key, T, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedMapType<Key, T, Hash, KeyEqual, Allocator>, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>>>,
std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>, std::is_same<Key, std::wstring_view>,
std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>, std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
constexpr bool wide_key_t = std::is_same_v<Key, wchar_t*> || std::is_same_v<Key, wchar_t const*> ||
std::is_same_v<Key, std::wstring> || std::is_same_v<Key, std::wstring_view>;
constexpr bool wide_value_t = std::is_same_v<T, wchar_t*> || std::is_same_v<T, wchar_t const*> ||
std::is_same_v<T, std::wstring> || std::is_same_v<T, std::wstring_view>;
if constexpr (wide_key_t && !wide_value_t)
{
std::vector<std::pair<std::string, T>> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::pair<std::string, T> elem;
std::wstring_view v = Decoder<Key>::decode(buffer, nullptr);
elem.first = utf8_encode(v);
elem.second = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(elem);
}
args_store->push_back(encoded_values);
}
else if constexpr (!wide_key_t && wide_value_t)
{
std::vector<std::pair<Key, std::string>> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::pair<Key, std::string> elem;
elem.first = Decoder<Key>::decode(buffer, nullptr);
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
elem.second = utf8_encode(v);
encoded_values.emplace_back(elem);
}
args_store->push_back(encoded_values);
}
else
{
std::vector<std::pair<std::string, std::string>> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::pair<std::string, std::string> elem;
std::wstring_view v1 = Decoder<Key>::decode(buffer, nullptr);
elem.first = utf8_encode(v1);
std::wstring_view v2 = Decoder<T>::decode(buffer, nullptr);
elem.second = utf8_encode(v2);
encoded_values.emplace_back(elem);
}
args_store->push_back(encoded_values);
}
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,149 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <unordered_set>
#include <vector>
namespace quill::detail
{
/***/
template <template <typename...> class UnorderedSetType, typename Key, typename Hash, typename KeyEqual, typename Allocator>
struct ArgSizeCalculator<
UnorderedSetType<Key, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_set<Key, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>>>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
UnorderedSetType<Key, Hash, KeyEqual, Allocator> const& arg) noexcept
{
// We need to store the size of the set in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::disjunction_v<std::is_arithmetic<Key>, std::is_enum<Key>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += sizeof(Key) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<Key>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <template <typename...> class UnorderedSetType, typename Key, typename Hash, typename KeyEqual, typename Allocator>
struct Encoder<UnorderedSetType<Key, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::disjunction_v<
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_set<Key, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>>>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index,
UnorderedSetType<Key, Hash, KeyEqual, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<Key>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <template <typename...> class UnorderedSetType, typename Key, typename Hash, typename KeyEqual, typename Allocator>
#if defined(_WIN32)
struct Decoder<
UnorderedSetType<Key, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_set<Key, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>>,
std::negation<std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>, std::is_same<Key, std::wstring_view>>>>>>
#else
struct Decoder<UnorderedSetType<Key, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::disjunction_v<
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_set<Key, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>>>>
#endif
{
static UnorderedSetType<Key, Hash, KeyEqual, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
UnorderedSetType<Key, Hash, KeyEqual, Allocator> arg;
// Read the size of the set
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
arg.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg.emplace(Decoder<Key>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <template <typename...> class UnorderedSetType, typename Key, typename Hash, typename KeyEqual, typename Allocator>
struct Decoder<
UnorderedSetType<Key, Hash, KeyEqual, Allocator>,
std::enable_if_t<std::conjunction_v<
std::disjunction<std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_set<Key, Hash, KeyEqual, Allocator>>,
std::is_same<UnorderedSetType<Key, Hash, KeyEqual, Allocator>, std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>>,
std::disjunction<std::is_same<Key, wchar_t*>, std::is_same<Key, wchar_t const*>, std::is_same<Key, std::wstring>, std::is_same<Key, std::wstring_view>>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static auto decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
std::vector<std::string> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::wstring_view v = Decoder<Key>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail

View file

@ -0,0 +1,133 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/
#pragma once
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"
#include "quill/bundled/fmt/core.h"
#include "quill/bundled/fmt/ranges.h"
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <vector>
namespace quill::detail
{
/***/
template <typename T, typename Allocator>
struct ArgSizeCalculator<std::vector<T, Allocator>>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
std::vector<T, Allocator> const& arg) noexcept
{
// We need to store the size of the vector in the buffer, so we reserve space for it.
// We add sizeof(size_t) bytes to accommodate the size information.
size_t total_size{sizeof(size_t)};
if constexpr (std::disjunction_v<std::is_arithmetic<T>, std::is_enum<T>>)
{
// For built-in types, such as arithmetic or enum types, iteration is unnecessary
total_size += sizeof(T) * arg.size();
}
else
{
// For other complex types it's essential to determine the exact size of each element.
// For instance, in the case of a collection of std::string, we need to know the exact size
// of each string as we will be copying them directly to our queue buffer.
for (auto const& elem : arg)
{
total_size += ArgSizeCalculator<T>::calculate(conditional_arg_size_cache, elem);
}
}
return total_size;
}
};
/***/
template <typename T, typename Allocator>
struct Encoder<std::vector<T, Allocator>>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, std::vector<T, Allocator> const& arg) noexcept
{
Encoder<size_t>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index,
arg.size());
for (auto const& elem : arg)
{
Encoder<T>::encode(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, elem);
}
}
};
/***/
template <typename T, typename Allocator>
#if defined(_WIN32)
struct Decoder<std::vector<T, Allocator>,
std::enable_if_t<std::negation_v<std::disjunction<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>>
#else
struct Decoder<std::vector<T, Allocator>>
#endif
{
static std::vector<T, Allocator> decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
std::vector<T, Allocator> arg;
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
arg.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
arg.emplace_back(Decoder<T>::decode(buffer, nullptr));
}
if (args_store)
{
args_store->push_back(arg);
}
return arg;
}
};
#if defined(_WIN32)
/***/
template <typename T, typename Allocator>
struct Decoder<std::vector<T, Allocator>,
std::enable_if_t<std::disjunction_v<std::is_same<T, wchar_t*>, std::is_same<T, wchar_t const*>,
std::is_same<T, std::wstring>, std::is_same<T, std::wstring_view>>>>
{
/**
* Chaining stl types not supported for wstrings so we do not return anything
*/
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
if (args_store)
{
// Read the size of the vector
size_t const number_of_elements = Decoder<size_t>::decode(buffer, nullptr);
std::vector<std::string> encoded_values;
encoded_values.reserve(number_of_elements);
for (size_t i = 0; i < number_of_elements; ++i)
{
std::wstring_view v = Decoder<T>::decode(buffer, nullptr);
encoded_values.emplace_back(utf8_encode(v));
}
args_store->push_back(encoded_values);
}
}
};
#endif
} // namespace quill::detail