Added periodic flusher support, and fixed some registry issues

This commit is contained in:
gabime 2018-07-21 23:30:26 +03:00
parent d5af87a8e1
commit a96b4d7529
10 changed files with 135 additions and 59 deletions

View File

@ -145,6 +145,7 @@ void rotating_example()
auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
} }
``` ```
--- ---
#### Daily files #### Daily files
```c++ ```c++
@ -157,6 +158,16 @@ void daily_example()
} }
``` ```
---
#### Periodic flush
```c++
// periodically flush all *registered* loggers every 3 seconds:
// warning: only use if all your loggers are thread safe!
spdlog::flush_every(std::chrono::seconds(3));
```
--- ---
#### Asynchronous logging #### Asynchronous logging
```c++ ```c++

View File

@ -45,8 +45,14 @@ int main(int, char *[])
// custom error handler // custom error handler
err_handler_example(); err_handler_example();
// flush all *registered* loggers using a worker thread every 3 seconds.
// note: registered loggers *must* be thread safe for this to work correctly!
spdlog::flush_every(std::chrono::seconds(3));
// apply some function on all registered loggers // apply some function on all registered loggers
spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); }); spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {
l->info("End of example.");
});
// Release and close all loggers // Release and close all loggers
spdlog::drop_all(); spdlog::drop_all();
@ -172,7 +178,9 @@ void user_defined_example()
void err_handler_example() void err_handler_example()
{ {
// can be set globally or per logger(logger->set_error_handler(..)) // can be set globally or per logger(logger->set_error_handler(..))
spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** ERROR HANDLER EXAMPLE ***: {}", msg); }); spdlog::set_error_handler([](const std::string &msg) {
spdlog::get("console")->error("*** ERROR HANDLER EXAMPLE ***: {}", msg);
});
spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
} }

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 2013
VisualStudioVersion = 15.0.27428.2037 VisualStudioVersion = 12.0.40629.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}"
EndProject EndProject
@ -29,6 +29,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "details", "details", "{08E9
..\include\spdlog\details\null_mutex.h = ..\include\spdlog\details\null_mutex.h ..\include\spdlog\details\null_mutex.h = ..\include\spdlog\details\null_mutex.h
..\include\spdlog\details\os.h = ..\include\spdlog\details\os.h ..\include\spdlog\details\os.h = ..\include\spdlog\details\os.h
..\include\spdlog\details\pattern_formatter.h = ..\include\spdlog\details\pattern_formatter.h ..\include\spdlog\details\pattern_formatter.h = ..\include\spdlog\details\pattern_formatter.h
..\include\spdlog\details\periodic_worker.h = ..\include\spdlog\details\periodic_worker.h
..\include\spdlog\details\registry.h = ..\include\spdlog\details\registry.h ..\include\spdlog\details\registry.h = ..\include\spdlog\details\registry.h
..\include\spdlog\details\spdlog_impl.h = ..\include\spdlog\details\spdlog_impl.h ..\include\spdlog\details\spdlog_impl.h = ..\include\spdlog\details\spdlog_impl.h
..\include\spdlog\details\thread_pool.h = ..\include\spdlog\details\thread_pool.h ..\include\spdlog\details\thread_pool.h = ..\include\spdlog\details\thread_pool.h
@ -101,16 +102,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1BF53532-C5DC-4236-B195-9E17CBE40A48} SolutionGuid = {1BF53532-C5DC-4236-B195-9E17CBE40A48}
EndGlobalSection EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal EndGlobal

View File

@ -31,7 +31,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset> <PlatformToolset>v120</PlatformToolset>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
@ -43,7 +43,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset> <PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
@ -86,7 +86,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

View File

@ -19,8 +19,12 @@
#include "spdlog/details/thread_pool.h" #include "spdlog/details/thread_pool.h"
#include <memory> #include <memory>
namespace spdlog { namespace spdlog {
namespace details {
static const size_t default_async_q_size = 8192;
}
// async logger factory - creates async loggers backed with thread pool. // 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. // if a global thread pool doesn't already exist, create it with default queue size of 8192 items and single thread.
@ -32,11 +36,11 @@ struct async_factory_impl
{ {
using details::registry; using details::registry;
std::lock_guard<registry::MutexT> lock(registry::instance().tp_mutex()); std::lock_guard<std::recursive_mutex> lock(registry::instance().tp_mutex());
auto tp = registry::instance().get_thread_pool(); auto tp = registry::instance().get_thread_pool();
if (tp == nullptr) if (tp == nullptr)
{ {
tp = std::make_shared<details::thread_pool>(8192, 1); tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1);
registry::instance().set_thread_pool(tp); registry::instance().set_thread_pool(tp);
} }
@ -62,7 +66,7 @@ inline std::shared_ptr<spdlog::logger> create_async_nb(const std::string &logger
{ {
return async_factory_nonblock::create<Sink>(logger_name, std::forward<SinkArgs>(sink_args)...); return async_factory_nonblock::create<Sink>(logger_name, std::forward<SinkArgs>(sink_args)...);
} }
// set global thread pool. // set global thread pool.
inline void init_thread_pool(size_t q_size, size_t thread_count) inline void init_thread_pool(size_t q_size, size_t thread_count)

View File

@ -12,6 +12,7 @@
#include "spdlog/common.h" #include "spdlog/common.h"
#include "spdlog/logger.h" #include "spdlog/logger.h"
#include "spdlog/details/periodic_worker.h"
#include <chrono> #include <chrono>
#include <functional> #include <functional>
@ -34,7 +35,7 @@ public:
void register_logger(std::shared_ptr<logger> new_logger) void register_logger(std::shared_ptr<logger> new_logger)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
auto logger_name = new_logger->name(); auto logger_name = new_logger->name();
throw_if_exists_(logger_name); throw_if_exists_(logger_name);
loggers_[logger_name] = new_logger; loggers_[logger_name] = new_logger;
@ -42,7 +43,7 @@ public:
void register_and_init(std::shared_ptr<logger> new_logger) void register_and_init(std::shared_ptr<logger> new_logger)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
auto logger_name = new_logger->name(); auto logger_name = new_logger->name();
throw_if_exists_(logger_name); throw_if_exists_(logger_name);
@ -63,26 +64,26 @@ public:
std::shared_ptr<logger> get(const std::string &logger_name) std::shared_ptr<logger> get(const std::string &logger_name)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
auto found = loggers_.find(logger_name); auto found = loggers_.find(logger_name);
return found == loggers_.end() ? nullptr : found->second; return found == loggers_.end() ? nullptr : found->second;
} }
void set_thread_pool(std::shared_ptr<thread_pool> tp) void set_thread_pool(std::shared_ptr<thread_pool> tp)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<decltype(tp_mutex_)> lock(tp_mutex_);
tp_ = std::move(tp); tp_ = std::move(tp);
} }
std::shared_ptr<thread_pool> get_thread_pool() std::shared_ptr<thread_pool> get_thread_pool()
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<decltype(tp_mutex_)> lock(tp_mutex_);
return tp_; return tp_;
} }
void set_pattern(const std::string &pattern, pattern_time_type time_type) void set_pattern(const std::string &pattern, pattern_time_type time_type)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
formatter_pattern_ = pattern; formatter_pattern_ = pattern;
pattern_time_type_ = time_type; pattern_time_type_ = time_type;
for (auto &l : loggers_) for (auto &l : loggers_)
@ -93,7 +94,7 @@ public:
void set_level(level::level_enum log_level) void set_level(level::level_enum log_level)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
for (auto &l : loggers_) for (auto &l : loggers_)
{ {
l.second->set_level(log_level); l.second->set_level(log_level);
@ -103,7 +104,7 @@ public:
void flush_on(level::level_enum log_level) void flush_on(level::level_enum log_level)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
for (auto &l : loggers_) for (auto &l : loggers_)
{ {
l.second->flush_on(log_level); l.second->flush_on(log_level);
@ -111,6 +112,13 @@ public:
flush_level_ = log_level; flush_level_ = log_level;
} }
void flush_every(std::chrono::seconds interval)
{
std::lock_guard<Mutex> lock(loggers_mutex_);
std::function<void()> clbk(std::bind(&registry_t::flush_all, this));
periodic_flusher_.reset(new periodic_worker(clbk, interval));
}
void set_error_handler(log_err_handler handler) void set_error_handler(log_err_handler handler)
{ {
for (auto &l : loggers_) for (auto &l : loggers_)
@ -122,7 +130,7 @@ public:
void apply_all(std::function<void(std::shared_ptr<logger>)> fun) void apply_all(std::function<void(std::shared_ptr<logger>)> fun)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
for (auto &l : loggers_) for (auto &l : loggers_)
{ {
fun(l.second); fun(l.second);
@ -131,24 +139,24 @@ public:
void drop(const std::string &logger_name) void drop(const std::string &logger_name)
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
loggers_.erase(logger_name); loggers_.erase(logger_name);
} }
void drop_all() void drop_all()
{ {
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(loggers_mutex_);
loggers_.clear(); loggers_.clear();
} }
{ {
std::lock_guard<Mutex> lock(tp_mutex_); std::lock_guard<decltype(tp_mutex_)> lock(tp_mutex_);
tp_.reset(); tp_.reset();
} }
} }
Mutex &tp_mutex() std::recursive_mutex &tp_mutex()
{ {
return tp_mutex_; return tp_mutex_;
} }
@ -162,6 +170,11 @@ public:
private: private:
registry_t<Mutex>() = default; registry_t<Mutex>() = default;
~registry_t<Mutex>()
{
periodic_flusher_.reset();
}
void throw_if_exists_(const std::string &logger_name) void throw_if_exists_(const std::string &logger_name)
{ {
if (loggers_.find(logger_name) != loggers_.end()) if (loggers_.find(logger_name) != loggers_.end())
@ -170,8 +183,17 @@ private:
} }
} }
Mutex mutex_; void flush_all()
Mutex tp_mutex_; {
std::lock_guard<Mutex> lock(loggers_mutex_);
for (auto &l : loggers_)
{
l.second->flush();
}
}
Mutex loggers_mutex_;
std::recursive_mutex tp_mutex_;
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
std::string formatter_pattern_ = "%+"; std::string formatter_pattern_ = "%+";
pattern_time_type pattern_time_type_ = pattern_time_type::local; pattern_time_type pattern_time_type_ = pattern_time_type::local;
@ -179,6 +201,7 @@ private:
level::level_enum flush_level_ = level::off; level::level_enum flush_level_ = level::off;
log_err_handler err_handler_; log_err_handler err_handler_;
std::shared_ptr<thread_pool> tp_; std::shared_ptr<thread_pool> tp_;
std::unique_ptr<periodic_worker> periodic_flusher_;
}; };
#ifdef SPDLOG_NO_REGISTRY_MUTEX #ifdef SPDLOG_NO_REGISTRY_MUTEX

View File

@ -71,6 +71,14 @@ inline void flush_on(level::level_enum log_level)
details::registry::instance().flush_on(log_level); details::registry::instance().flush_on(log_level);
} }
// Start/Restart a periodic flusher thread
// Warning: Use only if all your loggers are thread safe!
inline void flush_every(std::chrono::seconds interval)
{
details::registry::instance().flush_every(interval);
}
// Set global error handler // Set global error handler
inline void set_error_handler(log_err_handler handler) inline void set_error_handler(log_err_handler handler)
{ {

View File

@ -27,34 +27,34 @@ TEST_CASE("discard policy ", "[async]")
using namespace spdlog; using namespace spdlog;
auto test_sink = std::make_shared<sinks::test_sink_mt>(); auto test_sink = std::make_shared<sinks::test_sink_mt>();
size_t queue_size = 2; size_t queue_size = 2;
size_t messages = 1024; size_t messages = 10240;
{
auto tp = std::make_shared<details::thread_pool>(queue_size, 1);
auto logger = std::make_shared<async_logger>("as", test_sink, tp, async_overflow_policy::overrun_oldest);
for (size_t i = 0; i < messages; i++)
{
logger->info("Hello message #{}", i);
}
}
auto tp = std::make_shared<details::thread_pool>(queue_size, 1);
auto logger = std::make_shared<async_logger>("as", test_sink, tp, async_overflow_policy::overrun_oldest);
for (size_t i = 0; i < messages; i++)
{
logger->info("Hello message");
}
REQUIRE(test_sink->msg_counter() < messages); REQUIRE(test_sink->msg_counter() < messages);
} }
TEST_CASE("discard policy using factory ", "[async]") TEST_CASE("discard policy using factory ", "[async]")
{ {
using namespace spdlog; using namespace spdlog;
//auto test_sink = std::make_shared<sinks::test_sink_mt>();
size_t queue_size = 2; size_t queue_size = 2;
size_t messages = 1024; size_t messages = 10240;
{ spdlog::init_thread_pool(queue_size, 1);
auto logger = spdlog::create_async_nb<sinks::test_sink_mt>("as2");
for (size_t i = 0; i < messages; i++) auto logger = spdlog::create_async_nb<sinks::test_sink_mt>("as2");
{ for (size_t i = 0; i < messages; i++)
logger->info("Hello message #{}", i); {
} logger->info("Hello message");
auto sink = std::static_pointer_cast<sinks::test_sink_mt>(logger->sinks()[0]); }
REQUIRE(sink->msg_counter() < messages); auto sink = std::static_pointer_cast<sinks::test_sink_mt>(logger->sinks()[0]);
} REQUIRE(sink->msg_counter() < messages);
spdlog::drop_all();
} }
TEST_CASE("flush", "[async]") TEST_CASE("flush", "[async]")
@ -73,11 +73,27 @@ TEST_CASE("flush", "[async]")
logger->flush(); logger->flush();
} }
std::this_thread::sleep_for(std::chrono::milliseconds(250)); //std::this_thread::sleep_for(std::chrono::milliseconds(250));
REQUIRE(test_sink->msg_counter() == messages); REQUIRE(test_sink->msg_counter() == messages);
REQUIRE(test_sink->flush_counter() == 1); REQUIRE(test_sink->flush_counter() == 1);
} }
TEST_CASE("async periodic flush", "[async]")
{
using namespace spdlog;
auto logger = spdlog::create_async<sinks::test_sink_mt>("as");
auto test_sink = std::static_pointer_cast<sinks::test_sink_mt>(logger->sinks()[0]);
spdlog::flush_every(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
REQUIRE(test_sink->flush_counter() == 1);
spdlog::flush_every(std::chrono::seconds(0));
spdlog::drop_all();
}
TEST_CASE("tp->wait_empty() ", "[async]") TEST_CASE("tp->wait_empty() ", "[async]")
{ {
using namespace spdlog; using namespace spdlog;

View File

@ -1,4 +1,5 @@
#include "includes.h" #include "includes.h"
#include "test_sink.h"
template<class T> template<class T>
std::string log_info(const T &what, spdlog::level::level_enum logger_level = spdlog::level::info) std::string log_info(const T &what, spdlog::level::level_enum logger_level = spdlog::level::info)
@ -75,3 +76,19 @@ TEST_CASE("to_level_enum", "[convert_to_level_enum]")
REQUIRE(spdlog::level::from_str("off") == spdlog::level::off); REQUIRE(spdlog::level::from_str("off") == spdlog::level::off);
REQUIRE(spdlog::level::from_str("null") == spdlog::level::off); REQUIRE(spdlog::level::from_str("null") == spdlog::level::off);
} }
TEST_CASE("periodic flush", "[periodic_flush]")
{
using namespace spdlog;
auto logger = spdlog::create<sinks::test_sink_mt>("periodic_flush");
auto test_sink = std::static_pointer_cast<sinks::test_sink_mt>(logger->sinks()[0]);
spdlog::flush_every(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
REQUIRE(test_sink->flush_counter() == 1);
spdlog::flush_every(std::chrono::seconds(0));
spdlog::drop_all();
}

View File

@ -110,7 +110,7 @@
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile> <ClCompile>
<WarningLevel>Level4</WarningLevel> <WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>