Path: blob/main/contrib/kyua/utils/logging/operations.cpp
48199 views
// Copyright 2011 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/logging/operations.hpp"2930extern "C" {31#include <unistd.h>32}3334#include <stdexcept>35#include <string>36#include <utility>37#include <vector>3839#include "utils/datetime.hpp"40#include "utils/format/macros.hpp"41#include "utils/fs/path.hpp"42#include "utils/optional.ipp"43#include "utils/sanity.hpp"44#include "utils/stream.hpp"4546namespace datetime = utils::datetime;47namespace fs = utils::fs;48namespace logging = utils::logging;4950using utils::none;51using utils::optional;525354/// The general idea for the application-wide logging goes like this:55///56/// 1. The application starts. Logging is initialized to capture _all_ log57/// messages into memory regardless of their level by issuing a call to the58/// set_inmemory() function.59///60/// 2. The application offers the user a way to select the logging level and a61/// file into which to store the log.62///63/// 3. The application calls set_persistency providing a new log level and a log64/// file. This must be done as early as possible, to minimize the chances of an65/// early crash not capturing any logs.66///67/// 4. At this point, any log messages stored into memory are flushed to disk68/// respecting the provided log level.69///70/// 5. The internal state of the logging module is updated to only capture71/// messages that are of the provided log level (or below) and is configured to72/// directly send messages to disk.73///74/// 6. The user may choose to call set_inmemory() again at a later stage, which75/// will cause the log to be flushed and messages to be recorded in memory76/// again. This is useful in case the logs are being sent to either stdout or77/// stderr and the process forks and wants to keep those child channels78/// unpolluted.79///80/// The call to set_inmemory() should only be performed by the user-facing81/// application. Tests should skip this call so that the logging messages go to82/// stderr by default, thus generating a useful log to debug the tests.838485namespace {868788/// Constant string to strftime to format timestamps.89static const char* timestamp_format = "%Y%m%d-%H%M%S";909192/// Mutable global state.93struct global_state {94/// Current log level.95logging::level log_level;9697/// Indicates whether set_persistency() will be called automatically or not.98bool auto_set_persistency;99100/// First time recorded by the logging module.101optional< datetime::timestamp > first_timestamp;102103/// In-memory record of log entries before persistency is enabled.104std::vector< std::pair< logging::level, std::string > > backlog;105106/// Stream to the currently open log file.107std::unique_ptr< std::ostream > logfile;108109global_state() :110log_level(logging::level_debug),111auto_set_persistency(true)112{113}114};115116117/// Single instance of the mutable global state.118///119/// Note that this is a raw pointer that we intentionally leak. We must do120/// this, instead of making all of the singleton's members static values,121/// because we want other destructors in the program to be able to log critical122/// conditions. If we use complex types in this translation unit, they may be123/// destroyed before the logging methods in the destructors get a chance to run124/// thus resulting in a premature crash. By using a plain pointer, we ensure125/// this state never gets cleaned up.126static struct global_state* globals_singleton = NULL;127128129/// Gets the singleton instance of global_state.130///131/// \return A pointer to the unique global_state instance.132static struct global_state*133get_globals(void)134{135if (globals_singleton == NULL) {136globals_singleton = new global_state();137}138return globals_singleton;139}140141142/// Converts a level to a printable character.143///144/// \param level The level to convert.145///146/// \return The printable character, to be used in log messages.147static char148level_to_char(const logging::level level)149{150switch (level) {151case logging::level_error: return 'E';152case logging::level_warning: return 'W';153case logging::level_info: return 'I';154case logging::level_debug: return 'D';155default: UNREACHABLE;156}157}158159160} // anonymous namespace161162163/// Generates a standard log name.164///165/// This always adds the same timestamp to the log name for a particular run.166/// Also, the timestamp added to the file name corresponds to the first167/// timestamp recorded by the module; it does not necessarily contain the168/// current value of "now".169///170/// \param logdir The path to the directory in which to place the log.171/// \param progname The name of the program that is generating the log.172///173/// \return A string representation of the log name based on \p logdir and174/// \p progname.175fs::path176logging::generate_log_name(const fs::path& logdir, const std::string& progname)177{178struct global_state* globals = get_globals();179180if (!globals->first_timestamp)181globals->first_timestamp = datetime::timestamp::now();182// Update kyua(1) if you change the name format.183return logdir / (F("%s.%s.log") % progname %184globals->first_timestamp.get().strftime(timestamp_format));185}186187188/// Logs an entry to the log file.189///190/// If the log is not yet set to persistent mode, the entry is recorded in the191/// in-memory backlog. Otherwise, it is just written to disk.192///193/// \param message_level The level of the entry.194/// \param file The file from which the log message is generated.195/// \param line The line from which the log message is generated.196/// \param user_message The raw message to store.197void198logging::log(const level message_level, const char* file, const int line,199const std::string& user_message)200{201struct global_state* globals = get_globals();202203const datetime::timestamp now = datetime::timestamp::now();204if (!globals->first_timestamp)205globals->first_timestamp = now;206207if (globals->auto_set_persistency) {208// These values are hardcoded here for testing purposes. The209// application should call set_inmemory() by itself during210// initialization to avoid this, so that it has explicit control on how211// the call to set_persistency() happens.212set_persistency("debug", fs::path("/dev/stderr"));213globals->auto_set_persistency = false;214}215216if (message_level > globals->log_level)217return;218219// Update doc/troubleshooting.texi if you change the log format.220const std::string message = F("%s %s %s %s:%s: %s") %221now.strftime(timestamp_format) % level_to_char(message_level) %222::getpid() % file % line % user_message;223if (globals->logfile.get() == NULL)224globals->backlog.push_back(std::make_pair(message_level, message));225else {226INV(globals->backlog.empty());227(*globals->logfile) << message << '\n';228globals->logfile->flush();229}230}231232233/// Sets the logging to record messages in memory for later flushing.234///235/// Can be called after set_persistency to flush logs and set recording to be236/// in-memory again.237void238logging::set_inmemory(void)239{240struct global_state* globals = get_globals();241242globals->auto_set_persistency = false;243244if (globals->logfile.get() != NULL) {245INV(globals->backlog.empty());246globals->logfile->flush();247globals->logfile.reset();248}249}250251252/// Makes the log persistent.253///254/// Calling this function flushes the in-memory log, if any, to disk and sets255/// the logging module to send log entries to disk from this point onwards.256/// There is no way back, and the caller program should execute this function as257/// early as possible to ensure that a crash at startup does not discard too258/// many useful log entries.259///260/// Any log entries above the provided new_level are discarded.261///262/// \param new_level The new log level.263/// \param path The file to write the logs to.264///265/// \throw std::range_error If the given log level is invalid.266/// \throw std::runtime_error If the given file cannot be created.267void268logging::set_persistency(const std::string& new_level, const fs::path& path)269{270struct global_state* globals = get_globals();271272globals->auto_set_persistency = false;273274PRE(globals->logfile.get() == NULL);275276// Update doc/troubleshooting.info if you change the log levels.277if (new_level == "debug")278globals->log_level = level_debug;279else if (new_level == "error")280globals->log_level = level_error;281else if (new_level == "info")282globals->log_level = level_info;283else if (new_level == "warning")284globals->log_level = level_warning;285else286throw std::range_error(F("Unrecognized log level '%s'") % new_level);287288try {289globals->logfile = utils::open_ostream(path);290} catch (const std::runtime_error& unused_error) {291throw std::runtime_error(F("Failed to create log file %s") % path);292}293294for (std::vector< std::pair< logging::level, std::string > >::const_iterator295iter = globals->backlog.begin(); iter != globals->backlog.end();296++iter) {297if ((*iter).first <= globals->log_level)298(*globals->logfile) << (*iter).second << '\n';299}300globals->logfile->flush();301globals->backlog.clear();302}303304305