1 #include "log/logger.hpp"
16 * Manages the standard-logger and the logger-stack.
18 * CAREFULL: THIS FUNCTION CONTAINS GLOBAL STATE!
20 std::vector<logger_set*>& logger_stack() {
21 static auto stack = std::vector<logger_set*> {};
22 // To avoid infinite recursion, the base-logger must
23 // not auto-register but be added manually
24 static auto std_logger = logger_set {{std::cout}, auto_register::off};
25 // in order to avoid use-after-free bugs, the logger must be created after
26 // the stack, to avoid that it's destructor tries to access
27 // parts of the destroyed stack
29 static auto dummy = [&] {
30 stack.push_back( &std_logger );
38 void reassign_stack_pointer( logger_set*& ptr ) {
39 const auto old_ptr = ptr;
42 ptr->m_stackpointer = &ptr;
46 assert( ptr == old_ptr );
49 void register_logger( logger_set& set ) {
50 auto& stack = logger_stack();
52 // we need to reassign everything if the vector reallocated:
53 const auto old_capacity = stack.capacity();
54 stack.push_back( &set );
56 if( stack.capacity() == old_capacity ) {
57 reassign_stack_pointer( stack.back() );
59 for( auto& ptr : stack ) {
60 reassign_stack_pointer( ptr );
66 * Pops loggers from the stack until the last one is not a nullptr
69 auto& stack = logger_stack();
71 while( !stack.empty() and stack.back() == nullptr ) {
75 assert( stack.empty() or stack.back() != nullptr );
78 logger_set& active_logger() {
79 const auto result = logger_stack().back();
80 assert( result != nullptr );
86 logger_set::logger_set( std::initializer_list<log_target> lst, auto_register r ):
87 m_loggers{lst}, m_min_level{default_level} {
88 if( lst.size() > 0 ) {
89 m_min_level = std::min_element( lst.begin(), lst.end(),
90 []( const log_target& l, const log_target& r ) {
91 return l.min_level < r.min_level;
95 if( r == auto_register::on ) {
96 impl::register_logger( *this );
100 logger_set::~logger_set() {
101 if( m_stackpointer ) {
102 *m_stackpointer = nullptr;
107 logger_set::logger_set( logger_set&& other ) noexcept :
108 m_loggers{std::move( other.m_loggers )}, m_stackpointer{other.m_stackpointer}, m_min_level{other.m_min_level} {
109 other.m_stackpointer = nullptr;
111 if( m_stackpointer ) {
112 *m_stackpointer = this;
116 logger_set& logger_set::operator=( logger_set && other ) noexcept {
117 if( m_stackpointer ) {
118 *m_stackpointer = nullptr;
122 m_loggers = std::move( other.m_loggers );
123 m_stackpointer = other.m_stackpointer;
124 m_min_level = other.m_min_level;
125 other.m_stackpointer = nullptr;
127 if( m_stackpointer ) {
128 *m_stackpointer = this;
134 void logger_set::log_impl( level l, const std::string& msg ) {
135 for( auto& logger : m_loggers ) {
136 if( l >= logger.min_level ) {
137 *logger.stream << msg << std::flush;
142 logger_set current_logger_extended( std::initializer_list<log_target> further_targets ) {
143 auto& active = impl::active_logger();
144 auto returnvalue = logger_set{further_targets};
145 returnvalue.m_loggers.insert( returnvalue.m_loggers.end(), active.m_loggers.begin(), active.m_loggers.end() );
146 returnvalue.m_min_level = active.m_min_level;
152 std::string make_prefix( level l ) {
153 auto prefix = std::string {};
177 using clock = std::chrono::system_clock;
178 const auto now = clock::to_time_t( clock::now() );
179 // ctime appends a newline, we don't want that here:
180 auto time_str = std::ctime( &now );
181 prefix.append( time_str, time_str + std::strlen( time_str ) - 1 );
186 } // anonymous namespace
190 std::string replace_newlines( const std::string& str, std::size_t length ) {
191 auto returnstring = std::string {};
192 auto it = str.begin();
193 const auto end = str.end();
196 while( ( nl_it = std::find( it, end, '\n' ) ) != end ) {
198 returnstring.append( it, nl_it );
199 returnstring.append( length, ' ' );
203 returnstring.append( it, end );
208 std::string concat_msg( level l, const std::vector<std::string>& args ) {
209 auto msg = make_prefix( l );
210 const auto prefix_length = msg.length();
212 for( const auto& arg : args ) {
213 msg += replace_newlines( arg, prefix_length );
220 std::string format_msg( level l, const std::string& format, std::vector<std::string> args ) {
221 const auto prefix = make_prefix( l );
222 const auto length = prefix.length();
223 const auto fmt = replace_newlines( format, length );
224 std::transform( args.begin(), args.end(), args.begin(),
225 [length]( const std::string & str ) {
226 return replace_newlines( str, length );
230 auto arg_index = std::size_t {0};
231 auto it = fmt.begin();
232 const auto end = fmt.end();
235 auto pos = std::find( it, end, '%' );
236 msg.append( it, pos );
245 throw std::invalid_argument {"Invalid formatstring (ends on single '%')"};
250 msg.push_back( '%' );
254 if( arg_index >= args.size() ) {
255 throw std::invalid_argument {"Invalid formatstring (not enough arguments)"};
258 msg.append( args[arg_index++] );
262 throw std::invalid_argument {"Invalid formatstring (unknown format-character)"};
265 it = std::next( pos );
268 msg.push_back( '\n' );
274 } // namespace logger