bleh
This commit is contained in:
parent
6e5045f1f4
commit
693fa17d10
266 changed files with 60543 additions and 1000 deletions
173
subprojects/quill-4.2.0/quill/include/quill/Backend.h
Normal file
173
subprojects/quill-4.2.0/quill/include/quill/Backend.h
Normal 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
|
|
@ -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
|
211
subprojects/quill-4.2.0/quill/include/quill/Frontend.h
Normal file
211
subprojects/quill-4.2.0/quill/include/quill/Frontend.h
Normal 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
|
284
subprojects/quill-4.2.0/quill/include/quill/LogMacros.h
Normal file
284
subprojects/quill-4.2.0/quill/include/quill/LogMacros.h
Normal 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, ¯o_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, ¯o_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, ¯o_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, ¯o_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
|
320
subprojects/quill-4.2.0/quill/include/quill/Logger.h
Normal file
320
subprojects/quill-4.2.0/quill/include/quill/Logger.h
Normal 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, ¤t_timestamp, sizeof(current_timestamp));
|
||||
write_buffer += sizeof(current_timestamp);
|
||||
|
||||
std::memcpy(write_buffer, ¯o_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, ¯o_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, ¯o_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, ¯o_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
|
|
@ -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
|
88
subprojects/quill-4.2.0/quill/include/quill/Utility.h
Normal file
88
subprojects/quill-4.2.0/quill/include/quill/Utility.h
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
1295
subprojects/quill-4.2.0/quill/include/quill/backend/BackendWorker.h
Normal file
1295
subprojects/quill-4.2.0/quill/include/quill/backend/BackendWorker.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
|
@ -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
|
212
subprojects/quill-4.2.0/quill/include/quill/backend/RdtscClock.h
Normal file
212
subprojects/quill-4.2.0/quill/include/quill/backend/RdtscClock.h
Normal 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
|
|
@ -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, ¯o_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
|
|
@ -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(×tamp);
|
||||
|
||||
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(×tamp, &time_info);
|
||||
}
|
||||
else
|
||||
{
|
||||
localtime_rs(×tamp, &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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
235
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/args.h
Normal file
235
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/args.h
Normal 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_
|
2240
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/chrono.h
Normal file
2240
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/chrono.h
Normal file
File diff suppressed because it is too large
Load diff
643
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/color.h
Normal file
643
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/color.h
Normal 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_
|
|
@ -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_
|
2970
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/core.h
Normal file
2970
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/core.h
Normal file
File diff suppressed because it is too large
Load diff
1678
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/format-inl.h
Normal file
1678
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/format-inl.h
Normal file
File diff suppressed because it is too large
Load diff
4539
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/format.h
Normal file
4539
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/format.h
Normal file
File diff suppressed because it is too large
Load diff
455
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/os.h
Normal file
455
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/os.h
Normal 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_
|
|
@ -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_
|
675
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/printf.h
Normal file
675
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/printf.h
Normal 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_
|
738
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/ranges.h
Normal file
738
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/ranges.h
Normal 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_
|
537
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/std.h
Normal file
537
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/std.h
Normal 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_
|
259
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/xchar.h
Normal file
259
subprojects/quill-4.2.0/quill/include/quill/bundled/fmt/xchar.h
Normal 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_
|
|
@ -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
|
|
@ -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
|
435
subprojects/quill-4.2.0/quill/include/quill/core/Codec.h
Normal file
435
subprojects/quill-4.2.0/quill/include/quill/core/Codec.h
Normal 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
|
89
subprojects/quill-4.2.0/quill/include/quill/core/Common.h
Normal file
89
subprojects/quill-4.2.0/quill/include/quill/core/Common.h
Normal 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
|
|
@ -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
|
|
@ -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
|
110
subprojects/quill-4.2.0/quill/include/quill/core/FormatBuffer.h
Normal file
110
subprojects/quill-4.2.0/quill/include/quill/core/FormatBuffer.h
Normal 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
|
|
@ -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
|
156
subprojects/quill-4.2.0/quill/include/quill/core/LogLevel.h
Normal file
156
subprojects/quill-4.2.0/quill/include/quill/core/LogLevel.h
Normal 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
|
140
subprojects/quill-4.2.0/quill/include/quill/core/LoggerBase.h
Normal file
140
subprojects/quill-4.2.0/quill/include/quill/core/LoggerBase.h
Normal 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
|
223
subprojects/quill-4.2.0/quill/include/quill/core/LoggerManager.h
Normal file
223
subprojects/quill-4.2.0/quill/include/quill/core/LoggerManager.h
Normal 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
|
220
subprojects/quill-4.2.0/quill/include/quill/core/MacroMetadata.h
Normal file
220
subprojects/quill-4.2.0/quill/include/quill/core/MacroMetadata.h
Normal 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
|
57
subprojects/quill-4.2.0/quill/include/quill/core/MathUtils.h
Normal file
57
subprojects/quill-4.2.0/quill/include/quill/core/MathUtils.h
Normal 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
|
|
@ -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
|
92
subprojects/quill-4.2.0/quill/include/quill/core/Rdtsc.h
Normal file
92
subprojects/quill-4.2.0/quill/include/quill/core/Rdtsc.h
Normal 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
|
152
subprojects/quill-4.2.0/quill/include/quill/core/SinkManager.h
Normal file
152
subprojects/quill-4.2.0/quill/include/quill/core/SinkManager.h
Normal 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
|
|
@ -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
|
|
@ -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
|
104
subprojects/quill-4.2.0/quill/include/quill/core/TimeUtilities.h
Normal file
104
subprojects/quill-4.2.0/quill/include/quill/core/TimeUtilities.h
Normal 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
|
|
@ -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
|
69
subprojects/quill-4.2.0/quill/include/quill/filters/Filter.h
Normal file
69
subprojects/quill-4.2.0/quill/include/quill/filters/Filter.h
Normal 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
|
457
subprojects/quill-4.2.0/quill/include/quill/sinks/ConsoleSink.h
Normal file
457
subprojects/quill-4.2.0/quill/include/quill/sinks/ConsoleSink.h
Normal 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
|
370
subprojects/quill-4.2.0/quill/include/quill/sinks/FileSink.h
Normal file
370
subprojects/quill-4.2.0/quill/include/quill/sinks/FileSink.h
Normal 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
|
|
@ -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
|
|
@ -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
|
36
subprojects/quill-4.2.0/quill/include/quill/sinks/NullSink.h
Normal file
36
subprojects/quill-4.2.0/quill/include/quill/sinks/NullSink.h
Normal 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
|
|
@ -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
|
192
subprojects/quill-4.2.0/quill/include/quill/sinks/Sink.h
Normal file
192
subprojects/quill-4.2.0/quill/include/quill/sinks/Sink.h
Normal 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
|
195
subprojects/quill-4.2.0/quill/include/quill/sinks/StreamSink.h
Normal file
195
subprojects/quill-4.2.0/quill/include/quill/sinks/StreamSink.h
Normal 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
|
124
subprojects/quill-4.2.0/quill/include/quill/std/Array.h
Normal file
124
subprojects/quill-4.2.0/quill/include/quill/std/Array.h
Normal 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
|
136
subprojects/quill-4.2.0/quill/include/quill/std/Deque.h
Normal file
136
subprojects/quill-4.2.0/quill/include/quill/std/Deque.h
Normal 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
|
|
@ -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
|
146
subprojects/quill-4.2.0/quill/include/quill/std/ForwardList.h
Normal file
146
subprojects/quill-4.2.0/quill/include/quill/std/ForwardList.h
Normal 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
|
135
subprojects/quill-4.2.0/quill/include/quill/std/List.h
Normal file
135
subprojects/quill-4.2.0/quill/include/quill/std/List.h
Normal 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
|
195
subprojects/quill-4.2.0/quill/include/quill/std/Map.h
Normal file
195
subprojects/quill-4.2.0/quill/include/quill/std/Map.h
Normal 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
|
119
subprojects/quill-4.2.0/quill/include/quill/std/Optional.h
Normal file
119
subprojects/quill-4.2.0/quill/include/quill/std/Optional.h
Normal 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
|
124
subprojects/quill-4.2.0/quill/include/quill/std/Pair.h
Normal file
124
subprojects/quill-4.2.0/quill/include/quill/std/Pair.h
Normal 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
|
146
subprojects/quill-4.2.0/quill/include/quill/std/Set.h
Normal file
146
subprojects/quill-4.2.0/quill/include/quill/std/Set.h
Normal 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
|
78
subprojects/quill-4.2.0/quill/include/quill/std/Tuple.h
Normal file
78
subprojects/quill-4.2.0/quill/include/quill/std/Tuple.h
Normal 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
|
203
subprojects/quill-4.2.0/quill/include/quill/std/UnorderedMap.h
Normal file
203
subprojects/quill-4.2.0/quill/include/quill/std/UnorderedMap.h
Normal 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
|
149
subprojects/quill-4.2.0/quill/include/quill/std/UnorderedSet.h
Normal file
149
subprojects/quill-4.2.0/quill/include/quill/std/UnorderedSet.h
Normal 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
|
133
subprojects/quill-4.2.0/quill/include/quill/std/Vector.h
Normal file
133
subprojects/quill-4.2.0/quill/include/quill/std/Vector.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue