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