1// Copyright 2011 Google Inc. 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 31extern "C" { 32#include <unistd.h> 33} 34 35#include <fstream> 36#include <stdexcept> 37#include <string> 38#include <utility> 39#include <vector> 40 41#include "utils/datetime.hpp" 42#include "utils/format/macros.hpp" 43#include "utils/optional.ipp" 44#include "utils/sanity.hpp" 45 46namespace datetime = utils::datetime; 47namespace fs = utils::fs; 48namespace logging = utils::logging; 49 50using utils::none; 51using utils::optional; 52 53 54/// The general idea for the application-wide logging goes like this: 55/// 56/// 1. The application starts. Logging is initialized to capture _all_ log 57/// messages into memory regardless of their level by issuing a call to the 58/// set_inmemory() function. 59/// 60/// 2. The application offers the user a way to select the logging level and a 61/// file into which to store the log. 62/// 63/// 3. The application calls set_persistency providing a new log level and a log 64/// file. This must be done as early as possible, to minimize the chances of an 65/// early crash not capturing any logs. 66/// 67/// 4. At this point, any log messages stored into memory are flushed to disk 68/// respecting the provided log level. 69/// 70/// 5. The internal state of the logging module is updated to only capture 71/// messages that are of the provided log level (or below) and is configured to 72/// directly send messages to disk. 73/// 74/// The call to set_inmemory() should only be performed by the user-facing 75/// application. Tests should skip this call so that the logging messages go to 76/// stderr by default, thus generating a useful log to debug the tests. 77 78 79namespace { 80 81 82/// Current log level. 83static logging::level log_level = logging::level_debug; 84 85 86/// Indicates whether set_persistency() will be called automatically or not. 87static bool auto_set_persistency = true; 88 89 90/// First time recorded by the logging module. 91static optional< datetime::timestamp > first_timestamp = none; 92 93 94/// In-memory record of log entries before persistency is enabled. 95static std::vector< std::pair< logging::level, std::string > > backlog; 96 97 98/// Stream to the currently open log file. 99static std::unique_ptr< std::ofstream > logfile; 100 101 102/// Constant string to strftime to format timestamps. 103static const char* timestamp_format = "%Y%m%d-%H%M%S"; 104 105 106/// Converts a level to a printable character. 107/// 108/// \param level The level to convert. 109/// 110/// \return The printable character, to be used in log messages. 111static char 112level_to_char(const logging::level level) 113{ 114 switch (level) { 115 case logging::level_error: return 'E'; 116 case logging::level_warning: return 'W'; 117 case logging::level_info: return 'I'; 118 case logging::level_debug: return 'D'; 119 default: UNREACHABLE; 120 } 121} 122 123 124} // anonymous namespace 125 126 127/// Generates a standard log name. 128/// 129/// This always adds the same timestamp to the log name for a particular run. 130/// Also, the timestamp added to the file name corresponds to the first 131/// timestamp recorded by the module; it does not necessarily contain the 132/// current value of "now". 133/// 134/// \param logdir The path to the directory in which to place the log. 135/// \param progname The name of the program that is generating the log. 136fs::path 137logging::generate_log_name(const fs::path& logdir, const std::string& progname) 138{ 139 if (!first_timestamp) 140 first_timestamp = datetime::timestamp::now(); 141 // Update doc/troubleshooting.texi if you change the name format. 142 return logdir / (F("%s.%s.log") % progname % 143 first_timestamp.get().strftime(timestamp_format)); 144} 145 146 147/// Logs an entry to the log file. 148/// 149/// If the log is not yet set to persistent mode, the entry is recorded in the 150/// in-memory backlog. Otherwise, it is just written to disk. 151/// 152/// \param message_level The level of the entry. 153/// \param file The file from which the log message is generated. 154/// \param line The line from which the log message is generated. 155/// \param user_message The raw message to store. 156void 157logging::log(const level message_level, const char* file, const int line, 158 const std::string& user_message) 159{ 160 const datetime::timestamp now = datetime::timestamp::now(); 161 if (!first_timestamp) 162 first_timestamp = now; 163 164 if (auto_set_persistency) { 165 // These values are hardcoded here for testing purposes. The 166 // application should call set_inmemory() by itself during 167 // initialization to avoid this, so that it has explicit control on how 168 // the call to set_persistency() happens. 169 set_persistency("debug", fs::path("/dev/stderr")); 170 auto_set_persistency = false; 171 } 172 173 if (message_level > log_level) 174 return; 175 176 // Update doc/troubleshooting.texi if you change the log format. 177 const std::string message = F("%s %s %s %s:%s: %s") % 178 now.strftime(timestamp_format) % level_to_char(message_level) % 179 ::getpid() % file % line % user_message; 180 if (logfile.get() == NULL) 181 backlog.push_back(std::make_pair(message_level, message)); 182 else { 183 INV(backlog.empty()); 184 (*logfile) << message << '\n'; 185 (*logfile).flush(); 186 } 187} 188 189 190/// Sets the logging to record messages in memory for later flushing. 191void 192logging::set_inmemory(void) 193{ 194 auto_set_persistency = false; 195} 196 197 198/// Makes the log persistent. 199/// 200/// Calling this function flushes the in-memory log, if any, to disk and sets 201/// the logging module to send log entries to disk from this point onwards. 202/// There is no way back, and the caller program should execute this function as 203/// early as possible to ensure that a crash at startup does not discard too 204/// many useful log entries. 205/// 206/// Any log entries above the provided new_level are discarded. 207/// 208/// \param new_level The new log level. 209/// \param path The file to write the logs to. 210/// 211/// \throw std::range_error If the given log level is invalid. 212/// \throw std::runtime_error If the given file cannot be created. 213void 214logging::set_persistency(const std::string& new_level, const fs::path& path) 215{ 216 auto_set_persistency = false; 217 218 PRE(logfile.get() == NULL); 219 220 // Update doc/troubleshooting.info if you change the log levels. 221 if (new_level == "debug") 222 log_level = level_debug; 223 else if (new_level == "error") 224 log_level = level_error; 225 else if (new_level == "info") 226 log_level = level_info; 227 else if (new_level == "warning") 228 log_level = level_warning; 229 else 230 throw std::range_error(F("Unrecognized log level '%s'") % new_level); 231 232 logfile.reset(new std::ofstream(path.c_str())); 233 if (!(*logfile)) 234 throw std::runtime_error(F("Failed to create log file %s") % path); 235 236 for (std::vector< std::pair< logging::level, std::string > >::const_iterator 237 iter = backlog.begin(); iter != backlog.end(); iter++) { 238 if ((*iter).first <= log_level) 239 (*logfile) << (*iter).second << '\n'; 240 } 241 (*logfile).flush(); 242 backlog.clear(); 243} 244