LCOV - code coverage report
Current view: top level - snaplogger - file_appender.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 2 130 1.5 %
Date: 2022-07-01 22:43:09 Functions: 5 15 33.3 %
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 declares the base appender class.
      24             :  */
      25             : 
      26             : // self
      27             : //
      28             : #include    "snaplogger/file_appender.h"
      29             : 
      30             : #include    "snaplogger/exception.h"
      31             : #include    "snaplogger/guard.h"
      32             : #include    "snaplogger/map_diagnostic.h"
      33             : #include    "snaplogger/syslog_appender.h"
      34             : 
      35             : 
      36             : // advgetopt lib
      37             : //
      38             : #include    <advgetopt/validator_size.h>
      39             : 
      40             : 
      41             : // snapdev lib
      42             : //
      43             : #include    <snapdev/lockfile.h>
      44             : 
      45             : 
      46             : // C++ lib
      47             : //
      48             : #include    <iostream>
      49             : 
      50             : 
      51             : // C lib
      52             : //
      53             : #include    <fcntl.h>
      54             : #include    <syslog.h>
      55             : #include    <sys/stat.h>
      56             : #include    <sys/types.h>
      57             : #include    <unistd.h>
      58             : 
      59             : 
      60             : // last include
      61             : //
      62             : #include    <snapdev/poison.h>
      63             : 
      64             : 
      65             : 
      66             : namespace snaplogger
      67             : {
      68             : 
      69             : 
      70             : namespace
      71             : {
      72             : 
      73             : 
      74           7 : APPENDER_FACTORY(file);
      75             : 
      76             : 
      77             : }
      78             : // no name namespace
      79             : 
      80             : 
      81             : 
      82           0 : file_appender::file_appender(std::string const & name)
      83           0 :     : appender(name, "file")
      84             : {
      85           0 : }
      86             : 
      87             : 
      88           0 : file_appender::~file_appender()
      89             : {
      90           0 : }
      91             : 
      92             : 
      93           0 : void file_appender::set_config(advgetopt::getopt const & opts)
      94             : {
      95           0 :     guard g;
      96             : 
      97           0 :     appender::set_config(opts);
      98             : 
      99             :     // PATH
     100             :     //
     101           0 :     std::string const path_field(get_name() + "::path");
     102           0 :     if(opts.is_defined(path_field))
     103             :     {
     104           0 :         f_path = opts.get_string(path_field);
     105             :     }
     106           0 :     else if(opts.is_defined("path"))
     107             :     {
     108           0 :         f_path = opts.get_string("path");
     109             :     }
     110             : 
     111             :     // FILENAME
     112             :     //
     113           0 :     std::string const filename_field(get_name() + "::filename");
     114           0 :     if(opts.is_defined(filename_field))
     115             :     {
     116           0 :         f_filename = opts.get_string(filename_field);
     117             :     }
     118             :     // else -- we'll try to dynamically determine a filename when we
     119             :     //         reach the process_message() function
     120             : 
     121             :     // MAXIMUM SIZE
     122             :     //
     123           0 :     std::string const maximum_size_field(get_name() + "::maximum_size");
     124           0 :     if(opts.is_defined(maximum_size_field))
     125             :     {
     126           0 :         std::string const size_str(opts.get_string(maximum_size_field));
     127             : #pragma GCC diagnostic push
     128             : #pragma GCC diagnostic ignored "-Wpedantic"
     129           0 :         __int128 size(0);
     130           0 :         if(advgetopt::validator_size::convert_string(
     131             :                       size_str
     132             :                     , advgetopt::validator_size::VALIDATOR_SIZE_DEFAULT_FLAGS
     133             :                     , size))
     134             :         {
     135           0 :             f_maximum_size = std::max(size, static_cast<__int128>(INT64_MAX));
     136             :         }
     137             : #pragma GCC diagnostic pop
     138             :     }
     139             : 
     140             :     // ON OVERFLOW
     141             :     //
     142           0 :     std::string const on_overflow_field(get_name() + "::on_overflow");
     143           0 :     if(opts.is_defined(on_overflow_field))
     144             :     {
     145           0 :         f_on_overflow = opts.get_string(on_overflow_field);
     146             :     }
     147             : 
     148             :     // LOCK
     149             :     //
     150           0 :     std::string const lock_field(get_name() + "::lock");
     151           0 :     if(opts.is_defined(lock_field))
     152             :     {
     153           0 :         f_lock = opts.get_string(lock_field) == "true";
     154             :     }
     155             : 
     156             :     // FLUSH
     157             :     //
     158           0 :     std::string const flush_field(get_name() + "::flush");
     159           0 :     if(opts.is_defined(flush_field))
     160             :     {
     161           0 :         f_flush = opts.get_string(flush_field) == "true";
     162             :     }
     163             : 
     164             :     // SECURE
     165             :     //
     166           0 :     std::string const secure_field(get_name() + "::secure");
     167           0 :     if(opts.is_defined(secure_field))
     168             :     {
     169           0 :         f_secure = opts.get_string(secure_field) != "false";
     170             :     }
     171             : 
     172             :     // FALLBACK TO CONSOLE
     173             :     //
     174           0 :     std::string const fallback_to_console_field(get_name() + "::fallback_to_console");
     175           0 :     if(opts.is_defined(fallback_to_console_field))
     176             :     {
     177           0 :         f_fallback_to_console = opts.get_string(fallback_to_console_field) == "true";
     178             :     }
     179             : 
     180             :     // FALLBACK TO SYSLOG
     181             :     //
     182           0 :     std::string const fallback_to_syslog_field(get_name() + "::fallback_to_syslog");
     183           0 :     if(opts.is_defined(fallback_to_syslog_field))
     184             :     {
     185           0 :         f_fallback_to_syslog = opts.get_string(fallback_to_syslog_field) == "true";
     186             :     }
     187           0 : }
     188             : 
     189             : 
     190           0 : void file_appender::reopen()
     191             : {
     192           0 :     guard g;
     193             : 
     194           0 :     f_fd.reset();
     195           0 :     f_initialized = false;
     196           0 : }
     197             : 
     198             : 
     199           0 : void file_appender::set_filename(std::string const & filename)
     200             : {
     201           0 :     guard g;
     202             : 
     203           0 :     if(f_filename != filename)
     204             :     {
     205           0 :         f_filename = filename;
     206           0 :         f_initialized = false;
     207             :     }
     208           0 : }
     209             : 
     210             : 
     211           0 : void file_appender::process_message(message const & msg, std::string const & formatted_message)
     212             : {
     213           0 :     guard g;
     214             : 
     215             :     // verify whether the output file is too large, if so rename it .log.1
     216             :     // and create a new file; the process will delete an existing .log.1 if
     217             :     // present; as a result we make sure that files never grow over a
     218             :     // user specified maximum; by default this feature uses a maximum size
     219             :     // of 10Mb
     220             :     //
     221           0 :     if(f_maximum_size > 0
     222           0 :     && !!f_fd)
     223             :     {
     224           0 :         struct stat s = {};
     225           0 :         int const r(fstat(f_fd.get(), &s));
     226           0 :         if(r != 0
     227           0 :         || s.st_size >= f_maximum_size)
     228             :         {
     229           0 :             if(!f_limit_reached)
     230             :             {
     231           0 :                 f_limit_reached = true;
     232             : 
     233             :                 // TODO: look at properly formatting this message
     234             :                 //
     235           0 :                 output_message(msg, "-- file size limit reached, this will be the last message --", false);
     236             :             }
     237             : 
     238           0 :             if(f_on_overflow == "skip")
     239             :             {
     240           0 :                 return;
     241             :             }
     242             : 
     243           0 :             if(f_on_overflow == "fatal")
     244             :             {
     245           0 :                 throw fatal_error("logger's output file is full");
     246             :             }
     247             : 
     248           0 :             if(f_on_overflow == "rotate")
     249             :             {
     250             :                 // poorman's log rotate replacing the .1 if it exists
     251             :                 // this way we avoid the compression which is really
     252             :                 // slow...
     253             :                 //
     254           0 :                 std::string one(f_filename + ".1");
     255           0 :                 snapdev::NOT_USED(unlink(one.c_str()));
     256           0 :                 if(rename(f_filename.c_str(), one.c_str()) != 0)
     257             :                 {
     258           0 :                     snapdev::NOT_USED(unlink(f_filename.c_str()));
     259             :                 }
     260             :             }
     261           0 :             else if(f_on_overflow == "logrotate")
     262             :             {
     263             :                 // TODO: run logrotate properly
     264             :                 //
     265             :                 //       right now, we could only run it as ourselves
     266             :                 //       also we do want to specify which configuration
     267             :                 //       file to use otherwise it would attempt to
     268             :                 //       rotate everything which is not what we want
     269             :                 //
     270           0 :                 if(system("/usr/sbin/logrotate /etc/logrotate.conf") != 0)
     271             :                 {
     272             :                     // assume our rotation failed
     273             :                     //
     274           0 :                     return;
     275             :                 }
     276             :             }
     277             :             else
     278             :             {
     279             :                 // act like "skip" (we could also throw an error, but that
     280             :                 // would then act like "fatal" which may not be the best
     281             :                 // default action)
     282             :                 //
     283           0 :                 return;
     284             :             }
     285             : 
     286             :             // force a reopen as when we do a logrotate with an event
     287             :             // (we would not yet have received the event here)
     288             :             //
     289           0 :             reopen();
     290             :         }
     291             :         else
     292             :         {
     293           0 :             f_limit_reached = false;
     294             :         }
     295             :     }
     296             : 
     297           0 :     if(!f_initialized)
     298             :     {
     299           0 :         f_initialized = true;
     300             : 
     301           0 :         if(f_filename.empty())
     302             :         {
     303             :             // try to generate a filename
     304             :             //
     305           0 :             map_diagnostics_t map(get_map_diagnostics());
     306           0 :             auto const it(map.find("progname"));
     307           0 :             if(it == map.end())
     308             :             {
     309           0 :                 return;
     310             :             }
     311           0 :             if(it->second.empty())
     312             :             {
     313           0 :                 return;
     314             :             }
     315             : 
     316           0 :             f_filename = f_path + '/';
     317           0 :             if(f_secure)
     318             :             {
     319           0 :                 f_filename += "secure/";
     320             :             }
     321           0 :             f_filename += it->second;
     322           0 :             f_filename += ".log";
     323             :         }
     324           0 :         else if(f_filename.find('/') == std::string::npos)
     325             :         {
     326           0 :             f_filename = f_path + '/' + f_filename;
     327             :         }
     328           0 :         std::string::size_type pos(f_filename.rfind('/'));
     329           0 :         if(pos == std::string::npos)
     330             :         {
     331           0 :             pos = 0;
     332             :         }
     333           0 :         if(f_filename.find('.', pos + 1) == std::string::npos)
     334             :         {
     335           0 :             f_filename += ".log";
     336             :         }
     337             : 
     338           0 :         if(access(f_filename.c_str(), R_OK | W_OK) != 0
     339           0 :         && errno != ENOENT)
     340             :         {
     341           0 :             return;
     342             :         }
     343             : 
     344           0 :         int flags(O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC | O_LARGEFILE | O_NOCTTY);
     345           0 :         int mode(S_IRUSR | S_IWUSR);
     346           0 :         if(!f_secure)
     347             :         {
     348           0 :             mode |= S_IRGRP;
     349             :         }
     350             : 
     351           0 :         f_fd.reset(open(f_filename.c_str(), flags, mode));
     352             : 
     353             :         // verify that the file isn't already too big
     354             :         //
     355           0 :         struct stat s = {};
     356           0 :         int const r(fstat(f_fd.get(), &s));
     357           0 :         if(r != 0
     358           0 :         || s.st_size >= f_maximum_size)
     359             :         {
     360           0 :             return;
     361             :         }
     362             :     }
     363             : 
     364           0 :     output_message(msg, formatted_message, true);
     365             : }
     366             : 
     367             : 
     368           0 : void file_appender::output_message(message const & msg, std::string const & formatted_message, bool allow_fallbacks)
     369             : {
     370           0 :     if(!f_fd)
     371             :     {
     372           0 :         return;
     373             :     }
     374             : 
     375           0 :     std::unique_ptr<snapdev::lockfd> lock_file;
     376           0 :     if(f_lock)
     377             :     {
     378           0 :         lock_file = std::make_unique<snapdev::lockfd>(f_fd.get(), snapdev::lockfd::mode_t::LOCKFILE_EXCLUSIVE);
     379             :     }
     380             : 
     381           0 :     ssize_t const l(write(f_fd.get(), formatted_message.c_str(), formatted_message.length()));
     382           0 :     if(static_cast<size_t>(l) != formatted_message.length())
     383             :     {
     384             :         // how could we report that? we are the logger...
     385             :         //
     386           0 :         if(allow_fallbacks)
     387             :         {
     388           0 :             if(f_fallback_to_console
     389           0 :             && isatty(fileno(stdout)))
     390             :             {
     391           0 :                 std::cout << formatted_message.c_str();
     392             :             }
     393           0 :             else if(f_fallback_to_syslog)
     394             :             {
     395             :                 // in this case we skip on the openlog() call...
     396             :                 //
     397           0 :                 int const priority(syslog_appender::message_severity_to_syslog_priority(msg.get_severity()));
     398           0 :                 syslog(priority, "%s", formatted_message.c_str());
     399             :             }
     400             :         }
     401             :     }
     402             : }
     403             : 
     404             : 
     405             : 
     406             : 
     407             : 
     408           6 : } // snaplogger namespace
     409             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13