LCOV - code coverage report
Current view: top level - snaplogger - appender.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 111 254 43.7 %
Date: 2022-01-29 21:11:29 Functions: 19 31 61.3 %
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/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 lib
      37             : //
      38             : #include    <snapdev/empty_set_intersection.h>
      39             : #include    <snapdev/not_used.h>
      40             : 
      41             : 
      42             : // C++ lib
      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          20 : appender::appender(std::string const & name, std::string const & type)
      76             :     : f_type(type)
      77             :     , f_name(name)
      78          20 :     , f_normal_component(get_component(COMPONENT_NORMAL))
      79             : {
      80          40 :     guard g;
      81             : 
      82          20 :     f_format = get_private_logger()->get_default_format();
      83          20 : }
      84             : 
      85             : 
      86          20 : appender::~appender()
      87             : {
      88          20 : }
      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       97944 : bool appender::is_enabled() const
     124             : {
     125      195888 :     guard g;
     126             : 
     127      195888 :     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          20 : bool appender::unique() const
     140             : {
     141          20 :     return false;
     142             : }
     143             : 
     144             : 
     145         308 : severity_t appender::get_severity() const
     146             : {
     147         616 :     guard g;
     148             : 
     149         616 :     return f_severity;
     150             : }
     151             : 
     152             : 
     153         294 : void appender::set_severity(severity_t severity_level)
     154             : {
     155         588 :     guard g;
     156             : 
     157         294 :     f_severity = severity_level;
     158         294 :     logger::get_instance()->severity_changed(severity_level);
     159         294 : }
     160             : 
     161             : 
     162           0 : void appender::reduce_severity(severity_t severity_level)
     163             : {
     164           0 :     guard g;
     165             : 
     166           0 :     if(severity_level < f_severity)
     167             :     {
     168           0 :         set_severity(severity_level);
     169             :     }
     170           0 : }
     171             : 
     172             : 
     173           0 : bool appender::operator < (appender const & rhs) const
     174             : {
     175           0 :     guard g;
     176             : 
     177           0 :     return f_severity < rhs.f_severity;
     178             : }
     179             : 
     180             : 
     181          20 : void appender::set_config(advgetopt::getopt const & opts)
     182             : {
     183          40 :     guard g;
     184             : 
     185             :     // ENABLE
     186             :     //
     187             :     {
     188          40 :         std::string const specialized_enabled(f_name + "::enabled");
     189          20 :         if(opts.is_defined(specialized_enabled))
     190             :         {
     191           0 :             f_enabled = opts.get_string(specialized_enabled) != "false";
     192             :         }
     193          20 :         else if(opts.is_defined("enabled"))
     194             :         {
     195           0 :             f_enabled = opts.get_string("enabled") != "false";
     196             :         }
     197             :         else
     198             :         {
     199          20 :             f_enabled = true;
     200             :         }
     201             :     }
     202             : 
     203             :     // FORMAT
     204             :     //
     205             :     {
     206          40 :         std::string const specialized_format(f_name + "::format");
     207          20 :         if(opts.is_defined(specialized_format))
     208             :         {
     209           0 :             f_format = std::make_shared<format>(opts.get_string(specialized_format));
     210             :         }
     211          20 :         else if(opts.is_defined("format"))
     212             :         {
     213           0 :             f_format = std::make_shared<format>(opts.get_string("format"));
     214             :         }
     215             :     }
     216             : 
     217             :     // BITRATE
     218             :     //
     219             :     {
     220             :         // the input is considered to be in mbps
     221             :         //
     222          40 :         std::string bitrate("0");
     223          40 :         std::string const specialized_bitrate(f_name + "::bitrate");
     224          20 :         if(opts.is_defined(specialized_bitrate))
     225             :         {
     226           0 :             bitrate = opts.get_string(specialized_bitrate);
     227             :         }
     228          20 :         else if(opts.is_defined("bitrate"))
     229             :         {
     230           0 :             bitrate = opts.get_string("bitrate");
     231             :         }
     232          20 :         char * end(nullptr);
     233          20 :         errno = 0;
     234          20 :         double rate(strtod(bitrate.c_str(), &end));
     235          20 :         if(rate < 0.0
     236          20 :         || end == nullptr
     237          20 :         || *end != '\0'
     238          20 :         || errno == ERANGE)
     239             :         {
     240           0 :             rate = 0.0;
     241             :         }
     242          20 :         if(rate > 0.0)
     243             :         {
     244             :             // transform the rate to bytes per minute
     245             :             //
     246           0 :             rate = rate * (60.0 * 1'000'000.0 / 8.0);
     247             :         }
     248          20 :         f_bytes_per_minute = static_cast<decltype(f_bytes_per_minute)>(floor(rate));
     249             :     }
     250             : 
     251             :     // SEVERITY
     252             :     //
     253          40 :     std::string const specialized_severity(f_name + "::severity");
     254          20 :     if(opts.is_defined(specialized_severity))
     255             :     {
     256           0 :         std::string const severity_name(opts.get_string(specialized_severity));
     257           0 :         severity::pointer_t sev(snaplogger::get_severity(severity_name));
     258           0 :         if(sev != nullptr)
     259             :         {
     260           0 :             set_severity(sev->get_severity());
     261             :         }
     262             :         else
     263             :         {
     264             :             throw invalid_severity(
     265             :                           "severity level named \""
     266           0 :                         + severity_name
     267           0 :                         + "\" not found.");
     268             :         }
     269             :     }
     270          20 :     else if(opts.is_defined("severity"))
     271             :     {
     272           0 :         std::string const severity_name(opts.get_string("severity"));
     273           0 :         severity::pointer_t sev(snaplogger::get_severity(severity_name));
     274           0 :         if(sev != nullptr)
     275             :         {
     276           0 :             set_severity(sev->get_severity());
     277             :         }
     278             :         else
     279             :         {
     280             :             throw invalid_severity(
     281             :                           "severity level named \""
     282           0 :                         + severity_name
     283           0 :                         + "\" not found.");
     284             :         }
     285             :     }
     286             : 
     287             :     // COMPONENTS
     288             :     //
     289          40 :     std::string comp;
     290          40 :     std::string const components(f_name + "::components");
     291          20 :     if(opts.is_defined(components))
     292             :     {
     293           0 :         comp = opts.get_string(components);
     294             :     }
     295          20 :     else if(opts.is_defined("components"))
     296             :     {
     297           0 :         comp = opts.get_string("components");
     298             :     }
     299          20 :     if(comp.empty())
     300             :     {
     301          20 :         add_component(f_normal_component);
     302             :     }
     303             :     else
     304             :     {
     305           0 :         advgetopt::string_list_t component_names;
     306           0 :         advgetopt::split_string(comp, component_names, {","});
     307           0 :         for(auto name : component_names)
     308             :         {
     309           0 :             add_component(get_component(name));
     310             :         }
     311             :     }
     312             : 
     313             :     // FILTER
     314             :     //
     315             :     {
     316          40 :         std::string filter;
     317          40 :         std::string const specialized_filter(f_name + "::filter");
     318          20 :         if(opts.is_defined(specialized_filter))
     319             :         {
     320           0 :             filter = opts.get_string(specialized_filter);
     321             :         }
     322          20 :         else if(opts.is_defined("filter"))
     323             :         {
     324           0 :             filter = opts.get_string("filter");
     325             :         }
     326          20 :         if(!filter.empty())
     327             :         {
     328           0 :             std::regex_constants::syntax_option_type flags(std::regex::nosubs | std::regex::optimize);
     329           0 :             std::regex_constants::syntax_option_type type(std::regex::extended);
     330           0 :             if(filter[0] == '/')
     331             :             {
     332           0 :                 std::string::size_type pos(filter.rfind('/'));
     333           0 :                 if(pos == 0)
     334             :                 {
     335             :                     throw invalid_variable(
     336             :                                   "invalid filter \""
     337           0 :                                 + filter
     338           0 :                                 + "\"; missing ending '/'.");
     339             :                 }
     340           0 :                 std::string const flag_list(filter.substr(pos + 1));
     341           0 :                 filter = filter.substr(1, pos - 2);
     342           0 :                 if(filter.empty())
     343             :                 {
     344             :                     throw invalid_variable(
     345             :                                   "invalid filter \""
     346           0 :                                 + filter
     347           0 :                                 + "\"; the regular expression is empty.");
     348             :                 }
     349             :                 // TODO: for errors we would need to iterate using the libutf8
     350             :                 //       (since we could have a Unicode character after the /)
     351             :                 //
     352             :                 // TODO: if two type flags are found, err too
     353             :                 //
     354           0 :                 int count(0);
     355           0 :                 for(auto f : flag_list)
     356             :                 {
     357           0 :                     switch(f)
     358             :                     {
     359           0 :                     case 'i':
     360           0 :                         flags |= std::regex::icase;
     361           0 :                         break;
     362             : 
     363           0 :                     case 'c':
     364           0 :                         flags |= std::regex::collate;
     365           0 :                         break;
     366             : 
     367           0 :                     case 'j':
     368           0 :                         type = std::regex::ECMAScript;
     369           0 :                         ++count;
     370           0 :                         break;
     371             : 
     372           0 :                     case 'b':
     373           0 :                         type = std::regex::basic;
     374           0 :                         ++count;
     375           0 :                         break;
     376             : 
     377           0 :                     case 'x':
     378           0 :                         type = std::regex::extended;
     379           0 :                         ++count;
     380           0 :                         break;
     381             : 
     382           0 :                     case 'a':
     383           0 :                         type = std::regex::awk;
     384           0 :                         ++count;
     385           0 :                         break;
     386             : 
     387           0 :                     case 'g':
     388           0 :                         type = std::regex::grep;
     389           0 :                         ++count;
     390           0 :                         break;
     391             : 
     392           0 :                     case 'e':
     393           0 :                         type = std::regex::egrep;
     394           0 :                         ++count;
     395           0 :                         break;
     396             : 
     397           0 :                     default:
     398             :                         throw invalid_variable(
     399             :                                       "in \""
     400           0 :                                     + filter
     401           0 :                                     + "\", found invalid flag '"
     402           0 :                                     + f
     403           0 :                                     + "'.");
     404             : 
     405             :                     }
     406           0 :                     if(count > 1)
     407             :                     {
     408             :                         throw invalid_variable(
     409             :                                       "found multiple types in \""
     410           0 :                                     + filter
     411           0 :                                     + "\".");
     412             :                     }
     413             :                 }
     414             :             }
     415           0 :             f_filter = std::make_shared<std::regex>(filter, flags | type);
     416             :         }
     417             :     }
     418             : 
     419             :     // REPEAT
     420             :     //
     421             :     {
     422          40 :         std::string no_repeat(f_name + "::no-repeat");
     423          20 :         if(!opts.is_defined(no_repeat))
     424             :         {
     425          20 :             if(opts.is_defined("no-repeat"))
     426             :             {
     427           0 :                 no_repeat = "no-repeat";
     428             :             }
     429             :             else
     430             :             {
     431          20 :                 no_repeat.clear();
     432             :             }
     433             :         }
     434          20 :         if(!no_repeat.empty())
     435             :         {
     436           0 :             std::string const value(opts.get_string(no_repeat));
     437           0 :             if(value != "off")
     438             :             {
     439           0 :                 if(value == "max"
     440           0 :                 || value == "maximum")
     441             :                 {
     442           0 :                     f_no_repeat_size = NO_REPEAT_MAXIMUM;
     443             :                 }
     444           0 :                 else if(value == "default")
     445             :                 {
     446           0 :                     f_no_repeat_size = NO_REPEAT_DEFAULT;
     447             :                 }
     448             :                 else
     449             :                 {
     450           0 :                     f_no_repeat_size = opts.get_long("no-repeat", 0, 0, NO_REPEAT_MAXIMUM);
     451             :                 }
     452             :             }
     453             :         }
     454             :     }
     455          20 : }
     456             : 
     457             : 
     458           0 : void appender::reopen()
     459             : {
     460           0 : }
     461             : 
     462             : 
     463          21 : void appender::add_component(component::pointer_t comp)
     464             : {
     465          42 :     guard g;
     466             : 
     467          21 :     f_components.insert(comp);
     468          21 : }
     469             : 
     470             : 
     471          42 : format::pointer_t appender::set_format(format::pointer_t new_format)
     472             : {
     473          84 :     guard g;
     474             : 
     475          42 :     format::pointer_t old(f_format);
     476          42 :     f_format = new_format;
     477          84 :     return old;
     478             : }
     479             : 
     480             : 
     481           0 : long appender::get_bytes_per_minute() const
     482             : {
     483           0 :     return f_bytes_per_minute;
     484             : }
     485             : 
     486             : 
     487             : /** \brief Return the number of dropped message due to bitrate restrictions.
     488             :  *
     489             :  * It is possible to set the number of bits per second that an appender
     490             :  * will accept. Anything over that amount will be dropped.
     491             :  *
     492             :  * The logger converts the bits per second amount to a bytes per minute.
     493             :  * Each time a message gets sent, the number of bytes in that message
     494             :  * string is added to a counter. If that counter reaches a number of bytes
     495             :  * per minute larger than the allowed bytes per minutes, then the messages
     496             :  * get dropped until 1 minute elapses.
     497             :  *
     498             :  * This function returns any message that was sent and was dropped because
     499             :  * the bytes per minute limit was reached.
     500             :  *
     501             :  * \note
     502             :  * This counter doesn't get reset so reading it always returns a grand
     503             :  * total of all the messages that were dropped so far. This counter is
     504             :  * per appender.
     505             :  *
     506             :  * \return The number of messages that were dropped.
     507             :  */
     508           0 : std::size_t appender::get_bytes_dropped_messages() const
     509             : {
     510           0 :     return f_bytes_dropped_messages;
     511             : }
     512             : 
     513             : 
     514       97944 : void appender::send_message(message const & msg)
     515             : {
     516      141739 :     guard g;
     517             : 
     518      195888 :     if(!is_enabled()
     519       97944 :     || msg.get_severity() < f_severity)
     520             :     {
     521       49056 :         return;
     522             :     }
     523             : 
     524       48888 :     component::set_t const & components(msg.get_components());
     525       48888 :     if(components.empty())
     526             :     {
     527             :         // user did not supply any component in 'msg', check for
     528             :         // the normal component
     529             :         //
     530       97768 :         if(!f_components.empty()
     531      146652 :         && f_components.find(f_normal_component) == f_components.end())
     532             :         {
     533           0 :             return;
     534             :         }
     535             :     }
     536             :     else
     537             :     {
     538           4 :         if(snapdev::empty_set_intersection(f_components, components))
     539             :         {
     540           2 :             return;
     541             :         }
     542             :     }
     543             : 
     544       92675 :     std::string formatted_message(f_format->process_message(msg));
     545       48880 :     if(formatted_message.empty())
     546             :     {
     547        5091 :         return;
     548             :     }
     549             : 
     550       87578 :     if(f_filter != nullptr
     551       43789 :     && !std::regex_match(formatted_message, *f_filter))
     552             :     {
     553           0 :         return;
     554             :     }
     555             : 
     556       87578 :     if(formatted_message.back() != '\n'
     557       43789 :     && formatted_message.back() != '\r')
     558             :     {
     559             :         // TODO: add support to define line terminator (cr, nl, cr nl)
     560             :         //
     561       43789 :         formatted_message += '\n';
     562             :     }
     563             : 
     564             :     // TBD: should we use the time of the message rather than 'now'?
     565             :     //
     566       43789 :     if(f_bytes_per_minute != 0)
     567             :     {
     568           0 :         time_t const now(time(0));
     569           0 :         if(f_bytes_minute - now >= 60)
     570             :         {
     571           0 :             f_bytes_minute = now;
     572           0 :             f_bytes_received = 0;
     573             :         }
     574           0 :         else if(f_bytes_received + static_cast<long>(formatted_message.length())
     575           0 :                                         >= f_bytes_per_minute)
     576             :         {
     577             :             // overflow
     578             :             //
     579             :             // IMPORTANT NOTE: this algorithm may kick out a very long log
     580             :             // message and then accept a smaller one which still fits in the
     581             :             // `f_bytes_per_minute` bitrate
     582             :             //
     583           0 :             ++f_bytes_dropped_messages;
     584           0 :             return;
     585             :         }
     586           0 :         f_bytes_received += formatted_message.length();
     587             :     }
     588             : 
     589       43789 :     if(f_no_repeat_size > NO_REPEAT_OFF)
     590             :     {
     591           0 :         std::string const non_changing_message(f_format->process_message(msg, true));
     592           0 :         auto it(std::find(f_last_messages.rbegin(), f_last_messages.rend(), non_changing_message));
     593           0 :         if(it != f_last_messages.rend())
     594             :         {
     595             :             // TODO: look into a way to count said messages and print out
     596             :             //       the total number or something of the sort...
     597             :             //       (maybe we store those messages in a buffer and once
     598             :             //       we are to replace a message, that's when we forward
     599             :             //       it and that can include the count? also that extra
     600             :             //       write can be based on time and/or count)
     601             :             //
     602           0 :             f_last_messages.erase(std::next(it).base());    // erase() expects an iterator, not a reverse iterator
     603             :         }
     604           0 :         f_last_messages.push_back(non_changing_message);
     605           0 :         if(f_last_messages.size() > f_no_repeat_size)
     606             :         {
     607           0 :             f_last_messages.pop_front();
     608             :         }
     609             :     }
     610             : 
     611       43789 :     process_message(msg, formatted_message);
     612             : }
     613             : 
     614             : 
     615           0 : void appender::process_message(message const & msg, std::string const & formatted_message)
     616             : {
     617             :     // the default is a "null appender" -- do nothing
     618           0 :     snapdev::NOT_USED(msg, formatted_message);
     619           0 : }
     620             : 
     621             : 
     622             : 
     623             : 
     624             : 
     625           8 : appender_factory::appender_factory(std::string const & type)
     626           8 :     : f_type(type)
     627             : {
     628           8 : }
     629             : 
     630             : 
     631           8 : appender_factory::~appender_factory()
     632             : {
     633           8 : }
     634             : 
     635             : 
     636          16 : std::string const & appender_factory::get_type() const
     637             : {
     638          16 :     return f_type;
     639             : }
     640             : 
     641             : 
     642             : 
     643             : 
     644           8 : void register_appender_factory(appender_factory::pointer_t factory)
     645             : {
     646           8 :     get_private_logger()->register_appender_factory(factory);
     647           8 : }
     648             : 
     649             : 
     650           2 : appender::pointer_t create_appender(std::string const & type, std::string const & name)
     651             : {
     652           2 :     return get_private_logger()->create_appender(type, name);
     653             : }
     654             : 
     655             : 
     656             : 
     657             : 
     658             : 
     659             : 
     660           0 : safe_format::safe_format(appender::pointer_t a, format::pointer_t new_format)
     661             :     : f_appender(a)
     662           0 :     , f_old_format(a->set_format(new_format))
     663             : {
     664           0 : }
     665             : 
     666             : 
     667           0 : safe_format::~safe_format()
     668             : {
     669           0 :     snapdev::NOT_USED(f_appender->set_format(f_old_format));
     670           0 : }
     671             : 
     672             : 
     673             : 
     674             : 
     675             : 
     676             : 
     677             : 
     678           6 : } // snaplogger namespace
     679             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13