1*b0d29bc4SBrooks Davis // Copyright 2011 The Kyua Authors. 2*b0d29bc4SBrooks Davis // All rights reserved. 3*b0d29bc4SBrooks Davis // 4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without 5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are 6*b0d29bc4SBrooks Davis // met: 7*b0d29bc4SBrooks Davis // 8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright 9*b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer. 10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright 11*b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer in the 12*b0d29bc4SBrooks Davis // documentation and/or other materials provided with the distribution. 13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors 14*b0d29bc4SBrooks Davis // may be used to endorse or promote products derived from this software 15*b0d29bc4SBrooks Davis // without specific prior written permission. 16*b0d29bc4SBrooks Davis // 17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28*b0d29bc4SBrooks Davis 29*b0d29bc4SBrooks Davis #include "utils/logging/operations.hpp" 30*b0d29bc4SBrooks Davis 31*b0d29bc4SBrooks Davis extern "C" { 32*b0d29bc4SBrooks Davis #include <unistd.h> 33*b0d29bc4SBrooks Davis } 34*b0d29bc4SBrooks Davis 35*b0d29bc4SBrooks Davis #include <stdexcept> 36*b0d29bc4SBrooks Davis #include <string> 37*b0d29bc4SBrooks Davis #include <utility> 38*b0d29bc4SBrooks Davis #include <vector> 39*b0d29bc4SBrooks Davis 40*b0d29bc4SBrooks Davis #include "utils/datetime.hpp" 41*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp" 42*b0d29bc4SBrooks Davis #include "utils/fs/path.hpp" 43*b0d29bc4SBrooks Davis #include "utils/optional.ipp" 44*b0d29bc4SBrooks Davis #include "utils/sanity.hpp" 45*b0d29bc4SBrooks Davis #include "utils/stream.hpp" 46*b0d29bc4SBrooks Davis 47*b0d29bc4SBrooks Davis namespace datetime = utils::datetime; 48*b0d29bc4SBrooks Davis namespace fs = utils::fs; 49*b0d29bc4SBrooks Davis namespace logging = utils::logging; 50*b0d29bc4SBrooks Davis 51*b0d29bc4SBrooks Davis using utils::none; 52*b0d29bc4SBrooks Davis using utils::optional; 53*b0d29bc4SBrooks Davis 54*b0d29bc4SBrooks Davis 55*b0d29bc4SBrooks Davis /// The general idea for the application-wide logging goes like this: 56*b0d29bc4SBrooks Davis /// 57*b0d29bc4SBrooks Davis /// 1. The application starts. Logging is initialized to capture _all_ log 58*b0d29bc4SBrooks Davis /// messages into memory regardless of their level by issuing a call to the 59*b0d29bc4SBrooks Davis /// set_inmemory() function. 60*b0d29bc4SBrooks Davis /// 61*b0d29bc4SBrooks Davis /// 2. The application offers the user a way to select the logging level and a 62*b0d29bc4SBrooks Davis /// file into which to store the log. 63*b0d29bc4SBrooks Davis /// 64*b0d29bc4SBrooks Davis /// 3. The application calls set_persistency providing a new log level and a log 65*b0d29bc4SBrooks Davis /// file. This must be done as early as possible, to minimize the chances of an 66*b0d29bc4SBrooks Davis /// early crash not capturing any logs. 67*b0d29bc4SBrooks Davis /// 68*b0d29bc4SBrooks Davis /// 4. At this point, any log messages stored into memory are flushed to disk 69*b0d29bc4SBrooks Davis /// respecting the provided log level. 70*b0d29bc4SBrooks Davis /// 71*b0d29bc4SBrooks Davis /// 5. The internal state of the logging module is updated to only capture 72*b0d29bc4SBrooks Davis /// messages that are of the provided log level (or below) and is configured to 73*b0d29bc4SBrooks Davis /// directly send messages to disk. 74*b0d29bc4SBrooks Davis /// 75*b0d29bc4SBrooks Davis /// 6. The user may choose to call set_inmemory() again at a later stage, which 76*b0d29bc4SBrooks Davis /// will cause the log to be flushed and messages to be recorded in memory 77*b0d29bc4SBrooks Davis /// again. This is useful in case the logs are being sent to either stdout or 78*b0d29bc4SBrooks Davis /// stderr and the process forks and wants to keep those child channels 79*b0d29bc4SBrooks Davis /// unpolluted. 80*b0d29bc4SBrooks Davis /// 81*b0d29bc4SBrooks Davis /// The call to set_inmemory() should only be performed by the user-facing 82*b0d29bc4SBrooks Davis /// application. Tests should skip this call so that the logging messages go to 83*b0d29bc4SBrooks Davis /// stderr by default, thus generating a useful log to debug the tests. 84*b0d29bc4SBrooks Davis 85*b0d29bc4SBrooks Davis 86*b0d29bc4SBrooks Davis namespace { 87*b0d29bc4SBrooks Davis 88*b0d29bc4SBrooks Davis 89*b0d29bc4SBrooks Davis /// Constant string to strftime to format timestamps. 90*b0d29bc4SBrooks Davis static const char* timestamp_format = "%Y%m%d-%H%M%S"; 91*b0d29bc4SBrooks Davis 92*b0d29bc4SBrooks Davis 93*b0d29bc4SBrooks Davis /// Mutable global state. 94*b0d29bc4SBrooks Davis struct global_state { 95*b0d29bc4SBrooks Davis /// Current log level. 96*b0d29bc4SBrooks Davis logging::level log_level; 97*b0d29bc4SBrooks Davis 98*b0d29bc4SBrooks Davis /// Indicates whether set_persistency() will be called automatically or not. 99*b0d29bc4SBrooks Davis bool auto_set_persistency; 100*b0d29bc4SBrooks Davis 101*b0d29bc4SBrooks Davis /// First time recorded by the logging module. 102*b0d29bc4SBrooks Davis optional< datetime::timestamp > first_timestamp; 103*b0d29bc4SBrooks Davis 104*b0d29bc4SBrooks Davis /// In-memory record of log entries before persistency is enabled. 105*b0d29bc4SBrooks Davis std::vector< std::pair< logging::level, std::string > > backlog; 106*b0d29bc4SBrooks Davis 107*b0d29bc4SBrooks Davis /// Stream to the currently open log file. 108*b0d29bc4SBrooks Davis std::auto_ptr< std::ostream > logfile; 109*b0d29bc4SBrooks Davis 110*b0d29bc4SBrooks Davis global_state() : 111*b0d29bc4SBrooks Davis log_level(logging::level_debug), 112*b0d29bc4SBrooks Davis auto_set_persistency(true) 113*b0d29bc4SBrooks Davis { 114*b0d29bc4SBrooks Davis } 115*b0d29bc4SBrooks Davis }; 116*b0d29bc4SBrooks Davis 117*b0d29bc4SBrooks Davis 118*b0d29bc4SBrooks Davis /// Single instance of the mutable global state. 119*b0d29bc4SBrooks Davis /// 120*b0d29bc4SBrooks Davis /// Note that this is a raw pointer that we intentionally leak. We must do 121*b0d29bc4SBrooks Davis /// this, instead of making all of the singleton's members static values, 122*b0d29bc4SBrooks Davis /// because we want other destructors in the program to be able to log critical 123*b0d29bc4SBrooks Davis /// conditions. If we use complex types in this translation unit, they may be 124*b0d29bc4SBrooks Davis /// destroyed before the logging methods in the destructors get a chance to run 125*b0d29bc4SBrooks Davis /// thus resulting in a premature crash. By using a plain pointer, we ensure 126*b0d29bc4SBrooks Davis /// this state never gets cleaned up. 127*b0d29bc4SBrooks Davis static struct global_state* globals_singleton = NULL; 128*b0d29bc4SBrooks Davis 129*b0d29bc4SBrooks Davis 130*b0d29bc4SBrooks Davis /// Gets the singleton instance of global_state. 131*b0d29bc4SBrooks Davis /// 132*b0d29bc4SBrooks Davis /// \return A pointer to the unique global_state instance. 133*b0d29bc4SBrooks Davis static struct global_state* 134*b0d29bc4SBrooks Davis get_globals(void) 135*b0d29bc4SBrooks Davis { 136*b0d29bc4SBrooks Davis if (globals_singleton == NULL) { 137*b0d29bc4SBrooks Davis globals_singleton = new global_state(); 138*b0d29bc4SBrooks Davis } 139*b0d29bc4SBrooks Davis return globals_singleton; 140*b0d29bc4SBrooks Davis } 141*b0d29bc4SBrooks Davis 142*b0d29bc4SBrooks Davis 143*b0d29bc4SBrooks Davis /// Converts a level to a printable character. 144*b0d29bc4SBrooks Davis /// 145*b0d29bc4SBrooks Davis /// \param level The level to convert. 146*b0d29bc4SBrooks Davis /// 147*b0d29bc4SBrooks Davis /// \return The printable character, to be used in log messages. 148*b0d29bc4SBrooks Davis static char 149*b0d29bc4SBrooks Davis level_to_char(const logging::level level) 150*b0d29bc4SBrooks Davis { 151*b0d29bc4SBrooks Davis switch (level) { 152*b0d29bc4SBrooks Davis case logging::level_error: return 'E'; 153*b0d29bc4SBrooks Davis case logging::level_warning: return 'W'; 154*b0d29bc4SBrooks Davis case logging::level_info: return 'I'; 155*b0d29bc4SBrooks Davis case logging::level_debug: return 'D'; 156*b0d29bc4SBrooks Davis default: UNREACHABLE; 157*b0d29bc4SBrooks Davis } 158*b0d29bc4SBrooks Davis } 159*b0d29bc4SBrooks Davis 160*b0d29bc4SBrooks Davis 161*b0d29bc4SBrooks Davis } // anonymous namespace 162*b0d29bc4SBrooks Davis 163*b0d29bc4SBrooks Davis 164*b0d29bc4SBrooks Davis /// Generates a standard log name. 165*b0d29bc4SBrooks Davis /// 166*b0d29bc4SBrooks Davis /// This always adds the same timestamp to the log name for a particular run. 167*b0d29bc4SBrooks Davis /// Also, the timestamp added to the file name corresponds to the first 168*b0d29bc4SBrooks Davis /// timestamp recorded by the module; it does not necessarily contain the 169*b0d29bc4SBrooks Davis /// current value of "now". 170*b0d29bc4SBrooks Davis /// 171*b0d29bc4SBrooks Davis /// \param logdir The path to the directory in which to place the log. 172*b0d29bc4SBrooks Davis /// \param progname The name of the program that is generating the log. 173*b0d29bc4SBrooks Davis /// 174*b0d29bc4SBrooks Davis /// \return A string representation of the log name based on \p logdir and 175*b0d29bc4SBrooks Davis /// \p progname. 176*b0d29bc4SBrooks Davis fs::path 177*b0d29bc4SBrooks Davis logging::generate_log_name(const fs::path& logdir, const std::string& progname) 178*b0d29bc4SBrooks Davis { 179*b0d29bc4SBrooks Davis struct global_state* globals = get_globals(); 180*b0d29bc4SBrooks Davis 181*b0d29bc4SBrooks Davis if (!globals->first_timestamp) 182*b0d29bc4SBrooks Davis globals->first_timestamp = datetime::timestamp::now(); 183*b0d29bc4SBrooks Davis // Update kyua(1) if you change the name format. 184*b0d29bc4SBrooks Davis return logdir / (F("%s.%s.log") % progname % 185*b0d29bc4SBrooks Davis globals->first_timestamp.get().strftime(timestamp_format)); 186*b0d29bc4SBrooks Davis } 187*b0d29bc4SBrooks Davis 188*b0d29bc4SBrooks Davis 189*b0d29bc4SBrooks Davis /// Logs an entry to the log file. 190*b0d29bc4SBrooks Davis /// 191*b0d29bc4SBrooks Davis /// If the log is not yet set to persistent mode, the entry is recorded in the 192*b0d29bc4SBrooks Davis /// in-memory backlog. Otherwise, it is just written to disk. 193*b0d29bc4SBrooks Davis /// 194*b0d29bc4SBrooks Davis /// \param message_level The level of the entry. 195*b0d29bc4SBrooks Davis /// \param file The file from which the log message is generated. 196*b0d29bc4SBrooks Davis /// \param line The line from which the log message is generated. 197*b0d29bc4SBrooks Davis /// \param user_message The raw message to store. 198*b0d29bc4SBrooks Davis void 199*b0d29bc4SBrooks Davis logging::log(const level message_level, const char* file, const int line, 200*b0d29bc4SBrooks Davis const std::string& user_message) 201*b0d29bc4SBrooks Davis { 202*b0d29bc4SBrooks Davis struct global_state* globals = get_globals(); 203*b0d29bc4SBrooks Davis 204*b0d29bc4SBrooks Davis const datetime::timestamp now = datetime::timestamp::now(); 205*b0d29bc4SBrooks Davis if (!globals->first_timestamp) 206*b0d29bc4SBrooks Davis globals->first_timestamp = now; 207*b0d29bc4SBrooks Davis 208*b0d29bc4SBrooks Davis if (globals->auto_set_persistency) { 209*b0d29bc4SBrooks Davis // These values are hardcoded here for testing purposes. The 210*b0d29bc4SBrooks Davis // application should call set_inmemory() by itself during 211*b0d29bc4SBrooks Davis // initialization to avoid this, so that it has explicit control on how 212*b0d29bc4SBrooks Davis // the call to set_persistency() happens. 213*b0d29bc4SBrooks Davis set_persistency("debug", fs::path("/dev/stderr")); 214*b0d29bc4SBrooks Davis globals->auto_set_persistency = false; 215*b0d29bc4SBrooks Davis } 216*b0d29bc4SBrooks Davis 217*b0d29bc4SBrooks Davis if (message_level > globals->log_level) 218*b0d29bc4SBrooks Davis return; 219*b0d29bc4SBrooks Davis 220*b0d29bc4SBrooks Davis // Update doc/troubleshooting.texi if you change the log format. 221*b0d29bc4SBrooks Davis const std::string message = F("%s %s %s %s:%s: %s") % 222*b0d29bc4SBrooks Davis now.strftime(timestamp_format) % level_to_char(message_level) % 223*b0d29bc4SBrooks Davis ::getpid() % file % line % user_message; 224*b0d29bc4SBrooks Davis if (globals->logfile.get() == NULL) 225*b0d29bc4SBrooks Davis globals->backlog.push_back(std::make_pair(message_level, message)); 226*b0d29bc4SBrooks Davis else { 227*b0d29bc4SBrooks Davis INV(globals->backlog.empty()); 228*b0d29bc4SBrooks Davis (*globals->logfile) << message << '\n'; 229*b0d29bc4SBrooks Davis globals->logfile->flush(); 230*b0d29bc4SBrooks Davis } 231*b0d29bc4SBrooks Davis } 232*b0d29bc4SBrooks Davis 233*b0d29bc4SBrooks Davis 234*b0d29bc4SBrooks Davis /// Sets the logging to record messages in memory for later flushing. 235*b0d29bc4SBrooks Davis /// 236*b0d29bc4SBrooks Davis /// Can be called after set_persistency to flush logs and set recording to be 237*b0d29bc4SBrooks Davis /// in-memory again. 238*b0d29bc4SBrooks Davis void 239*b0d29bc4SBrooks Davis logging::set_inmemory(void) 240*b0d29bc4SBrooks Davis { 241*b0d29bc4SBrooks Davis struct global_state* globals = get_globals(); 242*b0d29bc4SBrooks Davis 243*b0d29bc4SBrooks Davis globals->auto_set_persistency = false; 244*b0d29bc4SBrooks Davis 245*b0d29bc4SBrooks Davis if (globals->logfile.get() != NULL) { 246*b0d29bc4SBrooks Davis INV(globals->backlog.empty()); 247*b0d29bc4SBrooks Davis globals->logfile->flush(); 248*b0d29bc4SBrooks Davis globals->logfile.reset(NULL); 249*b0d29bc4SBrooks Davis } 250*b0d29bc4SBrooks Davis } 251*b0d29bc4SBrooks Davis 252*b0d29bc4SBrooks Davis 253*b0d29bc4SBrooks Davis /// Makes the log persistent. 254*b0d29bc4SBrooks Davis /// 255*b0d29bc4SBrooks Davis /// Calling this function flushes the in-memory log, if any, to disk and sets 256*b0d29bc4SBrooks Davis /// the logging module to send log entries to disk from this point onwards. 257*b0d29bc4SBrooks Davis /// There is no way back, and the caller program should execute this function as 258*b0d29bc4SBrooks Davis /// early as possible to ensure that a crash at startup does not discard too 259*b0d29bc4SBrooks Davis /// many useful log entries. 260*b0d29bc4SBrooks Davis /// 261*b0d29bc4SBrooks Davis /// Any log entries above the provided new_level are discarded. 262*b0d29bc4SBrooks Davis /// 263*b0d29bc4SBrooks Davis /// \param new_level The new log level. 264*b0d29bc4SBrooks Davis /// \param path The file to write the logs to. 265*b0d29bc4SBrooks Davis /// 266*b0d29bc4SBrooks Davis /// \throw std::range_error If the given log level is invalid. 267*b0d29bc4SBrooks Davis /// \throw std::runtime_error If the given file cannot be created. 268*b0d29bc4SBrooks Davis void 269*b0d29bc4SBrooks Davis logging::set_persistency(const std::string& new_level, const fs::path& path) 270*b0d29bc4SBrooks Davis { 271*b0d29bc4SBrooks Davis struct global_state* globals = get_globals(); 272*b0d29bc4SBrooks Davis 273*b0d29bc4SBrooks Davis globals->auto_set_persistency = false; 274*b0d29bc4SBrooks Davis 275*b0d29bc4SBrooks Davis PRE(globals->logfile.get() == NULL); 276*b0d29bc4SBrooks Davis 277*b0d29bc4SBrooks Davis // Update doc/troubleshooting.info if you change the log levels. 278*b0d29bc4SBrooks Davis if (new_level == "debug") 279*b0d29bc4SBrooks Davis globals->log_level = level_debug; 280*b0d29bc4SBrooks Davis else if (new_level == "error") 281*b0d29bc4SBrooks Davis globals->log_level = level_error; 282*b0d29bc4SBrooks Davis else if (new_level == "info") 283*b0d29bc4SBrooks Davis globals->log_level = level_info; 284*b0d29bc4SBrooks Davis else if (new_level == "warning") 285*b0d29bc4SBrooks Davis globals->log_level = level_warning; 286*b0d29bc4SBrooks Davis else 287*b0d29bc4SBrooks Davis throw std::range_error(F("Unrecognized log level '%s'") % new_level); 288*b0d29bc4SBrooks Davis 289*b0d29bc4SBrooks Davis try { 290*b0d29bc4SBrooks Davis globals->logfile = utils::open_ostream(path); 291*b0d29bc4SBrooks Davis } catch (const std::runtime_error& unused_error) { 292*b0d29bc4SBrooks Davis throw std::runtime_error(F("Failed to create log file %s") % path); 293*b0d29bc4SBrooks Davis } 294*b0d29bc4SBrooks Davis 295*b0d29bc4SBrooks Davis for (std::vector< std::pair< logging::level, std::string > >::const_iterator 296*b0d29bc4SBrooks Davis iter = globals->backlog.begin(); iter != globals->backlog.end(); 297*b0d29bc4SBrooks Davis ++iter) { 298*b0d29bc4SBrooks Davis if ((*iter).first <= globals->log_level) 299*b0d29bc4SBrooks Davis (*globals->logfile) << (*iter).second << '\n'; 300*b0d29bc4SBrooks Davis } 301*b0d29bc4SBrooks Davis globals->logfile->flush(); 302*b0d29bc4SBrooks Davis globals->backlog.clear(); 303*b0d29bc4SBrooks Davis } 304