LCOV - code coverage report
Current view: top level - cppthread - log.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 5 123 4.1 %
Date: 2021-08-21 09:27:22 Functions: 3 10 30.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13