_build/ | |||||
__pycache__/ | |||||
.peru/ | |||||
.vscode/ |
#!/usr/bin/env python3 | |||||
import argparse | |||||
from pathlib import Path | |||||
import multiprocessing | |||||
import itertools | |||||
from concurrent.futures import ThreadPoolExecutor | |||||
from typing import Sequence, Iterable, Dict, Tuple | |||||
import subprocess | |||||
import time | |||||
import sys | |||||
HERE_DIR = Path(__file__).parent.absolute() | |||||
INCLUDE_DIRS = [ | |||||
'external/taywee-args/include', | |||||
'external/spdlog/include', | |||||
] | |||||
def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]: | |||||
build_dir = HERE_DIR / '_build' | |||||
src_dir = HERE_DIR / 'src' | |||||
relpath = cpp_file.relative_to(src_dir) | |||||
obj_path = build_dir / relpath.with_name(relpath.name + '.o') | |||||
obj_path.parent.mkdir(exist_ok=True, parents=True) | |||||
cmd = [ | |||||
cxx, | |||||
'-std=c++17', | |||||
'-static', | |||||
'-Wall', | |||||
'-Wextra', | |||||
'-Werror', | |||||
'-Wshadow', | |||||
'-Wconversion', | |||||
'-fdiagnostics-color', | |||||
'-g', | |||||
'-c', | |||||
'-O0', | |||||
f'-I{src_dir}', | |||||
cpp_file, | |||||
f'-o{obj_path}', | |||||
] | |||||
cmd.extend( | |||||
itertools.chain.from_iterable( | |||||
('-isystem', HERE_DIR / subdir) for subdir in INCLUDE_DIRS)) | |||||
msg = f'Compile C++ file: {cpp_file}' | |||||
print(msg) | |||||
start = time.time() | |||||
res = subprocess.run( | |||||
cmd, | |||||
stdout=subprocess.PIPE, | |||||
stderr=subprocess.STDOUT, | |||||
) | |||||
if res.returncode != 0: | |||||
raise RuntimeError( | |||||
f'Compile command ({cmd}) failed for {cpp_file}:\n{res.stdout.decode()}' | |||||
) | |||||
end = time.time() | |||||
print(f'{msg} - Done: {end - start:.2}s') | |||||
return cpp_file, obj_path | |||||
def compile_sources(cxx: Path, sources: Iterable[Path]) -> Dict[Path, Path]: | |||||
pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) | |||||
return { | |||||
src: obj | |||||
for src, obj in pool.map(lambda s: _compile_src(cxx, s), sources) | |||||
} | |||||
def make_library(objects: Iterable[Path]) -> Path: | |||||
lib_file = HERE_DIR / '_build/libddslim.a' | |||||
cmd = ['ar', 'rsc', lib_file] | |||||
cmd.extend(objects) | |||||
if lib_file.exists(): | |||||
lib_file.unlink() | |||||
print(f'Creating static library {lib_file}') | |||||
subprocess.check_call(cmd) | |||||
return lib_file | |||||
def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path: | |||||
if out is None: | |||||
basename = obj.stem | |||||
out = HERE_DIR / '_build/test' / (basename + '.exe') | |||||
out.parent.mkdir(exist_ok=True, parents=True) | |||||
print(f'Linking executable {out}') | |||||
subprocess.check_call([cxx, '-static', obj, lib, '-lstdc++fs', f'-o{out}']) | |||||
return out | |||||
def run_test(exe: Path) -> None: | |||||
print(f'Running test: {exe}') | |||||
subprocess.check_call([exe]) | |||||
def main(argv: Sequence[str]) -> int: | |||||
parser = argparse.ArgumentParser() | |||||
parser.add_argument( | |||||
'--test', action='store_true', help='Build and run tests') | |||||
parser.add_argument( | |||||
'--cxx', help='Path/name of the C++ compiler to use.', required=True) | |||||
args = parser.parse_args(argv) | |||||
all_sources = set(HERE_DIR.glob('src/**/*.cpp')) | |||||
test_sources = set(HERE_DIR.glob('src/**/*.test.cpp')) | |||||
main_sources = set(HERE_DIR.glob('src/**/*.main.cpp')) | |||||
lib_sources = (all_sources - test_sources) - main_sources | |||||
objects = compile_sources(Path(args.cxx), all_sources) | |||||
lib = make_library(objects[p] for p in lib_sources) | |||||
test_objs = (objects[p] for p in test_sources) | |||||
pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) | |||||
test_exes = list( | |||||
pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs)) | |||||
main_exe = link_exe( | |||||
Path(args.cxx), | |||||
objects[next(iter(main_sources))], | |||||
lib, | |||||
out=HERE_DIR / '_build/ddslim') | |||||
if args.test: | |||||
list(pool.map(run_test, test_exes)) | |||||
print(f'Main executable generated at {main_exe}') | |||||
return 0 | |||||
if __name__ == "__main__": | |||||
sys.exit(main(sys.argv[1:])) |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// | |||||
// Async logging using global thread pool | |||||
// All loggers created here share same global thread pool. | |||||
// Each log message is pushed to a queue along withe a shared pointer to the | |||||
// logger. | |||||
// If a logger deleted while having pending messages in the queue, it's actual | |||||
// destruction will defer | |||||
// until all its messages are processed by the thread pool. | |||||
// This is because each message in the queue holds a shared_ptr to the | |||||
// originating logger. | |||||
#include "spdlog/async_logger.h" | |||||
#include "spdlog/details/registry.h" | |||||
#include "spdlog/details/thread_pool.h" | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <functional> | |||||
namespace spdlog { | |||||
namespace details { | |||||
static const size_t default_async_q_size = 8192; | |||||
} | |||||
// async logger factory - creates async loggers backed with thread pool. | |||||
// if a global thread pool doesn't already exist, create it with default queue | |||||
// size of 8192 items and single thread. | |||||
template<async_overflow_policy OverflowPolicy = async_overflow_policy::block> | |||||
struct async_factory_impl | |||||
{ | |||||
template<typename Sink, typename... SinkArgs> | |||||
static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&... args) | |||||
{ | |||||
auto ®istry_inst = details::registry::instance(); | |||||
// create global thread pool if not already exists.. | |||||
auto &mutex = registry_inst.tp_mutex(); | |||||
std::lock_guard<std::recursive_mutex> tp_lock(mutex); | |||||
auto tp = registry_inst.get_tp(); | |||||
if (tp == nullptr) | |||||
{ | |||||
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1); | |||||
registry_inst.set_tp(tp); | |||||
} | |||||
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||||
auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy); | |||||
registry_inst.initialize_logger(new_logger); | |||||
return new_logger; | |||||
} | |||||
}; | |||||
using async_factory = async_factory_impl<async_overflow_policy::block>; | |||||
using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; | |||||
template<typename Sink, typename... SinkArgs> | |||||
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args) | |||||
{ | |||||
return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
} | |||||
template<typename Sink, typename... SinkArgs> | |||||
inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args) | |||||
{ | |||||
return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
} | |||||
// set global thread pool. | |||||
inline void init_thread_pool(size_t q_size, size_t thread_count, std::function<void()> on_thread_start) | |||||
{ | |||||
auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start); | |||||
details::registry::instance().set_tp(std::move(tp)); | |||||
} | |||||
// set global thread pool. | |||||
inline void init_thread_pool(size_t q_size, size_t thread_count) | |||||
{ | |||||
init_thread_pool(q_size, thread_count, [] {}); | |||||
} | |||||
// get the global thread pool. | |||||
inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() | |||||
{ | |||||
return details::registry::instance().get_tp(); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/async_logger.h" | |||||
#endif | |||||
#include "spdlog/sinks/sink.h" | |||||
#include "spdlog/details/thread_pool.h" | |||||
#include <memory> | |||||
#include <string> | |||||
SPDLOG_INLINE spdlog::async_logger::async_logger( | |||||
std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) | |||||
: async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy) | |||||
{} | |||||
SPDLOG_INLINE spdlog::async_logger::async_logger( | |||||
std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) | |||||
: async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) | |||||
{} | |||||
// send the log message to the thread pool | |||||
SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
if (auto pool_ptr = thread_pool_.lock()) | |||||
{ | |||||
pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); | |||||
} | |||||
else | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("async log: thread pool doesn't exist anymore")); | |||||
} | |||||
} | |||||
// send flush request to the thread pool | |||||
SPDLOG_INLINE void spdlog::async_logger::flush_() | |||||
{ | |||||
if (auto pool_ptr = thread_pool_.lock()) | |||||
{ | |||||
pool_ptr->post_flush(shared_from_this(), overflow_policy_); | |||||
} | |||||
else | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("async flush: thread pool doesn't exist anymore")); | |||||
} | |||||
} | |||||
// | |||||
// backend functions - called from the thread pool to do the actual job | |||||
// | |||||
SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg &msg) | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
if (sink->should_log(msg.level)) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->log(msg); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
if (should_flush_(msg)) | |||||
{ | |||||
backend_flush_(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void spdlog::async_logger::backend_flush_() | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->flush(); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) | |||||
{ | |||||
auto cloned = std::make_shared<spdlog::async_logger>(*this); | |||||
cloned->name_ = std::move(new_name); | |||||
return cloned; | |||||
} |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// Fast asynchronous logger. | |||||
// Uses pre allocated queue. | |||||
// Creates a single back thread to pop messages from the queue and log them. | |||||
// | |||||
// Upon each log write the logger: | |||||
// 1. Checks if its log level is enough to log the message | |||||
// 2. Push a new copy of the message to a queue (or block the caller until | |||||
// space is available in the queue) | |||||
// Upon destruction, logs all remaining messages in the queue before | |||||
// destructing.. | |||||
#include "spdlog/logger.h" | |||||
namespace spdlog { | |||||
// Async overflow policy - block by default. | |||||
enum class async_overflow_policy | |||||
{ | |||||
block, // Block until message can be enqueued | |||||
overrun_oldest // Discard oldest message in the queue if full when trying to | |||||
// add new item. | |||||
}; | |||||
namespace details { | |||||
class thread_pool; | |||||
} | |||||
class async_logger final : public std::enable_shared_from_this<async_logger>, public logger | |||||
{ | |||||
friend class details::thread_pool; | |||||
public: | |||||
template<typename It> | |||||
async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, | |||||
async_overflow_policy overflow_policy = async_overflow_policy::block) | |||||
: logger(std::move(logger_name), begin, end) | |||||
, thread_pool_(std::move(tp)) | |||||
, overflow_policy_(overflow_policy) | |||||
{} | |||||
async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, | |||||
async_overflow_policy overflow_policy = async_overflow_policy::block); | |||||
async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, | |||||
async_overflow_policy overflow_policy = async_overflow_policy::block); | |||||
std::shared_ptr<logger> clone(std::string new_name) override; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override; | |||||
void flush_() override; | |||||
void backend_sink_it_(const details::log_msg &incoming_log_msg); | |||||
void backend_flush_(); | |||||
private: | |||||
std::weak_ptr<details::thread_pool> thread_pool_; | |||||
async_overflow_policy overflow_policy_; | |||||
}; | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "async_logger-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/common.h" | |||||
#endif | |||||
namespace spdlog { | |||||
namespace level { | |||||
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES; | |||||
static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES; | |||||
SPDLOG_INLINE string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||||
{ | |||||
return level_string_views[l]; | |||||
} | |||||
SPDLOG_INLINE const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||||
{ | |||||
return short_level_names[l]; | |||||
} | |||||
SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT | |||||
{ | |||||
int level = 0; | |||||
for (const auto &level_str : level_string_views) | |||||
{ | |||||
if (level_str == name) | |||||
{ | |||||
return static_cast<level::level_enum>(level); | |||||
} | |||||
level++; | |||||
} | |||||
return level::off; | |||||
} | |||||
} // namespace level | |||||
SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg) | |||||
: msg_(std::move(msg)) | |||||
{} | |||||
SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno) | |||||
{ | |||||
memory_buf_t outbuf; | |||||
fmt::format_system_error(outbuf, last_errno, msg); | |||||
msg_ = fmt::to_string(outbuf); | |||||
} | |||||
SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT | |||||
{ | |||||
return msg_.c_str(); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/tweakme.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include <atomic> | |||||
#include <chrono> | |||||
#include <initializer_list> | |||||
#include <memory> | |||||
#include <exception> | |||||
#include <string> | |||||
#include <type_traits> | |||||
#include <functional> | |||||
#ifdef _WIN32 | |||||
#ifndef NOMINMAX | |||||
#define NOMINMAX // prevent windows redefining min/max | |||||
#endif | |||||
#ifndef WIN32_LEAN_AND_MEAN | |||||
#define WIN32_LEAN_AND_MEAN | |||||
#endif | |||||
#include <windows.h> | |||||
#endif //_WIN32 | |||||
#ifdef SPDLOG_COMPILED_LIB | |||||
#undef SPDLOG_HEADER_ONLY | |||||
#define SPDLOG_INLINE | |||||
#else | |||||
#define SPDLOG_HEADER_ONLY | |||||
#define SPDLOG_INLINE inline | |||||
#endif | |||||
#include "spdlog/fmt/fmt.h" | |||||
// visual studio upto 2013 does not support noexcept nor constexpr | |||||
#if defined(_MSC_VER) && (_MSC_VER < 1900) | |||||
#define SPDLOG_NOEXCEPT _NOEXCEPT | |||||
#define SPDLOG_CONSTEXPR | |||||
#else | |||||
#define SPDLOG_NOEXCEPT noexcept | |||||
#define SPDLOG_CONSTEXPR constexpr | |||||
#endif | |||||
#if defined(__GNUC__) || defined(__clang__) | |||||
#define SPDLOG_DEPRECATED __attribute__((deprecated)) | |||||
#elif defined(_MSC_VER) | |||||
#define SPDLOG_DEPRECATED __declspec(deprecated) | |||||
#else | |||||
#define SPDLOG_DEPRECATED | |||||
#endif | |||||
// disable thread local on msvc 2013 | |||||
#ifndef SPDLOG_NO_TLS | |||||
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) | |||||
#define SPDLOG_NO_TLS 1 | |||||
#endif | |||||
#endif | |||||
#ifndef SPDLOG_FUNCTION | |||||
#define SPDLOG_FUNCTION __FUNCTION__ | |||||
#endif | |||||
#ifdef SPDLOG_NO_EXCEPTIONS | |||||
#define SPDLOG_TRY | |||||
#define SPDLOG_THROW(ex) \ | |||||
do \ | |||||
{ \ | |||||
printf("spdlog fatal error: %s\n", ex.what()); \ | |||||
std::abort(); \ | |||||
} while (0) | |||||
#define SPDLOG_CATCH_ALL() | |||||
#else | |||||
#define SPDLOG_TRY try | |||||
#define SPDLOG_THROW(ex) throw(ex) | |||||
#define SPDLOG_CATCH_ALL() catch (...) | |||||
#endif | |||||
namespace spdlog { | |||||
class formatter; | |||||
namespace sinks { | |||||
class sink; | |||||
} | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
using filename_t = std::wstring; | |||||
#define SPDLOG_FILENAME_T(s) L##s | |||||
#else | |||||
using filename_t = std::string; | |||||
#define SPDLOG_FILENAME_T(s) s | |||||
#endif | |||||
using log_clock = std::chrono::system_clock; | |||||
using sink_ptr = std::shared_ptr<sinks::sink>; | |||||
using sinks_init_list = std::initializer_list<sink_ptr>; | |||||
using err_handler = std::function<void(const std::string &err_msg)>; | |||||
using string_view_t = fmt::basic_string_view<char>; | |||||
using wstring_view_t = fmt::basic_string_view<wchar_t>; | |||||
using memory_buf_t = fmt::basic_memory_buffer<char, 250>; | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#ifndef _WIN32 | |||||
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||||
#else | |||||
template<typename T> | |||||
struct is_convertible_to_wstring_view : std::is_convertible<T, wstring_view_t> | |||||
{}; | |||||
#endif // _WIN32 | |||||
#else | |||||
template<typename> | |||||
struct is_convertible_to_wstring_view : std::false_type | |||||
{}; | |||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#if defined(SPDLOG_NO_ATOMIC_LEVELS) | |||||
using level_t = details::null_atomic_int; | |||||
#else | |||||
using level_t = std::atomic<int>; | |||||
#endif | |||||
#define SPDLOG_LEVEL_TRACE 0 | |||||
#define SPDLOG_LEVEL_DEBUG 1 | |||||
#define SPDLOG_LEVEL_INFO 2 | |||||
#define SPDLOG_LEVEL_WARN 3 | |||||
#define SPDLOG_LEVEL_ERROR 4 | |||||
#define SPDLOG_LEVEL_CRITICAL 5 | |||||
#define SPDLOG_LEVEL_OFF 6 | |||||
#if !defined(SPDLOG_ACTIVE_LEVEL) | |||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | |||||
#endif | |||||
// Log level enum | |||||
namespace level { | |||||
enum level_enum | |||||
{ | |||||
trace = SPDLOG_LEVEL_TRACE, | |||||
debug = SPDLOG_LEVEL_DEBUG, | |||||
info = SPDLOG_LEVEL_INFO, | |||||
warn = SPDLOG_LEVEL_WARN, | |||||
err = SPDLOG_LEVEL_ERROR, | |||||
critical = SPDLOG_LEVEL_CRITICAL, | |||||
off = SPDLOG_LEVEL_OFF, | |||||
}; | |||||
#if !defined(SPDLOG_LEVEL_NAMES) | |||||
#define SPDLOG_LEVEL_NAMES \ | |||||
{ \ | |||||
"trace", "debug", "info", "warning", "error", "critical", "off" \ | |||||
} | |||||
#endif | |||||
#if !defined(SPDLOG_SHORT_LEVEL_NAMES) | |||||
#define SPDLOG_SHORT_LEVEL_NAMES \ | |||||
{ \ | |||||
"T", "D", "I", "W", "E", "C", "O" \ | |||||
} | |||||
#endif | |||||
string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||||
const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||||
spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT; | |||||
using level_hasher = std::hash<int>; | |||||
} // namespace level | |||||
// | |||||
// Color mode used by sinks with color support. | |||||
// | |||||
enum class color_mode | |||||
{ | |||||
always, | |||||
automatic, | |||||
never | |||||
}; | |||||
// | |||||
// Pattern time - specific time getting to use for pattern_formatter. | |||||
// local time by default | |||||
// | |||||
enum class pattern_time_type | |||||
{ | |||||
local, // log localtime | |||||
utc // log utc | |||||
}; | |||||
// | |||||
// Log exception | |||||
// | |||||
class spdlog_ex : public std::exception | |||||
{ | |||||
public: | |||||
explicit spdlog_ex(std::string msg); | |||||
spdlog_ex(const std::string &msg, int last_errno); | |||||
const char *what() const SPDLOG_NOEXCEPT override; | |||||
private: | |||||
std::string msg_; | |||||
}; | |||||
struct source_loc | |||||
{ | |||||
SPDLOG_CONSTEXPR source_loc() = default; | |||||
SPDLOG_CONSTEXPR source_loc(const char *filename_in, int line_in, const char *funcname_in) | |||||
: filename{filename_in} | |||||
, line{line_in} | |||||
, funcname{funcname_in} | |||||
{} | |||||
SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT | |||||
{ | |||||
return line == 0; | |||||
} | |||||
const char *filename{nullptr}; | |||||
int line{0}; | |||||
const char *funcname{nullptr}; | |||||
}; | |||||
namespace details { | |||||
// make_unique support for pre c++14 | |||||
#if __cplusplus >= 201402L // C++14 and beyond | |||||
using std::make_unique; | |||||
#else | |||||
template<typename T, typename... Args> | |||||
std::unique_ptr<T> make_unique(Args &&... args) | |||||
{ | |||||
static_assert(!std::is_array<T>::value, "arrays not supported"); | |||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); | |||||
} | |||||
#endif | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "common-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/backtracer.h" | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE backtracer::backtracer(const backtracer &other) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(other.mutex_); | |||||
enabled_ = other.enabled(); | |||||
messages_ = other.messages_; | |||||
} | |||||
SPDLOG_INLINE backtracer::backtracer(backtracer &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
std::lock_guard<std::mutex> lock(other.mutex_); | |||||
enabled_ = other.enabled(); | |||||
messages_ = std::move(other.messages_); | |||||
} | |||||
SPDLOG_INLINE backtracer &backtracer::operator=(backtracer other) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex_); | |||||
enabled_ = other.enabled(); | |||||
messages_ = other.messages_; | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE void backtracer::enable(size_t size) | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
enabled_.store(true, std::memory_order_relaxed); | |||||
messages_ = circular_q<log_msg_buffer>{size}; | |||||
} | |||||
SPDLOG_INLINE void backtracer::disable() | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
enabled_.store(false, std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE bool backtracer::enabled() const | |||||
{ | |||||
return enabled_.load(std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE backtracer::operator bool() const | |||||
{ | |||||
return enabled(); | |||||
} | |||||
SPDLOG_INLINE void backtracer::push_back(const log_msg &msg) | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
messages_.push_back(log_msg_buffer{msg}); | |||||
} | |||||
// pop all items in the q and apply the given fun on each of them. | |||||
SPDLOG_INLINE void backtracer::foreach_pop(std::function<void(const details::log_msg &)> fun) | |||||
{ | |||||
std::lock_guard<std::mutex> lock{mutex_}; | |||||
while (!messages_.empty()) | |||||
{ | |||||
auto &front_msg = messages_.front(); | |||||
fun(front_msg); | |||||
messages_.pop_front(); | |||||
} | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/log_msg_buffer.h" | |||||
#include "spdlog/details/circular_q.h" | |||||
#include <atomic> | |||||
#include <mutex> | |||||
#include <functional> | |||||
// Store log messages in circular buffer. | |||||
// Useful for storing debug data in case of error/warning happens. | |||||
namespace spdlog { | |||||
namespace details { | |||||
class backtracer | |||||
{ | |||||
mutable std::mutex mutex_; | |||||
std::atomic<bool> enabled_{false}; | |||||
circular_q<log_msg_buffer> messages_; | |||||
public: | |||||
backtracer() = default; | |||||
backtracer(const backtracer &other); | |||||
backtracer(backtracer &&other) SPDLOG_NOEXCEPT; | |||||
backtracer &operator=(backtracer other); | |||||
void enable(size_t size); | |||||
void disable(); | |||||
bool enabled() const; | |||||
explicit operator bool() const; | |||||
void push_back(const log_msg &msg); | |||||
// pop all items in the q and apply the given fun on each of them. | |||||
void foreach_pop(std::function<void(const details::log_msg &)> fun); | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "backtracer-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// cirucal q view of std::vector. | |||||
#pragma once | |||||
#include <vector> | |||||
namespace spdlog { | |||||
namespace details { | |||||
template<typename T> | |||||
class circular_q | |||||
{ | |||||
size_t max_items_ = 0; | |||||
typename std::vector<T>::size_type head_ = 0; | |||||
typename std::vector<T>::size_type tail_ = 0; | |||||
size_t overrun_counter_ = 0; | |||||
std::vector<T> v_; | |||||
public: | |||||
using value_type = T; | |||||
// empty ctor - create a disabled queue with no elements allocated at all | |||||
circular_q() = default; | |||||
explicit circular_q(size_t max_items) | |||||
: max_items_(max_items + 1) // one item is reserved as marker for full q | |||||
, v_(max_items_) | |||||
{} | |||||
circular_q(const circular_q &) = default; | |||||
circular_q &operator=(const circular_q &) = default; | |||||
// move cannot be default, | |||||
// since we need to reset head_, tail_, etc to zero in the moved object | |||||
circular_q(circular_q &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
copy_moveable(std::move(other)); | |||||
} | |||||
circular_q &operator=(circular_q &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
copy_moveable(std::move(other)); | |||||
return *this; | |||||
} | |||||
// push back, overrun (oldest) item if no room left | |||||
void push_back(T &&item) | |||||
{ | |||||
if (max_items_ > 0) | |||||
{ | |||||
v_[tail_] = std::move(item); | |||||
tail_ = (tail_ + 1) % max_items_; | |||||
if (tail_ == head_) // overrun last item if full | |||||
{ | |||||
head_ = (head_ + 1) % max_items_; | |||||
++overrun_counter_; | |||||
} | |||||
} | |||||
} | |||||
// Return reference to the front item. | |||||
// If there are no elements in the container, the behavior is undefined. | |||||
const T &front() const | |||||
{ | |||||
return v_[head_]; | |||||
} | |||||
T &front() | |||||
{ | |||||
return v_[head_]; | |||||
} | |||||
// Pop item from front. | |||||
// If there are no elements in the container, the behavior is undefined. | |||||
void pop_front() | |||||
{ | |||||
head_ = (head_ + 1) % max_items_; | |||||
} | |||||
bool empty() const | |||||
{ | |||||
return tail_ == head_; | |||||
} | |||||
bool full() const | |||||
{ | |||||
// head is ahead of the tail by 1 | |||||
if (max_items_ > 0) | |||||
{ | |||||
return ((tail_ + 1) % max_items_) == head_; | |||||
} | |||||
return false; | |||||
} | |||||
size_t overrun_counter() const | |||||
{ | |||||
return overrun_counter_; | |||||
} | |||||
private: | |||||
// copy from other&& and reset it to disabled state | |||||
void copy_moveable(circular_q &&other) SPDLOG_NOEXCEPT | |||||
{ | |||||
max_items_ = other.max_items_; | |||||
head_ = other.head_; | |||||
tail_ = other.tail_; | |||||
overrun_counter_ = other.overrun_counter_; | |||||
v_ = std::move(other.v_); | |||||
// put &&other in disabled, but valid state | |||||
other.max_items_ = 0; | |||||
other.head_ = other.tail_ = 0; | |||||
other.overrun_counter_ = 0; | |||||
} | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
namespace details { | |||||
struct console_mutex | |||||
{ | |||||
using mutex_t = std::mutex; | |||||
static mutex_t &mutex() | |||||
{ | |||||
static mutex_t s_mutex; | |||||
return s_mutex; | |||||
} | |||||
}; | |||||
struct console_nullmutex | |||||
{ | |||||
using mutex_t = null_mutex; | |||||
static mutex_t &mutex() | |||||
{ | |||||
static mutex_t s_mutex; | |||||
return s_mutex; | |||||
} | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/file_helper.h" | |||||
#endif | |||||
#include "spdlog/details/os.h" | |||||
#include "spdlog/common.h" | |||||
#include <cerrno> | |||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <string> | |||||
#include <thread> | |||||
#include <tuple> | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE file_helper::~file_helper() | |||||
{ | |||||
close(); | |||||
} | |||||
SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) | |||||
{ | |||||
close(); | |||||
auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); | |||||
_filename = fname; | |||||
for (int tries = 0; tries < open_tries; ++tries) | |||||
{ | |||||
if (!os::fopen_s(&fd_, fname, mode)) | |||||
{ | |||||
return; | |||||
} | |||||
details::os::sleep_for_millis(open_interval); | |||||
} | |||||
SPDLOG_THROW(spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno)); | |||||
} | |||||
SPDLOG_INLINE void file_helper::reopen(bool truncate) | |||||
{ | |||||
if (_filename.empty()) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed re opening file - was not opened before")); | |||||
} | |||||
open(_filename, truncate); | |||||
} | |||||
SPDLOG_INLINE void file_helper::flush() | |||||
{ | |||||
std::fflush(fd_); | |||||
} | |||||
SPDLOG_INLINE void file_helper::close() | |||||
{ | |||||
if (fd_ != nullptr) | |||||
{ | |||||
std::fclose(fd_); | |||||
fd_ = nullptr; | |||||
} | |||||
} | |||||
SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) | |||||
{ | |||||
size_t msg_size = buf.size(); | |||||
auto data = buf.data(); | |||||
if (std::fwrite(data, 1, msg_size, fd_) != msg_size) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno)); | |||||
} | |||||
} | |||||
SPDLOG_INLINE size_t file_helper::size() const | |||||
{ | |||||
if (fd_ == nullptr) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename))); | |||||
} | |||||
return os::filesize(fd_); | |||||
} | |||||
SPDLOG_INLINE const filename_t &file_helper::filename() const | |||||
{ | |||||
return _filename; | |||||
} | |||||
SPDLOG_INLINE bool file_helper::file_exists(const filename_t &fname) | |||||
{ | |||||
return os::file_exists(fname); | |||||
} | |||||
// | |||||
// return file path and its extension: | |||||
// | |||||
// "mylog.txt" => ("mylog", ".txt") | |||||
// "mylog" => ("mylog", "") | |||||
// "mylog." => ("mylog.", "") | |||||
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") | |||||
// | |||||
// the starting dot in filenames is ignored (hidden files): | |||||
// | |||||
// ".mylog" => (".mylog". "") | |||||
// "my_folder/.mylog" => ("my_folder/.mylog", "") | |||||
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") | |||||
SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension(const filename_t &fname) | |||||
{ | |||||
auto ext_index = fname.rfind('.'); | |||||
// no valid extension found - return whole path and empty string as | |||||
// extension | |||||
if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) | |||||
{ | |||||
return std::make_tuple(fname, filename_t()); | |||||
} | |||||
// treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" | |||||
auto folder_index = fname.rfind(details::os::folder_sep); | |||||
if (folder_index != filename_t::npos && folder_index >= ext_index - 1) | |||||
{ | |||||
return std::make_tuple(fname, filename_t()); | |||||
} | |||||
// finally - return a valid base and extension tuple | |||||
return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include <tuple> | |||||
namespace spdlog { | |||||
namespace details { | |||||
// Helper class for file sinks. | |||||
// When failing to open a file, retry several times(5) with a delay interval(10 ms). | |||||
// Throw spdlog_ex exception on errors. | |||||
class file_helper | |||||
{ | |||||
public: | |||||
explicit file_helper() = default; | |||||
file_helper(const file_helper &) = delete; | |||||
file_helper &operator=(const file_helper &) = delete; | |||||
~file_helper(); | |||||
void open(const filename_t &fname, bool truncate = false); | |||||
void reopen(bool truncate); | |||||
void flush(); | |||||
void close(); | |||||
void write(const memory_buf_t &buf); | |||||
size_t size() const; | |||||
const filename_t &filename() const; | |||||
static bool file_exists(const filename_t &fname); | |||||
// | |||||
// return file path and its extension: | |||||
// | |||||
// "mylog.txt" => ("mylog", ".txt") | |||||
// "mylog" => ("mylog", "") | |||||
// "mylog." => ("mylog.", "") | |||||
// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") | |||||
// | |||||
// the starting dot in filenames is ignored (hidden files): | |||||
// | |||||
// ".mylog" => (".mylog". "") | |||||
// "my_folder/.mylog" => ("my_folder/.mylog", "") | |||||
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") | |||||
static std::tuple<filename_t, filename_t> split_by_extension(const filename_t &fname); | |||||
private: | |||||
const int open_tries = 5; | |||||
const int open_interval = 10; | |||||
std::FILE *fd_{nullptr}; | |||||
filename_t _filename; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "file_helper-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <chrono> | |||||
#include <type_traits> | |||||
#include "spdlog/fmt/fmt.h" | |||||
#include "spdlog/common.h" | |||||
// Some fmt helpers to efficiently format and pad ints and strings | |||||
namespace spdlog { | |||||
namespace details { | |||||
namespace fmt_helper { | |||||
inline spdlog::string_view_t to_string_view(const memory_buf_t &buf) SPDLOG_NOEXCEPT | |||||
{ | |||||
return spdlog::string_view_t{buf.data(), buf.size()}; | |||||
} | |||||
inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) | |||||
{ | |||||
auto *buf_ptr = view.data(); | |||||
if (buf_ptr != nullptr) | |||||
{ | |||||
dest.append(buf_ptr, buf_ptr + view.size()); | |||||
} | |||||
} | |||||
template<typename T> | |||||
inline void append_int(T n, memory_buf_t &dest) | |||||
{ | |||||
fmt::format_int i(n); | |||||
dest.append(i.data(), i.data() + i.size()); | |||||
} | |||||
template<typename T> | |||||
inline unsigned count_digits(T n) | |||||
{ | |||||
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; | |||||
return static_cast<unsigned>(fmt::internal::count_digits(static_cast<count_type>(n))); | |||||
} | |||||
inline void pad2(int n, memory_buf_t &dest) | |||||
{ | |||||
if (n > 99) | |||||
{ | |||||
append_int(n, dest); | |||||
} | |||||
else if (n > 9) // 10-99 | |||||
{ | |||||
dest.push_back(static_cast<char>('0' + n / 10)); | |||||
dest.push_back(static_cast<char>('0' + n % 10)); | |||||
} | |||||
else if (n >= 0) // 0-9 | |||||
{ | |||||
dest.push_back('0'); | |||||
dest.push_back(static_cast<char>('0' + n)); | |||||
} | |||||
else // negatives (unlikely, but just in case, let fmt deal with it) | |||||
{ | |||||
fmt::format_to(dest, "{:02}", n); | |||||
} | |||||
} | |||||
template<typename T> | |||||
inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) | |||||
{ | |||||
static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T"); | |||||
auto digits = count_digits(n); | |||||
if (width > digits) | |||||
{ | |||||
const char *zeroes = "0000000000000000000"; | |||||
dest.append(zeroes, zeroes + width - digits); | |||||
} | |||||
append_int(n, dest); | |||||
} | |||||
template<typename T> | |||||
inline void pad3(T n, memory_buf_t &dest) | |||||
{ | |||||
pad_uint(n, 3, dest); | |||||
} | |||||
template<typename T> | |||||
inline void pad6(T n, memory_buf_t &dest) | |||||
{ | |||||
pad_uint(n, 6, dest); | |||||
} | |||||
template<typename T> | |||||
inline void pad9(T n, memory_buf_t &dest) | |||||
{ | |||||
pad_uint(n, 9, dest); | |||||
} | |||||
// return fraction of a second of the given time_point. | |||||
// e.g. | |||||
// fraction<std::milliseconds>(tp) -> will return the millis part of the second | |||||
template<typename ToDuration> | |||||
inline ToDuration time_fraction(log_clock::time_point tp) | |||||
{ | |||||
using std::chrono::duration_cast; | |||||
using std::chrono::seconds; | |||||
auto duration = tp.time_since_epoch(); | |||||
auto secs = duration_cast<seconds>(duration); | |||||
return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); | |||||
} | |||||
} // namespace fmt_helper | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/log_msg.h" | |||||
#endif | |||||
#include "spdlog/details/os.h" | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE log_msg::log_msg(spdlog::source_loc loc, string_view_t logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) | |||||
: logger_name(logger_name) | |||||
, level(lvl) | |||||
#ifndef SPDLOG_NO_DATETIME | |||||
, time(os::now()) | |||||
#endif | |||||
#ifndef SPDLOG_NO_THREAD_ID | |||||
, thread_id(os::thread_id()) | |||||
#endif | |||||
, source(loc) | |||||
, payload(msg) | |||||
{} | |||||
SPDLOG_INLINE log_msg::log_msg(string_view_t logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) | |||||
: log_msg(source_loc{}, logger_name, lvl, msg) | |||||
{} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace details { | |||||
struct log_msg | |||||
{ | |||||
log_msg() = default; | |||||
log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
log_msg(const log_msg &other) = default; | |||||
string_view_t logger_name; | |||||
level::level_enum level{level::off}; | |||||
log_clock::time_point time; | |||||
size_t thread_id{0}; | |||||
// wrapping the formatted text with color (updated by pattern_formatter). | |||||
mutable size_t color_range_start{0}; | |||||
mutable size_t color_range_end{0}; | |||||
source_loc source; | |||||
string_view_t payload; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "log_msg-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/log_msg_buffer.h" | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg &orig_msg) | |||||
: log_msg{orig_msg} | |||||
{ | |||||
buffer.append(logger_name.begin(), logger_name.end()); | |||||
buffer.append(payload.begin(), payload.end()); | |||||
update_string_views(); | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg_buffer &other) | |||||
: log_msg{other} | |||||
{ | |||||
buffer.append(logger_name.begin(), logger_name.end()); | |||||
buffer.append(payload.begin(), payload.end()); | |||||
update_string_views(); | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer::log_msg_buffer(log_msg_buffer &&other) | |||||
: log_msg{std::move(other)} | |||||
, buffer{std::move(other.buffer)} | |||||
{ | |||||
update_string_views(); | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(const log_msg_buffer &other) | |||||
{ | |||||
log_msg::operator=(other); | |||||
buffer.clear(); | |||||
buffer.append(other.buffer.data(), other.buffer.data() + other.buffer.size()); | |||||
update_string_views(); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(log_msg_buffer &&other) | |||||
{ | |||||
log_msg::operator=(std::move(other)); | |||||
buffer = std::move(other.buffer); | |||||
update_string_views(); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE void log_msg_buffer::update_string_views() | |||||
{ | |||||
logger_name = string_view_t{buffer.data(), logger_name.size()}; | |||||
payload = string_view_t{buffer.data() + logger_name.size(), payload.size()}; | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/log_msg.h" | |||||
namespace spdlog { | |||||
namespace details { | |||||
// Extend log_msg with internal buffer to store its payload. | |||||
// THis is needed since log_msg holds string_views that points to stack data. | |||||
class log_msg_buffer : public log_msg | |||||
{ | |||||
memory_buf_t buffer; | |||||
void update_string_views(); | |||||
public: | |||||
log_msg_buffer() = default; | |||||
explicit log_msg_buffer(const log_msg &orig_msg); | |||||
log_msg_buffer(const log_msg_buffer &other); | |||||
log_msg_buffer(log_msg_buffer &&other); | |||||
log_msg_buffer &operator=(const log_msg_buffer &other); | |||||
log_msg_buffer &operator=(log_msg_buffer &&other); | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "log_msg_buffer-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// multi producer-multi consumer blocking queue. | |||||
// enqueue(..) - will block until room found to put the new message. | |||||
// enqueue_nowait(..) - will return immediately with false if no room left in | |||||
// the queue. | |||||
// dequeue_for(..) - will block until the queue is not empty or timeout have | |||||
// passed. | |||||
#include "spdlog/details/circular_q.h" | |||||
#include <condition_variable> | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
namespace details { | |||||
template<typename T> | |||||
class mpmc_blocking_queue | |||||
{ | |||||
public: | |||||
using item_type = T; | |||||
explicit mpmc_blocking_queue(size_t max_items) | |||||
: q_(max_items) | |||||
{} | |||||
#ifndef __MINGW32__ | |||||
// try to enqueue and block if no room left | |||||
void enqueue(T &&item) | |||||
{ | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
pop_cv_.wait(lock, [this] { return !this->q_.full(); }); | |||||
q_.push_back(std::move(item)); | |||||
} | |||||
push_cv_.notify_one(); | |||||
} | |||||
// enqueue immediately. overrun oldest message in the queue if no room left. | |||||
void enqueue_nowait(T &&item) | |||||
{ | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
q_.push_back(std::move(item)); | |||||
} | |||||
push_cv_.notify_one(); | |||||
} | |||||
// try to dequeue item. if no item found. wait upto timeout and try again | |||||
// Return true, if succeeded dequeue item, false otherwise | |||||
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) | |||||
{ | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) | |||||
{ | |||||
return false; | |||||
} | |||||
popped_item = std::move(q_.front()); | |||||
q_.pop_front(); | |||||
} | |||||
pop_cv_.notify_one(); | |||||
return true; | |||||
} | |||||
#else | |||||
// apparently mingw deadlocks if the mutex is released before cv.notify_one(), | |||||
// so release the mutex at the very end each function. | |||||
// try to enqueue and block if no room left | |||||
void enqueue(T &&item) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
pop_cv_.wait(lock, [this] { return !this->q_.full(); }); | |||||
q_.push_back(std::move(item)); | |||||
push_cv_.notify_one(); | |||||
} | |||||
// enqueue immediately. overrun oldest message in the queue if no room left. | |||||
void enqueue_nowait(T &&item) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
q_.push_back(std::move(item)); | |||||
push_cv_.notify_one(); | |||||
} | |||||
// try to dequeue item. if no item found. wait upto timeout and try again | |||||
// Return true, if succeeded dequeue item, false otherwise | |||||
bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) | |||||
{ | |||||
return false; | |||||
} | |||||
popped_item = std::move(q_.front()); | |||||
q_.pop_front(); | |||||
pop_cv_.notify_one(); | |||||
return true; | |||||
} | |||||
#endif | |||||
size_t overrun_counter() | |||||
{ | |||||
std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
return q_.overrun_counter(); | |||||
} | |||||
private: | |||||
std::mutex queue_mutex_; | |||||
std::condition_variable push_cv_; | |||||
std::condition_variable pop_cv_; | |||||
spdlog::details::circular_q<T> q_; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include <atomic> | |||||
#include <utility> | |||||
// null, no cost dummy "mutex" and dummy "atomic" int | |||||
namespace spdlog { | |||||
namespace details { | |||||
struct null_mutex | |||||
{ | |||||
void lock() const {} | |||||
void unlock() const {} | |||||
bool try_lock() const | |||||
{ | |||||
return true; | |||||
} | |||||
}; | |||||
struct null_atomic_int | |||||
{ | |||||
int value; | |||||
null_atomic_int() = default; | |||||
explicit null_atomic_int(int new_value) | |||||
: value(new_value) | |||||
{} | |||||
int load(std::memory_order = std::memory_order_relaxed) const | |||||
{ | |||||
return value; | |||||
} | |||||
void store(int new_value, std::memory_order = std::memory_order_relaxed) | |||||
{ | |||||
value = new_value; | |||||
} | |||||
int exchange(int new_value, std::memory_order = std::memory_order_relaxed) | |||||
{ | |||||
std::swap(new_value, value); | |||||
return new_value; // return value before the call | |||||
} | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/os.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include <algorithm> | |||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <cstdlib> | |||||
#include <cstring> | |||||
#include <ctime> | |||||
#include <string> | |||||
#include <thread> | |||||
#include <array> | |||||
#include <sys/stat.h> | |||||
#include <sys/types.h> | |||||
#ifdef _WIN32 | |||||
#ifndef NOMINMAX | |||||
#define NOMINMAX // prevent windows redefining min/max | |||||
#endif | |||||
#ifndef WIN32_LEAN_AND_MEAN | |||||
#define WIN32_LEAN_AND_MEAN | |||||
#endif | |||||
#include <io.h> // _get_osfhandle and _isatty support | |||||
#include <process.h> // _get_pid support | |||||
#include <windows.h> | |||||
#ifdef __MINGW32__ | |||||
#include <share.h> | |||||
#endif | |||||
#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES) | |||||
#include <limits> | |||||
#endif | |||||
#else // unix | |||||
#include <fcntl.h> | |||||
#include <unistd.h> | |||||
#ifdef __linux__ | |||||
#include <sys/syscall.h> //Use gettid() syscall under linux to get thread id | |||||
#elif defined(_AIX) | |||||
#include <pthread.h> // for pthread_getthreadid_np | |||||
#elif defined(__DragonFly__) || defined(__FreeBSD__) | |||||
#include <pthread_np.h> // for pthread_getthreadid_np | |||||
#elif defined(__NetBSD__) | |||||
#include <lwp.h> // for _lwp_self | |||||
#elif defined(__sun) | |||||
#include <thread.h> // for thr_self | |||||
#endif | |||||
#endif // unix | |||||
#ifndef __has_feature // Clang - feature checking macros. | |||||
#define __has_feature(x) 0 // Compatibility with non-clang compilers. | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
namespace os { | |||||
SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined __linux__ && defined SPDLOG_CLOCK_COARSE | |||||
timespec ts; | |||||
::clock_gettime(CLOCK_REALTIME_COARSE, &ts); | |||||
return std::chrono::time_point<log_clock, typename log_clock::duration>( | |||||
std::chrono::duration_cast<typename log_clock::duration>(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); | |||||
#else | |||||
return log_clock::now(); | |||||
#endif | |||||
} | |||||
SPDLOG_INLINE std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
std::tm tm; | |||||
localtime_s(&tm, &time_tt); | |||||
#else | |||||
std::tm tm; | |||||
localtime_r(&time_tt, &tm); | |||||
#endif | |||||
return tm; | |||||
} | |||||
SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT | |||||
{ | |||||
std::time_t now_t = time(nullptr); | |||||
return localtime(now_t); | |||||
} | |||||
SPDLOG_INLINE std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
std::tm tm; | |||||
gmtime_s(&tm, &time_tt); | |||||
#else | |||||
std::tm tm; | |||||
gmtime_r(&time_tt, &tm); | |||||
#endif | |||||
return tm; | |||||
} | |||||
SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT | |||||
{ | |||||
std::time_t now_t = time(nullptr); | |||||
return gmtime(now_t); | |||||
} | |||||
SPDLOG_INLINE void prevent_child_fd(FILE *f) | |||||
{ | |||||
#ifdef _WIN32 | |||||
#if !defined(__cplusplus_winrt) | |||||
auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(f))); | |||||
if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) | |||||
SPDLOG_THROW(spdlog_ex("SetHandleInformation failed", errno)); | |||||
#endif | |||||
#else | |||||
auto fd = fileno(f); | |||||
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("fcntl with FD_CLOEXEC failed", errno)); | |||||
} | |||||
#endif | |||||
} | |||||
// fopen_s on non windows for writing | |||||
SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) | |||||
{ | |||||
#ifdef _WIN32 | |||||
#ifdef SPDLOG_WCHAR_FILENAMES | |||||
*fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); | |||||
#else | |||||
*fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); | |||||
#endif | |||||
#else // unix | |||||
*fp = fopen((filename.c_str()), mode.c_str()); | |||||
#endif | |||||
#ifdef SPDLOG_PREVENT_CHILD_FD | |||||
if (*fp != nullptr) | |||||
{ | |||||
prevent_child_fd(*fp); | |||||
} | |||||
#endif | |||||
return *fp == nullptr; | |||||
} | |||||
SPDLOG_INLINE int remove(const filename_t &filename) SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
return _wremove(filename.c_str()); | |||||
#else | |||||
return std::remove(filename.c_str()); | |||||
#endif | |||||
} | |||||
SPDLOG_INLINE int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT | |||||
{ | |||||
return file_exists(filename) ? remove(filename) : 0; | |||||
} | |||||
SPDLOG_INLINE int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
return _wrename(filename1.c_str(), filename2.c_str()); | |||||
#else | |||||
return std::rename(filename1.c_str(), filename2.c_str()); | |||||
#endif | |||||
} | |||||
// Return true if file exists | |||||
SPDLOG_INLINE bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
#ifdef SPDLOG_WCHAR_FILENAMES | |||||
auto attribs = GetFileAttributesW(filename.c_str()); | |||||
#else | |||||
auto attribs = GetFileAttributesA(filename.c_str()); | |||||
#endif | |||||
return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); | |||||
#else // common linux/unix all have the stat system call | |||||
struct stat buffer; | |||||
return (::stat(filename.c_str(), &buffer) == 0); | |||||
#endif | |||||
} | |||||
// Return file size according to open FILE* object | |||||
SPDLOG_INLINE size_t filesize(FILE *f) | |||||
{ | |||||
if (f == nullptr) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed getting file size. fd is null")); | |||||
} | |||||
#if defined(_WIN32) && !defined(__CYGWIN__) | |||||
int fd = _fileno(f); | |||||
#if _WIN64 // 64 bits | |||||
__int64 ret = _filelengthi64(fd); | |||||
if (ret >= 0) | |||||
{ | |||||
return static_cast<size_t>(ret); | |||||
} | |||||
#else // windows 32 bits | |||||
long ret = _filelength(fd); | |||||
if (ret >= 0) | |||||
{ | |||||
return static_cast<size_t>(ret); | |||||
} | |||||
#endif | |||||
#else // unix | |||||
int fd = fileno(f); | |||||
// 64 bits(but not in osx or cygwin, where fstat64 is deprecated) | |||||
#if (defined(__linux__) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64)) | |||||
struct stat64 st; | |||||
if (::fstat64(fd, &st) == 0) | |||||
{ | |||||
return static_cast<size_t>(st.st_size); | |||||
} | |||||
#else // unix 32 bits or cygwin | |||||
struct stat st; | |||||
if (::fstat(fd, &st) == 0) | |||||
{ | |||||
return static_cast<size_t>(st.st_size); | |||||
} | |||||
#endif | |||||
#endif | |||||
SPDLOG_THROW(spdlog_ex("Failed getting file size from fd", errno)); | |||||
} | |||||
// Return utc offset in minutes or throw spdlog_ex on failure | |||||
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) | |||||
{ | |||||
#ifdef _WIN32 | |||||
#if _WIN32_WINNT < _WIN32_WINNT_WS08 | |||||
TIME_ZONE_INFORMATION tzinfo; | |||||
auto rv = GetTimeZoneInformation(&tzinfo); | |||||
#else | |||||
DYNAMIC_TIME_ZONE_INFORMATION tzinfo; | |||||
auto rv = GetDynamicTimeZoneInformation(&tzinfo); | |||||
#endif | |||||
if (rv == TIME_ZONE_ID_INVALID) | |||||
SPDLOG_THROW(spdlog::spdlog_ex("Failed getting timezone info. ", errno)); | |||||
int offset = -tzinfo.Bias; | |||||
if (tm.tm_isdst) | |||||
{ | |||||
offset -= tzinfo.DaylightBias; | |||||
} | |||||
else | |||||
{ | |||||
offset -= tzinfo.StandardBias; | |||||
} | |||||
return offset; | |||||
#else | |||||
#if defined(sun) || defined(__sun) || defined(_AIX) | |||||
// 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris | |||||
struct helper | |||||
{ | |||||
static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) | |||||
{ | |||||
int local_year = localtm.tm_year + (1900 - 1); | |||||
int gmt_year = gmtm.tm_year + (1900 - 1); | |||||
long int days = ( | |||||
// difference in day of year | |||||
localtm.tm_yday - | |||||
gmtm.tm_yday | |||||
// + intervening leap days | |||||
+ ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + | |||||
((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) | |||||
// + difference in years * 365 */ | |||||
+ (long int)(local_year - gmt_year) * 365); | |||||
long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); | |||||
long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); | |||||
long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); | |||||
return secs; | |||||
} | |||||
}; | |||||
auto offset_seconds = helper::calculate_gmt_offset(tm); | |||||
#else | |||||
auto offset_seconds = tm.tm_gmtoff; | |||||
#endif | |||||
return static_cast<int>(offset_seconds / 60); | |||||
#endif | |||||
} | |||||
// Return current thread id as size_t | |||||
// It exists because the std::this_thread::get_id() is much slower(especially | |||||
// under VS 2013) | |||||
SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return static_cast<size_t>(::GetCurrentThreadId()); | |||||
#elif defined(__linux__) | |||||
#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) | |||||
#define SYS_gettid __NR_gettid | |||||
#endif | |||||
return static_cast<size_t>(syscall(SYS_gettid)); | |||||
#elif defined(_AIX) || defined(__DragonFly__) || defined(__FreeBSD__) | |||||
return static_cast<size_t>(pthread_getthreadid_np()); | |||||
#elif defined(__NetBSD__) | |||||
return static_cast<size_t>(_lwp_self()); | |||||
#elif defined(__OpenBSD__) | |||||
return static_cast<size_t>(getthrid()); | |||||
#elif defined(__sun) | |||||
return static_cast<size_t>(thr_self()); | |||||
#elif __APPLE__ | |||||
uint64_t tid; | |||||
pthread_threadid_np(nullptr, &tid); | |||||
return static_cast<size_t>(tid); | |||||
#else // Default to standard C++11 (other Unix) | |||||
return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id())); | |||||
#endif | |||||
} | |||||
// Return current thread id as size_t (from thread local storage) | |||||
SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(SPDLOG_NO_TLS) | |||||
return _thread_id(); | |||||
#else // cache thread id in tls | |||||
static thread_local const size_t tid = _thread_id(); | |||||
return tid; | |||||
#endif | |||||
} | |||||
// This is avoid msvc issue in sleep_for that happens if the clock changes. | |||||
// See https://github.com/gabime/spdlog/issues/609 | |||||
SPDLOG_INLINE void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT | |||||
{ | |||||
#if defined(_WIN32) | |||||
::Sleep(milliseconds); | |||||
#else | |||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); | |||||
#endif | |||||
} | |||||
// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) | |||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) | |||||
{ | |||||
memory_buf_t buf; | |||||
wstr_to_utf8buf(filename, buf); | |||||
return fmt::to_string(buf); | |||||
} | |||||
#else | |||||
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) | |||||
{ | |||||
return filename; | |||||
} | |||||
#endif | |||||
SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return static_cast<int>(::GetCurrentProcessId()); | |||||
#else | |||||
return static_cast<int>(::getpid()); | |||||
#endif | |||||
} | |||||
// Determine if the terminal supports colors | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return true; | |||||
#else | |||||
static constexpr std::array<const char *, 14> Terms = { | |||||
{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}}; | |||||
const char *env_p = std::getenv("TERM"); | |||||
if (env_p == nullptr) | |||||
{ | |||||
return false; | |||||
} | |||||
static const bool result = | |||||
std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); | |||||
return result; | |||||
#endif | |||||
} | |||||
// Detrmine if the terminal attached | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT | |||||
{ | |||||
#ifdef _WIN32 | |||||
return _isatty(_fileno(file)) != 0; | |||||
#else | |||||
return isatty(fileno(file)) != 0; | |||||
#endif | |||||
} | |||||
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target) | |||||
{ | |||||
if (wstr.size() > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
{ | |||||
SPDLOG_THROW(spdlog::spdlog_ex("UTF-16 string is too big to be converted to UTF-8")); | |||||
} | |||||
int wstr_size = static_cast<int>(wstr.size()); | |||||
if (wstr_size == 0) | |||||
{ | |||||
target.resize(0); | |||||
return; | |||||
} | |||||
int result_size = static_cast<int>(target.capacity()); | |||||
if ((wstr_size + 1) * 2 > result_size) | |||||
{ | |||||
result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL); | |||||
} | |||||
if (result_size > 0) | |||||
{ | |||||
target.resize(result_size); | |||||
result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(), result_size, NULL, NULL); | |||||
if (result_size > 0) | |||||
{ | |||||
target.resize(result_size); | |||||
return; | |||||
} | |||||
} | |||||
SPDLOG_THROW(spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()))); | |||||
} | |||||
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
} // namespace os | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include <ctime> // std::time_t | |||||
namespace spdlog { | |||||
namespace details { | |||||
namespace os { | |||||
spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT; | |||||
std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; | |||||
std::tm localtime() SPDLOG_NOEXCEPT; | |||||
std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; | |||||
std::tm gmtime() SPDLOG_NOEXCEPT; | |||||
// eol definition | |||||
#if !defined(SPDLOG_EOL) | |||||
#ifdef _WIN32 | |||||
#define SPDLOG_EOL "\r\n" | |||||
#else | |||||
#define SPDLOG_EOL "\n" | |||||
#endif | |||||
#endif | |||||
SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; | |||||
// folder separator | |||||
#ifdef _WIN32 | |||||
const char folder_sep = '\\'; | |||||
#else | |||||
SPDLOG_CONSTEXPR static const char folder_sep = '/'; | |||||
#endif | |||||
void prevent_child_fd(FILE *f); | |||||
// fopen_s on non windows for writing | |||||
bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode); | |||||
// Remove filename. return 0 on success | |||||
int remove(const filename_t &filename) SPDLOG_NOEXCEPT; | |||||
// Remove file if exists. return 0 on success | |||||
// Note: Non atomic (might return failure to delete if concurrently deleted by other process/thread) | |||||
int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT; | |||||
int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT; | |||||
// Return if file exists. | |||||
bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT; | |||||
// Return file size according to open FILE* object | |||||
size_t filesize(FILE *f); | |||||
// Return utc offset in minutes or throw spdlog_ex on failure | |||||
int utc_minutes_offset(const std::tm &tm = details::os::localtime()); | |||||
// Return current thread id as size_t | |||||
// It exists because the std::this_thread::get_id() is much slower(especially | |||||
// under VS 2013) | |||||
size_t _thread_id() SPDLOG_NOEXCEPT; | |||||
// Return current thread id as size_t (from thread local storage) | |||||
size_t thread_id() SPDLOG_NOEXCEPT; | |||||
// This is avoid msvc issue in sleep_for that happens if the clock changes. | |||||
// See https://github.com/gabime/spdlog/issues/609 | |||||
void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT; | |||||
std::string filename_to_str(const filename_t &filename); | |||||
int pid() SPDLOG_NOEXCEPT; | |||||
// Determine if the terminal supports colors | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
bool is_color_terminal() SPDLOG_NOEXCEPT; | |||||
// Detrmine if the terminal attached | |||||
// Source: https://github.com/agauniyal/rang/ | |||||
bool in_terminal(FILE *file) SPDLOG_NOEXCEPT; | |||||
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target); | |||||
#endif | |||||
} // namespace os | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "os-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/log_msg.h" | |||||
#include "spdlog/details/os.h" | |||||
#include "spdlog/formatter.h" | |||||
#include <chrono> | |||||
#include <ctime> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace spdlog { | |||||
namespace details { | |||||
// padding information. | |||||
struct padding_info | |||||
{ | |||||
enum pad_side | |||||
{ | |||||
left, | |||||
right, | |||||
center | |||||
}; | |||||
padding_info() = default; | |||||
padding_info(size_t width, padding_info::pad_side side) | |||||
: width_(width) | |||||
, side_(side) | |||||
{} | |||||
bool enabled() const | |||||
{ | |||||
return width_ != 0; | |||||
} | |||||
const size_t width_ = 0; | |||||
const pad_side side_ = left; | |||||
}; | |||||
class flag_formatter | |||||
{ | |||||
public: | |||||
explicit flag_formatter(padding_info padinfo) | |||||
: padinfo_(padinfo) | |||||
{} | |||||
flag_formatter() = default; | |||||
virtual ~flag_formatter() = default; | |||||
virtual void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) = 0; | |||||
protected: | |||||
padding_info padinfo_; | |||||
}; | |||||
} // namespace details | |||||
class pattern_formatter final : public formatter | |||||
{ | |||||
public: | |||||
explicit pattern_formatter( | |||||
std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol); | |||||
// use default pattern is not given | |||||
explicit pattern_formatter(pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol); | |||||
pattern_formatter(const pattern_formatter &other) = delete; | |||||
pattern_formatter &operator=(const pattern_formatter &other) = delete; | |||||
std::unique_ptr<formatter> clone() const override; | |||||
void format(const details::log_msg &msg, memory_buf_t &dest) override; | |||||
private: | |||||
std::string pattern_; | |||||
std::string eol_; | |||||
pattern_time_type pattern_time_type_; | |||||
std::tm cached_tm_; | |||||
std::chrono::seconds last_log_secs_; | |||||
std::vector<std::unique_ptr<details::flag_formatter>> formatters_; | |||||
std::tm get_time_(const details::log_msg &msg); | |||||
template<typename Padder> | |||||
void handle_flag_(char flag, details::padding_info padding); | |||||
// Extract given pad spec (e.g. %8X) | |||||
// Advance the given it pass the end of the padding spec found (if any) | |||||
// Return padding. | |||||
details::padding_info handle_padspec_(std::string::const_iterator &it, std::string::const_iterator end); | |||||
void compile_pattern_(const std::string &pattern); | |||||
}; | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "pattern_formatter-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/periodic_worker.h" | |||||
#endif | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE periodic_worker::periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval) | |||||
{ | |||||
active_ = (interval > std::chrono::seconds::zero()); | |||||
if (!active_) | |||||
{ | |||||
return; | |||||
} | |||||
worker_thread_ = std::thread([this, callback_fun, interval]() { | |||||
for (;;) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(this->mutex_); | |||||
if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) | |||||
{ | |||||
return; // active_ == false, so exit this thread | |||||
} | |||||
callback_fun(); | |||||
} | |||||
}); | |||||
} | |||||
// stop the worker thread and join it | |||||
SPDLOG_INLINE periodic_worker::~periodic_worker() | |||||
{ | |||||
if (worker_thread_.joinable()) | |||||
{ | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex_); | |||||
active_ = false; | |||||
} | |||||
cv_.notify_one(); | |||||
worker_thread_.join(); | |||||
} | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// periodic worker thread - periodically executes the given callback function. | |||||
// | |||||
// RAII over the owned thread: | |||||
// creates the thread on construction. | |||||
// stops and joins the thread on destruction (if the thread is executing a callback, wait for it to finish first). | |||||
#include <chrono> | |||||
#include <condition_variable> | |||||
#include <functional> | |||||
#include <mutex> | |||||
#include <thread> | |||||
namespace spdlog { | |||||
namespace details { | |||||
class periodic_worker | |||||
{ | |||||
public: | |||||
periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval); | |||||
periodic_worker(const periodic_worker &) = delete; | |||||
periodic_worker &operator=(const periodic_worker &) = delete; | |||||
// stop the worker thread and join it | |||||
~periodic_worker(); | |||||
private: | |||||
bool active_; | |||||
std::thread worker_thread_; | |||||
std::mutex mutex_; | |||||
std::condition_variable cv_; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "periodic_worker-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/registry.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/periodic_worker.h" | |||||
#include "spdlog/logger.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
// support for the default stdout color logger | |||||
#ifdef _WIN32 | |||||
#include "spdlog/sinks/wincolor_sink.h" | |||||
#else | |||||
#include "spdlog/sinks/ansicolor_sink.h" | |||||
#endif | |||||
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
#include <chrono> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE registry::registry() | |||||
: formatter_(new pattern_formatter()) | |||||
{ | |||||
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
// create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows). | |||||
#ifdef _WIN32 | |||||
auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>(); | |||||
#else | |||||
auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>(); | |||||
#endif | |||||
const char *default_logger_name = ""; | |||||
default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink)); | |||||
loggers_[default_logger_name] = default_logger_; | |||||
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
} | |||||
SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
register_logger_(std::move(new_logger)); | |||||
} | |||||
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
new_logger->set_formatter(formatter_->clone()); | |||||
if (err_handler_) | |||||
{ | |||||
new_logger->set_error_handler(err_handler_); | |||||
} | |||||
new_logger->set_level(level_); | |||||
new_logger->flush_on(flush_level_); | |||||
if (backtrace_n_messages_ > 0) | |||||
{ | |||||
new_logger->enable_backtrace(backtrace_n_messages_); | |||||
} | |||||
if (automatic_registration_) | |||||
{ | |||||
register_logger_(std::move(new_logger)); | |||||
} | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<logger> registry::get(const std::string &logger_name) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
auto found = loggers_.find(logger_name); | |||||
return found == loggers_.end() ? nullptr : found->second; | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
return default_logger_; | |||||
} | |||||
// Return raw ptr to the default logger. | |||||
// To be used directly by the spdlog default api (e.g. spdlog::info) | |||||
// This make the default API faster, but cannot be used concurrently with set_default_logger(). | |||||
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
SPDLOG_INLINE logger *registry::get_default_raw() | |||||
{ | |||||
return default_logger_.get(); | |||||
} | |||||
// set default logger. | |||||
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||||
SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
// remove previous default logger from the map | |||||
if (default_logger_ != nullptr) | |||||
{ | |||||
loggers_.erase(default_logger_->name()); | |||||
} | |||||
if (new_default_logger != nullptr) | |||||
{ | |||||
loggers_[new_default_logger->name()] = new_default_logger; | |||||
} | |||||
default_logger_ = std::move(new_default_logger); | |||||
} | |||||
SPDLOG_INLINE void registry::set_tp(std::shared_ptr<thread_pool> tp) | |||||
{ | |||||
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
tp_ = std::move(tp); | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<thread_pool> registry::get_tp() | |||||
{ | |||||
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
return tp_; | |||||
} | |||||
// Set global formatter. Each sink in each logger will get a clone of this object | |||||
SPDLOG_INLINE void registry::set_formatter(std::unique_ptr<formatter> formatter) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
formatter_ = std::move(formatter); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->set_formatter(formatter_->clone()); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
backtrace_n_messages_ = n_messages; | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->enable_backtrace(n_messages); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::disable_backtrace() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
backtrace_n_messages_ = 0; | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->disable_backtrace(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::set_level(level::level_enum log_level) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->set_level(log_level); | |||||
} | |||||
level_ = log_level; | |||||
} | |||||
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->flush_on(log_level); | |||||
} | |||||
flush_level_ = log_level; | |||||
} | |||||
SPDLOG_INLINE void registry::flush_every(std::chrono::seconds interval) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(flusher_mutex_); | |||||
std::function<void()> clbk = std::bind(®istry::flush_all, this); | |||||
periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval); | |||||
} | |||||
SPDLOG_INLINE void registry::set_error_handler(void (*handler)(const std::string &msg)) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->set_error_handler(handler); | |||||
} | |||||
err_handler_ = handler; | |||||
} | |||||
SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
fun(l.second); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::flush_all() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
for (auto &l : loggers_) | |||||
{ | |||||
l.second->flush(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::drop(const std::string &logger_name) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
loggers_.erase(logger_name); | |||||
if (default_logger_ && default_logger_->name() == logger_name) | |||||
{ | |||||
default_logger_.reset(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::drop_all() | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
loggers_.clear(); | |||||
default_logger_.reset(); | |||||
} | |||||
// clean all resources and threads started by the registry | |||||
SPDLOG_INLINE void registry::shutdown() | |||||
{ | |||||
{ | |||||
std::lock_guard<std::mutex> lock(flusher_mutex_); | |||||
periodic_flusher_.reset(); | |||||
} | |||||
drop_all(); | |||||
{ | |||||
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
tp_.reset(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE std::recursive_mutex ®istry::tp_mutex() | |||||
{ | |||||
return tp_mutex_; | |||||
} | |||||
SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_regsistration) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
automatic_registration_ = automatic_regsistration; | |||||
} | |||||
SPDLOG_INLINE registry ®istry::instance() | |||||
{ | |||||
static registry s_instance; | |||||
return s_instance; | |||||
} | |||||
SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) | |||||
{ | |||||
if (loggers_.find(logger_name) != loggers_.end()) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("logger with name '" + logger_name + "' already exists")); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) | |||||
{ | |||||
auto logger_name = new_logger->name(); | |||||
throw_if_exists_(logger_name); | |||||
loggers_[logger_name] = std::move(new_logger); | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// Loggers registry of unique name->logger pointer | |||||
// An attempt to create a logger with an already existing name will result with spdlog_ex exception. | |||||
// If user requests a non existing logger, nullptr will be returned | |||||
// This class is thread safe | |||||
#include "spdlog/common.h" | |||||
#include <chrono> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
class logger; | |||||
namespace details { | |||||
class thread_pool; | |||||
class periodic_worker; | |||||
class registry | |||||
{ | |||||
public: | |||||
registry(const registry &) = delete; | |||||
registry &operator=(const registry &) = delete; | |||||
void register_logger(std::shared_ptr<logger> new_logger); | |||||
void initialize_logger(std::shared_ptr<logger> new_logger); | |||||
std::shared_ptr<logger> get(const std::string &logger_name); | |||||
std::shared_ptr<logger> default_logger(); | |||||
// Return raw ptr to the default logger. | |||||
// To be used directly by the spdlog default api (e.g. spdlog::info) | |||||
// This make the default API faster, but cannot be used concurrently with set_default_logger(). | |||||
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
logger *get_default_raw(); | |||||
// set default logger. | |||||
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||||
void set_default_logger(std::shared_ptr<logger> new_default_logger); | |||||
void set_tp(std::shared_ptr<thread_pool> tp); | |||||
std::shared_ptr<thread_pool> get_tp(); | |||||
// Set global formatter. Each sink in each logger will get a clone of this object | |||||
void set_formatter(std::unique_ptr<formatter> formatter); | |||||
void enable_backtrace(size_t n_messages); | |||||
void disable_backtrace(); | |||||
void set_level(level::level_enum log_level); | |||||
void flush_on(level::level_enum log_level); | |||||
void flush_every(std::chrono::seconds interval); | |||||
void set_error_handler(void (*handler)(const std::string &msg)); | |||||
void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun); | |||||
void flush_all(); | |||||
void drop(const std::string &logger_name); | |||||
void drop_all(); | |||||
// clean all resources and threads started by the registry | |||||
void shutdown(); | |||||
std::recursive_mutex &tp_mutex(); | |||||
void set_automatic_registration(bool automatic_regsistration); | |||||
static registry &instance(); | |||||
private: | |||||
registry(); | |||||
~registry() = default; | |||||
void throw_if_exists_(const std::string &logger_name); | |||||
void register_logger_(std::shared_ptr<logger> new_logger); | |||||
std::mutex logger_map_mutex_, flusher_mutex_; | |||||
std::recursive_mutex tp_mutex_; | |||||
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; | |||||
std::unique_ptr<formatter> formatter_; | |||||
level::level_enum level_ = level::info; | |||||
level::level_enum flush_level_ = level::off; | |||||
void (*err_handler_)(const std::string &msg); | |||||
std::shared_ptr<thread_pool> tp_; | |||||
std::unique_ptr<periodic_worker> periodic_flusher_; | |||||
std::shared_ptr<logger> default_logger_; | |||||
bool automatic_registration_ = true; | |||||
size_t backtrace_n_messages_ = 0; | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "registry-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "registry.h" | |||||
namespace spdlog { | |||||
// Default logger factory- creates synchronous loggers | |||||
class logger; | |||||
struct synchronous_factory | |||||
{ | |||||
template<typename Sink, typename... SinkArgs> | |||||
static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args) | |||||
{ | |||||
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||||
auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink)); | |||||
details::registry::instance().initialize_logger(new_logger); | |||||
return new_logger; | |||||
} | |||||
}; | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/details/thread_pool.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
namespace spdlog { | |||||
namespace details { | |||||
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start) | |||||
: q_(q_max_items) | |||||
{ | |||||
if (threads_n == 0 || threads_n > 1000) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " | |||||
"range is 1-1000)")); | |||||
} | |||||
for (size_t i = 0; i < threads_n; i++) | |||||
{ | |||||
threads_.emplace_back([this, on_thread_start] { | |||||
on_thread_start(); | |||||
this->thread_pool::worker_loop_(); | |||||
}); | |||||
} | |||||
} | |||||
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n) | |||||
: thread_pool(q_max_items, threads_n, [] {}) | |||||
{} | |||||
// message all threads to terminate gracefully join them | |||||
SPDLOG_INLINE thread_pool::~thread_pool() | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
for (size_t i = 0; i < threads_.size(); i++) | |||||
{ | |||||
post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); | |||||
} | |||||
for (auto &t : threads_) | |||||
{ | |||||
t.join(); | |||||
} | |||||
} | |||||
SPDLOG_CATCH_ALL() {} | |||||
} | |||||
void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy) | |||||
{ | |||||
async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg); | |||||
post_async_msg_(std::move(async_m), overflow_policy); | |||||
} | |||||
void SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy) | |||||
{ | |||||
post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); | |||||
} | |||||
size_t SPDLOG_INLINE thread_pool::overrun_counter() | |||||
{ | |||||
return q_.overrun_counter(); | |||||
} | |||||
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) | |||||
{ | |||||
if (overflow_policy == async_overflow_policy::block) | |||||
{ | |||||
q_.enqueue(std::move(new_msg)); | |||||
} | |||||
else | |||||
{ | |||||
q_.enqueue_nowait(std::move(new_msg)); | |||||
} | |||||
} | |||||
void SPDLOG_INLINE thread_pool::worker_loop_() | |||||
{ | |||||
while (process_next_msg_()) {}; | |||||
} | |||||
// process next message in the queue | |||||
// return true if this thread should still be active (while no terminate msg | |||||
// was received) | |||||
bool SPDLOG_INLINE thread_pool::process_next_msg_() | |||||
{ | |||||
async_msg incoming_async_msg; | |||||
bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); | |||||
if (!dequeued) | |||||
{ | |||||
return true; | |||||
} | |||||
switch (incoming_async_msg.msg_type) | |||||
{ | |||||
case async_msg_type::log: | |||||
{ | |||||
incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg); | |||||
return true; | |||||
} | |||||
case async_msg_type::flush: | |||||
{ | |||||
incoming_async_msg.worker_ptr->backend_flush_(); | |||||
return true; | |||||
} | |||||
case async_msg_type::terminate: | |||||
{ | |||||
return false; | |||||
} | |||||
default: | |||||
{ | |||||
assert(false && "Unexpected async_msg_type"); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
} // namespace details | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/log_msg_buffer.h" | |||||
#include "spdlog/details/mpmc_blocking_q.h" | |||||
#include "spdlog/details/os.h" | |||||
#include <chrono> | |||||
#include <memory> | |||||
#include <thread> | |||||
#include <vector> | |||||
#include <functional> | |||||
namespace spdlog { | |||||
class async_logger; | |||||
namespace details { | |||||
using async_logger_ptr = std::shared_ptr<spdlog::async_logger>; | |||||
enum class async_msg_type | |||||
{ | |||||
log, | |||||
flush, | |||||
terminate | |||||
}; | |||||
#include "spdlog/details/log_msg_buffer.h" | |||||
// Async msg to move to/from the queue | |||||
// Movable only. should never be copied | |||||
struct async_msg : log_msg_buffer | |||||
{ | |||||
async_msg_type msg_type{async_msg_type::log}; | |||||
async_logger_ptr worker_ptr; | |||||
async_msg() = default; | |||||
~async_msg() = default; | |||||
// should only be moved in or out of the queue.. | |||||
async_msg(const async_msg &) = delete; | |||||
// support for vs2013 move | |||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 | |||||
async_msg(async_msg &&other) | |||||
: log_msg_buffer(std::move(other)) | |||||
, msg_type(other.msg_type) | |||||
, worker_ptr(std::move(other.worker_ptr)) | |||||
{} | |||||
async_msg &operator=(async_msg &&other) | |||||
{ | |||||
*static_cast<log_msg_buffer *>(this) = std::move(other); | |||||
msg_type = other.msg_type; | |||||
worker_ptr = std::move(other.worker_ptr); | |||||
return *this; | |||||
} | |||||
#else // (_MSC_VER) && _MSC_VER <= 1800 | |||||
async_msg(async_msg &&) = default; | |||||
async_msg &operator=(async_msg &&) = default; | |||||
#endif | |||||
// construct from log_msg with given type | |||||
async_msg(async_logger_ptr &&worker, async_msg_type the_type, const details::log_msg &m) | |||||
: log_msg_buffer{m} | |||||
, msg_type{the_type} | |||||
, worker_ptr{std::move(worker)} | |||||
{} | |||||
async_msg(async_logger_ptr &&worker, async_msg_type the_type) | |||||
: log_msg_buffer{} | |||||
, msg_type{the_type} | |||||
, worker_ptr{std::move(worker)} | |||||
{} | |||||
explicit async_msg(async_msg_type the_type) | |||||
: async_msg{nullptr, the_type} | |||||
{} | |||||
}; | |||||
class thread_pool | |||||
{ | |||||
public: | |||||
using item_type = async_msg; | |||||
using q_type = details::mpmc_blocking_queue<item_type>; | |||||
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start); | |||||
thread_pool(size_t q_max_items, size_t threads_n); | |||||
// message all threads to terminate gracefully join them | |||||
~thread_pool(); | |||||
thread_pool(const thread_pool &) = delete; | |||||
thread_pool &operator=(thread_pool &&) = delete; | |||||
void post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy); | |||||
void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy); | |||||
size_t overrun_counter(); | |||||
private: | |||||
q_type q_; | |||||
std::vector<std::thread> threads_; | |||||
void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy); | |||||
void worker_loop_(); | |||||
// process next message in the queue | |||||
// return true if this thread should still be active (while no terminate msg | |||||
// was received) | |||||
bool process_next_msg_(); | |||||
}; | |||||
} // namespace details | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "thread_pool-inl.h" | |||||
#endif |
// | |||||
// Copyright(c) 2015 Gabi Melman. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// | |||||
#pragma once | |||||
// | |||||
// Support for logging binary data as hex | |||||
// format flags: | |||||
// {:X} - print in uppercase. | |||||
// {:s} - don't separate each byte with space. | |||||
// {:p} - don't print the position on each line start. | |||||
// {:n} - don't split the output to lines. | |||||
// | |||||
// Examples: | |||||
// | |||||
// std::vector<char> v(200, 0x0b); | |||||
// logger->info("Some buffer {}", spdlog::to_hex(v)); | |||||
// char buf[128]; | |||||
// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); | |||||
namespace spdlog { | |||||
namespace details { | |||||
template<typename It> | |||||
class bytes_range | |||||
{ | |||||
public: | |||||
bytes_range(It range_begin, It range_end) | |||||
: begin_(range_begin) | |||||
, end_(range_end) | |||||
{} | |||||
It begin() const | |||||
{ | |||||
return begin_; | |||||
} | |||||
It end() const | |||||
{ | |||||
return end_; | |||||
} | |||||
private: | |||||
It begin_, end_; | |||||
}; | |||||
} // namespace details | |||||
// create a bytes_range that wraps the given container | |||||
template<typename Container> | |||||
inline details::bytes_range<typename Container::const_iterator> to_hex(const Container &container) | |||||
{ | |||||
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); | |||||
using Iter = typename Container::const_iterator; | |||||
return details::bytes_range<Iter>(std::begin(container), std::end(container)); | |||||
} | |||||
// create bytes_range from ranges | |||||
template<typename It> | |||||
inline details::bytes_range<It> to_hex(const It range_begin, const It range_end) | |||||
{ | |||||
return details::bytes_range<It>(range_begin, range_end); | |||||
} | |||||
} // namespace spdlog | |||||
namespace fmt { | |||||
template<typename T> | |||||
struct formatter<spdlog::details::bytes_range<T>> | |||||
{ | |||||
const std::size_t line_size = 100; | |||||
const char delimiter = ' '; | |||||
bool put_newlines = true; | |||||
bool put_delimiters = true; | |||||
bool use_uppercase = false; | |||||
bool put_positions = true; // position on start of each line | |||||
// parse the format string flags | |||||
template<typename ParseContext> | |||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) | |||||
{ | |||||
auto it = ctx.begin(); | |||||
while (*it && *it != '}') | |||||
{ | |||||
switch (*it) | |||||
{ | |||||
case 'X': | |||||
use_uppercase = true; | |||||
break; | |||||
case 's': | |||||
put_delimiters = false; | |||||
break; | |||||
case 'p': | |||||
put_positions = false; | |||||
break; | |||||
case 'n': | |||||
put_newlines = false; | |||||
break; | |||||
} | |||||
++it; | |||||
} | |||||
return it; | |||||
} | |||||
// format the given bytes range as hex | |||||
template<typename FormatContext, typename Container> | |||||
auto format(const spdlog::details::bytes_range<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out()) | |||||
{ | |||||
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; | |||||
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; | |||||
const char *hex_chars = use_uppercase ? hex_upper : hex_lower; | |||||
std::size_t pos = 0; | |||||
std::size_t column = line_size; | |||||
#if FMT_VERSION < 60000 | |||||
auto inserter = ctx.begin(); | |||||
#else | |||||
auto inserter = ctx.out(); | |||||
#endif | |||||
for (auto &item : the_range) | |||||
{ | |||||
auto ch = static_cast<unsigned char>(item); | |||||
pos++; | |||||
if (put_newlines && column >= line_size) | |||||
{ | |||||
column = put_newline(inserter, pos); | |||||
// put first byte without delimiter in front of it | |||||
*inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||||
*inserter++ = hex_chars[ch & 0x0f]; | |||||
column += 2; | |||||
continue; | |||||
} | |||||
if (put_delimiters) | |||||
{ | |||||
*inserter++ = delimiter; | |||||
++column; | |||||
} | |||||
*inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||||
*inserter++ = hex_chars[ch & 0x0f]; | |||||
column += 2; | |||||
} | |||||
return inserter; | |||||
} | |||||
// put newline(and position header) | |||||
// return the next column | |||||
template<typename It> | |||||
std::size_t put_newline(It inserter, std::size_t pos) | |||||
{ | |||||
#ifdef _WIN32 | |||||
*inserter++ = '\r'; | |||||
#endif | |||||
*inserter++ = '\n'; | |||||
if (put_positions) | |||||
{ | |||||
fmt::format_to(inserter, "{:<04X}: ", pos - 1); | |||||
return 7; | |||||
} | |||||
else | |||||
{ | |||||
return 1; | |||||
} | |||||
} | |||||
}; | |||||
} // namespace fmt |
Copyright (c) 2012 - present, Victor Zverovich | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of this software and associated documentation files (the | |||||
"Software"), to deal in the Software without restriction, including | |||||
without limitation the rights to use, copy, modify, merge, publish, | |||||
distribute, sublicense, and/or sell copies of the Software, and to | |||||
permit persons to whom the Software is furnished to do so, subject to | |||||
the following conditions: | |||||
The above copyright notice and this permission notice shall be | |||||
included in all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
--- Optional exception to the license --- | |||||
As an exception, if, as a result of your compiling your source code, portions | |||||
of this Software are embedded into a machine-executable object form of such | |||||
source code, you may redistribute such embedded portions in such object form | |||||
without including the above copyright and permission notices. |
// Formatting library for C++ - chrono support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_CHRONO_H_ | |||||
#define FMT_CHRONO_H_ | |||||
#include "format.h" | |||||
#include "locale.h" | |||||
#include <chrono> | |||||
#include <ctime> | |||||
#include <locale> | |||||
#include <sstream> | |||||
// enable safe chrono durations, unless explicitly disabled | |||||
#ifndef FMT_SAFE_DURATION_CAST | |||||
# define FMT_SAFE_DURATION_CAST 1 | |||||
#endif | |||||
#if FMT_SAFE_DURATION_CAST | |||||
# include "safe-duration-cast.h" | |||||
#endif | |||||
FMT_BEGIN_NAMESPACE | |||||
// Prevents expansion of a preceding token as a function-style macro. | |||||
// Usage: f FMT_NOMACRO() | |||||
#define FMT_NOMACRO | |||||
namespace internal { | |||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } | |||||
inline null<> localtime_s(...) { return null<>(); } | |||||
inline null<> gmtime_r(...) { return null<>(); } | |||||
inline null<> gmtime_s(...) { return null<>(); } | |||||
} // namespace internal | |||||
// Thread-safe replacement for std::localtime | |||||
inline std::tm localtime(std::time_t time) { | |||||
struct dispatcher { | |||||
std::time_t time_; | |||||
std::tm tm_; | |||||
dispatcher(std::time_t t) : time_(t) {} | |||||
bool run() { | |||||
using namespace fmt::internal; | |||||
return handle(localtime_r(&time_, &tm_)); | |||||
} | |||||
bool handle(std::tm* tm) { return tm != nullptr; } | |||||
bool handle(internal::null<>) { | |||||
using namespace fmt::internal; | |||||
return fallback(localtime_s(&tm_, &time_)); | |||||
} | |||||
bool fallback(int res) { return res == 0; } | |||||
#if !FMT_MSC_VER | |||||
bool fallback(internal::null<>) { | |||||
using namespace fmt::internal; | |||||
std::tm* tm = std::localtime(&time_); | |||||
if (tm) tm_ = *tm; | |||||
return tm != nullptr; | |||||
} | |||||
#endif | |||||
}; | |||||
dispatcher lt(time); | |||||
// Too big time values may be unsupported. | |||||
if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); | |||||
return lt.tm_; | |||||
} | |||||
// Thread-safe replacement for std::gmtime | |||||
inline std::tm gmtime(std::time_t time) { | |||||
struct dispatcher { | |||||
std::time_t time_; | |||||
std::tm tm_; | |||||
dispatcher(std::time_t t) : time_(t) {} | |||||
bool run() { | |||||
using namespace fmt::internal; | |||||
return handle(gmtime_r(&time_, &tm_)); | |||||
} | |||||
bool handle(std::tm* tm) { return tm != nullptr; } | |||||
bool handle(internal::null<>) { | |||||
using namespace fmt::internal; | |||||
return fallback(gmtime_s(&tm_, &time_)); | |||||
} | |||||
bool fallback(int res) { return res == 0; } | |||||
#if !FMT_MSC_VER | |||||
bool fallback(internal::null<>) { | |||||
std::tm* tm = std::gmtime(&time_); | |||||
if (tm) tm_ = *tm; | |||||
return tm != nullptr; | |||||
} | |||||
#endif | |||||
}; | |||||
dispatcher gt(time); | |||||
// Too big time values may be unsupported. | |||||
if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); | |||||
return gt.tm_; | |||||
} | |||||
namespace internal { | |||||
inline std::size_t strftime(char* str, std::size_t count, const char* format, | |||||
const std::tm* time) { | |||||
return std::strftime(str, count, format, time); | |||||
} | |||||
inline std::size_t strftime(wchar_t* str, std::size_t count, | |||||
const wchar_t* format, const std::tm* time) { | |||||
return std::wcsftime(str, count, format, time); | |||||
} | |||||
} // namespace internal | |||||
template <typename Char> struct formatter<std::tm, Char> { | |||||
template <typename ParseContext> | |||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
auto it = ctx.begin(); | |||||
if (it != ctx.end() && *it == ':') ++it; | |||||
auto end = it; | |||||
while (end != ctx.end() && *end != '}') ++end; | |||||
tm_format.reserve(internal::to_unsigned(end - it + 1)); | |||||
tm_format.append(it, end); | |||||
tm_format.push_back('\0'); | |||||
return end; | |||||
} | |||||
template <typename FormatContext> | |||||
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
basic_memory_buffer<Char> buf; | |||||
std::size_t start = buf.size(); | |||||
for (;;) { | |||||
std::size_t size = buf.capacity() - start; | |||||
std::size_t count = | |||||
internal::strftime(&buf[start], size, &tm_format[0], &tm); | |||||
if (count != 0) { | |||||
buf.resize(start + count); | |||||
break; | |||||
} | |||||
if (size >= tm_format.size() * 256) { | |||||
// If the buffer is 256 times larger than the format string, assume | |||||
// that `strftime` gives an empty result. There doesn't seem to be a | |||||
// better way to distinguish the two cases: | |||||
// https://github.com/fmtlib/fmt/issues/367 | |||||
break; | |||||
} | |||||
const std::size_t MIN_GROWTH = 10; | |||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); | |||||
} | |||||
return std::copy(buf.begin(), buf.end(), ctx.out()); | |||||
} | |||||
basic_memory_buffer<Char> tm_format; | |||||
}; | |||||
namespace internal { | |||||
template <typename Period> FMT_CONSTEXPR const char* get_units() { | |||||
return nullptr; | |||||
} | |||||
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; } | |||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() { | |||||
return "m"; | |||||
} | |||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() { | |||||
return "h"; | |||||
} | |||||
enum class numeric_system { | |||||
standard, | |||||
// Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. | |||||
alternative | |||||
}; | |||||
// Parses a put_time-like format string and invokes handler actions. | |||||
template <typename Char, typename Handler> | |||||
FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, | |||||
const Char* end, | |||||
Handler&& handler) { | |||||
auto ptr = begin; | |||||
while (ptr != end) { | |||||
auto c = *ptr; | |||||
if (c == '}') break; | |||||
if (c != '%') { | |||||
++ptr; | |||||
continue; | |||||
} | |||||
if (begin != ptr) handler.on_text(begin, ptr); | |||||
++ptr; // consume '%' | |||||
if (ptr == end) FMT_THROW(format_error("invalid format")); | |||||
c = *ptr++; | |||||
switch (c) { | |||||
case '%': | |||||
handler.on_text(ptr - 1, ptr); | |||||
break; | |||||
case 'n': { | |||||
const char newline[] = "\n"; | |||||
handler.on_text(newline, newline + 1); | |||||
break; | |||||
} | |||||
case 't': { | |||||
const char tab[] = "\t"; | |||||
handler.on_text(tab, tab + 1); | |||||
break; | |||||
} | |||||
// Day of the week: | |||||
case 'a': | |||||
handler.on_abbr_weekday(); | |||||
break; | |||||
case 'A': | |||||
handler.on_full_weekday(); | |||||
break; | |||||
case 'w': | |||||
handler.on_dec0_weekday(numeric_system::standard); | |||||
break; | |||||
case 'u': | |||||
handler.on_dec1_weekday(numeric_system::standard); | |||||
break; | |||||
// Month: | |||||
case 'b': | |||||
handler.on_abbr_month(); | |||||
break; | |||||
case 'B': | |||||
handler.on_full_month(); | |||||
break; | |||||
// Hour, minute, second: | |||||
case 'H': | |||||
handler.on_24_hour(numeric_system::standard); | |||||
break; | |||||
case 'I': | |||||
handler.on_12_hour(numeric_system::standard); | |||||
break; | |||||
case 'M': | |||||
handler.on_minute(numeric_system::standard); | |||||
break; | |||||
case 'S': | |||||
handler.on_second(numeric_system::standard); | |||||
break; | |||||
// Other: | |||||
case 'c': | |||||
handler.on_datetime(numeric_system::standard); | |||||
break; | |||||
case 'x': | |||||
handler.on_loc_date(numeric_system::standard); | |||||
break; | |||||
case 'X': | |||||
handler.on_loc_time(numeric_system::standard); | |||||
break; | |||||
case 'D': | |||||
handler.on_us_date(); | |||||
break; | |||||
case 'F': | |||||
handler.on_iso_date(); | |||||
break; | |||||
case 'r': | |||||
handler.on_12_hour_time(); | |||||
break; | |||||
case 'R': | |||||
handler.on_24_hour_time(); | |||||
break; | |||||
case 'T': | |||||
handler.on_iso_time(); | |||||
break; | |||||
case 'p': | |||||
handler.on_am_pm(); | |||||
break; | |||||
case 'Q': | |||||
handler.on_duration_value(); | |||||
break; | |||||
case 'q': | |||||
handler.on_duration_unit(); | |||||
break; | |||||
case 'z': | |||||
handler.on_utc_offset(); | |||||
break; | |||||
case 'Z': | |||||
handler.on_tz_name(); | |||||
break; | |||||
// Alternative representation: | |||||
case 'E': { | |||||
if (ptr == end) FMT_THROW(format_error("invalid format")); | |||||
c = *ptr++; | |||||
switch (c) { | |||||
case 'c': | |||||
handler.on_datetime(numeric_system::alternative); | |||||
break; | |||||
case 'x': | |||||
handler.on_loc_date(numeric_system::alternative); | |||||
break; | |||||
case 'X': | |||||
handler.on_loc_time(numeric_system::alternative); | |||||
break; | |||||
default: | |||||
FMT_THROW(format_error("invalid format")); | |||||
} | |||||
break; | |||||
} | |||||
case 'O': | |||||
if (ptr == end) FMT_THROW(format_error("invalid format")); | |||||
c = *ptr++; | |||||
switch (c) { | |||||
case 'w': | |||||
handler.on_dec0_weekday(numeric_system::alternative); | |||||
break; | |||||
case 'u': | |||||
handler.on_dec1_weekday(numeric_system::alternative); | |||||
break; | |||||
case 'H': | |||||
handler.on_24_hour(numeric_system::alternative); | |||||
break; | |||||
case 'I': | |||||
handler.on_12_hour(numeric_system::alternative); | |||||
break; | |||||
case 'M': | |||||
handler.on_minute(numeric_system::alternative); | |||||
break; | |||||
case 'S': | |||||
handler.on_second(numeric_system::alternative); | |||||
break; | |||||
default: | |||||
FMT_THROW(format_error("invalid format")); | |||||
} | |||||
break; | |||||
default: | |||||
FMT_THROW(format_error("invalid format")); | |||||
} | |||||
begin = ptr; | |||||
} | |||||
if (begin != ptr) handler.on_text(begin, ptr); | |||||
return ptr; | |||||
} | |||||
struct chrono_format_checker { | |||||
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } | |||||
template <typename Char> void on_text(const Char*, const Char*) {} | |||||
FMT_NORETURN void on_abbr_weekday() { report_no_date(); } | |||||
FMT_NORETURN void on_full_weekday() { report_no_date(); } | |||||
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_abbr_month() { report_no_date(); } | |||||
FMT_NORETURN void on_full_month() { report_no_date(); } | |||||
void on_24_hour(numeric_system) {} | |||||
void on_12_hour(numeric_system) {} | |||||
void on_minute(numeric_system) {} | |||||
void on_second(numeric_system) {} | |||||
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } | |||||
FMT_NORETURN void on_us_date() { report_no_date(); } | |||||
FMT_NORETURN void on_iso_date() { report_no_date(); } | |||||
void on_12_hour_time() {} | |||||
void on_24_hour_time() {} | |||||
void on_iso_time() {} | |||||
void on_am_pm() {} | |||||
void on_duration_value() {} | |||||
void on_duration_unit() {} | |||||
FMT_NORETURN void on_utc_offset() { report_no_date(); } | |||||
FMT_NORETURN void on_tz_name() { report_no_date(); } | |||||
}; | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline bool isnan(T) { | |||||
return false; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
inline bool isnan(T value) { | |||||
return std::isnan(value); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline bool isfinite(T) { | |||||
return true; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
inline bool isfinite(T value) { | |||||
return std::isfinite(value); | |||||
} | |||||
// Convers value to int and checks that it's in the range [0, upper). | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline int to_nonnegative_int(T value, int upper) { | |||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); | |||||
(void)upper; | |||||
return static_cast<int>(value); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
inline int to_nonnegative_int(T value, int upper) { | |||||
FMT_ASSERT( | |||||
std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)), | |||||
"invalid value"); | |||||
(void)upper; | |||||
return static_cast<int>(value); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
inline T mod(T x, int y) { | |||||
return x % y; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
inline T mod(T x, int y) { | |||||
return std::fmod(x, static_cast<T>(y)); | |||||
} | |||||
// If T is an integral type, maps T to its unsigned counterpart, otherwise | |||||
// leaves it unchanged (unlike std::make_unsigned). | |||||
template <typename T, bool INTEGRAL = std::is_integral<T>::value> | |||||
struct make_unsigned_or_unchanged { | |||||
using type = T; | |||||
}; | |||||
template <typename T> struct make_unsigned_or_unchanged<T, true> { | |||||
using type = typename std::make_unsigned<T>::type; | |||||
}; | |||||
#if FMT_SAFE_DURATION_CAST | |||||
// throwing version of safe_duration_cast | |||||
template <typename To, typename FromRep, typename FromPeriod> | |||||
To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) { | |||||
int ec; | |||||
To to = safe_duration_cast::safe_duration_cast<To>(from, ec); | |||||
if (ec) FMT_THROW(format_error("cannot format duration")); | |||||
return to; | |||||
} | |||||
#endif | |||||
template <typename Rep, typename Period, | |||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)> | |||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds( | |||||
std::chrono::duration<Rep, Period> d) { | |||||
// this may overflow and/or the result may not fit in the | |||||
// target type. | |||||
#if FMT_SAFE_DURATION_CAST | |||||
using CommonSecondsType = | |||||
typename std::common_type<decltype(d), std::chrono::seconds>::type; | |||||
const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d); | |||||
const auto d_as_whole_seconds = | |||||
fmt_safe_duration_cast<std::chrono::seconds>(d_as_common); | |||||
// this conversion should be nonproblematic | |||||
const auto diff = d_as_common - d_as_whole_seconds; | |||||
const auto ms = | |||||
fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff); | |||||
return ms; | |||||
#else | |||||
auto s = std::chrono::duration_cast<std::chrono::seconds>(d); | |||||
return std::chrono::duration_cast<std::chrono::milliseconds>(d - s); | |||||
#endif | |||||
} | |||||
template <typename Rep, typename Period, | |||||
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)> | |||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds( | |||||
std::chrono::duration<Rep, Period> d) { | |||||
using common_type = typename std::common_type<Rep, std::intmax_t>::type; | |||||
auto ms = mod(d.count() * static_cast<common_type>(Period::num) / | |||||
static_cast<common_type>(Period::den) * 1000, | |||||
1000); | |||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms)); | |||||
} | |||||
template <typename Rep, typename OutputIt> | |||||
OutputIt format_chrono_duration_value(OutputIt out, Rep val, int precision) { | |||||
if (precision >= 0) return format_to(out, "{:.{}f}", val, precision); | |||||
return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}", | |||||
val); | |||||
} | |||||
template <typename Period, typename OutputIt> | |||||
static OutputIt format_chrono_duration_unit(OutputIt out) { | |||||
if (const char* unit = get_units<Period>()) return format_to(out, "{}", unit); | |||||
if (Period::den == 1) return format_to(out, "[{}]s", Period::num); | |||||
return format_to(out, "[{}/{}]s", Period::num, Period::den); | |||||
} | |||||
template <typename FormatContext, typename OutputIt, typename Rep, | |||||
typename Period> | |||||
struct chrono_formatter { | |||||
FormatContext& context; | |||||
OutputIt out; | |||||
int precision; | |||||
// rep is unsigned to avoid overflow. | |||||
using rep = | |||||
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), | |||||
unsigned, typename make_unsigned_or_unchanged<Rep>::type>; | |||||
rep val; | |||||
using seconds = std::chrono::duration<rep>; | |||||
seconds s; | |||||
using milliseconds = std::chrono::duration<rep, std::milli>; | |||||
bool negative; | |||||
using char_type = typename FormatContext::char_type; | |||||
explicit chrono_formatter(FormatContext& ctx, OutputIt o, | |||||
std::chrono::duration<Rep, Period> d) | |||||
: context(ctx), out(o), val(d.count()), negative(false) { | |||||
if (d.count() < 0) { | |||||
val = 0 - val; | |||||
negative = true; | |||||
} | |||||
// this may overflow and/or the result may not fit in the | |||||
// target type. | |||||
#if FMT_SAFE_DURATION_CAST | |||||
// might need checked conversion (rep!=Rep) | |||||
auto tmpval = std::chrono::duration<rep, Period>(val); | |||||
s = fmt_safe_duration_cast<seconds>(tmpval); | |||||
#else | |||||
s = std::chrono::duration_cast<seconds>( | |||||
std::chrono::duration<rep, Period>(val)); | |||||
#endif | |||||
} | |||||
// returns true if nan or inf, writes to out. | |||||
bool handle_nan_inf() { | |||||
if (isfinite(val)) { | |||||
return false; | |||||
} | |||||
if (isnan(val)) { | |||||
write_nan(); | |||||
return true; | |||||
} | |||||
// must be +-inf | |||||
if (val > 0) { | |||||
write_pinf(); | |||||
} else { | |||||
write_ninf(); | |||||
} | |||||
return true; | |||||
} | |||||
Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); } | |||||
Rep hour12() const { | |||||
Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12)); | |||||
return hour <= 0 ? 12 : hour; | |||||
} | |||||
Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); } | |||||
Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); } | |||||
std::tm time() const { | |||||
auto time = std::tm(); | |||||
time.tm_hour = to_nonnegative_int(hour(), 24); | |||||
time.tm_min = to_nonnegative_int(minute(), 60); | |||||
time.tm_sec = to_nonnegative_int(second(), 60); | |||||
return time; | |||||
} | |||||
void write_sign() { | |||||
if (negative) { | |||||
*out++ = '-'; | |||||
negative = false; | |||||
} | |||||
} | |||||
void write(Rep value, int width) { | |||||
write_sign(); | |||||
if (isnan(value)) return write_nan(); | |||||
uint32_or_64_t<int> n = to_unsigned( | |||||
to_nonnegative_int(value, (std::numeric_limits<int>::max)())); | |||||
int num_digits = internal::count_digits(n); | |||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); | |||||
out = format_decimal<char_type>(out, n, num_digits); | |||||
} | |||||
void write_nan() { std::copy_n("nan", 3, out); } | |||||
void write_pinf() { std::copy_n("inf", 3, out); } | |||||
void write_ninf() { std::copy_n("-inf", 4, out); } | |||||
void format_localized(const tm& time, const char* format) { | |||||
if (isnan(val)) return write_nan(); | |||||
auto locale = context.locale().template get<std::locale>(); | |||||
auto& facet = std::use_facet<std::time_put<char_type>>(locale); | |||||
std::basic_ostringstream<char_type> os; | |||||
os.imbue(locale); | |||||
facet.put(os, os, ' ', &time, format, format + std::strlen(format)); | |||||
auto str = os.str(); | |||||
std::copy(str.begin(), str.end(), out); | |||||
} | |||||
void on_text(const char_type* begin, const char_type* end) { | |||||
std::copy(begin, end, out); | |||||
} | |||||
// These are not implemented because durations don't have date information. | |||||
void on_abbr_weekday() {} | |||||
void on_full_weekday() {} | |||||
void on_dec0_weekday(numeric_system) {} | |||||
void on_dec1_weekday(numeric_system) {} | |||||
void on_abbr_month() {} | |||||
void on_full_month() {} | |||||
void on_datetime(numeric_system) {} | |||||
void on_loc_date(numeric_system) {} | |||||
void on_loc_time(numeric_system) {} | |||||
void on_us_date() {} | |||||
void on_iso_date() {} | |||||
void on_utc_offset() {} | |||||
void on_tz_name() {} | |||||
void on_24_hour(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) return write(hour(), 2); | |||||
auto time = tm(); | |||||
time.tm_hour = to_nonnegative_int(hour(), 24); | |||||
format_localized(time, "%OH"); | |||||
} | |||||
void on_12_hour(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) return write(hour12(), 2); | |||||
auto time = tm(); | |||||
time.tm_hour = to_nonnegative_int(hour12(), 12); | |||||
format_localized(time, "%OI"); | |||||
} | |||||
void on_minute(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) return write(minute(), 2); | |||||
auto time = tm(); | |||||
time.tm_min = to_nonnegative_int(minute(), 60); | |||||
format_localized(time, "%OM"); | |||||
} | |||||
void on_second(numeric_system ns) { | |||||
if (handle_nan_inf()) return; | |||||
if (ns == numeric_system::standard) { | |||||
write(second(), 2); | |||||
#if FMT_SAFE_DURATION_CAST | |||||
// convert rep->Rep | |||||
using duration_rep = std::chrono::duration<rep, Period>; | |||||
using duration_Rep = std::chrono::duration<Rep, Period>; | |||||
auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val}); | |||||
#else | |||||
auto tmpval = std::chrono::duration<Rep, Period>(val); | |||||
#endif | |||||
auto ms = get_milliseconds(tmpval); | |||||
if (ms != std::chrono::milliseconds(0)) { | |||||
*out++ = '.'; | |||||
write(ms.count(), 3); | |||||
} | |||||
return; | |||||
} | |||||
auto time = tm(); | |||||
time.tm_sec = to_nonnegative_int(second(), 60); | |||||
format_localized(time, "%OS"); | |||||
} | |||||
void on_12_hour_time() { | |||||
if (handle_nan_inf()) return; | |||||
format_localized(time(), "%r"); | |||||
} | |||||
void on_24_hour_time() { | |||||
if (handle_nan_inf()) { | |||||
*out++ = ':'; | |||||
handle_nan_inf(); | |||||
return; | |||||
} | |||||
write(hour(), 2); | |||||
*out++ = ':'; | |||||
write(minute(), 2); | |||||
} | |||||
void on_iso_time() { | |||||
on_24_hour_time(); | |||||
*out++ = ':'; | |||||
if (handle_nan_inf()) return; | |||||
write(second(), 2); | |||||
} | |||||
void on_am_pm() { | |||||
if (handle_nan_inf()) return; | |||||
format_localized(time(), "%p"); | |||||
} | |||||
void on_duration_value() { | |||||
if (handle_nan_inf()) return; | |||||
write_sign(); | |||||
out = format_chrono_duration_value(out, val, precision); | |||||
} | |||||
void on_duration_unit() { out = format_chrono_duration_unit<Period>(out); } | |||||
}; | |||||
} // namespace internal | |||||
template <typename Rep, typename Period, typename Char> | |||||
struct formatter<std::chrono::duration<Rep, Period>, Char> { | |||||
private: | |||||
basic_format_specs<Char> specs; | |||||
int precision; | |||||
using arg_ref_type = internal::arg_ref<Char>; | |||||
arg_ref_type width_ref; | |||||
arg_ref_type precision_ref; | |||||
mutable basic_string_view<Char> format_str; | |||||
using duration = std::chrono::duration<Rep, Period>; | |||||
struct spec_handler { | |||||
formatter& f; | |||||
basic_parse_context<Char>& context; | |||||
basic_string_view<Char> format_str; | |||||
template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { | |||||
context.check_arg_id(arg_id); | |||||
return arg_ref_type(arg_id); | |||||
} | |||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) { | |||||
context.check_arg_id(arg_id); | |||||
const auto str_val = internal::string_view_metadata(format_str, arg_id); | |||||
return arg_ref_type(str_val); | |||||
} | |||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) { | |||||
return arg_ref_type(context.next_arg_id()); | |||||
} | |||||
void on_error(const char* msg) { FMT_THROW(format_error(msg)); } | |||||
void on_fill(Char fill) { f.specs.fill[0] = fill; } | |||||
void on_align(align_t align) { f.specs.align = align; } | |||||
void on_width(unsigned width) { f.specs.width = width; } | |||||
void on_precision(unsigned precision) { f.precision = precision; } | |||||
void end_precision() {} | |||||
template <typename Id> void on_dynamic_width(Id arg_id) { | |||||
f.width_ref = make_arg_ref(arg_id); | |||||
} | |||||
template <typename Id> void on_dynamic_precision(Id arg_id) { | |||||
f.precision_ref = make_arg_ref(arg_id); | |||||
} | |||||
}; | |||||
using iterator = typename basic_parse_context<Char>::iterator; | |||||
struct parse_range { | |||||
iterator begin; | |||||
iterator end; | |||||
}; | |||||
FMT_CONSTEXPR parse_range do_parse(basic_parse_context<Char>& ctx) { | |||||
auto begin = ctx.begin(), end = ctx.end(); | |||||
if (begin == end || *begin == '}') return {begin, begin}; | |||||
spec_handler handler{*this, ctx, format_str}; | |||||
begin = internal::parse_align(begin, end, handler); | |||||
if (begin == end) return {begin, begin}; | |||||
begin = internal::parse_width(begin, end, handler); | |||||
if (begin == end) return {begin, begin}; | |||||
if (*begin == '.') { | |||||
if (std::is_floating_point<Rep>::value) | |||||
begin = internal::parse_precision(begin, end, handler); | |||||
else | |||||
handler.on_error("precision not allowed for this argument type"); | |||||
} | |||||
end = parse_chrono_format(begin, end, internal::chrono_format_checker()); | |||||
return {begin, end}; | |||||
} | |||||
public: | |||||
formatter() : precision(-1) {} | |||||
FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx) | |||||
-> decltype(ctx.begin()) { | |||||
auto range = do_parse(ctx); | |||||
format_str = basic_string_view<Char>( | |||||
&*range.begin, internal::to_unsigned(range.end - range.begin)); | |||||
return range.end; | |||||
} | |||||
template <typename FormatContext> | |||||
auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
auto begin = format_str.begin(), end = format_str.end(); | |||||
// As a possible future optimization, we could avoid extra copying if width | |||||
// is not specified. | |||||
basic_memory_buffer<Char> buf; | |||||
auto out = std::back_inserter(buf); | |||||
using range = internal::output_range<decltype(ctx.out()), Char>; | |||||
internal::basic_writer<range> w(range(ctx.out())); | |||||
internal::handle_dynamic_spec<internal::width_checker>( | |||||
specs.width, width_ref, ctx, format_str.begin()); | |||||
internal::handle_dynamic_spec<internal::precision_checker>( | |||||
precision, precision_ref, ctx, format_str.begin()); | |||||
if (begin == end || *begin == '}') { | |||||
out = internal::format_chrono_duration_value(out, d.count(), precision); | |||||
internal::format_chrono_duration_unit<Period>(out); | |||||
} else { | |||||
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( | |||||
ctx, out, d); | |||||
f.precision = precision; | |||||
parse_chrono_format(begin, end, f); | |||||
} | |||||
w.write(buf.data(), buf.size(), specs); | |||||
return w.out(); | |||||
} | |||||
}; | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_CHRONO_H_ |
// 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 FMT_COLOR_H_ | |||||
#define FMT_COLOR_H_ | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
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, | |||||
italic = 1 << 1, | |||||
underline = 1 << 2, | |||||
strikethrough = 1 << 3 | |||||
}; | |||||
// 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 { | |||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} | |||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} | |||||
FMT_CONSTEXPR rgb(uint32_t hex) | |||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} | |||||
FMT_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 internal { | |||||
// color is a struct of either a rgb color or a terminal color. | |||||
struct color_type { | |||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} | |||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), | |||||
value{} { | |||||
value.rgb_color = static_cast<uint32_t>(rgb_color); | |||||
} | |||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_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; | |||||
} | |||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_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 internal | |||||
// Experimental text formatting support. | |||||
class text_style { | |||||
public: | |||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT | |||||
: set_foreground_color(), | |||||
set_background_color(), | |||||
ems(em) {} | |||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { | |||||
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) | |||||
FMT_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) | |||||
FMT_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 FMT_CONSTEXPR text_style operator|(text_style lhs, | |||||
const text_style& rhs) { | |||||
return lhs |= rhs; | |||||
} | |||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) { | |||||
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) | |||||
FMT_THROW(format_error("can't AND 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) | |||||
FMT_THROW(format_error("can't AND 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 FMT_CONSTEXPR text_style operator&(text_style lhs, | |||||
const text_style& rhs) { | |||||
return lhs &= rhs; | |||||
} | |||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { | |||||
return set_foreground_color; | |||||
} | |||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { | |||||
return set_background_color; | |||||
} | |||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { | |||||
return static_cast<uint8_t>(ems) != 0; | |||||
} | |||||
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT { | |||||
assert(has_foreground() && "no foreground specified for this style"); | |||||
return foreground_color; | |||||
} | |||||
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT { | |||||
assert(has_background() && "no background specified for this style"); | |||||
return background_color; | |||||
} | |||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { | |||||
assert(has_emphasis() && "no emphasis specified for this style"); | |||||
return ems; | |||||
} | |||||
private: | |||||
FMT_CONSTEXPR text_style(bool is_foreground, | |||||
internal::color_type text_color) FMT_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 FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground) | |||||
FMT_NOEXCEPT; | |||||
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background) | |||||
FMT_NOEXCEPT; | |||||
internal::color_type foreground_color; | |||||
internal::color_type background_color; | |||||
bool set_foreground_color; | |||||
bool set_background_color; | |||||
emphasis ems; | |||||
}; | |||||
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT { | |||||
return text_style(/*is_foreground=*/true, foreground); | |||||
} | |||||
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT { | |||||
return text_style(/*is_foreground=*/false, background); | |||||
} | |||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { | |||||
return text_style(lhs) | rhs; | |||||
} | |||||
namespace internal { | |||||
template <typename Char> struct ansi_color_escape { | |||||
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, | |||||
const char* esc) FMT_NOEXCEPT { | |||||
// If we have a terminal color, we need to output another escape code | |||||
// sequence. | |||||
if (!text_color.is_rgb) { | |||||
bool is_background = esc == internal::data::background_color; | |||||
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; | |||||
std::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); | |||||
} | |||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { | |||||
uint8_t em_codes[4] = {}; | |||||
uint8_t em_bits = static_cast<uint8_t>(em); | |||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; | |||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; | |||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; | |||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) | |||||
em_codes[3] = 9; | |||||
std::size_t index = 0; | |||||
for (int i = 0; i < 4; ++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); | |||||
} | |||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } | |||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } | |||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { | |||||
return buffer + std::strlen(buffer); | |||||
} | |||||
private: | |||||
Char buffer[7u + 3u * 4u + 1u]; | |||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, | |||||
char delimiter) FMT_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); | |||||
} | |||||
}; | |||||
template <typename Char> | |||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( | |||||
internal::color_type foreground) FMT_NOEXCEPT { | |||||
return ansi_color_escape<Char>(foreground, internal::data::foreground_color); | |||||
} | |||||
template <typename Char> | |||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( | |||||
internal::color_type background) FMT_NOEXCEPT { | |||||
return ansi_color_escape<Char>(background, internal::data::background_color); | |||||
} | |||||
template <typename Char> | |||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT { | |||||
return ansi_color_escape<Char>(em); | |||||
} | |||||
template <typename Char> | |||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT { | |||||
std::fputs(chars, stream); | |||||
} | |||||
template <> | |||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { | |||||
std::fputws(chars, stream); | |||||
} | |||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT { | |||||
fputs(internal::data::reset_color, stream); | |||||
} | |||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT { | |||||
fputs(internal::data::wreset_color, stream); | |||||
} | |||||
template <typename Char> | |||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT { | |||||
const char* begin = data::reset_color; | |||||
const char* end = begin + sizeof(data::reset_color) - 1; | |||||
buffer.append(begin, end); | |||||
} | |||||
template <typename Char> | |||||
std::basic_string<Char> vformat(const text_style& ts, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char> > args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
bool has_style = false; | |||||
if (ts.has_emphasis()) { | |||||
has_style = true; | |||||
ansi_color_escape<Char> escape = make_emphasis<Char>(ts.get_emphasis()); | |||||
buffer.append(escape.begin(), escape.end()); | |||||
} | |||||
if (ts.has_foreground()) { | |||||
has_style = true; | |||||
ansi_color_escape<Char> escape = | |||||
make_foreground_color<Char>(ts.get_foreground()); | |||||
buffer.append(escape.begin(), escape.end()); | |||||
} | |||||
if (ts.has_background()) { | |||||
has_style = true; | |||||
ansi_color_escape<Char> escape = | |||||
make_background_color<Char>(ts.get_background()); | |||||
buffer.append(escape.begin(), escape.end()); | |||||
} | |||||
internal::vformat_to(buffer, format_str, args); | |||||
if (has_style) { | |||||
reset_color<Char>(buffer); | |||||
} | |||||
return fmt::to_string(buffer); | |||||
} | |||||
} // namespace internal | |||||
template <typename S, typename Char = char_t<S> > | |||||
void vprint(std::FILE* f, const text_style& ts, const S& format, | |||||
basic_format_args<buffer_context<Char> > args) { | |||||
bool has_style = false; | |||||
if (ts.has_emphasis()) { | |||||
has_style = true; | |||||
internal::fputs<Char>(internal::make_emphasis<Char>(ts.get_emphasis()), f); | |||||
} | |||||
if (ts.has_foreground()) { | |||||
has_style = true; | |||||
internal::fputs<Char>( | |||||
internal::make_foreground_color<Char>(ts.get_foreground()), f); | |||||
} | |||||
if (ts.has_background()) { | |||||
has_style = true; | |||||
internal::fputs<Char>( | |||||
internal::make_background_color<Char>(ts.get_background()), f); | |||||
} | |||||
vprint(f, format, args); | |||||
if (has_style) { | |||||
internal::reset_color<Char>(f); | |||||
} | |||||
} | |||||
/** | |||||
Formats a string and prints it to the specified file stream using ANSI | |||||
escape sequences to specify text formatting. | |||||
Example: | |||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), | |||||
"Elapsed time: {0:.2f} seconds", 1.23); | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_string<S>::value)> | |||||
void print(std::FILE* f, const text_style& ts, const S& format_str, | |||||
const Args&... args) { | |||||
internal::check_format_string<Args...>(format_str); | |||||
using context = buffer_context<char_t<S> >; | |||||
format_arg_store<context, Args...> as{args...}; | |||||
vprint(f, ts, format_str, basic_format_args<context>(as)); | |||||
} | |||||
/** | |||||
Formats a string and prints it to stdout using ANSI escape sequences to | |||||
specify text formatting. | |||||
Example: | |||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), | |||||
"Elapsed time: {0:.2f} seconds", 1.23); | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::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 std::basic_string<Char> vformat( | |||||
const text_style& ts, const S& format_str, | |||||
basic_format_args<buffer_context<Char> > args) { | |||||
return internal::vformat(ts, to_string_view(format_str), args); | |||||
} | |||||
/** | |||||
\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 = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), | |||||
"The answer is {}", 42); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, typename Char = char_t<S> > | |||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str, | |||||
const Args&... args) { | |||||
return internal::vformat(ts, to_string_view(format_str), | |||||
{internal::make_args_checked(format_str, args...)}); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_COLOR_H_ |
// 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 FMT_COMPILE_H_ | |||||
#define FMT_COMPILE_H_ | |||||
#include <vector> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
template <typename Char> struct format_part { | |||||
public: | |||||
struct named_argument_id { | |||||
FMT_CONSTEXPR named_argument_id(internal::string_view_metadata id) | |||||
: id(id) {} | |||||
internal::string_view_metadata id; | |||||
}; | |||||
struct argument_id { | |||||
FMT_CONSTEXPR argument_id() : argument_id(0u) {} | |||||
FMT_CONSTEXPR argument_id(unsigned id) | |||||
: which(which_arg_id::index), val(id) {} | |||||
FMT_CONSTEXPR argument_id(internal::string_view_metadata id) | |||||
: which(which_arg_id::named_index), val(id) {} | |||||
enum class which_arg_id { index, named_index }; | |||||
which_arg_id which; | |||||
union value { | |||||
FMT_CONSTEXPR value() : index(0u) {} | |||||
FMT_CONSTEXPR value(unsigned id) : index(id) {} | |||||
FMT_CONSTEXPR value(internal::string_view_metadata id) | |||||
: named_index(id) {} | |||||
unsigned index; | |||||
internal::string_view_metadata named_index; | |||||
} val; | |||||
}; | |||||
struct specification { | |||||
FMT_CONSTEXPR specification() : arg_id(0u) {} | |||||
FMT_CONSTEXPR specification(unsigned id) : arg_id(id) {} | |||||
FMT_CONSTEXPR specification(internal::string_view_metadata id) | |||||
: arg_id(id) {} | |||||
argument_id arg_id; | |||||
internal::dynamic_format_specs<Char> parsed_specs; | |||||
}; | |||||
FMT_CONSTEXPR format_part() | |||||
: which(kind::argument_id), end_of_argument_id(0u), val(0u) {} | |||||
FMT_CONSTEXPR format_part(internal::string_view_metadata text) | |||||
: which(kind::text), end_of_argument_id(0u), val(text) {} | |||||
FMT_CONSTEXPR format_part(unsigned id) | |||||
: which(kind::argument_id), end_of_argument_id(0u), val(id) {} | |||||
FMT_CONSTEXPR format_part(named_argument_id arg_id) | |||||
: which(kind::named_argument_id), end_of_argument_id(0u), val(arg_id) {} | |||||
FMT_CONSTEXPR format_part(specification spec) | |||||
: which(kind::specification), end_of_argument_id(0u), val(spec) {} | |||||
enum class kind { argument_id, named_argument_id, text, specification }; | |||||
kind which; | |||||
std::size_t end_of_argument_id; | |||||
union value { | |||||
FMT_CONSTEXPR value() : arg_id(0u) {} | |||||
FMT_CONSTEXPR value(unsigned id) : arg_id(id) {} | |||||
FMT_CONSTEXPR value(named_argument_id named_id) | |||||
: named_arg_id(named_id.id) {} | |||||
FMT_CONSTEXPR value(internal::string_view_metadata t) : text(t) {} | |||||
FMT_CONSTEXPR value(specification s) : spec(s) {} | |||||
unsigned arg_id; | |||||
internal::string_view_metadata named_arg_id; | |||||
internal::string_view_metadata text; | |||||
specification spec; | |||||
} val; | |||||
}; | |||||
template <typename Char, typename PartsContainer> | |||||
class format_preparation_handler : public internal::error_handler { | |||||
private: | |||||
using part = format_part<Char>; | |||||
public: | |||||
using iterator = typename basic_string_view<Char>::iterator; | |||||
FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format, | |||||
PartsContainer& parts) | |||||
: parts_(parts), format_(format), parse_context_(format) {} | |||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { | |||||
if (begin == end) return; | |||||
const auto offset = begin - format_.data(); | |||||
const auto size = end - begin; | |||||
parts_.push_back(part(string_view_metadata(offset, size))); | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id() { | |||||
parts_.push_back(part(parse_context_.next_arg_id())); | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id(unsigned id) { | |||||
parse_context_.check_arg_id(id); | |||||
parts_.push_back(part(id)); | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) { | |||||
const auto view = string_view_metadata(format_, id); | |||||
const auto arg_id = typename part::named_argument_id(view); | |||||
parts_.push_back(part(arg_id)); | |||||
} | |||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) { | |||||
parts_.back().end_of_argument_id = ptr - format_.begin(); | |||||
} | |||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, | |||||
const Char* end) { | |||||
const auto specs_offset = to_unsigned(begin - format_.begin()); | |||||
using parse_context = basic_parse_context<Char>; | |||||
internal::dynamic_format_specs<Char> parsed_specs; | |||||
dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_); | |||||
begin = parse_format_specs(begin, end, handler); | |||||
if (*begin != '}') on_error("missing '}' in format string"); | |||||
auto& last_part = parts_.back(); | |||||
auto specs = last_part.which == part::kind::argument_id | |||||
? typename part::specification(last_part.val.arg_id) | |||||
: typename part::specification(last_part.val.named_arg_id); | |||||
specs.parsed_specs = parsed_specs; | |||||
last_part = part(specs); | |||||
last_part.end_of_argument_id = specs_offset; | |||||
return begin; | |||||
} | |||||
private: | |||||
PartsContainer& parts_; | |||||
basic_string_view<Char> format_; | |||||
basic_parse_context<Char> parse_context_; | |||||
}; | |||||
template <typename Format, typename PreparedPartsProvider, typename... Args> | |||||
class prepared_format { | |||||
public: | |||||
using char_type = char_t<Format>; | |||||
using format_part_t = format_part<char_type>; | |||||
constexpr prepared_format(Format f) | |||||
: format_(std::move(f)), parts_provider_(to_string_view(format_)) {} | |||||
prepared_format() = delete; | |||||
using context = buffer_context<char_type>; | |||||
template <typename Range, typename Context> | |||||
auto vformat_to(Range out, basic_format_args<Context> args) const -> | |||||
typename Context::iterator { | |||||
const auto format_view = internal::to_string_view(format_); | |||||
basic_parse_context<char_type> parse_ctx(format_view); | |||||
Context ctx(out.begin(), args); | |||||
const auto& parts = parts_provider_.parts(); | |||||
for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) { | |||||
const auto& part = *part_it; | |||||
const auto& value = part.val; | |||||
switch (part.which) { | |||||
case format_part_t::kind::text: { | |||||
const auto text = value.text.to_view(format_view.data()); | |||||
auto output = ctx.out(); | |||||
auto&& it = internal::reserve(output, text.size()); | |||||
it = std::copy_n(text.begin(), text.size(), it); | |||||
ctx.advance_to(output); | |||||
} break; | |||||
case format_part_t::kind::argument_id: { | |||||
advance_parse_context_to_specification(parse_ctx, part); | |||||
format_arg<Range>(parse_ctx, ctx, value.arg_id); | |||||
} break; | |||||
case format_part_t::kind::named_argument_id: { | |||||
advance_parse_context_to_specification(parse_ctx, part); | |||||
const auto named_arg_id = | |||||
value.named_arg_id.to_view(format_view.data()); | |||||
format_arg<Range>(parse_ctx, ctx, named_arg_id); | |||||
} break; | |||||
case format_part_t::kind::specification: { | |||||
const auto& arg_id_value = value.spec.arg_id.val; | |||||
const auto arg = value.spec.arg_id.which == | |||||
format_part_t::argument_id::which_arg_id::index | |||||
? ctx.arg(arg_id_value.index) | |||||
: ctx.arg(arg_id_value.named_index.to_view( | |||||
to_string_view(format_).data())); | |||||
auto specs = value.spec.parsed_specs; | |||||
handle_dynamic_spec<internal::width_checker>( | |||||
specs.width, specs.width_ref, ctx, format_view.begin()); | |||||
handle_dynamic_spec<internal::precision_checker>( | |||||
specs.precision, specs.precision_ref, ctx, format_view.begin()); | |||||
check_prepared_specs(specs, arg.type()); | |||||
advance_parse_context_to_specification(parse_ctx, part); | |||||
ctx.advance_to( | |||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg)); | |||||
} break; | |||||
} | |||||
} | |||||
return ctx.out(); | |||||
} | |||||
private: | |||||
void advance_parse_context_to_specification( | |||||
basic_parse_context<char_type>& parse_ctx, | |||||
const format_part_t& part) const { | |||||
const auto view = to_string_view(format_); | |||||
const auto specification_begin = view.data() + part.end_of_argument_id; | |||||
advance_to(parse_ctx, specification_begin); | |||||
} | |||||
template <typename Range, typename Context, typename Id> | |||||
void format_arg(basic_parse_context<char_type>& parse_ctx, Context& ctx, | |||||
Id arg_id) const { | |||||
parse_ctx.check_arg_id(arg_id); | |||||
const auto stopped_at = | |||||
visit_format_arg(arg_formatter<Range>(ctx), ctx.arg(arg_id)); | |||||
ctx.advance_to(stopped_at); | |||||
} | |||||
template <typename Char> | |||||
void check_prepared_specs(const basic_format_specs<Char>& specs, | |||||
internal::type arg_type) const { | |||||
internal::error_handler h; | |||||
numeric_specs_checker<internal::error_handler> checker(h, arg_type); | |||||
if (specs.align == align::numeric) checker.require_numeric_argument(); | |||||
if (specs.sign != sign::none) checker.check_sign(); | |||||
if (specs.alt) checker.require_numeric_argument(); | |||||
if (specs.precision >= 0) checker.check_precision(); | |||||
} | |||||
private: | |||||
Format format_; | |||||
PreparedPartsProvider parts_provider_; | |||||
}; | |||||
template <typename Char> struct part_counter { | |||||
unsigned num_parts = 0; | |||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { | |||||
if (begin != end) ++num_parts; | |||||
} | |||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; } | |||||
FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; } | |||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; } | |||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {} | |||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, | |||||
const Char* end) { | |||||
// Find the matching brace. | |||||
unsigned braces_counter = 0; | |||||
for (; begin != end; ++begin) { | |||||
if (*begin == '{') { | |||||
++braces_counter; | |||||
} else if (*begin == '}') { | |||||
if (braces_counter == 0u) break; | |||||
--braces_counter; | |||||
} | |||||
} | |||||
return begin; | |||||
} | |||||
FMT_CONSTEXPR void on_error(const char*) {} | |||||
}; | |||||
template <typename Format> class compiletime_prepared_parts_type_provider { | |||||
private: | |||||
using char_type = char_t<Format>; | |||||
static FMT_CONSTEXPR unsigned count_parts() { | |||||
FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{}); | |||||
part_counter<char_type> counter; | |||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, counter); | |||||
return counter.num_parts; | |||||
} | |||||
// Workaround for old compilers. Compiletime parts preparation will not be | |||||
// performed with them anyway. | |||||
#if FMT_USE_CONSTEXPR | |||||
static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts = | |||||
compiletime_prepared_parts_type_provider::count_parts(); | |||||
#else | |||||
static const unsigned number_of_format_parts = 0u; | |||||
#endif | |||||
public: | |||||
template <unsigned N> struct format_parts_array { | |||||
using value_type = format_part<char_type>; | |||||
FMT_CONSTEXPR format_parts_array() : arr{} {} | |||||
FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; } | |||||
FMT_CONSTEXPR const value_type* begin() const { return arr; } | |||||
FMT_CONSTEXPR const value_type* end() const { return begin() + N; } | |||||
private: | |||||
value_type arr[N]; | |||||
}; | |||||
struct empty { | |||||
// Parts preparator will search for it | |||||
using value_type = format_part<char_type>; | |||||
}; | |||||
using type = conditional_t<number_of_format_parts != 0, | |||||
format_parts_array<number_of_format_parts>, empty>; | |||||
}; | |||||
template <typename Parts> class compiletime_prepared_parts_collector { | |||||
private: | |||||
using format_part = typename Parts::value_type; | |||||
public: | |||||
FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts) | |||||
: parts_{parts}, counter_{0u} {} | |||||
FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; } | |||||
FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; } | |||||
private: | |||||
Parts& parts_; | |||||
unsigned counter_; | |||||
}; | |||||
template <typename PartsContainer, typename Char> | |||||
FMT_CONSTEXPR PartsContainer prepare_parts(basic_string_view<Char> format) { | |||||
PartsContainer parts; | |||||
internal::parse_format_string</*IS_CONSTEXPR=*/false>( | |||||
format, format_preparation_handler<Char, PartsContainer>(format, parts)); | |||||
return parts; | |||||
} | |||||
template <typename PartsContainer, typename Char> | |||||
FMT_CONSTEXPR PartsContainer | |||||
prepare_compiletime_parts(basic_string_view<Char> format) { | |||||
using collector = compiletime_prepared_parts_collector<PartsContainer>; | |||||
PartsContainer parts; | |||||
collector c(parts); | |||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>( | |||||
format, format_preparation_handler<Char, collector>(format, c)); | |||||
return parts; | |||||
} | |||||
template <typename PartsContainer> class runtime_parts_provider { | |||||
public: | |||||
runtime_parts_provider() = delete; | |||||
template <typename Char> | |||||
runtime_parts_provider(basic_string_view<Char> format) | |||||
: parts_(prepare_parts<PartsContainer>(format)) {} | |||||
const PartsContainer& parts() const { return parts_; } | |||||
private: | |||||
PartsContainer parts_; | |||||
}; | |||||
template <typename Format, typename PartsContainer> | |||||
struct compiletime_parts_provider { | |||||
compiletime_parts_provider() = delete; | |||||
template <typename Char> | |||||
FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {} | |||||
const PartsContainer& parts() const { | |||||
static FMT_CONSTEXPR_DECL const PartsContainer prepared_parts = | |||||
prepare_compiletime_parts<PartsContainer>( | |||||
internal::to_string_view(Format{})); | |||||
return prepared_parts; | |||||
} | |||||
}; | |||||
} // namespace internal | |||||
#if FMT_USE_CONSTEXPR | |||||
template <typename... Args, typename S, | |||||
FMT_ENABLE_IF(is_compile_string<S>::value)> | |||||
FMT_CONSTEXPR auto compile(S format_str) -> internal::prepared_format< | |||||
S, | |||||
internal::compiletime_parts_provider< | |||||
S, | |||||
typename internal::compiletime_prepared_parts_type_provider<S>::type>, | |||||
Args...> { | |||||
return format_str; | |||||
} | |||||
#endif | |||||
template <typename... Args, typename Char, size_t N> | |||||
auto compile(const Char (&format_str)[N]) -> internal::prepared_format< | |||||
std::basic_string<Char>, | |||||
internal::runtime_parts_provider<std::vector<internal::format_part<Char>>>, | |||||
Args...> { | |||||
return std::basic_string<Char>(format_str, N - 1); | |||||
} | |||||
template <typename CompiledFormat, typename... Args, | |||||
typename Char = typename CompiledFormat::char_type> | |||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
using range = internal::buffer_range<Char>; | |||||
using context = buffer_context<Char>; | |||||
cf.template vformat_to<range, context>(range(buffer), | |||||
{make_format_args<context>(args...)}); | |||||
return to_string(buffer); | |||||
} | |||||
template <typename OutputIt, typename CompiledFormat, typename... Args> | |||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf, | |||||
const Args&... args) { | |||||
using char_type = typename CompiledFormat::char_type; | |||||
using range = internal::output_range<OutputIt, char_type>; | |||||
using context = format_context_t<OutputIt, char_type>; | |||||
return cf.template vformat_to<range, context>( | |||||
range(out), {make_format_args<context>(args...)}); | |||||
} | |||||
template <typename OutputIt, typename CompiledFormat, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)> | |||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, | |||||
const CompiledFormat& cf, | |||||
const Args&... args) { | |||||
auto it = | |||||
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...); | |||||
return {it.base(), it.count()}; | |||||
} | |||||
template <typename CompiledFormat, typename... Args> | |||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { | |||||
return fmt::format_to( | |||||
internal::counting_iterator<typename CompiledFormat::char_type>(), | |||||
cf, args...) | |||||
.count(); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_COMPILE_H_ |
// Formatting library for C++ - std::locale support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_LOCALE_H_ | |||||
#define FMT_LOCALE_H_ | |||||
#include <locale> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
template <typename Char> | |||||
typename buffer_context<Char>::iterator vformat_to( | |||||
const std::locale& loc, buffer<Char>& buf, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
using range = buffer_range<Char>; | |||||
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args, | |||||
internal::locale_ref(loc)); | |||||
} | |||||
template <typename Char> | |||||
std::basic_string<Char> vformat(const std::locale& loc, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
internal::vformat_to(loc, buffer, format_str, args); | |||||
return fmt::to_string(buffer); | |||||
} | |||||
} // namespace internal | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline std::basic_string<Char> vformat( | |||||
const std::locale& loc, const S& format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
return internal::vformat(loc, to_string_view(format_str), args); | |||||
} | |||||
template <typename S, typename... Args, typename Char = char_t<S>> | |||||
inline std::basic_string<Char> format(const std::locale& loc, | |||||
const S& format_str, Args&&... args) { | |||||
return internal::vformat( | |||||
loc, to_string_view(format_str), | |||||
{internal::make_args_checked<Args...>(format_str, args...)}); | |||||
} | |||||
template <typename S, typename OutputIt, typename... Args, | |||||
typename Char = enable_if_t< | |||||
internal::is_output_iterator<OutputIt>::value, char_t<S>>> | |||||
inline OutputIt vformat_to(OutputIt out, const std::locale& loc, | |||||
const S& format_str, | |||||
format_args_t<OutputIt, Char> args) { | |||||
using range = internal::output_range<OutputIt, Char>; | |||||
return vformat_to<arg_formatter<range>>( | |||||
range(out), to_string_view(format_str), args, internal::locale_ref(loc)); | |||||
} | |||||
template <typename OutputIt, typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&& | |||||
internal::is_string<S>::value)> | |||||
inline OutputIt format_to(OutputIt out, const std::locale& loc, | |||||
const S& format_str, Args&&... args) { | |||||
internal::check_format_string<Args...>(format_str); | |||||
using context = format_context_t<OutputIt, char_t<S>>; | |||||
format_arg_store<context, Args...> as{args...}; | |||||
return vformat_to(out, loc, to_string_view(format_str), | |||||
basic_format_args<context>(as)); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_LOCALE_H_ |
// 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 FMT_OSTREAM_H_ | |||||
#define FMT_OSTREAM_H_ | |||||
#include <ostream> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> { | |||||
private: | |||||
using int_type = typename std::basic_streambuf<Char>::int_type; | |||||
using traits_type = typename std::basic_streambuf<Char>::traits_type; | |||||
buffer<Char>& buffer_; | |||||
public: | |||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {} | |||||
protected: | |||||
// The put-area is actually 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. The obvious | |||||
// 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. | |||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { | |||||
if (!traits_type::eq_int_type(ch, traits_type::eof())) | |||||
buffer_.push_back(static_cast<Char>(ch)); | |||||
return ch; | |||||
} | |||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE { | |||||
buffer_.append(s, s + count); | |||||
return count; | |||||
} | |||||
}; | |||||
template <typename Char> struct test_stream : std::basic_ostream<Char> { | |||||
private: | |||||
struct null; | |||||
// Hide all operator<< from std::basic_ostream<Char>. | |||||
void operator<<(null); | |||||
}; | |||||
// Checks if T has a user-defined operator<< (e.g. not a member of | |||||
// std::ostream). | |||||
template <typename T, typename Char> class is_streamable { | |||||
private: | |||||
template <typename U> | |||||
static decltype((void)(std::declval<test_stream<Char>&>() | |||||
<< std::declval<U>()), | |||||
std::true_type()) | |||||
test(int); | |||||
template <typename> static std::false_type test(...); | |||||
using result = decltype(test<T>(0)); | |||||
public: | |||||
static const bool value = result::value; | |||||
}; | |||||
// Write the content of buf to os. | |||||
template <typename Char> | |||||
void write(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((std::numeric_limits<std::streamsize>::max)()); | |||||
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) { | |||||
formatbuf<Char> format_buf(buf); | |||||
std::basic_ostream<Char> output(&format_buf); | |||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit); | |||||
output << value; | |||||
buf.resize(buf.size()); | |||||
} | |||||
// Formats an object of type T that has an overloaded ostream operator<<. | |||||
template <typename T, typename Char> | |||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> | |||||
: formatter<basic_string_view<Char>, Char> { | |||||
template <typename Context> | |||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) { | |||||
basic_memory_buffer<Char> buffer; | |||||
format_value(buffer, value); | |||||
basic_string_view<Char> str(buffer.data(), buffer.size()); | |||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx); | |||||
} | |||||
}; | |||||
} // namespace internal | |||||
template <typename Char> | |||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, | |||||
basic_format_args<buffer_context<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
internal::vformat_to(buffer, format_str, args); | |||||
internal::write(os, buffer); | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to the stream *os*. | |||||
**Example**:: | |||||
fmt::print(cerr, "Don't {}!", "panic"); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) { | |||||
vprint(os, to_string_view(format_str), | |||||
{internal::make_args_checked<Args...>(format_str, args...)}); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_OSTREAM_H_ |
// A C++ interface to POSIX functions. | |||||
// | |||||
// Copyright (c) 2012 - 2016, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_POSIX_H_ | |||||
#define FMT_POSIX_H_ | |||||
#if defined(__MINGW32__) || defined(__CYGWIN__) | |||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. | |||||
# undef __STRICT_ANSI__ | |||||
#endif | |||||
#include <errno.h> | |||||
#include <fcntl.h> // for O_RDONLY | |||||
#include <locale.h> // for locale_t | |||||
#include <stdio.h> | |||||
#include <stdlib.h> // for strtod_l | |||||
#include <cstddef> | |||||
#if defined __APPLE__ || defined(__FreeBSD__) | |||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X | |||||
#endif | |||||
#include "format.h" | |||||
#ifndef FMT_POSIX | |||||
# if defined(_WIN32) && !defined(__MINGW32__) | |||||
// Fix warnings about deprecated symbols. | |||||
# define FMT_POSIX(call) _##call | |||||
# else | |||||
# define FMT_POSIX(call) call | |||||
# endif | |||||
#endif | |||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability. | |||||
#ifdef FMT_SYSTEM | |||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) | |||||
#else | |||||
# define FMT_SYSTEM(call) call | |||||
# ifdef _WIN32 | |||||
// Fix warnings about deprecated symbols. | |||||
# define FMT_POSIX_CALL(call) ::_##call | |||||
# else | |||||
# define FMT_POSIX_CALL(call) ::call | |||||
# endif | |||||
#endif | |||||
// Retries the expression while it evaluates to error_result and errno | |||||
// equals to EINTR. | |||||
#ifndef _WIN32 | |||||
# define FMT_RETRY_VAL(result, expression, error_result) \ | |||||
do { \ | |||||
result = (expression); \ | |||||
} while (result == error_result && errno == EINTR) | |||||
#else | |||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) | |||||
#endif | |||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) | |||||
FMT_BEGIN_NAMESPACE | |||||
/** | |||||
\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. */ | |||||
const Char* c_str() const { return data_; } | |||||
}; | |||||
using cstring_view = basic_cstring_view<char>; | |||||
using wcstring_view = basic_cstring_view<wchar_t>; | |||||
// An error code. | |||||
class error_code { | |||||
private: | |||||
int value_; | |||||
public: | |||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} | |||||
int get() const FMT_NOEXCEPT { return value_; } | |||||
}; | |||||
// A buffered file. | |||||
class buffered_file { | |||||
private: | |||||
FILE* file_; | |||||
friend class file; | |||||
explicit buffered_file(FILE* f) : file_(f) {} | |||||
public: | |||||
// Constructs a buffered_file object which doesn't represent any file. | |||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {} | |||||
// Destroys the object closing the file it represents if any. | |||||
FMT_API ~buffered_file() FMT_NOEXCEPT; | |||||
private: | |||||
buffered_file(const buffered_file&) = delete; | |||||
void operator=(const buffered_file&) = delete; | |||||
public: | |||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) { | |||||
other.file_ = nullptr; | |||||
} | |||||
buffered_file& operator=(buffered_file&& other) { | |||||
close(); | |||||
file_ = other.file_; | |||||
other.file_ = nullptr; | |||||
return *this; | |||||
} | |||||
// Opens a file. | |||||
FMT_API buffered_file(cstring_view filename, cstring_view mode); | |||||
// Closes the file. | |||||
FMT_API void close(); | |||||
// Returns the pointer to a FILE object representing this file. | |||||
FILE* get() const FMT_NOEXCEPT { return file_; } | |||||
// We place parentheses around fileno to workaround a bug in some versions | |||||
// of MinGW that define fileno as a macro. | |||||
FMT_API int(fileno)() const; | |||||
void vprint(string_view format_str, format_args args) { | |||||
fmt::vprint(file_, format_str, args); | |||||
} | |||||
template <typename... Args> | |||||
inline void print(string_view format_str, const Args&... args) { | |||||
vprint(format_str, make_format_args(args...)); | |||||
} | |||||
}; | |||||
// A file. Closed file is represented by a file object with descriptor -1. | |||||
// Methods that are not declared with FMT_NOEXCEPT may throw | |||||
// fmt::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 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 = FMT_POSIX(O_RDONLY), // Open for reading only. | |||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. | |||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. | |||||
}; | |||||
// Constructs a file object which doesn't represent any file. | |||||
file() FMT_NOEXCEPT : fd_(-1) {} | |||||
// Opens a file and constructs a file object representing this file. | |||||
FMT_API file(cstring_view path, int oflag); | |||||
private: | |||||
file(const file&) = delete; | |||||
void operator=(const file&) = delete; | |||||
public: | |||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } | |||||
file& operator=(file&& other) { | |||||
close(); | |||||
fd_ = other.fd_; | |||||
other.fd_ = -1; | |||||
return *this; | |||||
} | |||||
// Destroys the object closing the file it represents if any. | |||||
FMT_API ~file() FMT_NOEXCEPT; | |||||
// Returns the file descriptor. | |||||
int descriptor() const FMT_NOEXCEPT { return fd_; } | |||||
// Closes the file. | |||||
FMT_API void close(); | |||||
// Returns the file size. The size has signed type for consistency with | |||||
// stat::st_size. | |||||
FMT_API long long size() const; | |||||
// Attempts to read count bytes from the file into the specified buffer. | |||||
FMT_API std::size_t read(void* buffer, std::size_t count); | |||||
// Attempts to write count bytes from the specified buffer to the file. | |||||
FMT_API std::size_t write(const void* buffer, std::size_t count); | |||||
// Duplicates a file descriptor with the dup function and returns | |||||
// the duplicate as a file object. | |||||
FMT_API static file dup(int fd); | |||||
// Makes fd be the copy of this file descriptor, closing fd first if | |||||
// necessary. | |||||
FMT_API void dup2(int fd); | |||||
// Makes fd be the copy of this file descriptor, closing fd first if | |||||
// necessary. | |||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT; | |||||
// Creates a pipe setting up read_end and write_end file objects for reading | |||||
// and writing respectively. | |||||
FMT_API 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. | |||||
FMT_API buffered_file fdopen(const char* mode); | |||||
}; | |||||
// Returns the memory page size. | |||||
long getpagesize(); | |||||
#ifdef FMT_LOCALE | |||||
// A "C" numeric locale. | |||||
class Locale { | |||||
private: | |||||
# ifdef _WIN32 | |||||
using locale_t = _locale_t; | |||||
enum { LC_NUMERIC_MASK = LC_NUMERIC }; | |||||
static locale_t newlocale(int category_mask, const char* locale, locale_t) { | |||||
return _create_locale(category_mask, locale); | |||||
} | |||||
static void freelocale(locale_t locale) { _free_locale(locale); } | |||||
static double strtod_l(const char* nptr, char** endptr, _locale_t locale) { | |||||
return _strtod_l(nptr, endptr, locale); | |||||
} | |||||
# endif | |||||
locale_t locale_; | |||||
Locale(const Locale&) = delete; | |||||
void operator=(const Locale&) = delete; | |||||
public: | |||||
using type = locale_t; | |||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) { | |||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale")); | |||||
} | |||||
~Locale() { freelocale(locale_); } | |||||
type get() const { return locale_; } | |||||
// Converts string to floating-point number and advances str past the end | |||||
// of the parsed input. | |||||
double strtod(const char*& str) const { | |||||
char* end = nullptr; | |||||
double result = strtod_l(str, &end, locale_); | |||||
str = end; | |||||
return result; | |||||
} | |||||
}; | |||||
#endif // FMT_LOCALE | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_POSIX_H_ |
// Formatting library for C++ | |||||
// | |||||
// Copyright (c) 2012 - 2016, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
#ifndef FMT_PRINTF_H_ | |||||
#define FMT_PRINTF_H_ | |||||
#include <algorithm> // std::fill_n | |||||
#include <limits> // std::numeric_limits | |||||
#include "ostream.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace internal { | |||||
// A helper function to suppress bogus "conditional expression is constant" | |||||
// warnings. | |||||
template <typename T> inline T const_check(T value) { return value; } | |||||
// 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 bool fits_in_int(T value) { | |||||
unsigned max = std::numeric_limits<int>::max(); | |||||
return value <= max; | |||||
} | |||||
static bool fits_in_int(bool) { return true; } | |||||
}; | |||||
template <> struct int_checker<true> { | |||||
template <typename T> static bool fits_in_int(T value) { | |||||
return value >= std::numeric_limits<int>::min() && | |||||
value <= std::numeric_limits<int>::max(); | |||||
} | |||||
static bool fits_in_int(int) { return true; } | |||||
}; | |||||
class printf_precision_handler { | |||||
public: | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
int operator()(T value) { | |||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) | |||||
FMT_THROW(format_error("number is too big")); | |||||
return (std::max)(static_cast<int>(value), 0); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
int operator()(T) { | |||||
FMT_THROW(format_error("precision is not integer")); | |||||
return 0; | |||||
} | |||||
}; | |||||
// An argument visitor that returns true iff arg is a zero integer. | |||||
class is_zero_int { | |||||
public: | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
bool operator()(T value) { | |||||
return value == 0; | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
bool operator()(T) { | |||||
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, FMT_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) { | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<int>(static_cast<target_type>(value))); | |||||
} else { | |||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<unsigned>(static_cast<unsigned_type>(value))); | |||||
} | |||||
} 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. | |||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value)); | |||||
} else { | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<typename make_unsigned_or_bool<U>::type>(value)); | |||||
} | |||||
} | |||||
} | |||||
template <typename U, FMT_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, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
void operator()(T value) { | |||||
arg_ = internal::make_arg<Context>( | |||||
static_cast<typename Context::char_type>(value)); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
void operator()(T) {} // No conversion needed for non-integral types. | |||||
}; | |||||
// 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: | |||||
using format_specs = basic_format_specs<Char>; | |||||
format_specs& specs_; | |||||
public: | |||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
unsigned operator()(T value) { | |||||
auto width = static_cast<uint32_or_64_t<T>>(value); | |||||
if (internal::is_negative(value)) { | |||||
specs_.align = align::left; | |||||
width = 0 - width; | |||||
} | |||||
unsigned int_max = std::numeric_limits<int>::max(); | |||||
if (width > int_max) FMT_THROW(format_error("number is too big")); | |||||
return static_cast<unsigned>(width); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
unsigned operator()(T) { | |||||
FMT_THROW(format_error("width is not integer")); | |||||
return 0; | |||||
} | |||||
}; | |||||
template <typename Char, typename Context> | |||||
void printf(buffer<Char>& buf, basic_string_view<Char> format, | |||||
basic_format_args<Context> args) { | |||||
Context(std::back_inserter(buf), format, args).format(); | |||||
} | |||||
template <typename OutputIt, typename Char, typename Context> | |||||
internal::truncating_iterator<OutputIt> printf( | |||||
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format, | |||||
basic_format_args<Context> args) { | |||||
return Context(it, format, args).format(); | |||||
} | |||||
} // namespace internal | |||||
using internal::printf; // For printing into memory_buffer. | |||||
template <typename Range> class printf_arg_formatter; | |||||
template <typename OutputIt, typename Char> class basic_printf_context; | |||||
/** | |||||
\rst | |||||
The ``printf`` argument formatter. | |||||
\endrst | |||||
*/ | |||||
template <typename Range> | |||||
class printf_arg_formatter : public internal::arg_formatter_base<Range> { | |||||
public: | |||||
using iterator = typename Range::iterator; | |||||
private: | |||||
using char_type = typename Range::value_type; | |||||
using base = internal::arg_formatter_base<Range>; | |||||
using context_type = basic_printf_context<iterator, char_type>; | |||||
context_type& context_; | |||||
void write_null_pointer(char) { | |||||
this->specs()->type = 0; | |||||
this->write("(nil)"); | |||||
} | |||||
void write_null_pointer(wchar_t) { | |||||
this->specs()->type = 0; | |||||
this->write(L"(nil)"); | |||||
} | |||||
public: | |||||
using format_specs = typename base::format_specs; | |||||
/** | |||||
\rst | |||||
Constructs an argument formatter object. | |||||
*buffer* is a reference to the output buffer and *specs* contains format | |||||
specifier information for standard argument types. | |||||
\endrst | |||||
*/ | |||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx) | |||||
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {} | |||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
iterator operator()(T value) { | |||||
// MSVC2013 fails to compile separate overloads for bool and char_type so | |||||
// use std::is_same instead. | |||||
if (std::is_same<T, bool>::value) { | |||||
format_specs& fmt_specs = *this->specs(); | |||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0); | |||||
fmt_specs.type = 0; | |||||
this->write(value != 0); | |||||
} else if (std::is_same<T, char_type>::value) { | |||||
format_specs& fmt_specs = *this->specs(); | |||||
if (fmt_specs.type && fmt_specs.type != 'c') | |||||
return (*this)(static_cast<int>(value)); | |||||
fmt_specs.sign = sign::none; | |||||
fmt_specs.alt = false; | |||||
fmt_specs.align = align::right; | |||||
return base::operator()(value); | |||||
} else { | |||||
return base::operator()(value); | |||||
} | |||||
return this->out(); | |||||
} | |||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
iterator operator()(T value) { | |||||
return base::operator()(value); | |||||
} | |||||
/** Formats a null-terminated C string. */ | |||||
iterator operator()(const char* value) { | |||||
if (value) | |||||
base::operator()(value); | |||||
else if (this->specs()->type == 'p') | |||||
write_null_pointer(char_type()); | |||||
else | |||||
this->write("(null)"); | |||||
return this->out(); | |||||
} | |||||
/** Formats a null-terminated wide C string. */ | |||||
iterator operator()(const wchar_t* value) { | |||||
if (value) | |||||
base::operator()(value); | |||||
else if (this->specs()->type == 'p') | |||||
write_null_pointer(char_type()); | |||||
else | |||||
this->write(L"(null)"); | |||||
return this->out(); | |||||
} | |||||
iterator operator()(basic_string_view<char_type> value) { | |||||
return base::operator()(value); | |||||
} | |||||
iterator operator()(monostate value) { return base::operator()(value); } | |||||
/** Formats a pointer. */ | |||||
iterator operator()(const void* value) { | |||||
if (value) return base::operator()(value); | |||||
this->specs()->type = 0; | |||||
write_null_pointer(char_type()); | |||||
return this->out(); | |||||
} | |||||
/** Formats an argument of a custom (user-defined) type. */ | |||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) { | |||||
handle.format(context_.parse_context(), context_); | |||||
return this->out(); | |||||
} | |||||
}; | |||||
template <typename T> struct printf_formatter { | |||||
template <typename ParseContext> | |||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return ctx.begin(); | |||||
} | |||||
template <typename FormatContext> | |||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
internal::format_value(internal::get_container(ctx.out()), value); | |||||
return ctx.out(); | |||||
} | |||||
}; | |||||
/** This template formats data and writes the output to a writer. */ | |||||
template <typename OutputIt, typename Char> class basic_printf_context { | |||||
public: | |||||
/** The character type for the output. */ | |||||
using char_type = Char; | |||||
using format_arg = basic_format_arg<basic_printf_context>; | |||||
template <typename T> using formatter_type = printf_formatter<T>; | |||||
private: | |||||
using format_specs = basic_format_specs<char_type>; | |||||
OutputIt out_; | |||||
basic_format_args<basic_printf_context> args_; | |||||
basic_parse_context<Char> parse_ctx_; | |||||
static void parse_flags(format_specs& specs, const Char*& it, | |||||
const Char* end); | |||||
// Returns the argument with specified index or, if arg_index is equal | |||||
// to the maximum unsigned value, the next argument. | |||||
format_arg get_arg(unsigned arg_index = std::numeric_limits<unsigned>::max()); | |||||
// Parses argument index, flags and width and returns the argument index. | |||||
unsigned parse_header(const Char*& it, const Char* end, format_specs& specs); | |||||
public: | |||||
/** | |||||
\rst | |||||
Constructs a ``printf_context`` object. References to the arguments and | |||||
the writer are stored in the context object so make sure they have | |||||
appropriate lifetimes. | |||||
\endrst | |||||
*/ | |||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str, | |||||
basic_format_args<basic_printf_context> args) | |||||
: out_(out), args_(args), parse_ctx_(format_str) {} | |||||
OutputIt out() { return out_; } | |||||
void advance_to(OutputIt it) { out_ = it; } | |||||
format_arg arg(unsigned id) const { return args_.get(id); } | |||||
basic_parse_context<Char>& parse_context() { return parse_ctx_; } | |||||
FMT_CONSTEXPR void on_error(const char* message) { | |||||
parse_ctx_.on_error(message); | |||||
} | |||||
/** Formats stored arguments and writes the output to the range. */ | |||||
template <typename ArgFormatter = | |||||
printf_arg_formatter<internal::buffer_range<Char>>> | |||||
OutputIt format(); | |||||
}; | |||||
template <typename OutputIt, typename Char> | |||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& 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 ' ': | |||||
specs.sign = sign::space; | |||||
break; | |||||
case '#': | |||||
specs.alt = true; | |||||
break; | |||||
default: | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
template <typename OutputIt, typename Char> | |||||
typename basic_printf_context<OutputIt, Char>::format_arg | |||||
basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) { | |||||
if (arg_index == std::numeric_limits<unsigned>::max()) | |||||
arg_index = parse_ctx_.next_arg_id(); | |||||
else | |||||
parse_ctx_.check_arg_id(--arg_index); | |||||
return internal::get_arg(*this, arg_index); | |||||
} | |||||
template <typename OutputIt, typename Char> | |||||
unsigned basic_printf_context<OutputIt, Char>::parse_header( | |||||
const Char*& it, const Char* end, format_specs& specs) { | |||||
unsigned arg_index = std::numeric_limits<unsigned>::max(); | |||||
char_type c = *it; | |||||
if (c >= '0' && c <= '9') { | |||||
// Parse an argument index (if followed by '$') or a width possibly | |||||
// preceded with '0' flag(s). | |||||
internal::error_handler eh; | |||||
unsigned value = parse_nonnegative_int(it, end, eh); | |||||
if (it != end && *it == '$') { // value is an argument index | |||||
++it; | |||||
arg_index = value; | |||||
} 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. | |||||
specs.width = value; | |||||
return arg_index; | |||||
} | |||||
} | |||||
} | |||||
parse_flags(specs, it, end); | |||||
// Parse width. | |||||
if (it != end) { | |||||
if (*it >= '0' && *it <= '9') { | |||||
internal::error_handler eh; | |||||
specs.width = parse_nonnegative_int(it, end, eh); | |||||
} else if (*it == '*') { | |||||
++it; | |||||
specs.width = visit_format_arg( | |||||
internal::printf_width_handler<char_type>(specs), get_arg()); | |||||
} | |||||
} | |||||
return arg_index; | |||||
} | |||||
template <typename OutputIt, typename Char> | |||||
template <typename ArgFormatter> | |||||
OutputIt basic_printf_context<OutputIt, Char>::format() { | |||||
auto out = this->out(); | |||||
const Char* start = parse_ctx_.begin(); | |||||
const Char* end = parse_ctx_.end(); | |||||
auto it = start; | |||||
while (it != end) { | |||||
char_type c = *it++; | |||||
if (c != '%') continue; | |||||
if (it != end && *it == c) { | |||||
out = std::copy(start, it, out); | |||||
start = ++it; | |||||
continue; | |||||
} | |||||
out = std::copy(start, it - 1, out); | |||||
format_specs specs; | |||||
specs.align = align::right; | |||||
// Parse argument index, flags and width. | |||||
unsigned arg_index = parse_header(it, end, specs); | |||||
// Parse precision. | |||||
if (it != end && *it == '.') { | |||||
++it; | |||||
c = it != end ? *it : 0; | |||||
if ('0' <= c && c <= '9') { | |||||
internal::error_handler eh; | |||||
specs.precision = static_cast<int>(parse_nonnegative_int(it, end, eh)); | |||||
} else if (c == '*') { | |||||
++it; | |||||
specs.precision = | |||||
visit_format_arg(internal::printf_precision_handler(), get_arg()); | |||||
} else { | |||||
specs.precision = 0; | |||||
} | |||||
} | |||||
format_arg arg = get_arg(arg_index); | |||||
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg)) | |||||
specs.alt = false; | |||||
if (specs.fill[0] == '0') { | |||||
if (arg.is_arithmetic()) | |||||
specs.align = align::numeric; | |||||
else | |||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types. | |||||
} | |||||
// Parse length and convert the argument to the required type. | |||||
c = it != end ? *it++ : 0; | |||||
char_type t = it != end ? *it : 0; | |||||
using internal::convert_arg; | |||||
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<std::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) FMT_THROW(format_error("invalid format string")); | |||||
specs.type = static_cast<char>(*it++); | |||||
if (arg.is_integral()) { | |||||
// Normalize type. | |||||
switch (specs.type) { | |||||
case 'i': | |||||
case 'u': | |||||
specs.type = 'd'; | |||||
break; | |||||
case 'c': | |||||
visit_format_arg(internal::char_converter<basic_printf_context>(arg), | |||||
arg); | |||||
break; | |||||
} | |||||
} | |||||
start = it; | |||||
// Format argument. | |||||
visit_format_arg(ArgFormatter(out, specs, *this), arg); | |||||
} | |||||
return std::copy(start, it, out); | |||||
} | |||||
template <typename Char> | |||||
using basic_printf_context_t = | |||||
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>, | |||||
Char>; | |||||
using printf_context = basic_printf_context_t<char>; | |||||
using wprintf_context = basic_printf_context_t<wchar_t>; | |||||
using printf_args = basic_format_args<printf_context>; | |||||
using wprintf_args = basic_format_args<wprintf_context>; | |||||
/** | |||||
\rst | |||||
Constructs an `~fmt::format_arg_store` object that contains references to | |||||
arguments and can be implicitly converted to `~fmt::printf_args`. | |||||
\endrst | |||||
*/ | |||||
template <typename... Args> | |||||
inline format_arg_store<printf_context, Args...> make_printf_args( | |||||
const Args&... args) { | |||||
return {args...}; | |||||
} | |||||
/** | |||||
\rst | |||||
Constructs an `~fmt::format_arg_store` object that contains references to | |||||
arguments and can be implicitly converted to `~fmt::wprintf_args`. | |||||
\endrst | |||||
*/ | |||||
template <typename... Args> | |||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args( | |||||
const Args&... args) { | |||||
return {args...}; | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline std::basic_string<Char> vsprintf( | |||||
const S& format, basic_format_args<basic_printf_context_t<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
printf(buffer, to_string_view(format), args); | |||||
return to_string(buffer); | |||||
} | |||||
/** | |||||
\rst | |||||
Formats arguments and returns the result as a string. | |||||
**Example**:: | |||||
std::string message = fmt::sprintf("The answer is %d", 42); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) { | |||||
using context = basic_printf_context_t<Char>; | |||||
return vsprintf(to_string_view(format), {make_format_args<context>(args...)}); | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline int vfprintf(std::FILE* f, const S& format, | |||||
basic_format_args<basic_printf_context_t<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
printf(buffer, to_string_view(format), args); | |||||
std::size_t size = buffer.size(); | |||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size | |||||
? -1 | |||||
: static_cast<int>(size); | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to the file *f*. | |||||
**Example**:: | |||||
fmt::fprintf(stderr, "Don't %s!", "panic"); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) { | |||||
using context = basic_printf_context_t<Char>; | |||||
return vfprintf(f, to_string_view(format), | |||||
{make_format_args<context>(args...)}); | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline int vprintf(const S& format, | |||||
basic_format_args<basic_printf_context_t<Char>> args) { | |||||
return vfprintf(stdout, to_string_view(format), args); | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to ``stdout``. | |||||
**Example**:: | |||||
fmt::printf("Elapsed time: %.2f seconds", 1.23); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, | |||||
FMT_ENABLE_IF(internal::is_string<S>::value)> | |||||
inline int printf(const S& format_str, const Args&... args) { | |||||
using context = basic_printf_context_t<char_t<S>>; | |||||
return vprintf(to_string_view(format_str), | |||||
{make_format_args<context>(args...)}); | |||||
} | |||||
template <typename S, typename Char = char_t<S>> | |||||
inline int vfprintf(std::basic_ostream<Char>& os, const S& format, | |||||
basic_format_args<basic_printf_context_t<Char>> args) { | |||||
basic_memory_buffer<Char> buffer; | |||||
printf(buffer, to_string_view(format), args); | |||||
internal::write(os, buffer); | |||||
return static_cast<int>(buffer.size()); | |||||
} | |||||
/** Formats arguments and writes the output to the range. */ | |||||
template <typename ArgFormatter, typename Char, | |||||
typename Context = | |||||
basic_printf_context<typename ArgFormatter::iterator, Char>> | |||||
typename ArgFormatter::iterator vprintf(internal::buffer<Char>& out, | |||||
basic_string_view<Char> format_str, | |||||
basic_format_args<Context> args) { | |||||
typename ArgFormatter::iterator iter(out); | |||||
Context(iter, format_str, args).template format<ArgFormatter>(); | |||||
return iter; | |||||
} | |||||
/** | |||||
\rst | |||||
Prints formatted data to the stream *os*. | |||||
**Example**:: | |||||
fmt::fprintf(cerr, "Don't %s!", "panic"); | |||||
\endrst | |||||
*/ | |||||
template <typename S, typename... Args, typename Char = char_t<S>> | |||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str, | |||||
const Args&... args) { | |||||
using context = basic_printf_context_t<Char>; | |||||
return vfprintf(os, to_string_view(format_str), | |||||
{make_format_args<context>(args...)}); | |||||
} | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_PRINTF_H_ |
// Formatting library for C++ - experimental range support | |||||
// | |||||
// Copyright (c) 2012 - present, Victor Zverovich | |||||
// All rights reserved. | |||||
// | |||||
// For the license information refer to format.h. | |||||
// | |||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz) | |||||
// All Rights Reserved | |||||
// {fmt} support for ranges, containers and types tuple interface. | |||||
#ifndef FMT_RANGES_H_ | |||||
#define FMT_RANGES_H_ | |||||
#include <type_traits> | |||||
#include "format.h" | |||||
// output only up to N items from the range. | |||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT | |||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 | |||||
#endif | |||||
FMT_BEGIN_NAMESPACE | |||||
template <typename Char> struct formatting_base { | |||||
template <typename ParseContext> | |||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return ctx.begin(); | |||||
} | |||||
}; | |||||
template <typename Char, typename Enable = void> | |||||
struct formatting_range : formatting_base<Char> { | |||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = | |||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the | |||||
// range. | |||||
Char prefix; | |||||
Char delimiter; | |||||
Char postfix; | |||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {} | |||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | |||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | |||||
}; | |||||
template <typename Char, typename Enable = void> | |||||
struct formatting_tuple : formatting_base<Char> { | |||||
Char prefix; | |||||
Char delimiter; | |||||
Char postfix; | |||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {} | |||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | |||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | |||||
}; | |||||
namespace internal { | |||||
template <typename RangeT, typename OutputIterator> | |||||
OutputIterator copy(const RangeT& range, OutputIterator out) { | |||||
for (auto it = range.begin(), end = range.end(); it != end; ++it) | |||||
*out++ = *it; | |||||
return out; | |||||
} | |||||
template <typename OutputIterator> | |||||
OutputIterator copy(const char* str, OutputIterator out) { | |||||
while (*str) *out++ = *str++; | |||||
return out; | |||||
} | |||||
template <typename OutputIterator> | |||||
OutputIterator copy(char ch, OutputIterator out) { | |||||
*out++ = ch; | |||||
return out; | |||||
} | |||||
/// Return true value if T has std::string interface, like std::string_view. | |||||
template <typename T> class is_like_std_string { | |||||
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 FMT_CONSTEXPR_DECL const bool value = | |||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value; | |||||
}; | |||||
template <typename Char> | |||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {}; | |||||
template <typename... Ts> struct conditional_helper {}; | |||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {}; | |||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800 | |||||
template <typename T> | |||||
struct is_range_< | |||||
T, conditional_t<false, | |||||
conditional_helper<decltype(std::declval<T>().begin()), | |||||
decltype(std::declval<T>().end())>, | |||||
void>> : std::true_type {}; | |||||
#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, | |||||
(void)std::declval<typename std::tuple_element<0, U>::type>(), | |||||
int()); | |||||
template <typename> static void check(...); | |||||
public: | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
!std::is_void<decltype(check<T>(nullptr))>::value; | |||||
}; | |||||
// Check for integer_sequence | |||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 | |||||
template <typename T, T... N> | |||||
using integer_sequence = std::integer_sequence<T, N...>; | |||||
template <std::size_t... N> using index_sequence = std::index_sequence<N...>; | |||||
template <std::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 FMT_CONSTEXPR std::size_t size() { return sizeof...(N); } | |||||
}; | |||||
template <std::size_t... N> | |||||
using index_sequence = integer_sequence<std::size_t, N...>; | |||||
template <typename T, std::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 <std::size_t N> | |||||
using make_index_sequence = make_integer_sequence<std::size_t, N>; | |||||
#endif | |||||
template <class Tuple, class F, size_t... Is> | |||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT { | |||||
using std::get; | |||||
// using free function get<I>(T) now. | |||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; | |||||
(void)_; // blocks warnings | |||||
} | |||||
template <class T> | |||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( | |||||
T const&) { | |||||
return {}; | |||||
} | |||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) { | |||||
const auto indexes = get_indexes(tup); | |||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); | |||||
} | |||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string< | |||||
typename std::decay<Arg>::type>::value)> | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { | |||||
return add_space ? " {}" : "{}"; | |||||
} | |||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string< | |||||
typename std::decay<Arg>::type>::value)> | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { | |||||
return add_space ? " \"{}\"" : "\"{}\""; | |||||
} | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) { | |||||
return add_space ? " \"{}\"" : "\"{}\""; | |||||
} | |||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) { | |||||
return add_space ? L" \"{}\"" : L"\"{}\""; | |||||
} | |||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) { | |||||
return add_space ? " '{}'" : "'{}'"; | |||||
} | |||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) { | |||||
return add_space ? L" '{}'" : L"'{}'"; | |||||
} | |||||
} // namespace internal | |||||
template <typename T> struct is_tuple_like { | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value; | |||||
}; | |||||
template <typename TupleT, typename Char> | |||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> { | |||||
private: | |||||
// C++11 generic lambda for format() | |||||
template <typename FormatContext> struct format_each { | |||||
template <typename T> void operator()(const T& v) { | |||||
if (i > 0) { | |||||
if (formatting.add_prepostfix_space) { | |||||
*out++ = ' '; | |||||
} | |||||
out = internal::copy(formatting.delimiter, out); | |||||
} | |||||
out = format_to(out, | |||||
internal::format_str_quoted( | |||||
(formatting.add_delimiter_spaces && i > 0), v), | |||||
v); | |||||
++i; | |||||
} | |||||
formatting_tuple<Char>& formatting; | |||||
std::size_t& i; | |||||
typename std::add_lvalue_reference<decltype( | |||||
std::declval<FormatContext>().out())>::type out; | |||||
}; | |||||
public: | |||||
formatting_tuple<Char> formatting; | |||||
template <typename ParseContext> | |||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return formatting.parse(ctx); | |||||
} | |||||
template <typename FormatContext = format_context> | |||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { | |||||
auto out = ctx.out(); | |||||
std::size_t i = 0; | |||||
internal::copy(formatting.prefix, out); | |||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out}); | |||||
if (formatting.add_prepostfix_space) { | |||||
*out++ = ' '; | |||||
} | |||||
internal::copy(formatting.postfix, out); | |||||
return ctx.out(); | |||||
} | |||||
}; | |||||
template <typename T, typename Char> struct is_range { | |||||
static FMT_CONSTEXPR_DECL const bool value = | |||||
internal::is_range_<T>::value && | |||||
!internal::is_like_std_string<T>::value && | |||||
!std::is_convertible<T, std::basic_string<Char>>::value; | |||||
}; | |||||
template <typename RangeT, typename Char> | |||||
struct formatter<RangeT, Char, | |||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> { | |||||
formatting_range<Char> formatting; | |||||
template <typename ParseContext> | |||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||||
return formatting.parse(ctx); | |||||
} | |||||
template <typename FormatContext> | |||||
typename FormatContext::iterator format(const RangeT& values, | |||||
FormatContext& ctx) { | |||||
auto out = internal::copy(formatting.prefix, ctx.out()); | |||||
std::size_t i = 0; | |||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) { | |||||
if (i > 0) { | |||||
if (formatting.add_prepostfix_space) *out++ = ' '; | |||||
out = internal::copy(formatting.delimiter, out); | |||||
} | |||||
out = format_to(out, | |||||
internal::format_str_quoted( | |||||
(formatting.add_delimiter_spaces && i > 0), *it), | |||||
*it); | |||||
if (++i > formatting.range_length_limit) { | |||||
out = format_to(out, " ... <other elements>"); | |||||
break; | |||||
} | |||||
} | |||||
if (formatting.add_prepostfix_space) *out++ = ' '; | |||||
return internal::copy(formatting.postfix, out); | |||||
} | |||||
}; | |||||
FMT_END_NAMESPACE | |||||
#endif // FMT_RANGES_H_ |
/* | |||||
* For conversion between std::chrono::durations without undefined | |||||
* behaviour or erroneous results. | |||||
* This is a stripped down version of duration_cast, for inclusion in fmt. | |||||
* See https://github.com/pauldreik/safe_duration_cast | |||||
* | |||||
* Copyright Paul Dreik 2019 | |||||
* | |||||
* This file is licensed under the fmt license, see format.h | |||||
*/ | |||||
#include <chrono> | |||||
#include <cmath> | |||||
#include <limits> | |||||
#include <type_traits> | |||||
#include "format.h" | |||||
FMT_BEGIN_NAMESPACE | |||||
namespace safe_duration_cast { | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(!std::is_same<From, To>::value && | |||||
std::numeric_limits<From>::is_signed == | |||||
std::numeric_limits<To>::is_signed)> | |||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
using F = std::numeric_limits<From>; | |||||
using T = std::numeric_limits<To>; | |||||
static_assert(F::is_integer, "From must be integral"); | |||||
static_assert(T::is_integer, "To must be integral"); | |||||
// A and B are both signed, or both unsigned. | |||||
if (F::digits <= T::digits) { | |||||
// From fits in To without any problem. | |||||
} else { | |||||
// From does not always fit in To, resort to a dynamic check. | |||||
if (from < T::min() || from > T::max()) { | |||||
// outside range. | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
} | |||||
return static_cast<To>(from); | |||||
} | |||||
/** | |||||
* converts From to To, without loss. If the dynamic value of from | |||||
* can't be converted to To without loss, ec is set. | |||||
*/ | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(!std::is_same<From, To>::value && | |||||
std::numeric_limits<From>::is_signed != | |||||
std::numeric_limits<To>::is_signed)> | |||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
using F = std::numeric_limits<From>; | |||||
using T = std::numeric_limits<To>; | |||||
static_assert(F::is_integer, "From must be integral"); | |||||
static_assert(T::is_integer, "To must be integral"); | |||||
if (F::is_signed && !T::is_signed) { | |||||
// From may be negative, not allowed! | |||||
if (from < 0) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
// From is positive. Can it always fit in To? | |||||
if (F::digits <= T::digits) { | |||||
// yes, From always fits in To. | |||||
} else { | |||||
// from may not fit in To, we have to do a dynamic check | |||||
if (from > static_cast<From>(T::max())) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
} | |||||
} | |||||
if (!F::is_signed && T::is_signed) { | |||||
// can from be held in To? | |||||
if (F::digits < T::digits) { | |||||
// yes, From always fits in To. | |||||
} else { | |||||
// from may not fit in To, we have to do a dynamic check | |||||
if (from > static_cast<From>(T::max())) { | |||||
// outside range. | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
} | |||||
} | |||||
// reaching here means all is ok for lossless conversion. | |||||
return static_cast<To>(from); | |||||
} // function | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(std::is_same<From, To>::value)> | |||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
return from; | |||||
} // function | |||||
// clang-format off | |||||
/** | |||||
* converts From to To if possible, otherwise ec is set. | |||||
* | |||||
* input | output | |||||
* ---------------------------------|--------------- | |||||
* NaN | NaN | |||||
* Inf | Inf | |||||
* normal, fits in output | converted (possibly lossy) | |||||
* normal, does not fit in output | ec is set | |||||
* subnormal | best effort | |||||
* -Inf | -Inf | |||||
*/ | |||||
// clang-format on | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(!std::is_same<From, To>::value)> | |||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
using T = std::numeric_limits<To>; | |||||
static_assert(std::is_floating_point<From>::value, "From must be floating"); | |||||
static_assert(std::is_floating_point<To>::value, "To must be floating"); | |||||
// catch the only happy case | |||||
if (std::isfinite(from)) { | |||||
if (from >= T::lowest() && from <= T::max()) { | |||||
return static_cast<To>(from); | |||||
} | |||||
// not within range. | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
// nan and inf will be preserved | |||||
return static_cast<To>(from); | |||||
} // function | |||||
template <typename To, typename From, | |||||
FMT_ENABLE_IF(std::is_same<From, To>::value)> | |||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { | |||||
ec = 0; | |||||
static_assert(std::is_floating_point<From>::value, "From must be floating"); | |||||
return from; | |||||
} | |||||
/** | |||||
* safe duration cast between integral durations | |||||
*/ | |||||
template <typename To, typename FromRep, typename FromPeriod, | |||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value), | |||||
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)> | |||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, | |||||
int& ec) { | |||||
using From = std::chrono::duration<FromRep, FromPeriod>; | |||||
ec = 0; | |||||
// the basic idea is that we need to convert from count() in the from type | |||||
// to count() in the To type, by multiplying it with this: | |||||
using Factor = std::ratio_divide<typename From::period, typename To::period>; | |||||
static_assert(Factor::num > 0, "num must be positive"); | |||||
static_assert(Factor::den > 0, "den must be positive"); | |||||
// the conversion is like this: multiply from.count() with Factor::num | |||||
// /Factor::den and convert it to To::rep, all this without | |||||
// overflow/underflow. let's start by finding a suitable type that can hold | |||||
// both To, From and Factor::num | |||||
using IntermediateRep = | |||||
typename std::common_type<typename From::rep, typename To::rep, | |||||
decltype(Factor::num)>::type; | |||||
// safe conversion to IntermediateRep | |||||
IntermediateRep count = | |||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
// multiply with Factor::num without overflow or underflow | |||||
if (Factor::num != 1) { | |||||
constexpr auto max1 = | |||||
std::numeric_limits<IntermediateRep>::max() / Factor::num; | |||||
if (count > max1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
constexpr auto min1 = | |||||
std::numeric_limits<IntermediateRep>::min() / Factor::num; | |||||
if (count < min1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
count *= Factor::num; | |||||
} | |||||
// this can't go wrong, right? den>0 is checked earlier. | |||||
if (Factor::den != 1) { | |||||
count /= Factor::den; | |||||
} | |||||
// convert to the to type, safely | |||||
using ToRep = typename To::rep; | |||||
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
return To{tocount}; | |||||
} | |||||
/** | |||||
* safe duration_cast between floating point durations | |||||
*/ | |||||
template <typename To, typename FromRep, typename FromPeriod, | |||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value), | |||||
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)> | |||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, | |||||
int& ec) { | |||||
using From = std::chrono::duration<FromRep, FromPeriod>; | |||||
ec = 0; | |||||
if (std::isnan(from.count())) { | |||||
// nan in, gives nan out. easy. | |||||
return To{std::numeric_limits<typename To::rep>::quiet_NaN()}; | |||||
} | |||||
// maybe we should also check if from is denormal, and decide what to do about | |||||
// it. | |||||
// +-inf should be preserved. | |||||
if (std::isinf(from.count())) { | |||||
return To{from.count()}; | |||||
} | |||||
// the basic idea is that we need to convert from count() in the from type | |||||
// to count() in the To type, by multiplying it with this: | |||||
using Factor = std::ratio_divide<typename From::period, typename To::period>; | |||||
static_assert(Factor::num > 0, "num must be positive"); | |||||
static_assert(Factor::den > 0, "den must be positive"); | |||||
// the conversion is like this: multiply from.count() with Factor::num | |||||
// /Factor::den and convert it to To::rep, all this without | |||||
// overflow/underflow. let's start by finding a suitable type that can hold | |||||
// both To, From and Factor::num | |||||
using IntermediateRep = | |||||
typename std::common_type<typename From::rep, typename To::rep, | |||||
decltype(Factor::num)>::type; | |||||
// force conversion of From::rep -> IntermediateRep to be safe, | |||||
// even if it will never happen be narrowing in this context. | |||||
IntermediateRep count = | |||||
safe_float_conversion<IntermediateRep>(from.count(), ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
// multiply with Factor::num without overflow or underflow | |||||
if (Factor::num != 1) { | |||||
constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() / | |||||
static_cast<IntermediateRep>(Factor::num); | |||||
if (count > max1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() / | |||||
static_cast<IntermediateRep>(Factor::num); | |||||
if (count < min1) { | |||||
ec = 1; | |||||
return {}; | |||||
} | |||||
count *= static_cast<IntermediateRep>(Factor::num); | |||||
} | |||||
// this can't go wrong, right? den>0 is checked earlier. | |||||
if (Factor::den != 1) { | |||||
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type; | |||||
count /= static_cast<common_t>(Factor::den); | |||||
} | |||||
// convert to the to type, safely | |||||
using ToRep = typename To::rep; | |||||
const ToRep tocount = safe_float_conversion<ToRep>(count, ec); | |||||
if (ec) { | |||||
return {}; | |||||
} | |||||
return To{tocount}; | |||||
} | |||||
} // namespace safe_duration_cast | |||||
FMT_END_NAMESPACE |
// | |||||
// Copyright(c) 2016-2018 Gabi Melman. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// | |||||
#pragma once | |||||
// | |||||
// Include a bundled header-only copy of fmtlib or an external one. | |||||
// By default spdlog include its own copy. | |||||
// | |||||
#if !defined(SPDLOG_FMT_EXTERNAL) | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#ifndef FMT_HEADER_ONLY | |||||
#define FMT_HEADER_ONLY | |||||
#endif | |||||
#endif | |||||
#ifndef FMT_USE_WINDOWS_H | |||||
#define FMT_USE_WINDOWS_H 0 | |||||
#endif | |||||
#include "bundled/core.h" | |||||
#include "bundled/format.h" | |||||
#else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib | |||||
#include "fmt/core.h" | |||||
#include "fmt/format.h" | |||||
#endif |
// | |||||
// Copyright(c) 2016 Gabi Melman. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// | |||||
#pragma once | |||||
// | |||||
// include bundled or external copy of fmtlib's ostream support | |||||
// | |||||
#if !defined(SPDLOG_FMT_EXTERNAL) | |||||
#ifndef FMT_HEADER_ONLY | |||||
#define FMT_HEADER_ONLY | |||||
#endif | |||||
#include "bundled/ostream.h" | |||||
#include "fmt.h" | |||||
#else | |||||
#include <fmt/ostream.h> | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "fmt/fmt.h" | |||||
#include "spdlog/details/log_msg.h" | |||||
namespace spdlog { | |||||
class formatter | |||||
{ | |||||
public: | |||||
virtual ~formatter() = default; | |||||
virtual void format(const details::log_msg &msg, memory_buf_t &dest) = 0; | |||||
virtual std::unique_ptr<formatter> clone() const = 0; | |||||
}; | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/logger.h" | |||||
#endif | |||||
#include "spdlog/sinks/sink.h" | |||||
#include "spdlog/details/backtracer.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
#include <cstdio> | |||||
namespace spdlog { | |||||
// public methods | |||||
SPDLOG_INLINE logger::logger(const logger &other) | |||||
: name_(other.name_) | |||||
, sinks_(other.sinks_) | |||||
, level_(other.level_.load(std::memory_order_relaxed)) | |||||
, flush_level_(other.flush_level_.load(std::memory_order_relaxed)) | |||||
, custom_err_handler_(other.custom_err_handler_) | |||||
, tracer_(other.tracer_) | |||||
{} | |||||
SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT : name_(std::move(other.name_)), | |||||
sinks_(std::move(other.sinks_)), | |||||
level_(other.level_.load(std::memory_order_relaxed)), | |||||
flush_level_(other.flush_level_.load(std::memory_order_relaxed)), | |||||
custom_err_handler_(std::move(other.custom_err_handler_)), | |||||
tracer_(std::move(other.tracer_)) | |||||
{} | |||||
SPDLOG_INLINE logger &logger::operator=(logger other) SPDLOG_NOEXCEPT | |||||
{ | |||||
this->swap(other); | |||||
return *this; | |||||
} | |||||
SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT | |||||
{ | |||||
name_.swap(other.name_); | |||||
sinks_.swap(other.sinks_); | |||||
// swap level_ | |||||
auto other_level = other.level_.load(); | |||||
auto my_level = level_.exchange(other_level); | |||||
other.level_.store(my_level); | |||||
// swap flush level_ | |||||
other_level = other.flush_level_.load(); | |||||
my_level = flush_level_.exchange(other_level); | |||||
other.flush_level_.store(my_level); | |||||
custom_err_handler_.swap(other.custom_err_handler_); | |||||
std::swap(tracer_, other.tracer_); | |||||
} | |||||
SPDLOG_INLINE void swap(logger &a, logger &b) | |||||
{ | |||||
a.swap(b); | |||||
} | |||||
SPDLOG_INLINE bool logger::should_log(level::level_enum msg_level) const | |||||
{ | |||||
return msg_level >= level_.load(std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE void logger::set_level(level::level_enum log_level) | |||||
{ | |||||
level_.store(log_level); | |||||
} | |||||
SPDLOG_INLINE level::level_enum logger::level() const | |||||
{ | |||||
return static_cast<level::level_enum>(level_.load(std::memory_order_relaxed)); | |||||
} | |||||
SPDLOG_INLINE const std::string &logger::name() const | |||||
{ | |||||
return name_; | |||||
} | |||||
// set formatting for the sinks in this logger. | |||||
// each sink will get a seperate instance of the formatter object. | |||||
SPDLOG_INLINE void logger::set_formatter(std::unique_ptr<formatter> f) | |||||
{ | |||||
for (auto it = sinks_.begin(); it != sinks_.end(); ++it) | |||||
{ | |||||
if (std::next(it) == sinks_.end()) | |||||
{ | |||||
// last element - we can be move it. | |||||
(*it)->set_formatter(std::move(f)); | |||||
} | |||||
else | |||||
{ | |||||
(*it)->set_formatter(f->clone()); | |||||
} | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::set_pattern(std::string pattern, pattern_time_type time_type) | |||||
{ | |||||
auto new_formatter = details::make_unique<pattern_formatter>(std::move(pattern), time_type); | |||||
set_formatter(std::move(new_formatter)); | |||||
} | |||||
// create new backtrace sink and move to it all our child sinks | |||||
SPDLOG_INLINE void logger::enable_backtrace(size_t n_messages) | |||||
{ | |||||
tracer_.enable(n_messages); | |||||
} | |||||
// restore orig sinks and level and delete the backtrace sink | |||||
SPDLOG_INLINE void logger::disable_backtrace() | |||||
{ | |||||
tracer_.disable(); | |||||
} | |||||
SPDLOG_INLINE void logger::dump_backtrace() | |||||
{ | |||||
dump_backtrace_(); | |||||
} | |||||
// flush functions | |||||
SPDLOG_INLINE void logger::flush() | |||||
{ | |||||
flush_(); | |||||
} | |||||
SPDLOG_INLINE void logger::flush_on(level::level_enum log_level) | |||||
{ | |||||
flush_level_.store(log_level); | |||||
} | |||||
SPDLOG_INLINE level::level_enum logger::flush_level() const | |||||
{ | |||||
return static_cast<level::level_enum>(flush_level_.load(std::memory_order_relaxed)); | |||||
} | |||||
// sinks | |||||
SPDLOG_INLINE const std::vector<sink_ptr> &logger::sinks() const | |||||
{ | |||||
return sinks_; | |||||
} | |||||
SPDLOG_INLINE std::vector<sink_ptr> &logger::sinks() | |||||
{ | |||||
return sinks_; | |||||
} | |||||
// error handler | |||||
SPDLOG_INLINE void logger::set_error_handler(err_handler handler) | |||||
{ | |||||
custom_err_handler_ = handler; | |||||
} | |||||
// create new logger with same sinks and configuration. | |||||
SPDLOG_INLINE std::shared_ptr<logger> logger::clone(std::string logger_name) | |||||
{ | |||||
auto cloned = std::make_shared<logger>(*this); | |||||
cloned->name_ = std::move(logger_name); | |||||
return cloned; | |||||
} | |||||
// protected methods | |||||
SPDLOG_INLINE void logger::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
if (sink->should_log(msg.level)) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->log(msg); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
if (should_flush_(msg)) | |||||
{ | |||||
flush_(); | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::flush_() | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
SPDLOG_TRY | |||||
{ | |||||
sink->flush(); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
} | |||||
SPDLOG_INLINE void logger::dump_backtrace_() | |||||
{ | |||||
using details::log_msg; | |||||
if (tracer_) | |||||
{ | |||||
sink_it_(log_msg{name(), level::info, "****************** Backtrace Start ******************"}); | |||||
tracer_.foreach_pop([this](const log_msg &msg) { this->sink_it_(msg); }); | |||||
sink_it_(log_msg{name(), level::info, "****************** Backtrace End ********************"}); | |||||
} | |||||
} | |||||
SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) | |||||
{ | |||||
auto flush_level = flush_level_.load(std::memory_order_relaxed); | |||||
return (msg.level >= flush_level) && (msg.level != level::off); | |||||
} | |||||
SPDLOG_INLINE void logger::err_handler_(const std::string &msg) | |||||
{ | |||||
if (custom_err_handler_) | |||||
{ | |||||
custom_err_handler_(msg); | |||||
} | |||||
else | |||||
{ | |||||
using std::chrono::system_clock; | |||||
static std::mutex mutex; | |||||
static std::chrono::system_clock::time_point last_report_time; | |||||
static size_t err_counter = 0; | |||||
std::lock_guard<std::mutex> lk{mutex}; | |||||
auto now = system_clock::now(); | |||||
err_counter++; | |||||
if (now - last_report_time < std::chrono::seconds(1)) | |||||
{ | |||||
return; | |||||
} | |||||
last_report_time = now; | |||||
auto tm_time = details::os::localtime(system_clock::to_time_t(now)); | |||||
char date_buf[64]; | |||||
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); | |||||
fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str()); | |||||
} | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// Thread safe logger (except for set_error_handler()) | |||||
// Has name, log level, vector of std::shared sink pointers and formatter | |||||
// Upon each log write the logger: | |||||
// 1. Checks if its log level is enough to log the message and if yes: | |||||
// 2. Call the underlying sinks to do the job. | |||||
// 3. Each sink use its own private copy of a formatter to format the message | |||||
// and send to its destination. | |||||
// | |||||
// The use of private formatter per sink provides the opportunity to cache some | |||||
// formatted data, and support for different format per sink. | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/log_msg.h" | |||||
#include "spdlog/details/backtracer.h" | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#include "spdlog/details/os.h" | |||||
#endif | |||||
#include <vector> | |||||
#ifndef SPDLOG_NO_EXCEPTIONS | |||||
#define SPDLOG_LOGGER_CATCH() \ | |||||
catch (const std::exception &ex) \ | |||||
{ \ | |||||
err_handler_(ex.what()); \ | |||||
} \ | |||||
catch (...) \ | |||||
{ \ | |||||
err_handler_("Unknown exception in logger"); \ | |||||
} | |||||
#else | |||||
#define SPDLOG_LOGGER_CATCH() | |||||
#endif | |||||
namespace spdlog { | |||||
class logger | |||||
{ | |||||
public: | |||||
// Empty logger | |||||
explicit logger(std::string name) | |||||
: name_(std::move(name)) | |||||
, sinks_() | |||||
{} | |||||
// Logger with range on sinks | |||||
template<typename It> | |||||
logger(std::string name, It begin, It end) | |||||
: name_(std::move(name)) | |||||
, sinks_(begin, end) | |||||
{} | |||||
// Logger with single sink | |||||
logger(std::string name, sink_ptr single_sink) | |||||
: logger(std::move(name), {std::move(single_sink)}) | |||||
{} | |||||
// Logger with sinks init list | |||||
logger(std::string name, sinks_init_list sinks) | |||||
: logger(std::move(name), sinks.begin(), sinks.end()) | |||||
{} | |||||
virtual ~logger() = default; | |||||
logger(const logger &other); | |||||
logger(logger &&other) SPDLOG_NOEXCEPT; | |||||
logger &operator=(logger other) SPDLOG_NOEXCEPT; | |||||
void swap(spdlog::logger &other) SPDLOG_NOEXCEPT; | |||||
template<typename... Args> | |||||
void log(source_loc loc, level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
auto level_enabled = should_log(lvl); | |||||
if (!level_enabled && !tracer_) | |||||
{ | |||||
return; | |||||
} | |||||
SPDLOG_TRY | |||||
{ | |||||
memory_buf_t buf; | |||||
fmt::format_to(buf, fmt, args...); | |||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
if (level_enabled) | |||||
{ | |||||
sink_it_(log_msg); | |||||
} | |||||
if (tracer_) | |||||
{ | |||||
tracer_.push_back(log_msg); | |||||
} | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
template<typename... Args> | |||||
void log(level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(source_loc{}, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void trace(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::trace, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void debug(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::debug, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void info(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::info, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void warn(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::warn, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void error(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::err, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void critical(string_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::critical, fmt, args...); | |||||
} | |||||
template<typename T> | |||||
void log(level::level_enum lvl, const T &msg) | |||||
{ | |||||
log(source_loc{}, lvl, msg); | |||||
} | |||||
// T can be statically converted to string_view | |||||
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, T>::type * = nullptr> | |||||
void log(source_loc loc, level::level_enum lvl, const T &msg) | |||||
{ | |||||
auto level_enabled = should_log(lvl); | |||||
if (!level_enabled && !tracer_) | |||||
{ | |||||
return; | |||||
} | |||||
SPDLOG_TRY | |||||
{ | |||||
details::log_msg log_msg(loc, name_, lvl, msg); | |||||
if (level_enabled) | |||||
{ | |||||
sink_it_(log_msg); | |||||
} | |||||
if (tracer_) | |||||
{ | |||||
tracer_.push_back(log_msg); | |||||
} | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
void log(level::level_enum lvl, string_view_t msg) | |||||
{ | |||||
log(source_loc{}, lvl, msg); | |||||
} | |||||
// T cannot be statically converted to string_view or wstring_view | |||||
template<class T, typename std::enable_if<!std::is_convertible<const T &, spdlog::string_view_t>::value && | |||||
!is_convertible_to_wstring_view<const T &>::value, | |||||
T>::type * = nullptr> | |||||
void log(source_loc loc, level::level_enum lvl, const T &msg) | |||||
{ | |||||
log(loc, lvl, "{}", msg); | |||||
} | |||||
template<typename T> | |||||
void trace(const T &msg) | |||||
{ | |||||
log(level::trace, msg); | |||||
} | |||||
template<typename T> | |||||
void debug(const T &msg) | |||||
{ | |||||
log(level::debug, msg); | |||||
} | |||||
template<typename T> | |||||
void info(const T &msg) | |||||
{ | |||||
log(level::info, msg); | |||||
} | |||||
template<typename T> | |||||
void warn(const T &msg) | |||||
{ | |||||
log(level::warn, msg); | |||||
} | |||||
template<typename T> | |||||
void error(const T &msg) | |||||
{ | |||||
log(level::err, msg); | |||||
} | |||||
template<typename T> | |||||
void critical(const T &msg) | |||||
{ | |||||
log(level::critical, msg); | |||||
} | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
#ifndef _WIN32 | |||||
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||||
#else | |||||
template<typename... Args> | |||||
void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
auto level_enabled = should_log(lvl); | |||||
if (!level_enabled && !tracer_) | |||||
{ | |||||
return; | |||||
} | |||||
SPDLOG_TRY | |||||
{ | |||||
// format to wmemory_buffer and convert to utf8 | |||||
fmt::wmemory_buffer wbuf; | |||||
fmt::format_to(wbuf, fmt, args...); | |||||
memory_buf_t buf; | |||||
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf); | |||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
if (level_enabled) | |||||
{ | |||||
sink_it_(log_msg); | |||||
} | |||||
if (tracer_) | |||||
{ | |||||
tracer_.push_back(log_msg); | |||||
} | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
template<typename... Args> | |||||
void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(source_loc{}, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void trace(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::trace, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void debug(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::debug, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void info(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::info, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void warn(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::warn, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void error(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::err, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
void critical(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
log(level::critical, fmt, args...); | |||||
} | |||||
// T can be statically converted to wstring_view | |||||
template<class T, typename std::enable_if<is_convertible_to_wstring_view<const T &>::value, T>::type * = nullptr> | |||||
void log(source_loc loc, level::level_enum lvl, const T &msg) | |||||
{ | |||||
if (!should_log(lvl)) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
memory_buf_t buf; | |||||
details::os::wstr_to_utf8buf(msg, buf); | |||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
sink_it_(log_msg); | |||||
} | |||||
SPDLOG_LOGGER_CATCH() | |||||
} | |||||
#endif // _WIN32 | |||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
bool should_log(level::level_enum msg_level) const; | |||||
void set_level(level::level_enum log_level); | |||||
level::level_enum level() const; | |||||
const std::string &name() const; | |||||
// set formatting for the sinks in this logger. | |||||
// each sink will get a seperate instance of the formatter object. | |||||
void set_formatter(std::unique_ptr<formatter> f); | |||||
void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); | |||||
// backtrace support. | |||||
// efficiently store all debug/trace messages in a circular buffer until needed for debugging. | |||||
void enable_backtrace(size_t n_messages); | |||||
void disable_backtrace(); | |||||
void dump_backtrace(); | |||||
// flush functions | |||||
void flush(); | |||||
void flush_on(level::level_enum log_level); | |||||
level::level_enum flush_level() const; | |||||
// sinks | |||||
const std::vector<sink_ptr> &sinks() const; | |||||
std::vector<sink_ptr> &sinks(); | |||||
// error handler | |||||
void set_error_handler(err_handler); | |||||
// create new logger with same sinks and configuration. | |||||
virtual std::shared_ptr<logger> clone(std::string logger_name); | |||||
protected: | |||||
std::string name_; | |||||
std::vector<sink_ptr> sinks_; | |||||
spdlog::level_t level_{level::info}; | |||||
spdlog::level_t flush_level_{level::off}; | |||||
err_handler custom_err_handler_{nullptr}; | |||||
details::backtracer tracer_; | |||||
virtual void sink_it_(const details::log_msg &msg); | |||||
virtual void flush_(); | |||||
void dump_backtrace_(); | |||||
bool should_flush_(const details::log_msg &msg); | |||||
// handle errors during logging. | |||||
// default handler prints the error to stderr at max rate of 1 message/sec. | |||||
void err_handler_(const std::string &msg); | |||||
}; | |||||
void swap(logger &a, logger &b); | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "logger-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifdef __ANDROID__ | |||||
#include "spdlog/details/fmt_helper.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/details/os.h" | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <android/log.h> | |||||
#include <chrono> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <thread> | |||||
#if !defined(SPDLOG_ANDROID_RETRIES) | |||||
#define SPDLOG_ANDROID_RETRIES 2 | |||||
#endif | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Android sink (logging using __android_log_write) | |||||
*/ | |||||
template<typename Mutex> | |||||
class android_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) | |||||
: tag_(std::move(tag)) | |||||
, use_raw_msg_(use_raw_msg) | |||||
{} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
const android_LogPriority priority = convert_to_android_(msg.level); | |||||
memory_buf_t formatted; | |||||
if (use_raw_msg_) | |||||
{ | |||||
details::fmt_helper::append_string_view(msg.payload, formatted); | |||||
} | |||||
else | |||||
{ | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
} | |||||
formatted.push_back('\0'); | |||||
const char *msg_output = formatted.data(); | |||||
// See system/core/liblog/logger_write.c for explanation of return value | |||||
int ret = __android_log_write(priority, tag_.c_str(), msg_output); | |||||
int retry_count = 0; | |||||
while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) | |||||
{ | |||||
details::os::sleep_for_millis(5); | |||||
ret = __android_log_write(priority, tag_.c_str(), msg_output); | |||||
retry_count++; | |||||
} | |||||
if (ret < 0) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("__android_log_write() failed", ret)); | |||||
} | |||||
} | |||||
void flush_() override {} | |||||
private: | |||||
static android_LogPriority convert_to_android_(spdlog::level::level_enum level) | |||||
{ | |||||
switch (level) | |||||
{ | |||||
case spdlog::level::trace: | |||||
return ANDROID_LOG_VERBOSE; | |||||
case spdlog::level::debug: | |||||
return ANDROID_LOG_DEBUG; | |||||
case spdlog::level::info: | |||||
return ANDROID_LOG_INFO; | |||||
case spdlog::level::warn: | |||||
return ANDROID_LOG_WARN; | |||||
case spdlog::level::err: | |||||
return ANDROID_LOG_ERROR; | |||||
case spdlog::level::critical: | |||||
return ANDROID_LOG_FATAL; | |||||
default: | |||||
return ANDROID_LOG_DEFAULT; | |||||
} | |||||
} | |||||
std::string tag_; | |||||
bool use_raw_msg_; | |||||
}; | |||||
using android_sink_mt = android_sink<std::mutex>; | |||||
using android_sink_st = android_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// Create and register android syslog logger | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") | |||||
{ | |||||
return Factory::template create<sinks::android_sink_mt>(logger_name, tag); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") | |||||
{ | |||||
return Factory::template create<sinks::android_sink_st>(logger_name, tag); | |||||
} | |||||
} // namespace spdlog | |||||
#endif // __ANDROID__ |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/ansicolor_sink.h" | |||||
#endif | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
#include "spdlog/details/os.h" | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE ansicolor_sink<ConsoleMutex>::ansicolor_sink(FILE *target_file, color_mode mode) | |||||
: target_file_(target_file) | |||||
, mutex_(ConsoleMutex::mutex()) | |||||
, formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
{ | |||||
set_color_mode(mode); | |||||
colors_[level::trace] = white; | |||||
colors_[level::debug] = cyan; | |||||
colors_[level::info] = green; | |||||
colors_[level::warn] = yellow_bold; | |||||
colors_[level::err] = red_bold; | |||||
colors_[level::critical] = bold_on_red; | |||||
colors_[level::off] = reset; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color(level::level_enum color_level, string_view_t color) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
colors_[color_level] = color; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::log(const details::log_msg &msg) | |||||
{ | |||||
// Wrap the originally formatted message in color codes. | |||||
// If color is not supported in the terminal, log as is instead. | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
memory_buf_t formatted; | |||||
formatter_->format(msg, formatted); | |||||
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) | |||||
{ | |||||
// before color range | |||||
print_range_(formatted, 0, msg.color_range_start); | |||||
// in color range | |||||
print_ccode_(colors_[msg.level]); | |||||
print_range_(formatted, msg.color_range_start, msg.color_range_end); | |||||
print_ccode_(reset); | |||||
// after color range | |||||
print_range_(formatted, msg.color_range_end, formatted.size()); | |||||
} | |||||
else // no color | |||||
{ | |||||
print_range_(formatted, 0, formatted.size()); | |||||
} | |||||
fflush(target_file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::flush() | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
fflush(target_file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::move(sink_formatter); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE bool ansicolor_sink<ConsoleMutex>::should_color() | |||||
{ | |||||
return should_do_colors_; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) | |||||
{ | |||||
switch (mode) | |||||
{ | |||||
case color_mode::always: | |||||
should_do_colors_ = true; | |||||
return; | |||||
case color_mode::automatic: | |||||
should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); | |||||
return; | |||||
case color_mode::never: | |||||
should_do_colors_ = false; | |||||
return; | |||||
} | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code) | |||||
{ | |||||
fwrite(color_code.data(), sizeof(string_view_t::char_type), color_code.size(), target_file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) | |||||
{ | |||||
fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); | |||||
} | |||||
// ansicolor_stdout_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE ansicolor_stdout_sink<ConsoleMutex>::ansicolor_stdout_sink(color_mode mode) | |||||
: ansicolor_sink<ConsoleMutex>(stdout, mode) | |||||
{} | |||||
// ansicolor_stderr_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE ansicolor_stderr_sink<ConsoleMutex>::ansicolor_stderr_sink(color_mode mode) | |||||
: ansicolor_sink<ConsoleMutex>(stderr, mode) | |||||
{} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/console_globals.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/sinks/sink.h" | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/** | |||||
* This sink prefixes the output with an ANSI escape sequence color code | |||||
* depending on the severity | |||||
* of the message. | |||||
* If no color terminal detected, omit the escape codes. | |||||
*/ | |||||
template<typename ConsoleMutex> | |||||
class ansicolor_sink : public sink | |||||
{ | |||||
public: | |||||
using mutex_t = typename ConsoleMutex::mutex_t; | |||||
ansicolor_sink(FILE *target_file, color_mode mode); | |||||
~ansicolor_sink() override = default; | |||||
ansicolor_sink(const ansicolor_sink &other) = delete; | |||||
ansicolor_sink &operator=(const ansicolor_sink &other) = delete; | |||||
void set_color(level::level_enum color_level, string_view_t color); | |||||
void set_color_mode(color_mode mode); | |||||
bool should_color(); | |||||
void log(const details::log_msg &msg) override; | |||||
void flush() override; | |||||
void set_pattern(const std::string &pattern) final; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; | |||||
// Formatting codes | |||||
const string_view_t reset = "\033[m"; | |||||
const string_view_t bold = "\033[1m"; | |||||
const string_view_t dark = "\033[2m"; | |||||
const string_view_t underline = "\033[4m"; | |||||
const string_view_t blink = "\033[5m"; | |||||
const string_view_t reverse = "\033[7m"; | |||||
const string_view_t concealed = "\033[8m"; | |||||
const string_view_t clear_line = "\033[K"; | |||||
// Foreground colors | |||||
const string_view_t black = "\033[30m"; | |||||
const string_view_t red = "\033[31m"; | |||||
const string_view_t green = "\033[32m"; | |||||
const string_view_t yellow = "\033[33m"; | |||||
const string_view_t blue = "\033[34m"; | |||||
const string_view_t magenta = "\033[35m"; | |||||
const string_view_t cyan = "\033[36m"; | |||||
const string_view_t white = "\033[37m"; | |||||
/// Background colors | |||||
const string_view_t on_black = "\033[40m"; | |||||
const string_view_t on_red = "\033[41m"; | |||||
const string_view_t on_green = "\033[42m"; | |||||
const string_view_t on_yellow = "\033[43m"; | |||||
const string_view_t on_blue = "\033[44m"; | |||||
const string_view_t on_magenta = "\033[45m"; | |||||
const string_view_t on_cyan = "\033[46m"; | |||||
const string_view_t on_white = "\033[47m"; | |||||
/// Bold colors | |||||
const string_view_t yellow_bold = "\033[33m\033[1m"; | |||||
const string_view_t red_bold = "\033[31m\033[1m"; | |||||
const string_view_t bold_on_red = "\033[1m\033[41m"; | |||||
private: | |||||
FILE *target_file_; | |||||
mutex_t &mutex_; | |||||
bool should_do_colors_; | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
std::unordered_map<level::level_enum, string_view_t, level::level_hasher> colors_; | |||||
void print_ccode_(const string_view_t &color_code); | |||||
void print_range_(const memory_buf_t &formatted, size_t start, size_t end); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class ansicolor_stdout_sink : public ansicolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit ansicolor_stdout_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class ansicolor_stderr_sink : public ansicolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit ansicolor_stderr_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
using ansicolor_stdout_sink_mt = ansicolor_stdout_sink<details::console_mutex>; | |||||
using ansicolor_stdout_sink_st = ansicolor_stdout_sink<details::console_nullmutex>; | |||||
using ansicolor_stderr_sink_mt = ansicolor_stderr_sink<details::console_mutex>; | |||||
using ansicolor_stderr_sink_st = ansicolor_stderr_sink<details::console_nullmutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "ansicolor_sink-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
#include <memory> | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink() | |||||
: formatter_{details::make_unique<spdlog::pattern_formatter>()} | |||||
{} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink(std::unique_ptr<spdlog::formatter> formatter) | |||||
: formatter_{std::move(formatter)} | |||||
{} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg) | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
sink_it_(msg); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::flush() | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
flush_(); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
set_pattern_(pattern); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<Mutex> lock(mutex_); | |||||
set_formatter_(std::move(sink_formatter)); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern_(const std::string &pattern) | |||||
{ | |||||
set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||||
} | |||||
template<typename Mutex> | |||||
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
formatter_ = std::move(sink_formatter); | |||||
} |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
// | |||||
// base sink templated over a mutex (either dummy or real) | |||||
// concrete implementation should override the sink_it_() and flush_() methods. | |||||
// locking is taken care of in this class - no locking needed by the | |||||
// implementers.. | |||||
// | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/log_msg.h" | |||||
#include "spdlog/sinks/sink.h" | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class base_sink : public sink | |||||
{ | |||||
public: | |||||
base_sink(); | |||||
explicit base_sink(std::unique_ptr<spdlog::formatter> formatter); | |||||
base_sink(const base_sink &) = delete; | |||||
base_sink &operator=(const base_sink &) = delete; | |||||
void log(const details::log_msg &msg) final; | |||||
void flush() final; | |||||
void set_pattern(const std::string &pattern) final; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; | |||||
protected: | |||||
// sink formatter | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
Mutex mutex_; | |||||
virtual void sink_it_(const details::log_msg &msg) = 0; | |||||
virtual void flush_() = 0; | |||||
virtual void set_pattern_(const std::string &pattern); | |||||
virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter); | |||||
}; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "base_sink-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/basic_file_sink.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/os.h" | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE basic_file_sink<Mutex>::basic_file_sink(const filename_t &filename, bool truncate) | |||||
{ | |||||
file_helper_.open(filename, truncate); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE const filename_t &basic_file_sink<Mutex>::filename() const | |||||
{ | |||||
return file_helper_.filename(); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
file_helper_.write(formatted); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void basic_file_sink<Mutex>::flush_() | |||||
{ | |||||
file_helper_.flush(); | |||||
} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/file_helper.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Trivial file sink with single file as target | |||||
*/ | |||||
template<typename Mutex> | |||||
class basic_file_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit basic_file_sink(const filename_t &filename, bool truncate = false); | |||||
const filename_t &filename() const; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override; | |||||
void flush_() override; | |||||
private: | |||||
details::file_helper file_helper_; | |||||
}; | |||||
using basic_file_sink_mt = basic_file_sink<std::mutex>; | |||||
using basic_file_sink_st = basic_file_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// | |||||
// factory functions | |||||
// | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate); | |||||
} | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "basic_file_sink-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/file_helper.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/fmt/fmt.h" | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/os.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <ctime> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Generator of daily log file names in format basename.YYYY-MM-DD.ext | |||||
*/ | |||||
struct daily_filename_calculator | |||||
{ | |||||
// Create filename for the form basename.YYYY-MM-DD | |||||
static filename_t calc_filename(const filename_t &filename, const tm &now_tm) | |||||
{ | |||||
filename_t basename, ext; | |||||
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
return fmt::format( | |||||
SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); | |||||
} | |||||
}; | |||||
/* | |||||
* Rotating file sink based on date. | |||||
* If truncate != false , the created file will be truncated. | |||||
* If max_files > 0, retain only the last max_files and delete previous. | |||||
*/ | |||||
template<typename Mutex, typename FileNameCalc = daily_filename_calculator> | |||||
class daily_file_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
// create daily file sink which rotates on given time | |||||
daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0) | |||||
: base_filename_(std::move(base_filename)) | |||||
, rotation_h_(rotation_hour) | |||||
, rotation_m_(rotation_minute) | |||||
, truncate_(truncate) | |||||
, max_files_(max_files) | |||||
, filenames_q_() | |||||
{ | |||||
if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("daily_file_sink: Invalid rotation time in ctor")); | |||||
} | |||||
auto now = log_clock::now(); | |||||
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||||
file_helper_.open(filename, truncate_); | |||||
rotation_tp_ = next_rotation_tp_(); | |||||
if (max_files_ > 0) | |||||
{ | |||||
filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_)); | |||||
filenames_q_.push_back(std::move(filename)); | |||||
} | |||||
} | |||||
const filename_t &filename() const | |||||
{ | |||||
return file_helper_.filename(); | |||||
} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
#ifdef SPDLOG_NO_DATETIME | |||||
auto time = log_clock::now(); | |||||
#else | |||||
auto time = msg.time; | |||||
#endif | |||||
bool should_rotate = time >= rotation_tp_; | |||||
if (should_rotate) | |||||
{ | |||||
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); | |||||
file_helper_.open(filename, truncate_); | |||||
rotation_tp_ = next_rotation_tp_(); | |||||
} | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
file_helper_.write(formatted); | |||||
// Do the cleaning ony at the end because it might throw on failure. | |||||
if (should_rotate && max_files_ > 0) | |||||
{ | |||||
delete_old_(); | |||||
} | |||||
} | |||||
void flush_() override | |||||
{ | |||||
file_helper_.flush(); | |||||
} | |||||
private: | |||||
tm now_tm(log_clock::time_point tp) | |||||
{ | |||||
time_t tnow = log_clock::to_time_t(tp); | |||||
return spdlog::details::os::localtime(tnow); | |||||
} | |||||
log_clock::time_point next_rotation_tp_() | |||||
{ | |||||
auto now = log_clock::now(); | |||||
tm date = now_tm(now); | |||||
date.tm_hour = rotation_h_; | |||||
date.tm_min = rotation_m_; | |||||
date.tm_sec = 0; | |||||
auto rotation_time = log_clock::from_time_t(std::mktime(&date)); | |||||
if (rotation_time > now) | |||||
{ | |||||
return rotation_time; | |||||
} | |||||
return {rotation_time + std::chrono::hours(24)}; | |||||
} | |||||
// Delete the file N rotations ago. | |||||
// Throw spdlog_ex on failure to delete the old file. | |||||
void delete_old_() | |||||
{ | |||||
using details::os::filename_to_str; | |||||
using details::os::remove_if_exists; | |||||
filename_t current_file = filename(); | |||||
if (filenames_q_.full()) | |||||
{ | |||||
auto old_filename = std::move(filenames_q_.front()); | |||||
filenames_q_.pop_front(); | |||||
bool ok = remove_if_exists(old_filename) == 0; | |||||
if (!ok) | |||||
{ | |||||
filenames_q_.push_back(std::move(current_file)); | |||||
SPDLOG_THROW(spdlog_ex("Failed removing daily file " + filename_to_str(old_filename), errno)); | |||||
} | |||||
} | |||||
filenames_q_.push_back(std::move(current_file)); | |||||
} | |||||
filename_t base_filename_; | |||||
int rotation_h_; | |||||
int rotation_m_; | |||||
log_clock::time_point rotation_tp_; | |||||
details::file_helper file_helper_; | |||||
bool truncate_; | |||||
uint16_t max_files_; | |||||
details::circular_q<filename_t> filenames_q_; | |||||
}; | |||||
using daily_file_sink_mt = daily_file_sink<std::mutex>; | |||||
using daily_file_sink_st = daily_file_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// | |||||
// factory functions | |||||
// | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> daily_logger_mt( | |||||
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> daily_logger_st( | |||||
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) | |||||
{ | |||||
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "base_sink.h" | |||||
#include "spdlog/details/log_msg.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
#include <algorithm> | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <vector> | |||||
// Distribution sink (mux). Stores a vector of sinks which get called when log | |||||
// is called | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class dist_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
dist_sink() = default; | |||||
dist_sink(const dist_sink &) = delete; | |||||
dist_sink &operator=(const dist_sink &) = delete; | |||||
void add_sink(std::shared_ptr<sink> sink) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
sinks_.push_back(sink); | |||||
} | |||||
void remove_sink(std::shared_ptr<sink> sink) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); | |||||
} | |||||
void set_sinks(std::vector<std::shared_ptr<sink>> sinks) | |||||
{ | |||||
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
sinks_ = std::move(sinks); | |||||
} | |||||
std::vector<std::shared_ptr<sink>> &sinks() | |||||
{ | |||||
return sinks_; | |||||
} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
if (sink->should_log(msg.level)) | |||||
{ | |||||
sink->log(msg); | |||||
} | |||||
} | |||||
} | |||||
void flush_() override | |||||
{ | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
sink->flush(); | |||||
} | |||||
} | |||||
void set_pattern_(const std::string &pattern) override | |||||
{ | |||||
set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||||
} | |||||
void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override | |||||
{ | |||||
base_sink<Mutex>::formatter_ = std::move(sink_formatter); | |||||
for (auto &sink : sinks_) | |||||
{ | |||||
sink->set_formatter(base_sink<Mutex>::formatter_->clone()); | |||||
} | |||||
} | |||||
std::vector<std::shared_ptr<sink>> sinks_; | |||||
}; | |||||
using dist_sink_mt = dist_sink<std::mutex>; | |||||
using dist_sink_st = dist_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "dist_sink.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/details/log_msg.h" | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <chrono> | |||||
// Duplicate message removal sink. | |||||
// Skip the message if previous one is identical and less than "max_skip_duration" have passed | |||||
// | |||||
// Example: | |||||
// | |||||
// #include "spdlog/sinks/dup_filter_sink.h" | |||||
// | |||||
// int main() { | |||||
// auto dup_filter = std::make_shared<dup_filter_sink_st>(std::chrono::seconds(5)); | |||||
// dup_filter->add_sink(std::make_shared<stdout_color_sink_mt>()); | |||||
// spdlog::logger l("logger", dup_filter); | |||||
// l.info("Hello"); | |||||
// l.info("Hello"); | |||||
// l.info("Hello"); | |||||
// l.info("Different Hello"); | |||||
// } | |||||
// | |||||
// Will produce: | |||||
// [2019-06-25 17:50:56.511] [logger] [info] Hello | |||||
// [2019-06-25 17:50:56.512] [logger] [info] Skipped 3 duplicate messages.. | |||||
// [2019-06-25 17:50:56.512] [logger] [info] Different Hello | |||||
#ifdef SPDLOG_NO_DATETIME | |||||
#error "spdlog::sinks::dup_filter_sink: cannot work when SPDLOG_NO_DATETIME is defined" | |||||
#endif | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class dup_filter_sink : public dist_sink<Mutex> | |||||
{ | |||||
public: | |||||
template<class Rep, class Period> | |||||
explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration) | |||||
: max_skip_duration_{max_skip_duration} | |||||
{} | |||||
protected: | |||||
std::chrono::microseconds max_skip_duration_; | |||||
log_clock::time_point last_msg_time_; | |||||
std::string last_msg_payload_; | |||||
size_t skip_counter_ = 0; | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
bool filtered = filter_(msg); | |||||
if (!filtered) | |||||
{ | |||||
skip_counter_ += 1; | |||||
return; | |||||
} | |||||
// log the "skipped.." message | |||||
if (skip_counter_ > 0) | |||||
{ | |||||
memory_buf_t buf; | |||||
fmt::format_to(buf, "Skipped {} duplicate messages..", skip_counter_); | |||||
details::log_msg skipped_msg{msg.logger_name, msg.level, string_view_t{buf.data(), buf.size()}}; | |||||
dist_sink<Mutex>::sink_it_(skipped_msg); | |||||
} | |||||
// log current message | |||||
dist_sink<Mutex>::sink_it_(msg); | |||||
last_msg_time_ = msg.time; | |||||
skip_counter_ = 0; | |||||
last_msg_payload_.assign(msg.payload.data(), msg.payload.data() + msg.payload.size()); | |||||
} | |||||
// return whether the log msg should be displayed (true) or skipped (false) | |||||
bool filter_(const details::log_msg &msg) | |||||
{ | |||||
auto filter_duration = msg.time - last_msg_time_; | |||||
return (filter_duration > max_skip_duration_) || (msg.payload != last_msg_payload_); | |||||
} | |||||
}; | |||||
using dup_filter_sink_mt = dup_filter_sink<std::mutex>; | |||||
using dup_filter_sink_st = dup_filter_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2016 Alexander Dalshov. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#if defined(_WIN32) | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include <winbase.h> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* MSVC sink (logging using OutputDebugStringA) | |||||
*/ | |||||
template<typename Mutex> | |||||
class msvc_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit msvc_sink() {} | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
OutputDebugStringA(fmt::to_string(formatted).c_str()); | |||||
} | |||||
void flush_() override {} | |||||
}; | |||||
using msvc_sink_mt = msvc_sink<std::mutex>; | |||||
using msvc_sink_st = msvc_sink<details::null_mutex>; | |||||
using windebug_sink_mt = msvc_sink_mt; | |||||
using windebug_sink_st = msvc_sink_st; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <mutex> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class null_sink : public base_sink<Mutex> | |||||
{ | |||||
protected: | |||||
void sink_it_(const details::log_msg &) override {} | |||||
void flush_() override {} | |||||
}; | |||||
using null_sink_mt = null_sink<details::null_mutex>; | |||||
using null_sink_st = null_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name) | |||||
{ | |||||
auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name); | |||||
null_logger->set_level(level::off); | |||||
return null_logger; | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name) | |||||
{ | |||||
auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name); | |||||
null_logger->set_level(level::off); | |||||
return null_logger; | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include <mutex> | |||||
#include <ostream> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
class ostream_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
explicit ostream_sink(std::ostream &os, bool force_flush = false) | |||||
: ostream_(os) | |||||
, force_flush_(force_flush) | |||||
{} | |||||
ostream_sink(const ostream_sink &) = delete; | |||||
ostream_sink &operator=(const ostream_sink &) = delete; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); | |||||
if (force_flush_) | |||||
{ | |||||
ostream_.flush(); | |||||
} | |||||
} | |||||
void flush_() override | |||||
{ | |||||
ostream_.flush(); | |||||
} | |||||
std::ostream &ostream_; | |||||
bool force_flush_; | |||||
}; | |||||
using ostream_sink_mt = ostream_sink<std::mutex>; | |||||
using ostream_sink_st = ostream_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/rotating_file_sink.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/file_helper.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/fmt/fmt.h" | |||||
#include <cerrno> | |||||
#include <chrono> | |||||
#include <ctime> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <tuple> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink( | |||||
filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open) | |||||
: base_filename_(std::move(base_filename)) | |||||
, max_size_(max_size) | |||||
, max_files_(max_files) | |||||
{ | |||||
file_helper_.open(calc_filename(base_filename_, 0)); | |||||
current_size_ = file_helper_.size(); // expensive. called only once | |||||
if (rotate_on_open && current_size_ > 0) | |||||
{ | |||||
rotate_(); | |||||
} | |||||
} | |||||
// calc filename according to index and file extension if exists. | |||||
// e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename, std::size_t index) | |||||
{ | |||||
if (index == 0u) | |||||
{ | |||||
return filename; | |||||
} | |||||
filename_t basename, ext; | |||||
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
return fmt::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE const filename_t &rotating_file_sink<Mutex>::filename() const | |||||
{ | |||||
return file_helper_.filename(); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) | |||||
{ | |||||
memory_buf_t formatted; | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
current_size_ += formatted.size(); | |||||
if (current_size_ > max_size_) | |||||
{ | |||||
rotate_(); | |||||
current_size_ = formatted.size(); | |||||
} | |||||
file_helper_.write(formatted); | |||||
} | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void rotating_file_sink<Mutex>::flush_() | |||||
{ | |||||
file_helper_.flush(); | |||||
} | |||||
// Rotate files: | |||||
// log.txt -> log.1.txt | |||||
// log.1.txt -> log.2.txt | |||||
// log.2.txt -> log.3.txt | |||||
// log.3.txt -> delete | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_() | |||||
{ | |||||
using details::os::filename_to_str; | |||||
file_helper_.close(); | |||||
for (auto i = max_files_; i > 0; --i) | |||||
{ | |||||
filename_t src = calc_filename(base_filename_, i - 1); | |||||
if (!details::file_helper::file_exists(src)) | |||||
{ | |||||
continue; | |||||
} | |||||
filename_t target = calc_filename(base_filename_, i); | |||||
if (!rename_file(src, target)) | |||||
{ | |||||
// if failed try again after a small delay. | |||||
// this is a workaround to a windows issue, where very high rotation | |||||
// rates can cause the rename to fail with permission denied (because of antivirus?). | |||||
details::os::sleep_for_millis(100); | |||||
if (!rename_file(src, target)) | |||||
{ | |||||
file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! | |||||
current_size_ = 0; | |||||
SPDLOG_THROW( | |||||
spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno)); | |||||
} | |||||
} | |||||
} | |||||
file_helper_.reopen(true); | |||||
} | |||||
// delete the target if exists, and rename the src file to target | |||||
// return true on success, false otherwise. | |||||
template<typename Mutex> | |||||
SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file(const filename_t &src_filename, const filename_t &target_filename) | |||||
{ | |||||
// try to delete the target file in case it already exists. | |||||
(void)details::os::remove(target_filename); | |||||
return details::os::rename(src_filename, target_filename) == 0; | |||||
} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/file_helper.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <chrono> | |||||
#include <mutex> | |||||
#include <string> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
// | |||||
// Rotating file sink based on size | |||||
// | |||||
template<typename Mutex> | |||||
class rotating_file_sink final : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false); | |||||
static filename_t calc_filename(const filename_t &filename, std::size_t index); | |||||
const filename_t &filename() const; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override; | |||||
void flush_() override; | |||||
private: | |||||
// Rotate files: | |||||
// log.txt -> log.1.txt | |||||
// log.1.txt -> log.2.txt | |||||
// log.2.txt -> log.3.txt | |||||
// log.3.txt -> delete | |||||
void rotate_(); | |||||
// delete the target if exists, and rename the src file to target | |||||
// return true on success, false otherwise. | |||||
bool rename_file(const filename_t &src_filename, const filename_t &target_filename); | |||||
filename_t base_filename_; | |||||
std::size_t max_size_; | |||||
std::size_t max_files_; | |||||
std::size_t current_size_; | |||||
details::file_helper file_helper_; | |||||
}; | |||||
using rotating_file_sink_mt = rotating_file_sink<std::mutex>; | |||||
using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// | |||||
// factory functions | |||||
// | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> rotating_logger_mt( | |||||
const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false) | |||||
{ | |||||
return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files, rotate_on_open); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> rotating_logger_st( | |||||
const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false) | |||||
{ | |||||
return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files, rotate_on_open); | |||||
} | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "rotating_file_sink-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/sink.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
SPDLOG_INLINE bool spdlog::sinks::sink::should_log(spdlog::level::level_enum msg_level) const | |||||
{ | |||||
return msg_level >= level_.load(std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE void spdlog::sinks::sink::set_level(level::level_enum log_level) | |||||
{ | |||||
level_.store(log_level, std::memory_order_relaxed); | |||||
} | |||||
SPDLOG_INLINE spdlog::level::level_enum spdlog::sinks::sink::level() const | |||||
{ | |||||
return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); | |||||
} |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/log_msg.h" | |||||
#include "spdlog/formatter.h" | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
class sink | |||||
{ | |||||
public: | |||||
virtual ~sink() = default; | |||||
virtual void log(const details::log_msg &msg) = 0; | |||||
virtual void flush() = 0; | |||||
virtual void set_pattern(const std::string &pattern) = 0; | |||||
virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0; | |||||
void set_level(level::level_enum log_level); | |||||
level::level_enum level() const; | |||||
bool should_log(level::level_enum msg_level) const; | |||||
protected: | |||||
// sink log level - default is all | |||||
level_t level_{level::trace}; | |||||
}; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "sink-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/stdout_color_sinks.h" | |||||
#endif | |||||
#include "spdlog/logger.h" | |||||
#include "spdlog/common.h" | |||||
namespace spdlog { | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string &logger_name, color_mode mode) | |||||
{ | |||||
return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifdef _WIN32 | |||||
#include "spdlog/sinks/wincolor_sink.h" | |||||
#else | |||||
#include "spdlog/sinks/ansicolor_sink.h" | |||||
#endif | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
#ifdef _WIN32 | |||||
using stdout_color_sink_mt = wincolor_stdout_sink_mt; | |||||
using stdout_color_sink_st = wincolor_stdout_sink_st; | |||||
using stderr_color_sink_mt = wincolor_stderr_sink_mt; | |||||
using stderr_color_sink_st = wincolor_stderr_sink_st; | |||||
#else | |||||
using stdout_color_sink_mt = ansicolor_stdout_sink_mt; | |||||
using stdout_color_sink_st = ansicolor_stdout_sink_st; | |||||
using stderr_color_sink_mt = ansicolor_stderr_sink_mt; | |||||
using stderr_color_sink_st = ansicolor_stderr_sink_st; | |||||
#endif | |||||
} // namespace sinks | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_color_st(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "stdout_color_sinks-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/stdout_sinks.h" | |||||
#endif | |||||
#include "spdlog/details/console_globals.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
#include <memory> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file) | |||||
: mutex_(ConsoleMutex::mutex()) | |||||
, file_(file) | |||||
, formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
{} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
memory_buf_t formatted; | |||||
formatter_->format(msg, formatted); | |||||
fwrite(formatted.data(), sizeof(char), formatted.size(), file_); | |||||
fflush(file_); // flush every line to terminal | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::flush() | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
fflush(file_); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::move(sink_formatter); | |||||
} | |||||
// stdout sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE stdout_sink<ConsoleMutex>::stdout_sink() | |||||
: stdout_sink_base<ConsoleMutex>(stdout) | |||||
{} | |||||
// stderr sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE stderr_sink<ConsoleMutex>::stderr_sink() | |||||
: stdout_sink_base<ConsoleMutex>(stderr) | |||||
{} | |||||
} // namespace sinks | |||||
// factory methods | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stdout_sink_mt>(logger_name); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stdout_sink_st>(logger_name); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stderr_sink_mt>(logger_name); | |||||
} | |||||
template<typename Factory> | |||||
SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::stderr_sink_st>(logger_name); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/details/console_globals.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include "spdlog/sinks/sink.h" | |||||
#include <cstdio> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
class stdout_sink_base : public sink | |||||
{ | |||||
public: | |||||
using mutex_t = typename ConsoleMutex::mutex_t; | |||||
explicit stdout_sink_base(FILE *file); | |||||
~stdout_sink_base() override = default; | |||||
stdout_sink_base(const stdout_sink_base &other) = delete; | |||||
stdout_sink_base &operator=(const stdout_sink_base &other) = delete; | |||||
void log(const details::log_msg &msg) override; | |||||
void flush() override; | |||||
void set_pattern(const std::string &pattern) override; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; | |||||
protected: | |||||
mutex_t &mutex_; | |||||
FILE *file_; | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class stdout_sink : public stdout_sink_base<ConsoleMutex> | |||||
{ | |||||
public: | |||||
stdout_sink(); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class stderr_sink : public stdout_sink_base<ConsoleMutex> | |||||
{ | |||||
public: | |||||
stderr_sink(); | |||||
}; | |||||
using stdout_sink_mt = stdout_sink<details::console_mutex>; | |||||
using stdout_sink_st = stdout_sink<details::console_nullmutex>; | |||||
using stderr_sink_mt = stderr_sink<details::console_mutex>; | |||||
using stderr_sink_st = stderr_sink<details::console_nullmutex>; | |||||
} // namespace sinks | |||||
// factory methods | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name); | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name); | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "stdout_sinks-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include <array> | |||||
#include <string> | |||||
#include <syslog.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/** | |||||
* Sink that write to syslog using the `syscall()` library call. | |||||
*/ | |||||
template<typename Mutex> | |||||
class syslog_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
syslog_sink(std::string ident, int syslog_option, int syslog_facility, bool enable_formatting) | |||||
: enable_formatting_{enable_formatting} | |||||
, syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, | |||||
/* spdlog::level::debug */ LOG_DEBUG, | |||||
/* spdlog::level::info */ LOG_INFO, | |||||
/* spdlog::level::warn */ LOG_WARNING, | |||||
/* spdlog::level::err */ LOG_ERR, | |||||
/* spdlog::level::critical */ LOG_CRIT, | |||||
/* spdlog::level::off */ LOG_INFO}} | |||||
, ident_{std::move(ident)} | |||||
{ | |||||
// set ident to be program name if empty | |||||
::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); | |||||
} | |||||
~syslog_sink() override | |||||
{ | |||||
::closelog(); | |||||
} | |||||
syslog_sink(const syslog_sink &) = delete; | |||||
syslog_sink &operator=(const syslog_sink &) = delete; | |||||
protected: | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
string_view_t payload; | |||||
memory_buf_t formatted; | |||||
if (enable_formatting_) | |||||
{ | |||||
base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
payload = string_view_t(formatted.data(), formatted.size()); | |||||
} | |||||
else | |||||
{ | |||||
payload = msg.payload; | |||||
} | |||||
size_t length = payload.size(); | |||||
// limit to max int | |||||
if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
{ | |||||
length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||||
} | |||||
::syslog(syslog_prio_from_level(msg), "%.*s", static_cast<int>(length), payload.data()); | |||||
} | |||||
void flush_() override {} | |||||
bool enable_formatting_ = false; | |||||
private: | |||||
using levels_array = std::array<int, 7>; | |||||
levels_array syslog_levels_; | |||||
// must store the ident because the man says openlog might use the pointer as | |||||
// is and not a string copy | |||||
const std::string ident_; | |||||
// | |||||
// Simply maps spdlog's log level to syslog priority level. | |||||
// | |||||
int syslog_prio_from_level(const details::log_msg &msg) const | |||||
{ | |||||
return syslog_levels_.at(static_cast<levels_array::size_type>(msg.level)); | |||||
} | |||||
}; | |||||
using syslog_sink_mt = syslog_sink<std::mutex>; | |||||
using syslog_sink_st = syslog_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// Create and register a syslog logger | |||||
template<typename Factory = default_factory> | |||||
inline std::shared_ptr<logger> syslog_logger_mt(const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, | |||||
int syslog_facility = LOG_USER, bool enable_formatting = false) | |||||
{ | |||||
return Factory::template create<sinks::syslog_sink_mt>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||||
} | |||||
template<typename Factory = default_factory> | |||||
inline std::shared_ptr<logger> syslog_logger_st(const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, | |||||
int syslog_facility = LOG_USER, bool enable_formatting = false) | |||||
{ | |||||
return Factory::template create<sinks::syslog_sink_st>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2019 ZVYAGIN.Alexander@gmail.com | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/sinks/base_sink.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <systemd/sd-journal.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/** | |||||
* Sink that write to systemd journal using the `sd_journal_send()` library call. | |||||
* | |||||
* Locking is not needed, as `sd_journal_send()` itself is thread-safe. | |||||
*/ | |||||
template<typename Mutex> | |||||
class systemd_sink : public base_sink<Mutex> | |||||
{ | |||||
public: | |||||
// | |||||
systemd_sink() | |||||
: syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, | |||||
/* spdlog::level::debug */ LOG_DEBUG, | |||||
/* spdlog::level::info */ LOG_INFO, | |||||
/* spdlog::level::warn */ LOG_WARNING, | |||||
/* spdlog::level::err */ LOG_ERR, | |||||
/* spdlog::level::critical */ LOG_CRIT, | |||||
/* spdlog::level::off */ LOG_INFO}} | |||||
{} | |||||
~systemd_sink() override {} | |||||
systemd_sink(const systemd_sink &) = delete; | |||||
systemd_sink &operator=(const systemd_sink &) = delete; | |||||
protected: | |||||
using levels_array = std::array<int, 7>; | |||||
levels_array syslog_levels_; | |||||
void sink_it_(const details::log_msg &msg) override | |||||
{ | |||||
int err; | |||||
size_t length = msg.payload.size(); | |||||
// limit to max int | |||||
if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
{ | |||||
length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||||
} | |||||
// Do not send source location if not available | |||||
if (msg.source.empty()) | |||||
{ | |||||
// Note: function call inside '()' to avoid macro expansion | |||||
err = (sd_journal_send)( | |||||
"MESSAGE=%.*s", static_cast<int>(length), msg.payload.data(), "PRIORITY=%d", syslog_level(msg.level), nullptr); | |||||
} | |||||
else | |||||
{ | |||||
err = (sd_journal_send)("MESSAGE=%.*s", static_cast<int>(length), msg.payload.data(), "PRIORITY=%d", syslog_level(msg.level), | |||||
"SOURCE_FILE=%s", msg.source.filename, "SOURCE_LINE=%d", msg.source.line, "SOURCE_FUNC=%s", msg.source.funcname, nullptr); | |||||
} | |||||
if (err) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("Failed writing to systemd", errno)); | |||||
} | |||||
} | |||||
int syslog_level(level::level_enum l) | |||||
{ | |||||
return syslog_levels_.at(static_cast<levels_array::size_type>(l)); | |||||
} | |||||
void flush_() override {} | |||||
}; | |||||
using systemd_sink_mt = systemd_sink<std::mutex>; | |||||
using systemd_sink_st = systemd_sink<details::null_mutex>; | |||||
} // namespace sinks | |||||
// Create and register a syslog logger | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> systemd_logger_mt(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::systemd_sink_mt>(logger_name); | |||||
} | |||||
template<typename Factory = spdlog::synchronous_factory> | |||||
inline std::shared_ptr<logger> systemd_logger_st(const std::string &logger_name) | |||||
{ | |||||
return Factory::template create<sinks::systemd_sink_st>(logger_name); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/sinks/wincolor_sink.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(HANDLE out_handle, color_mode mode) | |||||
: out_handle_(out_handle) | |||||
, mutex_(ConsoleMutex::mutex()) | |||||
, formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
{ | |||||
// check if out_handle is points to the actual console. | |||||
// ::GetConsoleMode() should return 0 if it is redirected or not valid console handle. | |||||
DWORD console_mode; | |||||
in_console_ = ::GetConsoleMode(out_handle, &console_mode) != 0; | |||||
set_color_mode(mode); | |||||
colors_[level::trace] = WHITE; | |||||
colors_[level::debug] = CYAN; | |||||
colors_[level::info] = GREEN; | |||||
colors_[level::warn] = YELLOW | BOLD; | |||||
colors_[level::err] = RED | BOLD; // red bold | |||||
colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background | |||||
colors_[level::off] = 0; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::~wincolor_sink() | |||||
{ | |||||
this->flush(); | |||||
} | |||||
// change the color for the given level | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, WORD color) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
colors_[level] = color; | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
memory_buf_t formatted; | |||||
formatter_->format(msg, formatted); | |||||
if (!in_console_) | |||||
{ | |||||
write_to_file_(formatted); | |||||
return; | |||||
} | |||||
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) | |||||
{ | |||||
// before color range | |||||
print_range_(formatted, 0, msg.color_range_start); | |||||
// in color range | |||||
auto orig_attribs = set_foreground_color_(colors_[msg.level]); | |||||
print_range_(formatted, msg.color_range_start, msg.color_range_end); | |||||
// reset to orig colors | |||||
::SetConsoleTextAttribute(out_handle_, orig_attribs); | |||||
print_range_(formatted, msg.color_range_end, formatted.size()); | |||||
} | |||||
else // print without colors if color range is invalid (or color is disabled) | |||||
{ | |||||
print_range_(formatted, 0, formatted.size()); | |||||
} | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::flush() | |||||
{ | |||||
// windows console always flushed? | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_pattern(const std::string &pattern) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
{ | |||||
std::lock_guard<mutex_t> lock(mutex_); | |||||
formatter_ = std::move(sink_formatter); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) | |||||
{ | |||||
switch (mode) | |||||
{ | |||||
case color_mode::always: | |||||
case color_mode::automatic: | |||||
should_do_colors_ = true; | |||||
break; | |||||
case color_mode::never: | |||||
should_do_colors_ = false; | |||||
break; | |||||
default: | |||||
should_do_colors_ = true; | |||||
} | |||||
} | |||||
// set foreground color and return the orig console attributes (for resetting later) | |||||
template<typename ConsoleMutex> | |||||
WORD SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(WORD attribs) | |||||
{ | |||||
CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; | |||||
::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); | |||||
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 | |||||
::SetConsoleTextAttribute(out_handle_, attribs | back_color); | |||||
return orig_buffer_info.wAttributes; // return orig attribs | |||||
} | |||||
// print a range of formatted message to console | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) | |||||
{ | |||||
auto size = static_cast<DWORD>(end - start); | |||||
::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); | |||||
} | |||||
template<typename ConsoleMutex> | |||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::write_to_file_(const memory_buf_t &formatted) | |||||
{ | |||||
if (out_handle_ == nullptr) // no console and no file redirect | |||||
{ | |||||
return; | |||||
} | |||||
auto size = static_cast<DWORD>(formatted.size()); | |||||
if (size == 0) | |||||
{ | |||||
return; | |||||
} | |||||
DWORD total_written = 0; | |||||
do | |||||
{ | |||||
DWORD bytes_written = 0; | |||||
bool ok = ::WriteFile(out_handle_, formatted.data() + total_written, size - total_written, &bytes_written, nullptr) != 0; | |||||
if (!ok || bytes_written == 0) | |||||
{ | |||||
SPDLOG_THROW(spdlog_ex("wincolor_sink: write_to_file_ failed. GetLastError(): " + std::to_string(::GetLastError()))); | |||||
} | |||||
total_written += bytes_written; | |||||
} while (total_written < size); | |||||
} | |||||
// wincolor_stdout_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_stdout_sink<ConsoleMutex>::wincolor_stdout_sink(color_mode mode) | |||||
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_OUTPUT_HANDLE), mode) | |||||
{} | |||||
// wincolor_stderr_sink | |||||
template<typename ConsoleMutex> | |||||
SPDLOG_INLINE wincolor_stderr_sink<ConsoleMutex>::wincolor_stderr_sink(color_mode mode) | |||||
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode) | |||||
{} | |||||
} // namespace sinks | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/console_globals.h" | |||||
#include "spdlog/details/null_mutex.h" | |||||
#include "spdlog/sinks/sink.h" | |||||
#include <memory> | |||||
#include <mutex> | |||||
#include <string> | |||||
#include <unordered_map> | |||||
#include <wincon.h> | |||||
namespace spdlog { | |||||
namespace sinks { | |||||
/* | |||||
* Windows color console sink. Uses WriteConsoleA to write to the console with | |||||
* colors | |||||
*/ | |||||
template<typename ConsoleMutex> | |||||
class wincolor_sink : public sink | |||||
{ | |||||
public: | |||||
const WORD BOLD = FOREGROUND_INTENSITY; | |||||
const WORD RED = FOREGROUND_RED; | |||||
const WORD GREEN = FOREGROUND_GREEN; | |||||
const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; | |||||
const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; | |||||
const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; | |||||
wincolor_sink(HANDLE out_handle, color_mode mode); | |||||
~wincolor_sink() override; | |||||
wincolor_sink(const wincolor_sink &other) = delete; | |||||
wincolor_sink &operator=(const wincolor_sink &other) = delete; | |||||
// change the color for the given level | |||||
void set_color(level::level_enum level, WORD color); | |||||
void log(const details::log_msg &msg) final override; | |||||
void flush() final override; | |||||
void set_pattern(const std::string &pattern) override final; | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final; | |||||
void set_color_mode(color_mode mode); | |||||
protected: | |||||
using mutex_t = typename ConsoleMutex::mutex_t; | |||||
HANDLE out_handle_; | |||||
mutex_t &mutex_; | |||||
bool in_console_; | |||||
bool should_do_colors_; | |||||
std::unique_ptr<spdlog::formatter> formatter_; | |||||
std::unordered_map<level::level_enum, WORD, level::level_hasher> colors_; | |||||
// set foreground color and return the orig console attributes (for resetting later) | |||||
WORD set_foreground_color_(WORD attribs); | |||||
// print a range of formatted message to console | |||||
void print_range_(const memory_buf_t &formatted, size_t start, size_t end); | |||||
// in case we are redirected to file (not in console mode) | |||||
void write_to_file_(const memory_buf_t &formatted); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class wincolor_stdout_sink : public wincolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit wincolor_stdout_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
template<typename ConsoleMutex> | |||||
class wincolor_stderr_sink : public wincolor_sink<ConsoleMutex> | |||||
{ | |||||
public: | |||||
explicit wincolor_stderr_sink(color_mode mode = color_mode::automatic); | |||||
}; | |||||
using wincolor_stdout_sink_mt = wincolor_stdout_sink<details::console_mutex>; | |||||
using wincolor_stdout_sink_st = wincolor_stdout_sink<details::console_nullmutex>; | |||||
using wincolor_stderr_sink_mt = wincolor_stderr_sink<details::console_mutex>; | |||||
using wincolor_stderr_sink_st = wincolor_stderr_sink<details::console_nullmutex>; | |||||
} // namespace sinks | |||||
} // namespace spdlog | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "wincolor_sink-inl.h" | |||||
#endif |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#ifndef SPDLOG_HEADER_ONLY | |||||
#include "spdlog/spdlog.h" | |||||
#endif | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/pattern_formatter.h" | |||||
namespace spdlog { | |||||
SPDLOG_INLINE void initialize_logger(std::shared_ptr<logger> logger) | |||||
{ | |||||
details::registry::instance().initialize_logger(std::move(logger)); | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<logger> get(const std::string &name) | |||||
{ | |||||
return details::registry::instance().get(name); | |||||
} | |||||
SPDLOG_INLINE void set_formatter(std::unique_ptr<spdlog::formatter> formatter) | |||||
{ | |||||
details::registry::instance().set_formatter(std::move(formatter)); | |||||
} | |||||
SPDLOG_INLINE void set_pattern(std::string pattern, pattern_time_type time_type) | |||||
{ | |||||
set_formatter(std::unique_ptr<spdlog::formatter>(new pattern_formatter(std::move(pattern), time_type))); | |||||
} | |||||
SPDLOG_INLINE void enable_backtrace(size_t n_messages) | |||||
{ | |||||
details::registry::instance().enable_backtrace(n_messages); | |||||
} | |||||
SPDLOG_INLINE void disable_backtrace() | |||||
{ | |||||
details::registry::instance().disable_backtrace(); | |||||
} | |||||
SPDLOG_INLINE void dump_backtrace() | |||||
{ | |||||
default_logger_raw()->dump_backtrace(); | |||||
} | |||||
SPDLOG_INLINE void set_level(level::level_enum log_level) | |||||
{ | |||||
details::registry::instance().set_level(log_level); | |||||
} | |||||
SPDLOG_INLINE void flush_on(level::level_enum log_level) | |||||
{ | |||||
details::registry::instance().flush_on(log_level); | |||||
} | |||||
SPDLOG_INLINE void flush_every(std::chrono::seconds interval) | |||||
{ | |||||
details::registry::instance().flush_every(interval); | |||||
} | |||||
SPDLOG_INLINE void set_error_handler(void (*handler)(const std::string &msg)) | |||||
{ | |||||
details::registry::instance().set_error_handler(handler); | |||||
} | |||||
SPDLOG_INLINE void register_logger(std::shared_ptr<logger> logger) | |||||
{ | |||||
details::registry::instance().register_logger(std::move(logger)); | |||||
} | |||||
SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) | |||||
{ | |||||
details::registry::instance().apply_all(fun); | |||||
} | |||||
SPDLOG_INLINE void drop(const std::string &name) | |||||
{ | |||||
details::registry::instance().drop(name); | |||||
} | |||||
SPDLOG_INLINE void drop_all() | |||||
{ | |||||
details::registry::instance().drop_all(); | |||||
} | |||||
SPDLOG_INLINE void shutdown() | |||||
{ | |||||
details::registry::instance().shutdown(); | |||||
} | |||||
SPDLOG_INLINE void set_automatic_registration(bool automatic_registation) | |||||
{ | |||||
details::registry::instance().set_automatic_registration(automatic_registation); | |||||
} | |||||
SPDLOG_INLINE std::shared_ptr<spdlog::logger> default_logger() | |||||
{ | |||||
return details::registry::instance().default_logger(); | |||||
} | |||||
SPDLOG_INLINE spdlog::logger *default_logger_raw() | |||||
{ | |||||
return details::registry::instance().get_default_raw(); | |||||
} | |||||
SPDLOG_INLINE void set_default_logger(std::shared_ptr<spdlog::logger> default_logger) | |||||
{ | |||||
details::registry::instance().set_default_logger(std::move(default_logger)); | |||||
} | |||||
} // namespace spdlog |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
// spdlog main header file. | |||||
// see example.cpp for usage example | |||||
#ifndef SPDLOG_H | |||||
#define SPDLOG_H | |||||
#pragma once | |||||
#include "spdlog/common.h" | |||||
#include "spdlog/details/registry.h" | |||||
#include "spdlog/logger.h" | |||||
#include "spdlog/version.h" | |||||
#include "spdlog/details/synchronous_factory.h" | |||||
#include <chrono> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
namespace spdlog { | |||||
using default_factory = synchronous_factory; | |||||
// Create and register a logger with a templated sink type | |||||
// The logger's level, formatter and flush level will be set according the | |||||
// global settings. | |||||
// | |||||
// Example: | |||||
// spdlog::create<daily_file_sink_st>("logger_name", "dailylog_filename", 11, 59); | |||||
template<typename Sink, typename... SinkArgs> | |||||
inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... sink_args) | |||||
{ | |||||
return default_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
} | |||||
// Initialize and register a logger, | |||||
// formatter and flush level will be set according the global settings. | |||||
// | |||||
// NOTE: | |||||
// Use this function when creating loggers manually. | |||||
// | |||||
// Example: | |||||
// auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>(); | |||||
// auto console_logger = std::make_shared<spdlog::logger>("console_logger", console_sink); | |||||
// spdlog::initialize_logger(console_logger); | |||||
void initialize_logger(std::shared_ptr<logger> logger); | |||||
// Return an existing logger or nullptr if a logger with such name doesn't | |||||
// exist. | |||||
// example: spdlog::get("my_logger")->info("hello {}", "world"); | |||||
std::shared_ptr<logger> get(const std::string &name); | |||||
// Set global formatter. Each sink in each logger will get a clone of this object | |||||
void set_formatter(std::unique_ptr<spdlog::formatter> formatter); | |||||
// Set global format string. | |||||
// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); | |||||
void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); | |||||
// enable global backtrace support | |||||
void enable_backtrace(size_t n_messages); | |||||
// disable global backtrace support | |||||
void disable_backtrace(); | |||||
// call dump backtrace on default logger | |||||
void dump_backtrace(); | |||||
// Set global logging level | |||||
void set_level(level::level_enum log_level); | |||||
// Set global flush level | |||||
void flush_on(level::level_enum log_level); | |||||
// Start/Restart a periodic flusher thread | |||||
// Warning: Use only if all your loggers are thread safe! | |||||
void flush_every(std::chrono::seconds interval); | |||||
// Set global error handler | |||||
void set_error_handler(void (*handler)(const std::string &msg)); | |||||
// Register the given logger with the given name | |||||
void register_logger(std::shared_ptr<logger> logger); | |||||
// Apply a user defined function on all registered loggers | |||||
// Example: | |||||
// spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); | |||||
void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun); | |||||
// Drop the reference to the given logger | |||||
void drop(const std::string &name); | |||||
// Drop all references from the registry | |||||
void drop_all(); | |||||
// stop any running threads started by spdlog and clean registry loggers | |||||
void shutdown(); | |||||
// Automatic registration of loggers when using spdlog::create() or spdlog::create_async | |||||
void set_automatic_registration(bool automatic_registation); | |||||
// API for using default logger (stdout_color_mt), | |||||
// e.g: spdlog::info("Message {}", 1); | |||||
// | |||||
// The default logger object can be accessed using the spdlog::default_logger(): | |||||
// For example, to add another sink to it: | |||||
// spdlog::default_logger()->sinks()->push_back(some_sink); | |||||
// | |||||
// The default logger can replaced using spdlog::set_default_logger(new_logger). | |||||
// For example, to replace it with a file logger. | |||||
// | |||||
// IMPORTANT: | |||||
// The default API is thread safe (for _mt loggers), but: | |||||
// set_default_logger() *should not* be used concurrently with the default API. | |||||
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
std::shared_ptr<spdlog::logger> default_logger(); | |||||
spdlog::logger *default_logger_raw(); | |||||
void set_default_logger(std::shared_ptr<spdlog::logger> default_logger); | |||||
template<typename... Args> | |||||
inline void log(source_loc source, level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(source, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void log(level::level_enum lvl, string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(source_loc{}, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void trace(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->trace(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void debug(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->debug(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void info(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->info(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void warn(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->warn(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void error(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->error(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void critical(string_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->critical(fmt, args...); | |||||
} | |||||
template<typename T> | |||||
inline void log(source_loc source, level::level_enum lvl, const T &msg) | |||||
{ | |||||
default_logger_raw()->log(source, lvl, msg); | |||||
} | |||||
template<typename T> | |||||
inline void log(level::level_enum lvl, const T &msg) | |||||
{ | |||||
default_logger_raw()->log(lvl, msg); | |||||
} | |||||
template<typename T> | |||||
inline void trace(const T &msg) | |||||
{ | |||||
default_logger_raw()->trace(msg); | |||||
} | |||||
template<typename T> | |||||
inline void debug(const T &msg) | |||||
{ | |||||
default_logger_raw()->debug(msg); | |||||
} | |||||
template<typename T> | |||||
inline void info(const T &msg) | |||||
{ | |||||
default_logger_raw()->info(msg); | |||||
} | |||||
template<typename T> | |||||
inline void warn(const T &msg) | |||||
{ | |||||
default_logger_raw()->warn(msg); | |||||
} | |||||
template<typename T> | |||||
inline void error(const T &msg) | |||||
{ | |||||
default_logger_raw()->error(msg); | |||||
} | |||||
template<typename T> | |||||
inline void critical(const T &msg) | |||||
{ | |||||
default_logger_raw()->critical(msg); | |||||
} | |||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
template<typename... Args> | |||||
inline void log(source_loc source, level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(source, lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->log(lvl, fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void trace(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->trace(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void debug(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->debug(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void info(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->info(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void warn(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->warn(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void error(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->error(fmt, args...); | |||||
} | |||||
template<typename... Args> | |||||
inline void critical(wstring_view_t fmt, const Args &... args) | |||||
{ | |||||
default_logger_raw()->critical(fmt, args...); | |||||
} | |||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
} // namespace spdlog | |||||
// | |||||
// enable/disable log calls at compile time according to global level. | |||||
// | |||||
// define SPDLOG_ACTIVE_LEVEL to one of those (before including spdlog.h): | |||||
// SPDLOG_LEVEL_TRACE, | |||||
// SPDLOG_LEVEL_DEBUG, | |||||
// SPDLOG_LEVEL_INFO, | |||||
// SPDLOG_LEVEL_WARN, | |||||
// SPDLOG_LEVEL_ERROR, | |||||
// SPDLOG_LEVEL_CRITICAL, | |||||
// SPDLOG_LEVEL_OFF | |||||
// | |||||
#define SPDLOG_LOGGER_CALL(logger, level, ...) logger->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__) | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE | |||||
#define SPDLOG_LOGGER_TRACE(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__) | |||||
#define SPDLOG_TRACE(...) SPDLOG_LOGGER_TRACE(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_TRACE(logger, ...) (void)0 | |||||
#define SPDLOG_TRACE(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG | |||||
#define SPDLOG_LOGGER_DEBUG(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__) | |||||
#define SPDLOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_DEBUG(logger, ...) (void)0 | |||||
#define SPDLOG_DEBUG(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO | |||||
#define SPDLOG_LOGGER_INFO(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__) | |||||
#define SPDLOG_INFO(...) SPDLOG_LOGGER_INFO(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_INFO(logger, ...) (void)0 | |||||
#define SPDLOG_INFO(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_WARN | |||||
#define SPDLOG_LOGGER_WARN(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::warn, __VA_ARGS__) | |||||
#define SPDLOG_WARN(...) SPDLOG_LOGGER_WARN(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_WARN(logger, ...) (void)0 | |||||
#define SPDLOG_WARN(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_ERROR | |||||
#define SPDLOG_LOGGER_ERROR(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::err, __VA_ARGS__) | |||||
#define SPDLOG_ERROR(...) SPDLOG_LOGGER_ERROR(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_ERROR(logger, ...) (void)0 | |||||
#define SPDLOG_ERROR(...) (void)0 | |||||
#endif | |||||
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL | |||||
#define SPDLOG_LOGGER_CRITICAL(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::critical, __VA_ARGS__) | |||||
#define SPDLOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(spdlog::default_logger_raw(), __VA_ARGS__) | |||||
#else | |||||
#define SPDLOG_LOGGER_CRITICAL(logger, ...) (void)0 | |||||
#define SPDLOG_CRITICAL(...) (void)0 | |||||
#endif | |||||
#ifdef SPDLOG_HEADER_ONLY | |||||
#include "spdlog-inl.h" | |||||
#endif | |||||
#endif // SPDLOG_H |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// | |||||
// Edit this file to squeeze more performance, and to customize supported | |||||
// features | |||||
// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. | |||||
// This clock is less accurate - can be off by dozens of millis - depending on | |||||
// the kernel HZ. | |||||
// Uncomment to use it instead of the regular clock. | |||||
// | |||||
// #define SPDLOG_CLOCK_COARSE | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment if date/time logging is not needed and never appear in the log | |||||
// pattern. | |||||
// This will prevent spdlog from querying the clock on each log call. | |||||
// | |||||
// WARNING: If the log pattern contains any date/time while this flag is on, the | |||||
// result is undefined. | |||||
// You must set new pattern(spdlog::set_pattern(..") without any | |||||
// date/time in it | |||||
// | |||||
// #define SPDLOG_NO_DATETIME | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). | |||||
// This will prevent spdlog from querying the thread id on each log call. | |||||
// | |||||
// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is | |||||
// on, the result is undefined. | |||||
// | |||||
// #define SPDLOG_NO_THREAD_ID | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to prevent spdlog from using thread local storage. | |||||
// | |||||
// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined | |||||
// thread ids in the children logs. | |||||
// | |||||
// #define SPDLOG_NO_TLS | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment if logger name logging is not needed. | |||||
// This will prevent spdlog from copying the logger name on each log call. | |||||
// | |||||
// #define SPDLOG_NO_NAME | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to avoid spdlog's usage of atomic log levels | |||||
// Use only if your code never modifies a logger's log levels concurrently by | |||||
// different threads. | |||||
// | |||||
// #define SPDLOG_NO_ATOMIC_LEVELS | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to enable usage of wchar_t for file names on Windows. | |||||
// | |||||
// #define SPDLOG_WCHAR_FILENAMES | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) | |||||
// | |||||
// #define SPDLOG_EOL ";-)\n" | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to use your own copy of the fmt library instead of spdlog's copy. | |||||
// In this case spdlog will try to include <fmt/format.h> so set your -I flag | |||||
// accordingly. | |||||
// | |||||
// #define SPDLOG_FMT_EXTERNAL | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to enable wchar_t support (convert to utf8) | |||||
// | |||||
// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to prevent child processes from inheriting log file descriptors | |||||
// | |||||
// #define SPDLOG_PREVENT_CHILD_FD | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to customize level names (e.g. "MT TRACE") | |||||
// | |||||
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", | |||||
// "MY ERROR", "MY CRITICAL", "OFF" } | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to customize short level names (e.g. "MT") | |||||
// These can be longer than one character. | |||||
// | |||||
// #define SPDLOG_SHORT_LEVEL_NAMES { "T", "D", "I", "W", "E", "C", "O" } | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment to disable default logger creation. | |||||
// This might save some (very) small initialization time if no default logger is needed. | |||||
// | |||||
// #define SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment and set to compile time level with zero cost (default is INFO). | |||||
// Macros like SPDLOG_DEBUG(..), SPDLOG_INFO(..) will expand to empty statements if not enabled | |||||
// | |||||
// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
/////////////////////////////////////////////////////////////////////////////// | |||||
// Uncomment (and change if desired) macro to use for function names. | |||||
// This is compiler dependent. | |||||
// __PRETTY_FUNCTION__ might be nicer in clang/gcc, and __FUNCTION__ in msvc. | |||||
// Defaults to __FUNCTION__ (should work on all compilers) if not defined. | |||||
// | |||||
// #define SPDLOG_FUNCTION __PRETTY_FUNCTION__ | |||||
/////////////////////////////////////////////////////////////////////////////// |
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
#pragma once | |||||
#define SPDLOG_VER_MAJOR 1 | |||||
#define SPDLOG_VER_MINOR 4 | |||||
#define SPDLOG_VER_PATCH 2 | |||||
#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) |
Private-Include: external/spdlog/include |
imports: | |||||
spdlog: external/spdlog | |||||
taywee-args: external/taywee-args | |||||
git module spdlog: | |||||
url: https://github.com/gabime/spdlog.git | |||||
rev: v1.4.2 | |||||
pick: include | |||||
git module taywee-args: | |||||
url: https://github.com/Taywee/args.git | |||||
rev: 6.2.2 | |||||
move: | |||||
args.hxx: include/args.hxx | |||||
pick: include |
#include "./build.hpp" | |||||
#include <dds/logging.hpp> | |||||
#include <dds/proc.hpp> | |||||
#include <dds/toolchain.hpp> | |||||
#include <algorithm> | |||||
#include <iomanip> | |||||
#include <iostream> | |||||
#include <stdexcept> | |||||
using namespace dds; | |||||
namespace { | |||||
struct compile_failure : std::runtime_error { | |||||
using runtime_error::runtime_error; | |||||
}; | |||||
struct archive_failure : std::runtime_error { | |||||
using runtime_error::runtime_error; | |||||
}; | |||||
struct source_files { | |||||
std::vector<fs::path> headers; | |||||
std::vector<fs::path> sources; | |||||
}; | |||||
void collect_sources(source_files& sf, const fs::path& source_dir) { | |||||
static std::vector<std::string_view> header_exts = { | |||||
".h", | |||||
".H", | |||||
".H++", | |||||
".h++", | |||||
".hh", | |||||
".hpp", | |||||
".hxx", | |||||
".inl", | |||||
}; | |||||
static std::vector<std::string_view> source_exts = { | |||||
".C", | |||||
".c", | |||||
".c++", | |||||
".cc", | |||||
".cpp", | |||||
".cxx", | |||||
}; | |||||
static auto is_header = [&](const fs::path& p) { | |||||
auto found = std::lower_bound(header_exts.begin(), | |||||
header_exts.end(), | |||||
p.extension(), | |||||
std::less<>()); | |||||
return found != header_exts.end() && *found == p.extension(); | |||||
}; | |||||
static auto is_source = [&](const fs::path& p) { | |||||
auto found = std::lower_bound(source_exts.begin(), | |||||
source_exts.end(), | |||||
p.extension(), | |||||
std::less<>()); | |||||
bool is_cpp = found != source_exts.end() && *found == p.extension(); | |||||
auto leaf = p.string(); | |||||
return is_cpp && !ends_with(leaf, ".main.cpp") && !ends_with(leaf, ".test.cpp"); | |||||
}; | |||||
for (auto entry : fs::recursive_directory_iterator(source_dir)) { | |||||
if (!entry.is_regular_file()) { | |||||
continue; | |||||
} | |||||
auto entry_path = entry.path(); | |||||
if (is_header(entry_path)) { | |||||
sf.headers.push_back(std::move(entry_path)); | |||||
} else if (is_source(entry_path)) { | |||||
sf.sources.push_back(std::move(entry_path)); | |||||
} | |||||
} | |||||
} | |||||
fs::path compile_file(fs::path src_path, | |||||
const build_params& params, | |||||
const toolchain& tc, | |||||
const library_manifest& man) { | |||||
auto obj_dir = params.out_root / "obj"; | |||||
auto obj_relpath = fs::relative(src_path, params.root); | |||||
obj_relpath.replace_filename(obj_relpath.filename().string() + ".o"); | |||||
auto obj_path = obj_dir / obj_relpath; | |||||
fs::create_directories(obj_path.parent_path()); | |||||
spdlog::info("Compile file: {}", src_path.string()); | |||||
compile_file_spec spec{src_path, obj_path}; | |||||
spec.include_dirs.push_back(params.root / "src"); | |||||
spec.include_dirs.push_back(params.root / "include"); | |||||
for (auto& inc : man.private_includes) { | |||||
spec.include_dirs.push_back(inc); | |||||
} | |||||
for (auto& def : man.private_defines) { | |||||
spec.definitions.push_back(def); | |||||
} | |||||
auto cmd = tc.create_compile_command(spec); | |||||
auto compile_res = run_proc(cmd); | |||||
if (!compile_res.okay()) { | |||||
spdlog::error("Compilation failed: {}", spec.source_path.string()); | |||||
std::stringstream strm; | |||||
for (auto& arg : cmd) { | |||||
strm << std::quoted(arg) << ' '; | |||||
} | |||||
spdlog::error("Subcommand FAILED: {}\n{}", strm.str(), compile_res.output); | |||||
throw compile_failure("Compilation failed."); | |||||
} | |||||
return obj_path; | |||||
} | |||||
void copy_headers(const fs::path& source, const fs::path& dest, const source_files& sources) { | |||||
for (auto& header_fpath : sources.headers) { | |||||
auto relpath = fs::relative(header_fpath, source); | |||||
auto dest_fpath = dest / relpath; | |||||
spdlog::info("Export header: {}", relpath); | |||||
fs::create_directories(dest_fpath.parent_path()); | |||||
fs::copy_file(header_fpath, dest_fpath); | |||||
} | |||||
} | |||||
void generate_export(const build_params& params, | |||||
fs::path archive_file, | |||||
const source_files& sources) { | |||||
const auto export_root = params.out_root / (params.export_name + ".export-root"); | |||||
spdlog::info("Generating library export: {}", export_root); | |||||
fs::remove_all(export_root); | |||||
fs::create_directories(export_root); | |||||
fs::copy_file(archive_file, export_root / archive_file.filename()); | |||||
auto header_root = params.root / "include"; | |||||
if (!fs::is_directory(header_root)) { | |||||
header_root = params.root / "src"; | |||||
} | |||||
if (fs::is_directory(header_root)) { | |||||
copy_headers(header_root, export_root / "include", sources); | |||||
} | |||||
} | |||||
} // namespace | |||||
void dds::build(const build_params& params, const library_manifest& man) { | |||||
auto tc = toolchain::load_from_file(params.toolchain_file); | |||||
auto include_dir = params.root / "include"; | |||||
auto src_dir = params.root / "src"; | |||||
source_files files; | |||||
if (fs::exists(include_dir)) { | |||||
if (!fs::is_directory(include_dir)) { | |||||
throw std::runtime_error("The `include` at the root of the project is not a directory"); | |||||
} | |||||
collect_sources(files, include_dir); | |||||
for (auto&& sf : files.sources) { | |||||
spdlog::warn("Source file in `include/` will not be compiled: {}", sf); | |||||
} | |||||
// Drop any source files we found within `include/` | |||||
files.sources.clear(); | |||||
} | |||||
if (fs::exists(src_dir)) { | |||||
if (!fs::is_directory(src_dir)) { | |||||
throw std::runtime_error("The `src` at the root of the project is not a directory"); | |||||
} | |||||
collect_sources(files, src_dir); | |||||
} | |||||
if (files.sources.empty() && files.headers.empty()) { | |||||
spdlog::warn("No source files found to compile/export!"); | |||||
} | |||||
archive_spec arc; | |||||
for (auto&& sf : files.sources) { | |||||
arc.input_files.push_back(compile_file(sf, params, tc, man)); | |||||
} | |||||
arc.out_path = params.out_root / ("lib" + params.export_name + tc.archive_suffix()); | |||||
spdlog::info("Create archive {}", arc.out_path); | |||||
auto ar_cmd = tc.create_archive_command(arc); | |||||
auto ar_res = run_proc(ar_cmd); | |||||
if (!ar_res.okay()) { | |||||
spdlog::error("Failure creating archive library {}", arc.out_path); | |||||
std::stringstream strm; | |||||
for (auto& arg : ar_cmd) { | |||||
strm << std::quoted(arg) << ' '; | |||||
} | |||||
spdlog::error("Subcommand failed: {}", strm.str()); | |||||
spdlog::error("Subcommand produced output:\n{}", ar_res.output); | |||||
throw archive_failure("Failed to create the library archive"); | |||||
} | |||||
if (params.do_export) { | |||||
generate_export(params, arc.out_path, files); | |||||
} | |||||
} |
#ifndef DDS_BUILD_HPP_INCLUDED | |||||
#define DDS_BUILD_HPP_INCLUDED | |||||
#include <dds/util.hpp> | |||||
#include <dds/manifest.hpp> | |||||
#include <optional> | |||||
namespace dds { | |||||
struct build_params { | |||||
fs::path root; | |||||
fs::path out_root; | |||||
fs::path toolchain_file; | |||||
std::string export_name; | |||||
bool do_export = false; | |||||
}; | |||||
void build(const build_params&, const library_manifest& man); | |||||
} // namespace dds | |||||
#endif // DDS_BUILD_HPP_INCLUDED |
#include <dds/build.hpp> | |||||
#include <dds/lm_parse.hpp> | |||||
#include <dds/util.hpp> | |||||
#include <dds/logging.hpp> | |||||
#include <args.hxx> | |||||
#include <filesystem> | |||||
#include <iostream> | |||||
namespace { | |||||
using string_flag = args::ValueFlag<std::string>; | |||||
using path_flag = args::ValueFlag<dds::fs::path>; | |||||
struct cli_base { | |||||
args::ArgumentParser& parser; | |||||
args::HelpFlag _help{parser, "help", "Display this help message and exit", {'h', "help"}}; | |||||
std::shared_ptr<spdlog::logger> console = dds::get_logger(); | |||||
args::Group cmd_group{parser, "Available Commands"}; | |||||
}; | |||||
struct cli_build { | |||||
cli_base& base; | |||||
args::Command cmd{base.cmd_group, "build", "Build a library"}; | |||||
args::HelpFlag _help{cmd, "help", "Display this help message and exit", {'h', "help"}}; | |||||
path_flag lib_dir{cmd, | |||||
"lib_dir", | |||||
"The path to the directory containing the library", | |||||
{"lib-dir"}, | |||||
dds::fs::current_path()}; | |||||
path_flag out_dir{cmd, | |||||
"out_dir", | |||||
"The directory in which to write the built files", | |||||
{"out-dir"}, | |||||
dds::fs::current_path() / "_build"}; | |||||
string_flag export_name{cmd, | |||||
"export_name", | |||||
"Set the name of the export", | |||||
{"export-name", 'n'}, | |||||
dds::fs::current_path().filename()}; | |||||
path_flag tc_filepath{cmd, | |||||
"toolchain_file", | |||||
"Path to the toolchain file to use", | |||||
{"toolchain-file", 'T'}, | |||||
dds::fs::current_path() / "toolchain.dds"}; | |||||
args::Flag export_{cmd, "export_dir", "Generate a library export", {"export", 'E'}}; | |||||
int run() { | |||||
dds::build_params params; | |||||
params.root = lib_dir.Get(); | |||||
params.out_root = out_dir.Get(); | |||||
params.toolchain_file = tc_filepath.Get(); | |||||
params.export_name = export_name.Get(); | |||||
params.do_export = export_.Get(); | |||||
dds::library_manifest man; | |||||
const auto man_filepath = params.root / "manifest.dds"; | |||||
if (exists(man_filepath)) { | |||||
man = dds::library_manifest::load_from_file(man_filepath); | |||||
} | |||||
dds::build(params, man); | |||||
return 0; | |||||
} | |||||
}; | |||||
} // namespace | |||||
int main(int argc, char** argv) { | |||||
args::ArgumentParser parser("DDSLiM - The drop-dead-simple library manager"); | |||||
cli_base cli{parser}; | |||||
cli_build build{cli}; | |||||
try { | |||||
parser.ParseCLI(argc, argv); | |||||
} catch (const args::Help&) { | |||||
std::cout << parser; | |||||
return 0; | |||||
} catch (const args::Error& e) { | |||||
std::cerr << parser; | |||||
std::cerr << e.what() << '\n'; | |||||
return 1; | |||||
} | |||||
try { | |||||
if (build.cmd) { | |||||
return build.run(); | |||||
} else { | |||||
assert(false); | |||||
std::terminate(); | |||||
} | |||||
} catch (const std::exception& e) { | |||||
spdlog::critical(e.what()); | |||||
return 2; | |||||
} | |||||
} |
#include "./lm_parse.hpp" | |||||
#include <dds/util.hpp> | |||||
#include <fstream> | |||||
namespace fs = std::filesystem; | |||||
using namespace std::literals; | |||||
using namespace dds; | |||||
namespace { | |||||
std::string_view sview(std::string_view::const_iterator beg, std::string_view::const_iterator end) { | |||||
return std::string_view{beg, static_cast<std::size_t>(std::distance(beg, end))}; | |||||
} | |||||
std::string_view trim(std::string_view s) { | |||||
auto iter = s.begin(); | |||||
auto end = s.end(); | |||||
while (iter != end && std::isspace(*iter)) { | |||||
++iter; | |||||
} | |||||
auto riter = s.rbegin(); | |||||
auto rend = s.rend(); | |||||
while (riter != rend && std::isspace(*riter)) { | |||||
++riter; | |||||
} | |||||
auto new_end = riter.base(); | |||||
return sview(iter, new_end); | |||||
} | |||||
void parse_line(std::vector<lm_pair>& pairs, const std::string_view whole_line) { | |||||
const auto line = trim(whole_line); | |||||
if (line.empty() || line[0] == '#') { | |||||
return; | |||||
} | |||||
const auto begin = line.begin(); | |||||
auto iter = begin; | |||||
const auto end = line.end(); | |||||
while (true) { | |||||
if (iter == end) { | |||||
throw std::runtime_error("Invalid line in config file: '"s + std::string(whole_line) | |||||
+ "'"); | |||||
} | |||||
if (*iter == ':') { | |||||
if (++iter == end) { | |||||
// Empty value | |||||
break; | |||||
} else if (*iter == ' ') { | |||||
// Found the key | |||||
break; | |||||
} else { | |||||
// Just a regular character. Keep going... | |||||
} | |||||
} | |||||
++iter; | |||||
} | |||||
// `iter` now points to the space between the key and value | |||||
auto key = sview(begin, iter - 1); // -1 to trim the colon in the key | |||||
auto value = sview(iter, end); | |||||
key = trim(key); | |||||
value = trim(value); | |||||
pairs.emplace_back(key, value); | |||||
} | |||||
} // namespace | |||||
dds::lm_kv_pairs dds::lm_parse_string(std::string_view s) { | |||||
std::vector<lm_pair> pairs; | |||||
auto line_begin = s.begin(); | |||||
auto iter = line_begin; | |||||
auto end = s.end(); | |||||
while (iter != end) { | |||||
if (*iter == '\n') { | |||||
parse_line(pairs, sview(line_begin, iter)); | |||||
line_begin = ++iter; | |||||
continue; | |||||
} | |||||
++iter; | |||||
} | |||||
if (line_begin != end) { | |||||
parse_line(pairs, sview(line_begin, end)); | |||||
} | |||||
return lm_kv_pairs(std::move(pairs)); | |||||
} | |||||
dds::lm_kv_pairs dds::lm_parse_file(fs::path fpath) { | |||||
return lm_parse_string(slurp_file(fpath)); | |||||
} |
#ifndef DDS_LM_PARSE_HPP_INCLUDED | |||||
#define DDS_LM_PARSE_HPP_INCLUDED | |||||
#include <cassert> | |||||
#include <filesystem> | |||||
#include <string> | |||||
#include <utility> | |||||
#include <vector> | |||||
namespace dds { | |||||
class lm_pair { | |||||
std::string _key; | |||||
std::string _value; | |||||
public: | |||||
lm_pair(std::string_view k, std::string_view v) | |||||
: _key(k) | |||||
, _value(v) {} | |||||
auto& key() const noexcept { return _key; } | |||||
auto& value() const noexcept { return _value; } | |||||
auto tie() const noexcept { return std::tie(key(), value()); } | |||||
}; | |||||
struct kv_pair_iterator { | |||||
using vec_type = std::vector<lm_pair>; | |||||
using base_iter = vec_type::const_iterator; | |||||
base_iter _iter; | |||||
base_iter _end; | |||||
std::string _key; | |||||
public: | |||||
using iterator_category = std::forward_iterator_tag; | |||||
using difference_type = vec_type::difference_type; | |||||
using value_type = vec_type::value_type; | |||||
using pointer = vec_type::pointer; | |||||
using reference = vec_type::reference; | |||||
inline kv_pair_iterator(base_iter, base_iter, std::string_view k); | |||||
kv_pair_iterator& operator++() & noexcept { | |||||
assert(_iter != _end); | |||||
++_iter; | |||||
while (_iter != _end && _iter->key() != _key) { | |||||
++_iter; | |||||
} | |||||
return *this; | |||||
} | |||||
const lm_pair* operator->() const noexcept { | |||||
assert(_iter != _end); | |||||
return _iter.operator->(); | |||||
} | |||||
const lm_pair& operator*() const noexcept { | |||||
assert(_iter != _end); | |||||
return *_iter; | |||||
} | |||||
inline bool operator!=(const kv_pair_iterator& o) const noexcept { return _iter != o._iter; } | |||||
auto begin() const noexcept { return *this; } | |||||
auto end() const noexcept { return kv_pair_iterator(_end, _end, _key); } | |||||
explicit operator bool() const noexcept { return *this != end(); } | |||||
}; | |||||
class lm_kv_pairs { | |||||
std::vector<lm_pair> _kvs; | |||||
public: | |||||
explicit lm_kv_pairs(std::vector<lm_pair> kvs) | |||||
: _kvs(kvs) {} | |||||
auto& items() const noexcept { return _kvs; } | |||||
const lm_pair* find(const std::string_view& key) const noexcept { | |||||
for (auto&& item : items()) { | |||||
if (item.key() == key) { | |||||
return &item; | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
kv_pair_iterator iter(std::string_view key) const noexcept { | |||||
auto iter = items().begin(); | |||||
const auto end = items().end(); | |||||
while (iter != end && iter->key() != key) { | |||||
++iter; | |||||
} | |||||
return kv_pair_iterator{iter, end, key}; | |||||
} | |||||
std::vector<lm_pair> all_of(std::string_view key) const noexcept { | |||||
auto iter = this->iter(key); | |||||
return std::vector<lm_pair>(iter, iter.end()); | |||||
} | |||||
auto size() const noexcept { return _kvs.size(); } | |||||
}; | |||||
inline kv_pair_iterator::kv_pair_iterator(base_iter it, base_iter end, std::string_view k) | |||||
: _iter{it} | |||||
, _end{end} | |||||
, _key{k} {} | |||||
lm_kv_pairs lm_parse_string(std::string_view); | |||||
lm_kv_pairs lm_parse_file(std::filesystem::path); | |||||
void lm_write_pairs(std::filesystem::path, lm_kv_pairs); | |||||
} // namespace dds | |||||
#endif // DDS_LM_PARSE_HPP_INCLUDED |
#include <dds/lm_parse.hpp> | |||||
#include <dds/util.test.hpp> | |||||
#include <iostream> | |||||
using namespace dds; | |||||
void test_simple() { | |||||
auto lm_src = ""; | |||||
auto kvs = lm_parse_string(lm_src); | |||||
CHECK(kvs.size() == 0); | |||||
lm_src = "foo: bar"; | |||||
kvs = lm_parse_string(lm_src); | |||||
CHECK(kvs.size() == 1); | |||||
REQUIRE(kvs.find("foo")); | |||||
CHECK(kvs.find("foo")->value() == "bar"); | |||||
lm_src = "foo:bar: baz"; | |||||
kvs = lm_parse_string(lm_src); | |||||
CHECK(kvs.size() == 1); | |||||
REQUIRE(kvs.find("foo:bar")); | |||||
CHECK(kvs.find("foo:bar")->value() == "baz"); | |||||
CHECK(lm_parse_string("#comment").size() == 0); | |||||
CHECK(lm_parse_string("\n\n").size() == 0); | |||||
CHECK(lm_parse_string("\n#comment").size() == 0); | |||||
CHECK(lm_parse_string("#comment\n\n").size() == 0); | |||||
std::vector<std::string_view> empty_foos = { | |||||
"Foo:", | |||||
"Foo: ", | |||||
"Foo:\n", | |||||
"Foo: \n", | |||||
"\n\nFoo:", | |||||
" Foo:", | |||||
" Foo: ", | |||||
"Foo :", | |||||
"Foo :\n", | |||||
}; | |||||
for (auto s : empty_foos) { | |||||
kvs = lm_parse_string(s); | |||||
CHECK(kvs.size() == 1); | |||||
REQUIRE(kvs.find("Foo")); | |||||
CHECK(kvs.find("Foo")->value() == ""); | |||||
} | |||||
kvs = lm_parse_string("foo: # Not a comment"); | |||||
CHECK(kvs.size() == 1); | |||||
REQUIRE(kvs.find("foo")); | |||||
CHECK(kvs.find("foo")->value() == "# Not a comment"); | |||||
} | |||||
void test_multi() { | |||||
auto kvs = lm_parse_string("Foo: bar\nbaz: qux"); | |||||
CHECK(kvs.size() == 2); | |||||
REQUIRE(kvs.find("Foo")); | |||||
CHECK(kvs.find("Foo")->value() == "bar"); | |||||
REQUIRE(kvs.find("baz")); | |||||
CHECK(kvs.find("baz")->value() == "qux"); | |||||
kvs = lm_parse_string("foo: first\nfoo: second\n"); | |||||
CHECK(kvs.size() == 2); | |||||
auto iter = kvs.iter("foo"); | |||||
REQUIRE(iter); | |||||
CHECK(iter->key() == "foo"); | |||||
CHECK(iter->value() == "first"); | |||||
++iter; | |||||
REQUIRE(iter); | |||||
CHECK(iter->key() == "foo"); | |||||
CHECK(iter->value() == "second"); | |||||
++iter; | |||||
CHECK(!iter); | |||||
iter = kvs.iter("no-exist"); | |||||
CHECK(!iter); | |||||
} | |||||
void run_tests() { | |||||
test_simple(); | |||||
test_multi(); | |||||
} | |||||
DDS_TEST_MAIN; |
#ifndef DDS_LOGGING_HPP_INCLUDED | |||||
#define DDS_LOGGING_HPP_INCLUDED | |||||
#include <spdlog/fmt/ostr.h> | |||||
#include <spdlog/sinks/stdout_color_sinks.h> | |||||
#include <spdlog/spdlog.h> | |||||
namespace dds { | |||||
inline auto get_logger() { return spdlog::stdout_color_mt("console"); } | |||||
} // namespace dds | |||||
#endif // DDS_LOGGING_HPP_INCLUDED |
#include "./manifest.hpp" | |||||
#include <dds/lm_parse.hpp> | |||||
using namespace dds; | |||||
library_manifest library_manifest::load_from_file(const fs::path& fpath) { | |||||
auto kvs = lm_parse_file(fpath); | |||||
library_manifest ret; | |||||
for (auto& pair : kvs.items()) { | |||||
if (pair.key() == "Private-Include") { | |||||
ret.private_includes.emplace_back(pair.value()); | |||||
} else if (pair.key() == "Private-Defines") { | |||||
ret.private_defines.emplace_back(pair.value()); | |||||
} else { | |||||
throw std::runtime_error("Unknown key in " + fpath.string() + ": " + pair.key()); | |||||
} | |||||
} | |||||
return ret; | |||||
} |
#ifndef DDS_MANIFEST_HPP_INCLUDED | |||||
#define DDS_MANIFEST_HPP_INCLUDED | |||||
#include <dds/util.hpp> | |||||
namespace dds { | |||||
struct library_manifest { | |||||
std::vector<fs::path> private_includes; | |||||
std::vector<std::string> private_defines; | |||||
static library_manifest load_from_file(const fs::path&); | |||||
}; | |||||
} // namespace dds | |||||
#endif // DDS_MANIFEST_HPP_INCLUDED |
#include "./proc.hpp" | |||||
#include <poll.h> | |||||
#include <sys/wait.h> | |||||
#include <unistd.h> | |||||
#include <cassert> | |||||
#include <cerrno> | |||||
#include <deque> | |||||
#include <iostream> | |||||
#include <system_error> | |||||
using namespace dds; | |||||
namespace { | |||||
void check_rc(bool b, std::string_view s) { | |||||
if (!b) { | |||||
throw std::system_error(std::error_code(errno, std::system_category()), std::string(s)); | |||||
} | |||||
} | |||||
::pid_t | |||||
spawn_child(const std::vector<std::string>& command, int stdout_pipe, int close_me) noexcept { | |||||
auto child_pid = ::fork(); | |||||
if (child_pid != 0) { | |||||
return child_pid; | |||||
} | |||||
// We are child | |||||
::close(close_me); | |||||
auto rc = dup2(stdout_pipe, STDOUT_FILENO); | |||||
check_rc(rc != -1, "Failed to dup2 stdout"); | |||||
rc = dup2(stdout_pipe, STDERR_FILENO); | |||||
check_rc(rc != -1, "Failed to dup2 stderr"); | |||||
std::vector<const char*> strings; | |||||
strings.reserve(command.size() + 1); | |||||
for (auto& s : command) { | |||||
strings.push_back(s.data()); | |||||
} | |||||
strings.push_back(nullptr); | |||||
::execvp(strings[0], (char* const*)strings.data()); | |||||
std::cerr << "[ddslim child executor] execvp returned! This is a fatal error: " | |||||
<< std::system_category().message(errno) << '\n'; | |||||
std::terminate(); | |||||
} | |||||
} // namespace | |||||
proc_result dds::run_proc(const std::vector<std::string>& command) { | |||||
int stdio_pipe[2] = {}; | |||||
auto rc = ::pipe(stdio_pipe); | |||||
check_rc(rc == 0, "Create stdio pipe for subprocess"); | |||||
int read_pipe = stdio_pipe[0]; | |||||
int write_pipe = stdio_pipe[1]; | |||||
auto child = spawn_child(command, write_pipe, read_pipe); | |||||
::close(write_pipe); | |||||
pollfd stdio_fd; | |||||
stdio_fd.fd = read_pipe; | |||||
stdio_fd.events = POLLIN; | |||||
proc_result res; | |||||
while (true) { | |||||
rc = ::poll(&stdio_fd, 1, -1); | |||||
check_rc(rc > 0, "Failed in poll()"); | |||||
std::string buffer; | |||||
buffer.resize(1024); | |||||
auto nread = ::read(stdio_fd.fd, buffer.data(), buffer.size()); | |||||
if (nread == 0) { | |||||
break; | |||||
} | |||||
check_rc(nread > 0, "Failed in read()"); | |||||
res.output.append(buffer.begin(), buffer.begin() + nread); | |||||
} | |||||
int status = 0; | |||||
rc = ::waitpid(child, &status, 0); | |||||
check_rc(rc >= 0, "Failed in waitpid()"); | |||||
if (WIFEXITED(status)) { | |||||
res.retc = WEXITSTATUS(status); | |||||
} else if (WIFSIGNALED(status)) { | |||||
res.signal = WTERMSIG(status); | |||||
} | |||||
return res; | |||||
} |
#ifndef DDS_PROC_HPP_INCLUDED | |||||
#define DDS_PROC_HPP_INCLUDED | |||||
#include <string> | |||||
#include <vector> | |||||
namespace dds { | |||||
struct proc_result { | |||||
int signal = 0; | |||||
int retc = 0; | |||||
std::string output; | |||||
bool okay() const noexcept { return retc == 0 && signal == 0; } | |||||
}; | |||||
proc_result run_proc(const std::vector<std::string>& args); | |||||
} // namespace dds | |||||
#endif // DDS_PROC_HPP_INCLUDED |
#include "./toolchain.hpp" | |||||
#include <dds/lm_parse.hpp> | |||||
#include <optional> | |||||
#include <string> | |||||
#include <vector> | |||||
using namespace dds; | |||||
using std::optional; | |||||
using std::string; | |||||
using std::string_view; | |||||
using std::vector; | |||||
using opt_string = optional<string>; | |||||
namespace { | |||||
struct invalid_toolchain : std::runtime_error { | |||||
using std::runtime_error::runtime_error; | |||||
}; | |||||
} // namespace | |||||
toolchain toolchain::load_from_file(fs::path p) { | |||||
opt_string inc_template; | |||||
opt_string def_template; | |||||
opt_string c_compile_template; | |||||
opt_string cxx_compile_template; | |||||
opt_string create_archive_template; | |||||
opt_string archive_suffix; | |||||
auto require_key = [](auto k, auto& opt) { | |||||
if (!opt.has_value()) { | |||||
throw invalid_toolchain("Toolchain file is missing a required key: " + string(k)); | |||||
} | |||||
}; | |||||
auto kvs = lm_parse_file(p); | |||||
for (auto&& pair : kvs.items()) { | |||||
auto& key = pair.key(); | |||||
auto& value = pair.value(); | |||||
auto try_single = [&](auto k, auto& opt) { | |||||
if (key == k) { | |||||
if (opt.has_value()) { | |||||
throw invalid_toolchain("Duplicate key: " + key); | |||||
} | |||||
opt = value; | |||||
return true; | |||||
} | |||||
return false; | |||||
}; | |||||
// clang-format off | |||||
bool found_single = false // Bool to force alignment | |||||
// Argument templates | |||||
|| try_single("Include-Template", inc_template) | |||||
|| try_single("Define-Template", def_template) | |||||
// Command templates | |||||
|| try_single("Compile-C-Template", c_compile_template) | |||||
|| try_single("Compile-C++-Template", cxx_compile_template) | |||||
|| try_single("Create-Archive-Template", create_archive_template) | |||||
|| try_single("Archive-Suffix", archive_suffix) | |||||
|| false; | |||||
// clang-format on | |||||
if (!found_single) { | |||||
throw invalid_toolchain("Unknown toolchain file key: " + key); | |||||
} | |||||
} | |||||
require_key("Include-Template", inc_template); | |||||
require_key("Define-Template", def_template); | |||||
require_key("Compile-C-Template", c_compile_template); | |||||
require_key("Compile-C++-Template", cxx_compile_template); | |||||
require_key("Create-Archive-Template", create_archive_template); | |||||
require_key("Archive-Suffix", archive_suffix); | |||||
return toolchain{ | |||||
c_compile_template.value(), | |||||
cxx_compile_template.value(), | |||||
inc_template.value(), | |||||
def_template.value(), | |||||
create_archive_template.value(), | |||||
archive_suffix.value(), | |||||
}; | |||||
} | |||||
vector<string> dds::split_shell_string(std::string_view shell) { | |||||
char cur_quote = 0; | |||||
bool is_escaped = false; | |||||
vector<string> acc; | |||||
const auto begin = shell.begin(); | |||||
auto iter = begin; | |||||
const auto end = shell.end(); | |||||
opt_string token; | |||||
while (iter != end) { | |||||
const char c = *iter++; | |||||
if (is_escaped) { | |||||
if (c == '\n') { | |||||
// Ignore the newline | |||||
} else if (cur_quote || c != cur_quote || c == '\\') { | |||||
// Escaped `\` character | |||||
token = token.value_or("") + c; | |||||
} else { | |||||
// Regular escape sequence | |||||
token = token.value_or("") + '\\' + c; | |||||
} | |||||
is_escaped = false; | |||||
} else if (c == '\\') { | |||||
is_escaped = true; | |||||
} else if (cur_quote) { | |||||
if (c == cur_quote) { | |||||
// End of quoted token; | |||||
cur_quote = 0; | |||||
} else { | |||||
token = token.value_or("") + c; | |||||
} | |||||
} else if (c == '"' || c == '\'') { | |||||
// Beginning of a quoted token | |||||
cur_quote = c; | |||||
token = ""; | |||||
} else if (c == '\t' || c == ' ' || c == '\n' || c == '\r' || c == '\f') { | |||||
// We've reached unquoted whitespace | |||||
if (token.has_value()) { | |||||
acc.push_back(move(*token)); | |||||
} | |||||
token.reset(); | |||||
} else { | |||||
// Just a regular character | |||||
token = token.value_or("") + c; | |||||
} | |||||
} | |||||
if (token.has_value()) { | |||||
acc.push_back(move(*token)); | |||||
} | |||||
return acc; | |||||
} | |||||
namespace { | |||||
std::string replace(std::string_view str, std::string_view key, std::string_view repl) { | |||||
std::string ret; | |||||
std::string_view::size_type pos = 0; | |||||
std::string_view::size_type prev_pos = 0; | |||||
while (pos = str.find(key, pos), pos != key.npos) { | |||||
ret.append(str.begin() + prev_pos, str.begin() + pos); | |||||
ret.append(repl); | |||||
prev_pos = pos += key.size(); | |||||
} | |||||
ret.append(str.begin() + prev_pos, str.end()); | |||||
return ret; | |||||
} | |||||
vector<string> replace(vector<string> strings, std::string_view key, std::string_view repl) { | |||||
for (auto& item : strings) { | |||||
item = replace(item, key, repl); | |||||
} | |||||
return strings; | |||||
} | |||||
} // namespace | |||||
vector<string> toolchain::include_args(const fs::path& p) const noexcept { | |||||
return replace(_inc_template, "<PATH>", p.string()); | |||||
} | |||||
vector<string> toolchain::definition_args(std::string_view s) const noexcept { | |||||
return replace(_def_template, "<DEF>", s); | |||||
} | |||||
vector<string> toolchain::create_compile_command(const compile_file_spec& spec) const noexcept { | |||||
vector<string> flags; | |||||
for (auto&& inc_dir : spec.include_dirs) { | |||||
auto inc_args = include_args(inc_dir); | |||||
flags.insert(flags.end(), inc_args.begin(), inc_args.end()); | |||||
} | |||||
for (auto&& def : spec.definitions) { | |||||
auto def_args = definition_args(def); | |||||
flags.insert(flags.end(), def_args.begin(), def_args.end()); | |||||
} | |||||
vector<string> command; | |||||
for (auto arg : _cxx_compile) { | |||||
if (arg == "<FLAGS>") { | |||||
command.insert(command.end(), flags.begin(), flags.end()); | |||||
} else { | |||||
arg = replace(arg, "<FILE>", spec.source_path.string()); | |||||
arg = replace(arg, "<OUT>", spec.out_path.string()); | |||||
command.push_back(arg); | |||||
} | |||||
} | |||||
return command; | |||||
} | |||||
vector<string> toolchain::create_archive_command(const archive_spec& spec) const noexcept { | |||||
vector<string> cmd; | |||||
for (auto& arg : _archive_template) { | |||||
if (arg == "<OBJECTS>") { | |||||
cmd.insert(cmd.end(), spec.input_files.begin(), spec.input_files.end()); | |||||
} else { | |||||
cmd.push_back(replace(arg, "<ARCHIVE>", spec.out_path.string())); | |||||
} | |||||
} | |||||
return cmd; | |||||
} |
#ifndef DDS_TOOLCHAIN_HPP_INCLUDED | |||||
#define DDS_TOOLCHAIN_HPP_INCLUDED | |||||
#include <dds/util.hpp> | |||||
#include <string> | |||||
#include <vector> | |||||
namespace dds { | |||||
std::vector<std::string> split_shell_string(std::string_view s); | |||||
enum class language { | |||||
automatic, | |||||
c, | |||||
cxx, | |||||
}; | |||||
struct compile_file_spec { | |||||
fs::path source_path; | |||||
fs::path out_path; | |||||
std::vector<std::string> definitions = {}; | |||||
std::vector<fs::path> include_dirs = {}; | |||||
language lang = language::automatic; | |||||
}; | |||||
struct archive_spec { | |||||
std::vector<fs::path> input_files; | |||||
fs::path out_path; | |||||
}; | |||||
class toolchain { | |||||
using string_seq = std::vector<std::string>; | |||||
string_seq _c_compile; | |||||
string_seq _cxx_compile; | |||||
string_seq _inc_template; | |||||
string_seq _def_template; | |||||
string_seq _archive_template; | |||||
std::string _archive_suffix; | |||||
public: | |||||
toolchain(const std::string& c_compile, | |||||
const std::string& cxx_compile, | |||||
const std::string& inc_template, | |||||
const std::string& def_template, | |||||
const std::string& archive_template, | |||||
const std::string& archive_suffix) | |||||
: _c_compile(split_shell_string(c_compile)) | |||||
, _cxx_compile(split_shell_string(cxx_compile)) | |||||
, _inc_template(split_shell_string(inc_template)) | |||||
, _def_template(split_shell_string(def_template)) | |||||
, _archive_template(split_shell_string(archive_template)) | |||||
, _archive_suffix(archive_suffix) {} | |||||
static toolchain load_from_file(fs::path); | |||||
auto& archive_suffix() const noexcept { return _archive_suffix; } | |||||
std::vector<std::string> definition_args(std::string_view s) const noexcept; | |||||
std::vector<std::string> include_args(const fs::path& p) const noexcept; | |||||
std::vector<std::string> create_compile_command(const compile_file_spec&) const noexcept; | |||||
std::vector<std::string> create_archive_command(const archive_spec&) const noexcept; | |||||
}; | |||||
} // namespace dds | |||||
#endif // DDS_TOOLCHAIN_HPP_INCLUDED |
#include <dds/toolchain.hpp> | |||||
#include <dds/util.test.hpp> | |||||
using namespace dds; | |||||
namespace { | |||||
#define CHECK_SHLEX(str, ...) \ | |||||
do { \ | |||||
CHECK(dds::split_shell_string(str) == std::vector<std::string>(__VA_ARGS__)); \ | |||||
} while (0) | |||||
void test_shlex() { | |||||
CHECK_SHLEX("foo", {"foo"}); | |||||
CHECK_SHLEX("foo bar", {"foo", "bar"}); | |||||
CHECK_SHLEX("\"foo\" bar", {"foo", "bar"}); | |||||
CHECK_SHLEX("\"foo bar\"", {"foo bar"}); | |||||
CHECK_SHLEX("", {}); | |||||
CHECK_SHLEX(" ", {}); | |||||
CHECK_SHLEX("\"\"", {""}); | |||||
CHECK_SHLEX("'quoted arg'", {"quoted arg"}); | |||||
CHECK_SHLEX("word ", {"word"}); | |||||
CHECK_SHLEX("\" meow\"", {" meow"}); | |||||
CHECK_SHLEX("foo bar", {"foo", "bar"}); | |||||
CHECK_SHLEX("C:\\\\Program\\ Files", {"C:\\Program Files"}); | |||||
CHECK_SHLEX("Foo\nBar", {"Foo", "Bar"}); | |||||
CHECK_SHLEX("foo \"\" bar", {"foo", "", "bar"}); | |||||
} | |||||
void run_tests() { test_shlex(); } | |||||
} // namespace | |||||
DDS_TEST_MAIN; |
#include "./util.hpp" | |||||
#include <filesystem> | |||||
#include <fstream> | |||||
std::fstream dds::open(const fs::path& filepath, std::ios::openmode mode, std::error_code& ec) { | |||||
std::fstream ret; | |||||
auto mask = ret.exceptions() | std::ios::failbit; | |||||
ret.exceptions(mask); | |||||
try { | |||||
ret.open(filepath.string(), mode); | |||||
} catch (const std::ios::failure& e) { | |||||
ec = std::error_code(errno, std::system_category()); | |||||
} | |||||
return ret; | |||||
} | |||||
std::string dds::slurp_file(const fs::path& path, std::error_code& ec) { | |||||
auto file = dds::open(path, std::ios::in, ec); | |||||
if (ec) { | |||||
return std::string{}; | |||||
} | |||||
std::ostringstream out; | |||||
out << file.rdbuf(); | |||||
return std::move(out).str(); | |||||
} |
#ifndef DDS_UTIL_HPP_INCLUDED | |||||
#define DDS_UTIL_HPP_INCLUDED | |||||
#include <filesystem> | |||||
#include <fstream> | |||||
namespace dds { | |||||
namespace fs = std::filesystem; | |||||
std::fstream open(const fs::path& filepath, std::ios::openmode mode, std::error_code& ec); | |||||
std::string slurp_file(const fs::path& path, std::error_code& ec); | |||||
inline std::fstream open(const fs::path& filepath, std::ios::openmode mode) { | |||||
std::error_code ec; | |||||
auto ret = dds::open(filepath, mode, ec); | |||||
if (ec) { | |||||
throw std::system_error{ec, "Error opening file: " + filepath.string()}; | |||||
} | |||||
return ret; | |||||
} | |||||
inline std::string slurp_file(const fs::path& path) { | |||||
std::error_code ec; | |||||
auto contents = dds::slurp_file(path, ec); | |||||
if (ec) { | |||||
throw std::system_error{ec, "Reading file: " + path.string()}; | |||||
} | |||||
return contents; | |||||
} | |||||
inline bool ends_with(std::string_view s, std::string_view key) { | |||||
auto found = s.find(key); | |||||
return found != s.npos && found == s.size() - key.size(); | |||||
} | |||||
} // namespace dds | |||||
#endif // DDS_UTIL_HPP_INCLUDED |
#ifndef DDS_UTIL_TEST_HPP_INCLUDED | |||||
#define DDS_UTIL_TEST_HPP_INCLUDED | |||||
#include <iostream> | |||||
namespace dds { | |||||
int S_failed_checks = 0; | |||||
struct requirement_failed {}; | |||||
#define CHECK(...) \ | |||||
do { \ | |||||
if (!(__VA_ARGS__)) { \ | |||||
++::dds::S_failed_checks; \ | |||||
std::cerr << "Check failed at " << __FILE__ << ':' << __LINE__ << ": " << #__VA_ARGS__ \ | |||||
<< "\n"; \ | |||||
} \ | |||||
} while (0) | |||||
#define REQUIRE(...) \ | |||||
do { \ | |||||
if (!(__VA_ARGS__)) { \ | |||||
++::dds::S_failed_checks; \ | |||||
std::cerr << "Check failed at " << __FILE__ << ':' << __LINE__ << ": " << #__VA_ARGS__ \ | |||||
<< "\n"; \ | |||||
throw requirement_failed(); \ | |||||
} \ | |||||
} while (0) | |||||
#define DDS_TEST_MAIN \ | |||||
int main() { \ | |||||
try { \ | |||||
run_tests(); \ | |||||
} catch (const requirement_failed&) { \ | |||||
return S_failed_checks; \ | |||||
} catch (const std::exception& e) { \ | |||||
std::cerr << "An unhandled exception occured: " << e.what() << '\n'; \ | |||||
return 2; \ | |||||
} \ | |||||
return ::dds::S_failed_checks; \ | |||||
} \ | |||||
static_assert(true) | |||||
} // namespace dds | |||||
#endif // DDS_UTIL_TEST_HPP_INCLUDED |