Line data Source code
1 : // Copyright (c) 2013-2021 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/cppthread
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Implementation of the logging facility.
22 : *
23 : * The library is very often used by daemons meaning that it will be running
24 : * on its own in the background. For this reason, all the output is done
25 : * through the log facility.
26 : *
27 : * This interface defines a function which you are expected to call to setup
28 : * a callback. By default, the callback is set to a function that simply
29 : * prints errors to std::cerr.
30 : *
31 : * \note
32 : * Note that the log facility is used only in extreme cases. It was made
33 : * fully thread safe, however, that implementation is considered slow.
34 : * Especially, if two different threads attempt to log a message
35 : * simultaneously, only one of them will be allowed to build their
36 : * message and the other one will be blocked for the entire time. Our
37 : * snaplogger, on the other hand, allows any number of threads to generate
38 : * errors in parallel, only the processing at the end, which can be done
39 : * asynchronously, requires serialization.
40 : */
41 :
42 : // self
43 : //
44 : #include "cppthread/log.h"
45 :
46 : #include "cppthread/exception.h"
47 :
48 :
49 : // C++ lib
50 : //
51 : #include <iostream>
52 :
53 :
54 : // last include
55 : //
56 : #include <snapdev/poison.h>
57 :
58 :
59 :
60 : namespace cppthread
61 : {
62 :
63 :
64 : /** \brief Function used to initialize the system mutex.
65 : *
66 : * This is unfortunate, but the system mutex create relies on the log
67 : * object to exist and be properly initialized before it gets created.
68 : * So we call this function from the constructor of the logger.
69 : *
70 : * \note
71 : * This means it's not exact _properly initialized_ since the constructor
72 : * did not yet return. However, it is guaranteed to work properly since
73 : * we do not allow derivations or virtual tables.
74 : */
75 : extern void create_system_mutex();
76 :
77 :
78 : /** \brief The logger object used to send logs out.
79 : *
80 : * This object is used to send data to the logger.
81 : *
82 : * \note
83 : * This works because this library does not create a thread on
84 : * initialization and therefore there is no reason for this
85 : * library to initiate a logger call. At least, at this time,
86 : * this is true and I'm not too sure what could generate such
87 : * a problem.
88 : */
89 2 : logger log;
90 :
91 :
92 : namespace
93 : {
94 :
95 :
96 : /** \brief The log callback function.
97 : *
98 : * If define (not nullptr), this callback gets called whenever a log
99 : * message is generated.
100 : *
101 : * You are expected to log the message to a file, send over a network,
102 : * etc. The default (when the pointer is nullptr) is to send the
103 : * message to std::cerr.
104 : */
105 : log_callback g_log_callback = nullptr;
106 :
107 :
108 : /** \brief The mutex used to ensure proper synchronization.
109 : *
110 : * The g_log_mutex variable is used to lock the cppthread logger so
111 : * functions that need to run in a single thread at a time can run
112 : * properly.
113 : */
114 : pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER;
115 :
116 :
117 : /** \brief Whether the lock is currently active.
118 : *
119 : * This variable holds true or false. When false, no other thread holds
120 : * the lock so we are the only one logging data. Once true, we are the
121 : * one holding the lock if we pass through.
122 : */
123 : bool g_log_locked = false;
124 :
125 :
126 : /** \brief Whether the g_log_recursive_mutex was initialized.
127 : *
128 : * Whenever a log message is to be sent, we need a recursive lock
129 : * to make sure that we can properly lock one or the other thread.
130 : *
131 : * This flag tells us whether we still have to initialize the
132 : * g_log_recursive_mutex variable.
133 : */
134 : bool g_log_recursive_initialized = false;
135 :
136 :
137 : /** \brief We need a recursive lock and can't know whether the default is.
138 : *
139 : * In order to implement our lock() properly we need a recursive lock.
140 : * This variable is used for this purpose. The g_log_mutex is first
141 : * used to make sure we initialized this recursive lock. Since there
142 : * is nothing that tells us whether a mutex was initialized we also
143 : * need a flag: g_log_recursive_initialized.
144 : *
145 : * \warning
146 : * We do not use the cppthread mutex class because it calls us, so we
147 : * could otherwise end up in an infinite loop.
148 : */
149 : pthread_mutex_t g_log_recursive_mutex;
150 :
151 :
152 : } // no name namespace
153 :
154 :
155 :
156 : /** \brief Set a callback function.
157 : *
158 : * Set a callback function used to redirect the logs generated by the
159 : * cppthread library and any library that makes use of this log
160 : * facility (i.e. the advgetopt project does so).
161 : *
162 : * \note
163 : * You should not use this facility unless you do not have access to
164 : * the snaplogger, somehow. The snaplogger is a much more advanced
165 : * and better interface especially in a multithreaded application.
166 : *
167 : * \param[in] callback The function to call whenever a log is generated.
168 : */
169 0 : void set_log_callback(log_callback callback)
170 : {
171 0 : pthread_mutex_lock(&g_log_mutex);
172 0 : g_log_callback = callback;
173 0 : pthread_mutex_unlock(&g_log_mutex);
174 0 : }
175 :
176 :
177 : /** \class logger
178 : * \brief The cppthread logger.
179 : *
180 : * The cppthread is a dependency of the snaplogger so we have to have
181 : * our own logger implementation in this class.
182 : *
183 : * This class is very basic. It only supports 5 severity levels and a simple
184 : * mechanism to write messages to a log file or a callback.
185 : *
186 : * \note
187 : * If you can use the snaplogger library, then you should avoid using
188 : * this cppthread implementation. The snaplogger will automatically
189 : * provide a callback to this cppthread implementation and output
190 : * the logs as required. It's just that the snaplogger is way more
191 : * powerful.
192 : * \note
193 : * This is actually the main reason why the class is marked final.
194 : */
195 :
196 :
197 : /** \brief Initialize the logger.
198 : *
199 : * The logger makes sure that the system mutex gets allocated. This
200 : * way if an error occurs in that initialization process, it will
201 : * happen after the logger gets initialized. Although it can happen
202 : * before the logger initialization returns, the object initialization
203 : * is already complete when we reach the call to the
204 : * create_system_mutex() function.
205 : */
206 2 : logger::logger()
207 : {
208 : try
209 : {
210 2 : create_system_mutex();
211 : }
212 0 : catch(...)
213 : {
214 0 : std::cerr << "fatal: could not create system mutex."
215 0 : << std::endl;
216 0 : std::terminate();
217 : }
218 2 : }
219 :
220 :
221 : /** \brief Lock the system so a log can be emitted properly.
222 : *
223 : * This function allows the locking of the logger. This ensures that
224 : * we can use a simple object even in a multithread environment. The
225 : * only potential problem now is a deadlock.
226 : *
227 : * Note that you may call the lock() function any number of times. It
228 : * will not count the number of calls. It locks only once. If already
229 : * locked, it is like a passthrough. If another thread already acquired
230 : * the lock, then the function blocks until the other thread is done
231 : * with the logger.
232 : */
233 0 : void logger::lock()
234 : {
235 0 : int err(pthread_mutex_lock(&g_log_mutex));
236 0 : if(err != 0)
237 : {
238 0 : std::cerr << "fatal: a mutex lock generated error #"
239 0 : << err
240 0 : << std::endl;
241 0 : pthread_mutex_unlock(&g_log_mutex);
242 0 : std::terminate();
243 : }
244 :
245 0 : if(!g_log_recursive_initialized)
246 : {
247 0 : g_log_recursive_initialized = true;
248 :
249 0 : pthread_mutexattr_t mattr;
250 0 : err = pthread_mutexattr_init(&mattr);
251 0 : if(err != 0)
252 : {
253 0 : std::cerr << "fatal: a mutex attribute structure could not be initialized, error #"
254 0 : << err
255 0 : << std::endl;
256 0 : pthread_mutex_unlock(&g_log_mutex);
257 0 : std::terminate();
258 : }
259 0 : err = pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
260 0 : if(err != 0)
261 : {
262 0 : std::cerr << "fatal: a mutex attribute structure type could not be setup, error #"
263 0 : << err
264 0 : << std::endl;
265 0 : pthread_mutex_unlock(&g_log_mutex);
266 0 : std::terminate();
267 : }
268 0 : err = pthread_mutex_init(&g_log_recursive_mutex, &mattr);
269 0 : if(err != 0)
270 : {
271 0 : std::cerr << "fatal: a mutex structure could not be initialized, error #"
272 0 : << err
273 0 : << std::endl;
274 0 : pthread_mutex_unlock(&g_log_mutex);
275 0 : std::terminate();
276 : }
277 0 : err = pthread_mutexattr_destroy(&mattr);
278 0 : if(err != 0)
279 : {
280 0 : std::cerr << "fatal: a mutex attribute structure could not be destroyed, error #"
281 0 : << err
282 0 : << std::endl;
283 0 : pthread_mutex_unlock(&g_log_mutex);
284 0 : std::terminate();
285 : }
286 : }
287 0 : err = pthread_mutex_unlock(&g_log_mutex);
288 0 : if(err != 0)
289 : {
290 0 : std::cerr << "fatal: a mutex unlock generated error #"
291 0 : << err
292 0 : << std::endl;
293 0 : std::terminate();
294 : }
295 :
296 : // we have to lock only once so we use a flag to know whether we're
297 : // already locked, if so, we unlock immediately (but we stil have
298 : // one lock in place)
299 : //
300 0 : err = pthread_mutex_lock(&g_log_recursive_mutex);
301 0 : if(err != 0)
302 : {
303 0 : std::cerr << "fatal: a mutex lock generated error #"
304 0 : << err
305 0 : << std::endl;
306 0 : std::terminate();
307 : }
308 :
309 0 : if(g_log_locked)
310 : {
311 0 : err = pthread_mutex_unlock(&g_log_recursive_mutex);
312 0 : if(err != 0)
313 : {
314 0 : std::cerr << "fatal: a mutex unlock generated error #"
315 0 : << err
316 0 : << std::endl;
317 0 : std::terminate();
318 : }
319 : }
320 : else
321 : {
322 0 : g_log_locked = true;
323 : }
324 0 : }
325 :
326 :
327 : /** \brief Unlock the logger once we are done with it.
328 : *
329 : * Whenever the end() function gets called, the logger reacts by sending
330 : * the collected message to the user defined callback or to std::cerr.
331 : *
332 : * Note that the number of calls to the lock are not limited. However,
333 : * the unlock() function is called only once in the end() function.
334 : * Attempting to unlock the logger more than once is a bug and undefined
335 : * behavior may ensue. The class tries to emit an error when that happens,
336 : * but it is unlikely to catch the error each time.
337 : */
338 0 : void logger::unlock()
339 : {
340 0 : if(!g_log_locked)
341 : {
342 0 : std::cerr << "fatal: logger::unlock() called with g_log_locked == false"
343 0 : << std::endl;
344 0 : std::terminate();
345 : }
346 :
347 0 : g_log_locked = false;
348 :
349 0 : int err(pthread_mutex_unlock(&g_log_recursive_mutex));
350 0 : if(err != 0)
351 : {
352 0 : std::cerr << "fatal: a mutex unlock generated error #"
353 0 : << err
354 0 : << std::endl;
355 0 : pthread_mutex_unlock(&g_log_recursive_mutex);
356 0 : std::terminate();
357 : }
358 0 : }
359 :
360 :
361 : /** \brief Save the level at which to log this message.
362 : *
363 : * This function gets called whenever you apply a level. This is expected
364 : * as the very first parameter of the log. It may be used to shortcut
365 : * the addition of other message data to avoid wasting time.
366 : *
367 : * The supported levels are defined in the log_level_t enumeration:
368 : *
369 : * * log_level_t::debug
370 : * * log_level_t::info
371 : * * log_level_t::warning
372 : * * log_level_t::error
373 : * * log_level_t::fatal
374 : *
375 : * Note that the fatal error level has no specific effect. It just
376 : * displays a level of "fatal". If you want to stop the software,
377 : * you are in charge of calling std::terminate() or exit().
378 : *
379 : * \note
380 : * The snaplogger has a special handler you can setup to capture fatal
381 : * errors which allows you to exit your software when such an error
382 : * occurs. It uses an exception for the purpose.
383 : *
384 : * \param[in] level The level this message represents.
385 : *
386 : * \return A reference to this logger.
387 : */
388 0 : logger & logger::operator << (log_level_t const & level)
389 : {
390 0 : lock();
391 0 : f_level = level;
392 0 : return *this;
393 : }
394 :
395 :
396 : /** \brief Execute a function.
397 : *
398 : * This call accepts a special value representing a logger function which
399 : * gets called with the logger as a reference parameter.
400 : *
401 : * This is currently used by the end() function.
402 : *
403 : * \param[in] func The function to call with this logger as parameter.
404 : *
405 : * \return A reference to this logger.
406 : */
407 0 : logger & logger::operator << (logger & (*func)(logger &))
408 : {
409 0 : lock();
410 0 : func(*this);
411 0 : return *this;
412 : }
413 :
414 :
415 : /** \brief End the logger's message.
416 : *
417 : * This function is called whenever you apply the end() function to
418 : * the logger. It processes the message and sends it to the log
419 : * callback function or prints it to std::cerr.
420 : *
421 : * \note
422 : * If not callback was setup, the function throws away any debug
423 : * messages and prints out the other messages to std::cerr.
424 : *
425 : * \note
426 : * The log system has a lock in place whenever you start sending log
427 : * data. This function unlocks the logger before returning. It is
428 : * also exception safe.
429 : *
430 : * \return A reference to the logger object.
431 : */
432 0 : logger & logger::end()
433 : {
434 : // the std::cerr requires a lock so we keep the logger locked
435 : //
436 : try
437 : {
438 0 : if(g_log_callback != nullptr)
439 : {
440 0 : g_log_callback(f_level, f_log.str());
441 : }
442 0 : else if(f_level >= log_level_t::info)
443 : {
444 0 : std::cerr << to_string(f_level)
445 : << ": "
446 0 : << f_log.str()
447 0 : << std::endl;
448 : }
449 :
450 0 : f_log.str(std::string());
451 : }
452 0 : catch(...)
453 : {
454 0 : unlock();
455 0 : throw;
456 : }
457 :
458 0 : unlock();
459 :
460 0 : return *this;
461 : }
462 :
463 :
464 : /** \brief Convert a log level to a string.
465 : *
466 : * This function transforms a log_level_t value to a string which can then
467 : * be used in a log message.
468 : *
469 : * \exception cppthread_invalid_error
470 : * If the log level is not one of the know log levels, then the function
471 : * raises this exception.
472 : *
473 : * \param[in] level The message log level to convert to a string.
474 : *
475 : * \return A string representing the log level.
476 : */
477 0 : std::string to_string(log_level_t level)
478 : {
479 0 : switch(level)
480 : {
481 0 : case log_level_t::debug:
482 0 : return "debug";
483 :
484 0 : case log_level_t::info:
485 0 : return "info";
486 :
487 0 : case log_level_t::warning:
488 0 : return "warning";
489 :
490 0 : case log_level_t::error:
491 0 : return "error";
492 :
493 0 : case log_level_t::fatal:
494 0 : return "fatal";
495 :
496 : }
497 :
498 : throw cppthread_invalid_error("unknown log level ("
499 0 : + std::to_string(static_cast<int>(level))
500 0 : + ")");
501 : }
502 :
503 :
504 : /** \fn logger::operator << (T const & v)
505 : * \brief Send any type of data to the logger.
506 : *
507 : * This operator allows to send data of type T to the logger.
508 : *
509 : * The logger makes use of a stringstream to generate the final message.
510 : * The type T must be a type that the stringstream supports. In most
511 : * cases, these are the types that the iostream supports.
512 : *
513 : * \param[in] v The value to be output to this log message.
514 : *
515 : * \return A reference to this logger.
516 : */
517 :
518 :
519 : /** \enum log_level_t
520 : * \brief The log level or severity.
521 : *
522 : * How important/severe the log message is.
523 : *
524 : * \warning
525 : * Note that logging a message with level fatal does not stop your program.
526 : * It's just a log level. You are responsible for stopping your program
527 : * if the error is indeed fatal.
528 : */
529 :
530 :
531 : /** \var logger::f_level
532 : * \brief The level of this message.
533 : *
534 : * This variable member holds the level of the message. The level can
535 : * be specified within the set of `<<` operators.
536 : *
537 : * The default is to display informational, warning, error, and fatal
538 : * error messages to the error output (stderr). Other messages (debug)
539 : * get dropped. If you have a callback assigned (i.e. for example, if
540 : * you use snaplogger in your application), then all the messages get
541 : * redirected to that callback.
542 : */
543 :
544 :
545 : /** \var logger::f_log
546 : * \brief The log message.
547 : *
548 : * This variable member is a stringstream which holds the current message.
549 : * Once the end() is sent, it outputs the f_log.str() message.
550 : *
551 : * \note
552 : * In order for messages to not criss-cross each other, the implementation
553 : * makes use of a global lock. That means only one thread can be sending
554 : * a log message at a time. The unlock() happens when the end() function
555 : * gets called.
556 : */
557 :
558 :
559 : /** \fn end(logger & l)
560 : * \brief Close a log statement.
561 : *
562 : * This function is used to simplified the end of a log statement:
563 : *
564 : * \code
565 : * int err(errno);
566 : *
567 : * log << cppthread::log_level_t::fatal
568 : * << "the initialization failed with errno = "
569 : * << err
570 : * << cppthread::end;
571 : * \endcode
572 : *
573 : * The result is a call to the logger::end() function which sends the message
574 : * to the logger current output.
575 : *
576 : * \param[in] l A reference to the logger.
577 : *
578 : * \return A reference to the logger.
579 : */
580 :
581 :
582 : /** \typedef log_callback
583 : * \brief The log callback type definition.
584 : *
585 : * By default, log messages will be sent to your console using std::cerr.
586 : * By setting up a log_callback function instead, it will be sent to
587 : * your function.
588 : *
589 : * \param[in] level The level (severity) of this log message.
590 : * \param[in] message The message to be logged.
591 : */
592 :
593 :
594 6 : } // namespace cppthread
595 : // vim: ts=4 sw=4 et
|