LCOV - code coverage report
Current view: top level - snaplogger - appender.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 118 263 44.9 %
Date: 2022-07-01 22:43:09 Functions: 20 32 62.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2013-2022  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/snaplogger
       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 Appenders are used to append data to somewhere.
      22             :  *
      23             :  * This file implements the base appender class.
      24             :  */
      25             : 
      26             : 
      27             : // self
      28             : //
      29             : #include    "snaplogger/appender.h"
      30             : 
      31             : #include    "snaplogger/exception.h"
      32             : #include    "snaplogger/guard.h"
      33             : #include    "snaplogger/private_logger.h"
      34             : 
      35             : 
      36             : // snapdev
      37             : //
      38             : #include    <snapdev/empty_set_intersection.h>
      39             : #include    <snapdev/not_used.h>
      40             : 
      41             : 
      42             : // C++
      43             : //
      44             : #include    <iostream>
      45             : 
      46             : 
      47             : // C lib
      48             : //
      49             : #include    <math.h>
      50             : 
      51             : 
      52             : // last include
      53             : //
      54             : #include    <snapdev/poison.h>
      55             : 
      56             : 
      57             : 
      58             : namespace snaplogger
      59             : {
      60             : 
      61             : 
      62             : namespace
      63             : {
      64             : 
      65             : 
      66             : 
      67             : // if we want to be able to reference such we need to create it TBD
      68             : // (and it should probably be in the null_appender.cpp file instead)
      69             : //APPENDER_FACTORY(null);
      70             : 
      71             : 
      72             : }
      73             : 
      74             : 
      75          23 : appender::appender(std::string const & name, std::string const & type)
      76             :     : f_type(type)
      77             :     , f_name(name)
      78          23 :     , f_normal_component(get_component(COMPONENT_NORMAL))
      79             : {
      80          46 :     guard g;
      81             : 
      82          23 :     f_format = get_private_logger()->get_default_format();
      83          23 : }
      84             : 
      85             : 
      86          17 : appender::~appender()
      87             : {
      88          17 : }
      89             : 
      90             : 
      91           3 : std::string const & appender::get_type() const
      92             : {
      93             :     // we do not need to guard this one because we set it up on creation
      94             :     // and it can't be modified later
      95             :     //
      96           3 :     return f_type;
      97             : }
      98             : 
      99             : 
     100           0 : void appender::set_name(std::string const & name)
     101             : {
     102           0 :     guard g;
     103             : 
     104           0 :     if(f_name != "console"
     105           0 :     && f_name != "syslog")
     106             :     {
     107             :         throw invalid_parameter(
     108           0 :                   "the appender set_name() can only be used for the console & syslog appenders to rename them to your own appender name (and done internally only).");
     109             :     }
     110             : 
     111           0 :     f_name = name;
     112           0 : }
     113             : 
     114             : 
     115           1 : std::string const & appender::get_name() const
     116             : {
     117           2 :     guard g;
     118             : 
     119           2 :     return f_name;
     120             : }
     121             : 
     122             : 
     123       99214 : bool appender::is_enabled() const
     124             : {
     125      198428 :     guard g;
     126             : 
     127      198428 :     return f_enabled;
     128             : }
     129             : 
     130             : 
     131           0 : void appender::set_enabled(bool status)
     132             : {
     133           0 :     guard g;
     134             : 
     135           0 :     f_enabled = status;
     136           0 : }
     137             : 
     138             : 
     139          23 : bool appender::unique() const
     140             : {
     141          23 :     return false;
     142             : }
     143             : 
     144             : 
     145         313 : severity_t appender::get_severity() const
     146             : {
     147         626 :     guard g;
     148             : 
     149         626 :     return f_severity;
     150             : }
     151             : 
     152             : 
     153         298 : void appender::set_severity(severity_t severity_level)
     154             : {
     155         596 :     guard g;
     156             : 
     157         298 :     f_severity = severity_level;
     158         298 :     logger::get_instance()->severity_changed(severity_level);
     159         298 : }
     160             : 
     161             : 
     162           2 : void appender::reduce_severity(severity_t severity_level)
     163             : {
     164           4 :     guard g;
     165             : 
     166           2 :     if(severity_level < f_severity)
     167             :     {
     168           2 :         set_severity(severity_level);
     169             :     }
     170           2 : }
     171             : 
     172             : 
     173           0 : void appender::increase_severity(severity_t severity_level)
     174             : {
     175           0 :     guard g;
     176             : 
     177           0 :     if(severity_level > f_severity)
     178             :     {
     179           0 :         set_severity(severity_level);
     180             :     }
     181           0 : }
     182             : 
     183             : 
     184           0 : bool appender::operator < (appender const & rhs) const
     185             : {
     186           0 :     guard g;
     187             : 
     188           0 :     return f_severity < rhs.f_severity;
     189             : }
     190             : 
     191             : 
     192          27 : void appender::set_config(advgetopt::getopt const & opts)
     193             : {
     194          54 :     guard g;
     195             : 
     196             :     // ENABLE
     197             :     //
     198             :     {
     199          54 :         std::string const specialized_enabled(f_name + "::enabled");
     200          27 :         if(opts.is_defined(specialized_enabled))
     201             :         {
     202           0 :             f_enabled = opts.get_string(specialized_enabled) != "false";
     203             :         }
     204          27 :         else if(opts.is_defined("enabled"))
     205             :         {
     206           0 :             f_enabled = opts.get_string("enabled") != "false";
     207             :         }
     208             :         else
     209             :         {
     210          27 :             f_enabled = true;
     211             :         }
     212             :     }
     213             : 
     214             :     // FORMAT
     215             :     //
     216             :     {
     217          54 :         std::string const specialized_format(f_name + "::format");
     218          27 :         if(opts.is_defined(specialized_format))
     219             :         {
     220           0 :             f_format = std::make_shared<format>(opts.get_string(specialized_format));
     221             :         }
     222          27 :         else if(opts.is_defined("format"))
     223             :         {
     224           0 :             f_format = std::make_shared<format>(opts.get_string("format"));
     225             :         }
     226             :     }
     227             : 
     228             :     // BITRATE
     229             :     //
     230             :     {
     231             :         // the input is considered to be in mbps
     232             :         //
     233          54 :         std::string bitrate("0");
     234          54 :         std::string const specialized_bitrate(f_name + "::bitrate");
     235          27 :         if(opts.is_defined(specialized_bitrate))
     236             :         {
     237           0 :             bitrate = opts.get_string(specialized_bitrate);
     238             :         }
     239          27 :         else if(opts.is_defined("bitrate"))
     240             :         {
     241           0 :             bitrate = opts.get_string("bitrate");
     242             :         }
     243          27 :         char * end(nullptr);
     244          27 :         errno = 0;
     245          27 :         double rate(strtod(bitrate.c_str(), &end));
     246          27 :         if(rate < 0.0
     247          27 :         || end == nullptr
     248          27 :         || *end != '\0'
     249          27 :         || errno == ERANGE)
     250             :         {
     251           0 :             rate = 0.0;
     252             :         }
     253          27 :         if(rate > 0.0)
     254             :         {
     255             :             // transform the rate to bytes per minute
     256             :             //
     257           0 :             rate = rate * (60.0 * 1'000'000.0 / 8.0);
     258             :         }
     259          27 :         f_bytes_per_minute = static_cast<decltype(f_bytes_per_minute)>(floor(rate));
     260             :     }
     261             : 
     262             :     // SEVERITY
     263             :     //
     264          54 :     std::string const specialized_severity(f_name + "::severity");
     265          27 :     if(opts.is_defined(specialized_severity))
     266             :     {
     267           0 :         std::string const severity_name(opts.get_string(specialized_severity));
     268           0 :         severity::pointer_t sev(snaplogger::get_severity(severity_name));
     269           0 :         if(sev != nullptr)
     270             :         {
     271           0 :             set_severity(sev->get_severity());
     272             :         }
     273             :         else
     274             :         {
     275             :             throw invalid_severity(
     276             :                           "severity level named \""
     277           0 :                         + severity_name
     278           0 :                         + "\" not found.");
     279             :         }
     280             :     }
     281          27 :     else if(opts.is_defined("severity"))
     282             :     {
     283           0 :         std::string const severity_name(opts.get_string("severity"));
     284           0 :         severity::pointer_t sev(snaplogger::get_severity(severity_name));
     285           0 :         if(sev != nullptr)
     286             :         {
     287           0 :             set_severity(sev->get_severity());
     288             :         }
     289             :         else
     290             :         {
     291             :             throw invalid_severity(
     292             :                           "severity level named \""
     293           0 :                         + severity_name
     294           0 :                         + "\" not found.");
     295             :         }
     296             :     }
     297             : 
     298             :     // COMPONENTS
     299             :     //
     300          54 :     std::string comp;
     301          54 :     std::string const components(f_name + "::components");
     302          27 :     if(opts.is_defined(components))
     303             :     {
     304           0 :         comp = opts.get_string(components);
     305             :     }
     306          27 :     else if(opts.is_defined("components"))
     307             :     {
     308           0 :         comp = opts.get_string("components");
     309             :     }
     310          27 :     if(comp.empty())
     311             :     {
     312          27 :         add_component(f_normal_component);
     313             :     }
     314             :     else
     315             :     {
     316           0 :         advgetopt::string_list_t component_names;
     317           0 :         advgetopt::split_string(comp, component_names, {","});
     318           0 :         for(auto name : component_names)
     319             :         {
     320           0 :             add_component(get_component(name));
     321             :         }
     322             :     }
     323             : 
     324             :     // FILTER
     325             :     //
     326             :     {
     327          54 :         std::string filter;
     328          54 :         std::string const specialized_filter(f_name + "::filter");
     329          27 :         if(opts.is_defined(specialized_filter))
     330             :         {
     331           0 :             filter = opts.get_string(specialized_filter);
     332             :         }
     333          27 :         else if(opts.is_defined("filter"))
     334             :         {
     335           0 :             filter = opts.get_string("filter");
     336             :         }
     337          27 :         if(!filter.empty())
     338             :         {
     339           0 :             std::regex_constants::syntax_option_type flags(std::regex::nosubs | std::regex::optimize);
     340           0 :             std::regex_constants::syntax_option_type type(std::regex::extended);
     341           0 :             if(filter[0] == '/')
     342             :             {
     343           0 :                 std::string::size_type pos(filter.rfind('/'));
     344           0 :                 if(pos == 0)
     345             :                 {
     346             :                     throw invalid_variable(
     347             :                                   "invalid filter \""
     348           0 :                                 + filter
     349           0 :                                 + "\"; missing ending '/'.");
     350             :                 }
     351           0 :                 std::string const flag_list(filter.substr(pos + 1));
     352           0 :                 filter = filter.substr(1, pos - 2);
     353           0 :                 if(filter.empty())
     354             :                 {
     355             :                     throw invalid_variable(
     356             :                                   "invalid filter \""
     357           0 :                                 + filter
     358           0 :                                 + "\"; the regular expression is empty.");
     359             :                 }
     360             :                 // TODO: for errors we would need to iterate using the libutf8
     361             :                 //       (since we could have a Unicode character after the /)
     362             :                 //
     363             :                 // TODO: if two type flags are found, err too
     364             :                 //
     365           0 :                 int count(0);
     366           0 :                 for(auto f : flag_list)
     367             :                 {
     368           0 :                     switch(f)
     369             :                     {
     370           0 :                     case 'i':
     371           0 :                         flags |= std::regex::icase;
     372           0 :                         break;
     373             : 
     374           0 :                     case 'c':
     375           0 :                         flags |= std::regex::collate;
     376           0 :                         break;
     377             : 
     378           0 :                     case 'j':
     379           0 :                         type = std::regex::ECMAScript;
     380           0 :                         ++count;
     381           0 :                         break;
     382             : 
     383           0 :                     case 'b':
     384           0 :                         type = std::regex::basic;
     385           0 :                         ++count;
     386           0 :                         break;
     387             : 
     388           0 :                     case 'x':
     389           0 :                         type = std::regex::extended;
     390           0 :                         ++count;
     391           0 :                         break;
     392             : 
     393           0 :                     case 'a':
     394           0 :                         type = std::regex::awk;
     395           0 :                         ++count;
     396           0 :                         break;
     397             : 
     398           0 :                     case 'g':
     399           0 :                         type = std::regex::grep;
     400           0 :                         ++count;
     401           0 :                         break;
     402             : 
     403           0 :                     case 'e':
     404           0 :                         type = std::regex::egrep;
     405           0 :                         ++count;
     406           0 :                         break;
     407             : 
     408           0 :                     default:
     409             :                         throw invalid_variable(
     410             :                                       "in \""
     411           0 :                                     + filter
     412           0 :                                     + "\", found invalid flag '"
     413           0 :                                     + f
     414           0 :                                     + "'.");
     415             : 
     416             :                     }
     417           0 :                     if(count > 1)
     418             :                     {
     419             :                         throw invalid_variable(
     420             :                                       "found multiple types in \""
     421           0 :                                     + filter
     422           0 :                                     + "\".");
     423             :                     }
     424             :                 }
     425             :             }
     426           0 :             f_filter = std::make_shared<std::regex>(filter, flags | type);
     427             :         }
     428             :     }
     429             : 
     430             :     // NO REPEAT
     431             :     //
     432             :     {
     433          54 :         std::string no_repeat(f_name + "::no-repeat");
     434          27 :         if(!opts.is_defined(no_repeat))
     435             :         {
     436          27 :             if(opts.is_defined("no-repeat"))
     437             :             {
     438           0 :                 no_repeat = "no-repeat";
     439             :             }
     440             :             else
     441             :             {
     442          27 :                 no_repeat.clear();
     443             :             }
     444             :         }
     445          27 :         if(!no_repeat.empty())
     446             :         {
     447           0 :             std::string const value(opts.get_string(no_repeat));
     448           0 :             if(value != "off")
     449             :             {
     450           0 :                 if(value == "max"
     451           0 :                 || value == "maximum")
     452             :                 {
     453           0 :                     f_no_repeat_size = NO_REPEAT_MAXIMUM;
     454             :                 }
     455           0 :                 else if(value == "default")
     456             :                 {
     457           0 :                     f_no_repeat_size = NO_REPEAT_DEFAULT;
     458             :                 }
     459             :                 else
     460             :                 {
     461           0 :                     f_no_repeat_size = opts.get_long(no_repeat, 0, 0, NO_REPEAT_MAXIMUM);
     462             :                 }
     463             :             }
     464             :         }
     465             :     }
     466          27 : }
     467             : 
     468             : 
     469           0 : void appender::reopen()
     470             : {
     471           0 : }
     472             : 
     473             : 
     474          29 : void appender::add_component(component::pointer_t comp)
     475             : {
     476          58 :     guard g;
     477             : 
     478          29 :     f_components.insert(comp);
     479          29 : }
     480             : 
     481             : 
     482          45 : format::pointer_t appender::set_format(format::pointer_t new_format)
     483             : {
     484          90 :     guard g;
     485             : 
     486          45 :     format::pointer_t old(f_format);
     487          45 :     f_format = new_format;
     488          90 :     return old;
     489             : }
     490             : 
     491             : 
     492           0 : long appender::get_bytes_per_minute() const
     493             : {
     494           0 :     return f_bytes_per_minute;
     495             : }
     496             : 
     497             : 
     498             : /** \brief Return the number of dropped message due to bitrate restrictions.
     499             :  *
     500             :  * It is possible to set the number of bits per second that an appender
     501             :  * will accept. Anything over that amount will be dropped.
     502             :  *
     503             :  * The logger converts the bits per second amount to a bytes per minute.
     504             :  * Each time a message gets sent, the number of bytes in that message
     505             :  * string is added to a counter. If that counter reaches a number of bytes
     506             :  * per minute larger than the allowed bytes per minutes, then the messages
     507             :  * get dropped until 1 minute elapses.
     508             :  *
     509             :  * This function returns any message that was sent and was dropped because
     510             :  * the bytes per minute limit was reached.
     511             :  *
     512             :  * \note
     513             :  * This counter doesn't get reset so reading it always returns a grand
     514             :  * total of all the messages that were dropped so far. This counter is
     515             :  * per appender.
     516             :  *
     517             :  * \return The number of messages that were dropped.
     518             :  */
     519           0 : std::size_t appender::get_bytes_dropped_messages() const
     520             : {
     521           0 :     return f_bytes_dropped_messages;
     522             : }
     523             : 
     524             : 
     525       99214 : void appender::send_message(message const & msg)
     526             : {
     527      141836 :     guard g;
     528             : 
     529      198428 :     if(!is_enabled()
     530       99214 :     || msg.get_severity() < f_severity)
     531             :     {
     532       50670 :         return;
     533             :     }
     534             : 
     535       48544 :     component::set_t const & components(msg.get_components());
     536       48544 :     if(components.empty())
     537             :     {
     538             :         // user did not supply any component in 'msg', check for
     539             :         // the normal component
     540             :         //
     541       97068 :         if(!f_components.empty()
     542      145602 :         && f_components.find(f_normal_component) == f_components.end())
     543             :         {
     544           0 :             return;
     545             :         }
     546             :     }
     547             :     else
     548             :     {
     549          10 :         if(snapdev::empty_set_intersection(f_components, components))
     550             :         {
     551           3 :             return;
     552             :         }
     553             :     }
     554             : 
     555       91157 :     std::string formatted_message(f_format->process_message(msg));
     556       48535 :     if(formatted_message.empty())
     557             :     {
     558        5919 :         return;
     559             :     }
     560             : 
     561       85232 :     if(f_filter != nullptr
     562       42616 :     && !std::regex_match(formatted_message, *f_filter))
     563             :     {
     564           0 :         return;
     565             :     }
     566             : 
     567       85232 :     if(formatted_message.back() != '\n'
     568       42616 :     && formatted_message.back() != '\r')
     569             :     {
     570             :         // TODO: add support to define line terminator (cr, nl, cr nl)
     571             :         //
     572       42616 :         formatted_message += '\n';
     573             :     }
     574             : 
     575             :     // TBD: should we use the time of the message rather than 'now'?
     576             :     //
     577       42616 :     if(f_bytes_per_minute != 0)
     578             :     {
     579           0 :         time_t const now(time(0));
     580           0 :         if(f_bytes_minute - now >= 60)
     581             :         {
     582           0 :             f_bytes_minute = now;
     583           0 :             f_bytes_received = 0;
     584             :         }
     585           0 :         else if(f_bytes_received + static_cast<long>(formatted_message.length())
     586           0 :                                         >= f_bytes_per_minute)
     587             :         {
     588             :             // overflow
     589             :             //
     590             :             // IMPORTANT NOTE: this algorithm may kick out a very long log
     591             :             // message and then accept a smaller one which still fits in the
     592             :             // `f_bytes_per_minute` bitrate
     593             :             //
     594           0 :             ++f_bytes_dropped_messages;
     595           0 :             return;
     596             :         }
     597           0 :         f_bytes_received += formatted_message.length();
     598             :     }
     599             : 
     600       42616 :     if(f_no_repeat_size > NO_REPEAT_OFF)
     601             :     {
     602           0 :         std::string const non_changing_message(f_format->process_message(msg, true));
     603           0 :         auto it(std::find(f_last_messages.rbegin(), f_last_messages.rend(), non_changing_message));
     604           0 :         if(it != f_last_messages.rend())
     605             :         {
     606             :             // TODO: count the number of times this message occurred and
     607             :             //       at a certain number, show it again -- add an option to
     608             :             //       defined that certain number
     609             :             //
     610             :             //       check the timestamp and if the message happened more
     611             :             //       than X seconds prior, repeat it anyway (this can be
     612             :             //       a timeout of our cache as far as implementation is
     613             :             //       concerned) -- add option to define time and this check
     614             :             //       could go "the other way around" where we cumulate messages
     615             :             //       and if not repeat within X seconds, then send them out
     616             :             //       so here we would just push messages on a "stack" and
     617             :             //       the thread would take care of send messages and if
     618             :             //       repeated then we can show a "count" in the message
     619             :             //       (i.e. something like "(message repeated N times in
     620             :             //       the last X seconds)") -- I think syslog sends the
     621             :             //       first message and then records the repeats for
     622             :             //       X seconds and finally sends the results to the screen
     623             :             //
     624             :             //       the current implementation helps, but it's weak in
     625             :             //       its results... (the results look weird)
     626             :             //
     627           0 :             return;
     628             :         }
     629           0 :         f_last_messages.push_back(non_changing_message);
     630           0 :         if(f_last_messages.size() > f_no_repeat_size)
     631             :         {
     632           0 :             f_last_messages.pop_front();
     633             :         }
     634             :     }
     635             : 
     636       42616 :     process_message(msg, formatted_message);
     637             : }
     638             : 
     639             : 
     640           0 : void appender::process_message(message const & msg, std::string const & formatted_message)
     641             : {
     642             :     // the default is a "null appender" -- do nothing
     643           0 :     snapdev::NOT_USED(msg, formatted_message);
     644           0 : }
     645             : 
     646             : 
     647             : 
     648             : 
     649             : 
     650           8 : appender_factory::appender_factory(std::string const & type)
     651           8 :     : f_type(type)
     652             : {
     653           8 :     char const * appender_factory_debug(getenv("APPENDER_FACTORY_DEBUG"));
     654           8 :     if(appender_factory_debug != nullptr
     655           0 :     && *appender_factory_debug != '\0')
     656             :     {
     657           0 :         std::cerr << "appender_factor:debug: adding appender factory \"" << type << "\".\n";
     658             :     }
     659           8 : }
     660             : 
     661             : 
     662           4 : appender_factory::~appender_factory()
     663             : {
     664           4 : }
     665             : 
     666             : 
     667          16 : std::string const & appender_factory::get_type() const
     668             : {
     669          16 :     return f_type;
     670             : }
     671             : 
     672             : 
     673             : 
     674             : 
     675           8 : void register_appender_factory(appender_factory::pointer_t factory)
     676             : {
     677           8 :     get_private_logger()->register_appender_factory(factory);
     678           8 : }
     679             : 
     680             : 
     681           2 : appender::pointer_t create_appender(std::string const & type, std::string const & name)
     682             : {
     683           2 :     return get_private_logger()->create_appender(type, name);
     684             : }
     685             : 
     686             : 
     687             : 
     688             : 
     689             : 
     690             : 
     691           0 : safe_format::safe_format(appender::pointer_t a, format::pointer_t new_format)
     692             :     : f_appender(a)
     693           0 :     , f_old_format(a->set_format(new_format))
     694             : {
     695           0 : }
     696             : 
     697             : 
     698           0 : safe_format::~safe_format()
     699             : {
     700           0 :     snapdev::NOT_USED(f_appender->set_format(f_old_format));
     701           0 : }
     702             : 
     703             : 
     704             : 
     705             : 
     706             : 
     707             : 
     708             : 
     709           6 : } // snaplogger namespace
     710             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13