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