]> WPIA git - cassiopeia.git/blob - src/log/logger.cpp
fmt: extract lambdas to make them better formatted
[cassiopeia.git] / src / log / logger.cpp
1 #include "log/logger.hpp"
2
3 #include <cassert>
4 #include <iterator>
5 #include <algorithm>
6 #include <chrono>
7 #include <cstring>
8 #include <ostream>
9 #include <iterator>
10
11 namespace logger {
12
13     namespace impl {
14
15         /**
16          * Manages the standard-logger and the logger-stack.
17          *
18          * CAREFULL: THIS FUNCTION CONTAINS GLOBAL STATE!
19          */
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
28
29             static auto dummy = [&] {
30                 stack.push_back( &std_logger );
31                 return 0;
32             }();
33
34             ( void ) dummy;
35             return stack;
36         }
37
38         void reassign_stack_pointer( logger_set *&ptr ) {
39             const auto old_ptr = ptr;
40
41             if( ptr ) {
42                 ptr->m_stackpointer = &ptr;
43             }
44
45             ( void ) old_ptr;
46             assert( ptr == old_ptr );
47         }
48
49         void register_logger( logger_set& set ) {
50             auto& stack = logger_stack();
51
52             // we need to reassign everything if the vector reallocated:
53             const auto old_capacity = stack.capacity();
54             stack.push_back( &set );
55
56             if( stack.capacity() == old_capacity ) {
57                 reassign_stack_pointer( stack.back() );
58             } else {
59                 for( auto& ptr : stack ) {
60                     reassign_stack_pointer( ptr );
61                 }
62             }
63         }
64
65         /**
66          * Pops loggers from the stack until the last one is not a nullptr
67          */
68         void pop_loggers() {
69             auto& stack = logger_stack();
70
71             while( !stack.empty() and stack.back() == nullptr ) {
72                 stack.pop_back();
73             }
74
75             assert( stack.empty() or stack.back() != nullptr );
76         }
77
78         logger_set& active_logger() {
79             const auto result = logger_stack().back();
80             assert( result != nullptr );
81             return *result;
82         }
83
84     } // namespace impl
85
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;
92             } )->min_level;
93         }
94
95         if( r == auto_register::on ) {
96             impl::register_logger( *this );
97         }
98     }
99
100     logger_set::~logger_set() {
101         if( m_stackpointer ) {
102             *m_stackpointer = nullptr;
103             impl::pop_loggers();
104         }
105     }
106
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;
110
111         if( m_stackpointer ) {
112             *m_stackpointer = this;
113         }
114     }
115
116     logger_set& logger_set::operator=( logger_set&& other ) noexcept {
117         if( m_stackpointer ) {
118             *m_stackpointer = nullptr;
119             impl::pop_loggers();
120         }
121
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;
126
127         if( m_stackpointer ) {
128             *m_stackpointer = this;
129         }
130
131         return *this;
132     }
133
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;
138             }
139         }
140     }
141
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;
147         return returnvalue;
148     }
149
150     namespace {
151
152         std::string make_prefix( level l ) {
153             auto prefix = std::string {};
154
155             switch( l ) {
156             case level::debug:
157                 prefix = "[debug][";
158                 break;
159
160             case level::note:
161                 prefix = "[note ][";
162                 break;
163
164             case level::warn:
165                 prefix = "[warn ][";
166                 break;
167
168             case level::error:
169                 prefix = "[error][";
170                 break;
171
172             case level::fatal:
173                 prefix = "[fatal][";
174                 break;
175             }
176
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 );
182             prefix += "]: ";
183             return prefix;
184         }
185
186     } // anonymous namespace
187
188     namespace impl {
189
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();
194             auto nl_it = it;
195
196             while( ( nl_it = std::find( it, end, '\n' ) ) != end ) {
197                 ++nl_it;
198                 returnstring.append( it, nl_it );
199                 returnstring.append( length, ' ' );
200                 it = nl_it;
201             }
202
203             returnstring.append( it, end );
204             return returnstring;
205         }
206
207
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();
211
212             for( const auto& arg : args ) {
213                 msg += replace_newlines( arg, prefix_length );
214             }
215
216             msg += '\n';
217             return msg;
218         }
219
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 );
227             } );
228
229             auto msg = prefix;
230             auto arg_index = std::size_t {0};
231             auto it = fmt.begin();
232             const auto end = fmt.end();
233
234             while( it != end ) {
235                 auto pos = std::find( it, end, '%' );
236                 msg.append( it, pos );
237
238                 if( pos == end ) {
239                     break;
240                 }
241
242                 ++pos;
243
244                 if( pos == end ) {
245                     throw std::invalid_argument {"Invalid formatstring (ends on single '%')"};
246                 }
247
248                 switch( *pos ) {
249                 case '%':
250                     msg.push_back( '%' );
251                     break;
252
253                 case 's':
254                     if( arg_index >= args.size() ) {
255                         throw std::invalid_argument {"Invalid formatstring (not enough arguments)"};
256                     }
257
258                     msg.append( args[arg_index++] );
259                     break;
260
261                 default:
262                     throw std::invalid_argument {"Invalid formatstring (unknown format-character)"};
263                 }
264
265                 it = std::next( pos );
266             }
267
268             msg.push_back( '\n' );
269             return msg;
270         }
271
272     } //  namespace impl
273
274 } // namespace logger