/*************************************************************************** * GLog.cpp - Information logger * * ----------------------------------------------------------------------- * * copyright (C) 2010-2016 by Juergen Knoedlseder * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file GLog.cpp * @brief Information logger class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include // for std::va_list type #include // std::fopen, std::fgets, std::fclose, etc... #include // std::memcpy #include #include #include #include #include // dup #include "GLog.hpp" #include "GTools.hpp" /* __ Method name definitions ____________________________________________ */ /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GLog::GLog(void) { // Initialise private members for clean destruction init_members(); // Return return; } /***********************************************************************//** * @brief Log file constructor * * @param[in] filename Name of log file. * @param[in] clobber If true overwrite any existing file, otherwise append. * * Construct a logger with an open log file. ***************************************************************************/ GLog::GLog(const GFilename& filename, const bool& clobber) { // Initialise private members for clean destruction init_members(); // Open file open(filename, clobber); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] log Object from which the instance should be built. ***************************************************************************/ GLog::GLog(const GLog& log) { // Initialise private members for clean destruction init_members(); // Copy members copy_members(log); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GLog::~GLog(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] log Object which should be assigned. ***************************************************************************/ GLog& GLog::operator= (const GLog& log) { // Execute only if object is not identical if (this != &log) { // Free members free_members(); // Initialise private members for clean destruction init_members(); // Copy members copy_members(log); } // endif: object was not identical // Return return *this; } /**************************************************************************//** * @brief Message logging operator * * @param[in] msgFormat Pointer to message format. * @param[in] ... Message to put into log. * * This operator provides message logging capabilities in C style. It expects * a C-style format string followed by an arbitrary number of arguments that * is used to fill the format. * * @todo A static buffer of length 8192 is allocated. Although this is likely * sufficient for any normal application there is nothing that prevents in * principle the overflow of the buffer. A more sophisticated scheme should be * implemented. ******************************************************************************/ void GLog::operator()(const char *msgFormat, ...) { // Allocate message buffer char buffer[8192]; // Put message into buffer std::va_list vl; va_start(vl, msgFormat); std::vsprintf(buffer, msgFormat, vl); va_end(vl); // Append message to string buffer append(std::string(buffer)+"\n"); // Return return; } /***********************************************************************//** * @brief Insert logger into logger * * @param[in] log Logger to be inserted. * * Insert a logger object into another logger object. This method handles * information that has either been written into a file, or that is still * present in the logger buffer. ***************************************************************************/ GLog& GLog::operator<<(GLog& log) { // If log object has an open file then append the log buffer to this // buffer if (log.m_file == NULL) { m_buffer.append(log.m_buffer); } // ... otherwise read all information from file and append it to the // buffer else { // Force flush so that the log object writes the entire buffer // into the file log.flush(true); // Get the filename GFilename filename = log.filename(); // Close log's file log.close(); // Open log's file in read mode std::ifstream file(filename.url().c_str(), std::ios::in); if (file) { // Append all lines from file to this buffer std::string line; while(std::getline(file,line)){ m_buffer.append(line+"\n"); } // Close log's file file.close(); } // Open log's file in append mode log.open(filename, false); } // Flush flush(); // Return logger return *this; } /***********************************************************************//** * @brief Insert C++ string into logger * * @param[in] str C++ string to be inserted. * * This method insert a C++ string into the logger. ***************************************************************************/ GLog& GLog::operator<<(const std::string& str) { // Add string to buffer append(str); // Return logger return *this; } /***********************************************************************//** * @brief Insert C string into logger * * @param[in] str C string to be inserted. * * This method insert a C string into the logger. ***************************************************************************/ GLog& GLog::operator<<(const char* str) { // Add string to buffer append(std::string(str)); // Return logger return *this; } /***********************************************************************//** * @brief Insert character value into logger * * @param[in] value Character value to be inserted. * * This method inserts a character value into the logger. ***************************************************************************/ GLog& GLog::operator<<(const char& value) { // Allocate output stream std::ostringstream oss; // Put value into stream oss << value; // Append stream to buffer append(oss.str()); // Return logger return *this; } /***********************************************************************//** * @brief Insert unsigned character value into logger * * @param[in] value Unsigned character value to be inserted. * * This method inserts an unsigned character value into the logger. ***************************************************************************/ GLog& GLog::operator<<(const unsigned char& value) { // Allocate output stream std::ostringstream oss; // Put value into stream oss << value; // Append stream to buffer append(oss.str()); // Return logger return *this; } /***********************************************************************//** * @brief Insert bool into logger * * @param[in] value Boolean to be inserted. * * Depending on the argument, this method inserts either the character string * "true" or "false" into the logger. ***************************************************************************/ GLog& GLog::operator<<(const bool& value) { // Add boolean to buffer if (value) { append("true"); } else { append("false"); } // Return logger return *this; } /***********************************************************************//** * @brief Insert integer into logger * * @param[in] value Integer to be inserted. * * This method inserts an integer into the logger. ***************************************************************************/ GLog& GLog::operator<<(const int& value) { // Allocate output stream std::ostringstream oss; // Put value into stream oss << value; // Append stream to buffer append(oss.str()); // Return logger return *this; } /***********************************************************************//** * @brief Insert unsigned integer into logger * * @param[in] value Unsigned integer to be inserted. * * This method inserts an unsigned integer into the logger. ***************************************************************************/ GLog& GLog::operator<<(const unsigned int& value) { // Allocate output stream std::ostringstream oss; // Put value into stream oss << value; // Append stream to buffer append(oss.str()); // Return logger return *this; } /***********************************************************************//** * @brief Insert double precision value into logger * * @param[in] value Double precision value to be inserted. * * This method inserts a double precision value into the logger. ***************************************************************************/ GLog& GLog::operator<<(const double& value) { // Allocate output stream std::ostringstream oss; // Put value into stream oss << value; // Append stream to buffer append(oss.str()); // Return logger return *this; } /***********************************************************************//** * @brief Ostream function output operator * * @param[in] fn Ostream function. * * The function output operator is interpreted as a line feed operator. * * @todo This is a quick an dirty implementation of the std::endl operator. * A clean GLog::endl operator should be implemented instead, yet this needs * some deeper understanding of our the ***************************************************************************/ GLog& GLog::operator<<(std::ostream& (*fn)(std::ostream&)) { // Append CR to buffer append("\n"); // Return logger return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear object ***************************************************************************/ void GLog::clear(void) { // Free members free_members(); // Init members init_members(); // Return return; } /***********************************************************************//** * @brief Return the number of characters that are actually in the buffer ***************************************************************************/ int GLog::size(void) const { // Return return m_buffer.size(); } /***********************************************************************//** * @brief Open log file * * @param[in] filename Name of log file. * @param[in] clobber If true overwrite any existing file, otherwise append. * * Opens a file for logging. If a log file was already open it is closed * before opening a new file. ***************************************************************************/ void GLog::open(const GFilename& filename, const bool& clobber) { // Store the filename m_filename = filename; // Close any existing file close(); // Open file if (clobber) { m_file = std::fopen(filename.url().c_str(), "w"); } else { m_file = std::fopen(filename.url().c_str(), "a"); } // Return return; } /***********************************************************************//** * @brief Close log file * * Close log file. This method even works if no file has been opened. In any * case flushing of the buffer is enforced. ***************************************************************************/ void GLog::close(void) { // Flush buffer flush(true); // Close any open file if (m_file != NULL) { std::fclose(m_file); m_file = NULL; } // Return return; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GLog::init_members(void) { // Initialise members m_max_length = 8192; m_indent = 0; m_stdout = false; m_stderr = false; m_use_date = false; m_linestart = true; m_file = NULL; m_filename.clear(); m_name.clear(); m_buffer.clear(); m_chatter = NORMAL; // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] log Logger. * * Copy all class members from @p log to the current logger instance. This * method will flush the buffer of @p log before copying. ***************************************************************************/ void GLog::copy_members(const GLog& log) { // Force flushing the buffer before doing the copy. This assures that the // log file is left in a clean state, and in particular, that logging // will be done in order. We have to circument const correctness for // doing this. const_cast(&log)->flush(true); // Copy attributes m_max_length = log.m_max_length; m_indent = log.m_indent; m_stdout = log.m_stdout; m_stderr = log.m_stderr; m_use_date = log.m_use_date; m_linestart = log.m_linestart; m_filename = log.m_filename; m_name = log.m_name; m_buffer = log.m_buffer; m_chatter = log.m_chatter; // Copy file pointer by duplicating the file descriptor and opening // the duplicated file descriptor if (log.m_file != NULL) { m_file = fdopen(dup(fileno(log.m_file)), "a"); } else { m_file = NULL; } // Return return; } /***********************************************************************//** * @brief Delete class members * * Deletes class member after flushing the buffer and closing the log file. ***************************************************************************/ void GLog::free_members(void) { // Flush buffer flush(true); // Close log file close(); // Return return; } /***********************************************************************//** * @brief Flush string buffer into log file * * @param[in] force If true, force flushing. * * Flush string buffer if it is full or if flushing is enforced. This method * writes the output string in the relevant streams. It decomposes the * buffer in lines that a separated by a '\n' character. The following * streams are currently implemented (and will be filled in parallel): * stdout, stderr, and an ASCII file. ***************************************************************************/ void GLog::flush(const bool& force) { // Check if buffer should be flushed bool flush = (force || size() > m_max_length); // Flush buffer if (flush) { // Flush buffer until it is empty while (m_buffer.size() > 0) { // Find next CR std::string line; std::size_t pos = m_buffer.find_first_of("\n", 0); if (pos == std::string::npos) { line = m_buffer; m_linestart = false; m_buffer.clear(); } else { line = m_buffer.substr(0, pos) + "\n"; m_buffer = m_buffer.substr(pos+1, m_buffer.size()-pos); m_linestart = true; } // Put line into file if (m_file != NULL) { std::fprintf(m_file, "%s", line.c_str()); } } // endwhile: flush until empty // And now flush the kernel buffer std::fflush(m_file); } // endif: flush was required // Return return; } /***********************************************************************//** * @brief Write string as header into logger * * @param[in] arg Header string. * @param[in] level Header level (0,1,2,3). * * Writes a string as header into the logger. Various header levels exist: * 0: 80 character wide centred header * 1: Header framed by '=' characters * 2: Header framed by '-' characters * 3: Header enclosed by '===' string ***************************************************************************/ void GLog::header(const std::string& arg, int level) { // Declare frame and text strings std::string frame; std::string text; // Create level dependent header strings switch (level) { case 0: break; case 1: case 2: text = "| " + arg + " |"; frame = (level == 1) ? "+" + gammalib::fill("=", text.length()-2) + "+" : "+" + gammalib::fill("-", text.length()-2) + "+"; break; case 3: text = "=== " + arg + " ==="; break; default: break; } // Write header into logger if (frame.length() > 0) { append(frame); append("\n"); } if (text.length() > 0) { append(text); append("\n"); } if (frame.length() > 0) { append(frame); append("\n"); } // Return return; } /***********************************************************************//** * @brief Return current date * * Returns the current date as string in the format yyyy-mm-ddThh:mm:ss. ***************************************************************************/ std::string GLog::strdate(void) const { // Allocate variables struct std::tm timeStruct; std::time_t now; char buffer[100]; // Get time now = std::time(NULL); #ifdef HAVE_GMTIME_R std::gmtime_r(&now, &timeStruct); #else std::memcpy(&timeStruct, gmtime(&now), sizeof(struct tm)); #endif // Write message type, time and task name to buffer std::sprintf(buffer, "%04d-%02d-%02dT%02d:%02d:%02d", timeStruct.tm_year + 1900, timeStruct.tm_mon + 1, timeStruct.tm_mday, timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec); // Build string from buffer std::string date = buffer; // Return date return date; } /***********************************************************************//** * @brief Return prefix * * Returns the prefix for each line. ***************************************************************************/ std::string GLog::prefix(void) const { // Initialize prefix to empty string std::string prefix = ""; // Add date if requested if (m_use_date){ prefix.append(strdate()); } // Add name if requested if (m_name.length() > 0){ prefix.append(" "+m_name); } // If there is a prefix then add separator if (prefix.length() > 0){ prefix.append(": "); } // Add any indent prefix.append(gammalib::fill(" ", m_indent)); // Return prefix return prefix; } /***********************************************************************//** * @brief Append string to the buffer * * @param[in] arg String to append * * This method appends a string to the buffer and prepends, if required, the * current date and name at the beginning of each line. ***************************************************************************/ void GLog::append(std::string arg) { // If the buffer is empty and at the beginning of a line or if the last // character is a \n then prepend a prefix at the beginning of the string // to be inserted. if ((m_buffer.size() == 0 && m_linestart) || (m_buffer.size() > 0 && m_buffer[m_buffer.size()-1] == '\n')) { // Prepend prefix arg.insert(0, prefix()); } // Search the first CR (\n) std::size_t pos = arg.find_first_of("\n",0); // Search all \n characters. Ignore the last CR. while (pos != std::string::npos && pos < arg.size()-1) { // Prepend prefix std::string pre = prefix(); arg.insert(pos+1, pre); // Search next CR pos = arg.find_first_of("\n",pos+1+pre.size()); } // endwhile // Add string to buffer m_buffer.append(arg); // Flush Buffer flush(); // Append string to stdout and/or stderr without any buffering // if requested if (m_stdout) { std::cout << arg; } if (m_stderr) { std::cerr << arg; } // Return return; }