Skip to content
Commits on Source (3)
Checks: 'modernize-*,modernize-use-override,google-*,-google-runtime-references,misc-*,clang-analyzer-*'
WarningsAsErrors: ''
HeaderFilterRegex: 'async.h|async_logger.h|common.h|details|formatter.h|logger.h|sinks|spdlog.h|tweakme.h|version.h'
AnalyzeTemporaryDtors: false
FormatStyle: none
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
......@@ -9,7 +9,6 @@ addons: &gcc48
apt:
packages:
- g++-4.8
- valgrind
sources:
- ubuntu-toolchain-r-test
......@@ -17,7 +16,6 @@ addons: &gcc7
apt:
packages:
- g++-7
- valgrind
sources:
- ubuntu-toolchain-r-test
......@@ -25,7 +23,6 @@ addons: &clang35
apt:
packages:
- clang-3.5
- valgrind
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.5
......@@ -34,11 +31,11 @@ addons: &clang6
apt:
packages:
- clang-6.0
- valgrind
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-6.0
matrix:
include:
# Test gcc-4.8: C++11, Build=Debug/Release
......@@ -81,16 +78,21 @@ matrix:
os: linux
addons: *clang6
# osx
- env: BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=Off
os: osx
before_script:
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
- if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CXX="clang++" CC="clang"; fi
- which $CXX
- which $CC
- which valgrind
- $CXX --version
- cmake --version
- valgrind --version
script:
- cd ${TRAVIS_BUILD_DIR}
......
......@@ -4,7 +4,7 @@
#
cmake_minimum_required(VERSION 3.1)
project(spdlog VERSION 1.1.0 LANGUAGES CXX)
project(spdlog VERSION 1.2.0 LANGUAGES CXX)
include(CTest)
include(CMakeDependentOption)
include(GNUInstallDirs)
......@@ -28,7 +28,10 @@ set(CMAKE_CXX_EXTENSIONS OFF)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
add_compile_options("-Wall")
add_compile_options("-Wextra")
add_compile_options("-Wconversion")
add_compile_options("-pedantic")
add_compile_options("-Wfatal-errors")
endif()
#---------------------------------------------------------------------------------------
......@@ -42,11 +45,17 @@ include(cmake/sanitizers.cmake)
add_library(spdlog INTERFACE)
add_library(spdlog::spdlog ALIAS spdlog)
option(SPDLOG_BUILD_EXAMPLES "Build examples" ON)
option(SPDLOG_BUILD_BENCH "Build benchmarks" ON)
# Check if spdlog is being used directly or via add_subdirectory
set(SPDLOG_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(SPDLOG_MASTER_PROJECT ON)
endif()
option(SPDLOG_BUILD_EXAMPLES "Build examples" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_BUILD_BENCH "Build benchmarks" ${SPDLOG_MASTER_PROJECT})
cmake_dependent_option(SPDLOG_BUILD_TESTING
"Build spdlog tests" ON
"Build spdlog tests" ${SPDLOG_MASTER_PROJECT}
"BUILD_TESTING" OFF
)
......
......@@ -3,6 +3,7 @@
Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)  [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog)
## Install
#### Just copy the headers:
......@@ -20,18 +21,17 @@ Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.
## Platforms
* Linux, FreeBSD, Solaris, AIX
* Windows (vc 2013+, cygwin)
* Mac OSX (clang 3.5+)
* Linux, FreeBSD, OpenBSD, Solaris, AIX
* Windows (msvc 2013+, cygwin)
* macOS (clang 3.5+)
* Android
## Features
* Very fast - performance is the primary goal (see [benchmarks](#benchmarks) below).
* Very fast (see [benchmarks](#benchmarks) below).
* Headers only, just copy and use.
* Feature rich using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Fast asynchronous mode (optional)
* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
* Conditional Logging
* Multi/Single threaded loggers.
* Various log targets:
* Rotating log files.
......@@ -41,7 +41,7 @@ Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.
* Windows debugger (```OutputDebugString(..)```)
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
* Severity based filtering - threshold levels can be modified in runtime as well as in compile time.
* Binary data logging.
## Benchmarks
......@@ -53,28 +53,26 @@ Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/ben
*******************************************************************************
Single thread, 1,000,000 iterations
*******************************************************************************
basic_st... Elapsed: 0.226664 4,411,806/sec
rotating_st... Elapsed: 0.214339 4,665,499/sec
daily_st... Elapsed: 0.211292 4,732,797/sec
null_st... Elapsed: 0.102815 9,726,227/sec
basic_st... Elapsed: 0.181652 5,505,042/sec
rotating_st... Elapsed: 0.181781 5,501,117/sec
daily_st... Elapsed: 0.187595 5,330,630/sec
null_st... Elapsed: 0.0504704 19,813,602/sec
*******************************************************************************
10 threads sharing same logger, 1,000,000 iterations
*******************************************************************************
basic_mt... Elapsed: 0.882268 1,133,441/sec
rotating_mt... Elapsed: 0.875515 1,142,184/sec
daily_mt... Elapsed: 0.879573 1,136,915/sec
null_mt... Elapsed: 0.220114 4,543,105/sec
basic_mt... Elapsed: 0.616035 1,623,284/sec
rotating_mt... Elapsed: 0.620344 1,612,008/sec
daily_mt... Elapsed: 0.648353 1,542,369/sec
null_mt... Elapsed: 0.151972 6,580,166/sec
```
#### Asynchronous mode
```
*******************************************************************************
10 threads sharing same logger, 1,000,000 iterations
*******************************************************************************
async... Elapsed: 0.429088 2,330,524/sec
async... Elapsed: 0.411501 2,430,126/sec
async... Elapsed: 0.428979 2,331,116/sec
async... Elapsed: 0.350066 2,856,606/sec
async... Elapsed: 0.314865 3,175,960/sec
async... Elapsed: 0.349851 2,858,358/sec
```
## Usage samples
......@@ -130,7 +128,6 @@ void basic_logfile_example()
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
return 1;
}
}
```
......@@ -158,6 +155,18 @@ void daily_example()
```
---
#### Cloning loggers
```c++
// clone a logger and give it new name.
// Useful for creating subsystem loggers from some "root" logger
void clone_example()
{
auto network_logger = spdlog::get("root")->clone("network");
network_logger->info("Logging network stuff..");
}
```
---
#### Periodic flush
```c++
......@@ -167,6 +176,33 @@ spdlog::flush_every(std::chrono::seconds(3));
```
---
#### Binary logging
```c++
// log binary data as hex.
// many types of std::container<char> types can be used.
// ranges are supported too.
// 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.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
auto console = spdlog::get("console");
std::array<char, 80> buf;
console->info("Binary example: {}", spdlog::to_hex(buf));
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
```
---
#### Logger with multi sinks - each with different format and log level
......
......@@ -35,6 +35,7 @@ int count_lines(const char *filename)
if ('\n' == ch)
counter++;
}
fclose(infile);
return counter;
}
......
......@@ -35,8 +35,8 @@ int main(int argc, char *argv[])
int howmany = 1000000;
int queue_size = howmany + 2;
int threads = 10;
int file_size = 30 * 1024 * 1024;
int rotating_files = 5;
size_t file_size = 30 * 1024 * 1024;
size_t rotating_files = 5;
try
{
......@@ -54,7 +54,7 @@ int main(int argc, char *argv[])
cout << "******************************************************************"
"*************\n";
auto basic_st = spdlog::basic_logger_mt("basic_st", "logs/basic_st.log", true);
auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
bench(howmany, basic_st);
auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files);
......@@ -89,7 +89,7 @@ int main(int argc, char *argv[])
for (int i = 0; i < 3; ++i)
{
spdlog::init_thread_pool(queue_size, 1);
spdlog::init_thread_pool(static_cast<size_t>(queue_size), 1);
auto as = spdlog::basic_logger_mt<spdlog::async_factory>("async", "logs/basic_async.log", true);
bench_mt(howmany, as, threads);
spdlog::drop("async");
......
......@@ -35,8 +35,8 @@ int main(int, char *[])
int howmany = 1000000;
int queue_size = howmany + 2;
int threads = 10;
int file_size = 30 * 1024 * 1024;
int rotating_files = 5;
size_t file_size = 30 * 1024 * 1024;
size_t rotating_files = 5;
try
{
......@@ -82,7 +82,7 @@ int main(int, char *[])
for (int i = 0; i < 3; ++i)
{
spdlog::init_thread_pool(queue_size, 1);
spdlog::init_thread_pool(static_cast<size_t>(queue_size), 1);
auto as = spdlog::basic_logger_mt<spdlog::async_factory>("async", "logs/basic_async.log", true);
bench_mt(howmany, as, threads);
spdlog::drop("async");
......
#!/bin/bash
clang-tidy example/example.cpp -- -I ./include
spdlog (1:1.2.0-1) unstable; urgency=medium
* New upstream release.
-- Michael R. Crusoe <michael.crusoe@gmail.com> Wed, 17 Oct 2018 03:10:17 -0700
spdlog (1:1.1.0-2) unstable; urgency=medium
* Team upload.
......
CXX ?= g++
CXX_FLAGS = -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1
CXX_FLAGS = -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 -Wconversion
CXX_RELEASE_FLAGS = -O3 -march=native
CXX_DEBUG_FLAGS= -g
......
#-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded
CXX ?= g++
CXX_FLAGS = -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 -Wconversion -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-weak-vtables -Wno-global-constructors
CXX_RELEASE_FLAGS = -O3 -march=native
CXX_DEBUG_FLAGS= -g
all: example
debug: example-debug
example: example.cpp
$(CXX) example.cpp -o example $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS)
example-debug: example.cpp
$(CXX) example.cpp -o example-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS)
clean:
rm -f *.o logs/*.txt example example-debug
rebuild: clean all
rebuild-debug: clean debug
......@@ -14,10 +14,12 @@ void basic_example();
void rotating_example();
void daily_example();
void async_example();
void binary_example();
void multi_sink_example();
void user_defined_example();
void err_handler_example();
void syslog_example();
void clone_example();
#include "spdlog/spdlog.h"
......@@ -34,9 +36,14 @@ int main(int, char *[])
rotating_example();
daily_example();
clone_example();
// async logging using a backing thread pool
async_example();
// log binary data
binary_example();
// a logger can have multiple targets with different formats
multi_sink_example();
......@@ -94,6 +101,7 @@ void stdout_example()
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
console->info("This an info message with custom format");
spdlog::set_pattern("%+"); // back to default format
// Compile time log levels
// define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON
......@@ -122,6 +130,14 @@ void daily_example()
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}
// clone a logger and give it new name.
// Useful for creating component/subsystem loggers from some "root" logger
void clone_example()
{
auto network_logger = spdlog::get("console")->clone("network");
network_logger->info("Logging network stuff..");
}
#include "spdlog/async.h"
void async_example()
{
......@@ -137,6 +153,29 @@ void async_example()
}
}
// log binary data as hex.
// many types of std::container<char> types can be used.
// ranges are supported too.
// 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.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
auto console = spdlog::get("console");
std::array<char, 80> buf;
console->info("Binary example: {}", spdlog::to_hex(buf));
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
// create logger with 2 targets with different log levels and formats
// the console will show only warnings or errors, while the file will log all
......
#!/bin/bash
echo -n "Running dos2unix "
find . -name "*\.h" -o -name "*\.cpp"|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
echo
echo -n "Running clang-format "
find . -name "*\.h" -o -name "*\.cpp"|xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
echo
......@@ -40,13 +40,13 @@ namespace details {
class thread_pool;
}
class async_logger SPDLOG_FINAL : public std::enable_shared_from_this<async_logger>, public logger
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, const It &begin, const It &end, std::weak_ptr<details::thread_pool> tp,
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);
async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp,
......@@ -55,11 +55,13 @@ public:
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_(details::log_msg &msg) override;
void flush_() override;
void backend_log_(details::log_msg &incoming_log_msg);
void backend_log_(const details::log_msg &incoming_log_msg);
void backend_flush_();
private:
......
......@@ -14,6 +14,7 @@
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
......@@ -32,13 +33,6 @@
#define SPDLOG_CONSTEXPR constexpr
#endif
// final keyword support. On by default. See tweakme.h
#if defined(SPDLOG_NO_FINAL)
#define SPDLOG_FINAL
#else
#define SPDLOG_FINAL final
#endif
#if defined(__GNUC__) || defined(__clang__)
#define SPDLOG_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
......@@ -135,8 +129,8 @@ enum class pattern_time_type
class spdlog_ex : public std::exception
{
public:
explicit spdlog_ex(const std::string &msg)
: msg_(msg)
explicit spdlog_ex(std::string msg)
: msg_(std::move(msg))
{
}
......@@ -174,4 +168,19 @@ using filename_t = std::string;
{ \
err_handler_("Unknown exeption in logger"); \
}
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
......@@ -16,7 +16,7 @@
template<typename It>
inline spdlog::async_logger::async_logger(
std::string logger_name, const It &begin, const It &end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
: logger(std::move(logger_name), begin, end)
, thread_pool_(tp)
, overflow_policy_(overflow_policy)
......@@ -67,7 +67,7 @@ inline void spdlog::async_logger::flush_()
//
// backend functions - called from the thread pool to do the actual job
//
inline void spdlog::async_logger::backend_log_(details::log_msg &incoming_log_msg)
inline void spdlog::async_logger::backend_log_(const details::log_msg &incoming_log_msg)
{
try
{
......@@ -98,3 +98,13 @@ inline void spdlog::async_logger::backend_flush_()
}
SPDLOG_CATCH_AND_HANDLE
}
inline std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name)
{
auto cloned = std::make_shared<spdlog::async_logger>(std::move(new_name), sinks_.begin(), sinks_.end(), thread_pool_, overflow_policy_);
cloned->set_level(this->level());
cloned->flush_on(this->flush_level());
cloned->set_error_handler(this->error_handler());
return std::move(cloned);
}
......@@ -8,6 +8,19 @@
#include <cstdio>
#include <mutex>
#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
namespace spdlog {
namespace details {
struct console_stdout
......
......@@ -5,13 +5,12 @@
#pragma once
// Helper class for file sink
// When failing to open a file, retry several times(5) with small delay between
// the tries(10 ms)
// Throw spdlog_ex exception on errors
// 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.
#include "../details/log_msg.h"
#include "../details/os.h"
#include "spdlog/details/log_msg.h"
#include "spdlog/details/os.h"
#include <cerrno>
#include <chrono>
......@@ -146,7 +145,7 @@ public:
}
private:
FILE *fd_{nullptr};
std::FILE *fd_{nullptr};
filename_t _filename;
};
} // namespace details
......
......@@ -22,12 +22,8 @@ inline void append_str(const std::string &str, fmt::basic_memory_buffer<char, Bu
template<size_t Buffer_Size>
inline void append_c_str(const char *c_str, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{
char ch;
while ((ch = *c_str) != '\0')
{
dest.push_back(ch);
++c_str;
}
auto len = std::char_traits<char>::length(c_str);
dest.append(c_str, c_str + len);
}
template<size_t Buffer_Size1, size_t Buffer_Size2>
......@@ -54,14 +50,14 @@ inline void pad2(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
}
if (n > 9) // 10-99
{
dest.push_back('0' + static_cast<char>(n / 10));
dest.push_back('0' + static_cast<char>(n % 10));
dest.push_back(static_cast<char>('0' + n / 10));
dest.push_back(static_cast<char>('0' + n % 10));
return;
}
if (n >= 0) // 0-9
{
dest.push_back('0');
dest.push_back('0' + static_cast<char>(n));
dest.push_back(static_cast<char>('0' + n));
return;
}
// negatives (unlikely, but just in case, let fmt deal with it)
......@@ -79,22 +75,22 @@ inline void pad3(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
if (n > 99) // 100-999
{
append_int(n / 100, dest);
dest.push_back(static_cast<char>('0' + n / 100));
pad2(n % 100, dest);
return;
}
if (n > 9) // 10-99
{
dest.push_back('0');
dest.push_back('0' + static_cast<char>(n / 10));
dest.push_back('0' + static_cast<char>(n % 10));
dest.push_back(static_cast<char>('0' + n / 10));
dest.push_back(static_cast<char>('0' + n % 10));
return;
}
if (n >= 0)
{
dest.push_back('0');
dest.push_back('0');
dest.push_back('0' + static_cast<char>(n));
dest.push_back(static_cast<char>('0' + n));
return;
}
// negatives (unlikely, but just in case let fmt deal with it)
......@@ -119,7 +115,8 @@ inline void pad6(size_t n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
template<typename ToDuration>
inline ToDuration time_fraction(const log_clock::time_point &tp)
{
using namespace std::chrono;
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);
......
......@@ -39,8 +39,9 @@ struct log_msg
log_clock::time_point time;
size_t thread_id;
fmt::memory_buffer raw;
size_t msg_id{0};
// info about wrapping the formatted text with color
size_t msg_id;
// info about wrapping the formatted text with color (updated by pattern_formatter).
mutable size_t color_range_start{0};
mutable size_t color_range_end{0};
};
......