Back to index

nux  3.0.0
RollingFileAppender.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright 2011 Inalogic® Inc.
00004  *
00005  * This program is free software: you can redistribute it and/or modify it
00006  * under the terms of the GNU Lesser General Public License, as
00007  * published by the  Free Software Foundation; either version 2.1 or 3.0
00008  * of the License.
00009  *
00010  * This program is distributed in the hope that it will be useful, but
00011  * WITHOUT ANY WARRANTY; without even the implied warranties of
00012  * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
00013  * PURPOSE.  See the applicable version of the GNU Lesser General Public
00014  * License for more details.
00015  *
00016  * You should have received a copy of both the GNU Lesser General Public
00017  * License along with this program. If not, see <http://www.gnu.org/licenses/>
00018  *
00019  * Authored by: Tim Penhey <tim.penhey@canonical.com>
00020  *
00021  */
00022 
00023 #include "NuxCore.h"
00024 #include "RollingFileAppender.h"
00025 
00026 #include <fstream>
00027 #include <memory>
00028 #include <sstream>
00029 #include <stdexcept>
00030 
00031 #include <gio/gio.h>
00032 
00033 #include <boost/lexical_cast.hpp>
00034 
00035 #include "AsyncFileWriter.h"
00036 
00037 namespace nux {
00038 namespace logging {
00039 
00040 namespace {
00041 
00042 std::string backup_path(std::string const& path, unsigned number)
00043 {
00044   if (number == 0)
00045     return path;
00046 
00047   std::ostringstream sout;
00048   sout << path << "." << number;
00049   return sout.str();
00050 }
00051 
00052 // Change the implementation to string buf
00053 // Have a GFile representing the file on disk
00054 // g_file_append gives a GFileOutputStream (subclass of GOutputStream)
00055 // g_output_stream_write_async (only have one in progress at a time)
00056 // g_output_stream_flush_async (probably don't want a pending async)
00057 // keep our own count of the written bytes.
00058 // Tests don't have a g_object main loop, so we have another way...
00059 // while (g_main_context_pending(g_main_context_get_thread_default())) {
00060 //   g_main_context_iteration(g_main_context_get_thread_default());
00061 // }
00062 class RollingFileStreamBuffer : public std::stringbuf
00063 {
00064 public:
00065   RollingFileStreamBuffer(std::string const& filename,
00066                           unsigned number_of_backup_files,
00067                           unsigned long long max_log_size,
00068                           sigc::signal<void>& files_rolled);
00069   ~RollingFileStreamBuffer();
00070 protected:
00071   virtual int sync();
00072 private:
00073   void AsyncWriterClosed();
00074   void RotateFiles();
00075 
00076 #if defined(NUX_OS_WINDOWS) && (!defined(NUX_VISUAL_STUDIO_2010))
00077   std::tr1::shared_ptr<AsyncFileWriter> writer_;
00078 #else
00079   std::shared_ptr<AsyncFileWriter> writer_;
00080 #endif
00081 
00082   GFile* log_file_;
00083   std::string filename_;
00084   unsigned number_of_backup_files_;
00085   unsigned long long max_log_size_;
00086   unsigned long long bytes_written_;
00087   sigc::connection writer_closed_;
00088   sigc::signal<void>& files_rolled_;
00089 };
00090 
00091 RollingFileStreamBuffer::RollingFileStreamBuffer(std::string const& filename,
00092                                                  unsigned number_of_backup_files,
00093                                                  unsigned long long max_log_size,
00094                                                  sigc::signal<void>& files_rolled)
00095   : filename_(filename)
00096   , number_of_backup_files_(number_of_backup_files)
00097   , max_log_size_(max_log_size)
00098   , bytes_written_(0)
00099   , files_rolled_(files_rolled)
00100 {
00101   // Make sure that the filename starts with a '/' for a full path.
00102   if (filename.empty() || filename[0] != '/')
00103   {
00104       std::string error_msg = "\"" + filename + "\" is not a full path";
00105       throw std::runtime_error(error_msg.c_str());
00106   }
00107   // Looks to see if our filename exists.
00108   if (g_file_test(filename.c_str(), G_FILE_TEST_EXISTS))
00109   {
00110     // The filename needs to be a regular file.
00111     if (!g_file_test(filename.c_str(), G_FILE_TEST_IS_REGULAR))
00112     {
00113       std::string error_msg = filename + " is not a regular file";
00114       throw std::runtime_error(error_msg.c_str());
00115     }
00116     // Rotate the files.
00117     RotateFiles();
00118   }
00119   else
00120   {
00121     GFile* log_file = g_file_new_for_path(filename.c_str());
00122     GFile* log_dir = g_file_get_parent(log_file);
00123     if (log_dir)
00124     {
00125       g_file_make_directory_with_parents(log_dir, NULL, NULL);
00126       g_object_unref(log_dir);
00127       g_object_unref(log_file);
00128     }
00129     else
00130     {
00131       g_object_unref(log_file);
00132       std::string error_msg = "Can't get parent for " + filename;
00133       throw std::runtime_error(error_msg.c_str());
00134     }
00135   }
00136   // Now open the filename.
00137   writer_.reset(new AsyncFileWriter(filename));
00138   log_file_ = g_file_new_for_path(filename_.c_str());
00139 }
00140 
00141 RollingFileStreamBuffer::~RollingFileStreamBuffer()
00142 {
00143   // We don't want notification when the writer closes now.
00144   if (writer_closed_.connected())
00145     writer_closed_.disconnect();
00146   g_object_unref(log_file_);
00147 }
00148 
00149 void RollingFileStreamBuffer::RotateFiles()
00150 {
00151   // If we aren't keeping backups, no rolling needed.
00152   if (number_of_backup_files_ == 0)
00153     return;
00154 
00155   unsigned backup = number_of_backup_files_;
00156   std::string last_log(backup_path(filename_, backup));
00157   if (g_file_test(last_log.c_str(), G_FILE_TEST_EXISTS))
00158   {
00159     // Attempt to remove it.
00160     GFile* logfile = g_file_new_for_path(last_log.c_str());
00161     g_file_delete(logfile, NULL, NULL);
00162     g_object_unref(logfile);
00163   }
00164   // Move the previous files out.
00165   while (backup > 0)
00166   {
00167     std::string prev_log(backup_path(filename_, --backup));
00168     if (g_file_test(prev_log.c_str(), G_FILE_TEST_EXISTS))
00169     {
00170       GFile* dest = g_file_new_for_path(last_log.c_str());
00171       GFile* src = g_file_new_for_path(prev_log.c_str());
00172       // We don't really care if there are errors for now.
00173       g_file_move(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL);
00174       g_object_unref(src);
00175       g_object_unref(dest);
00176     }
00177     last_log = prev_log;
00178   }
00179 }
00180 
00181 void RollingFileStreamBuffer::AsyncWriterClosed()
00182 {
00183   // Rotate the files and open a new file writer.
00184   RotateFiles();
00185   writer_.reset(new AsyncFileWriter(filename_));
00186   bytes_written_ = 0;
00187   // We emit the files_rolled_ here and not in the RotateFiles method as the
00188   // RotateFiles is called from the constructor, which has a reference to the
00189   // files_rolled signal from the parent stream.  If this is emitted due
00190   // rotating the files in the contructor, we get a seg fault due to trying to
00191   // use the signal before it is constructed.
00192   files_rolled_.emit();
00193 }
00194 
00195 int RollingFileStreamBuffer::sync()
00196 {
00197   // If the async file writer is in the middle of closing, there is nothing we can do.
00198   if (writer_->IsClosing())
00199     return 0;
00200 
00201   std::string message = str();
00202   // reset the stream
00203   str("");
00204 
00205   std::size_t message_size = message.size();
00206   if (message_size > 0)
00207   {
00208     bytes_written_ += message_size;
00209     writer_->Write(message);
00210     if (bytes_written_ > max_log_size_)
00211     {
00212       // Close the writer and once it is closed, rotate the files and open a new file.
00213       writer_closed_ = writer_->closed.connect(
00214         sigc::mem_fun(this, &RollingFileStreamBuffer::AsyncWriterClosed));
00215       writer_->Close();
00216     }
00217   }
00218   return 0; // success
00219 }
00220 
00221 } // anon namespace
00222 
00223 RollingFileAppender::RollingFileAppender(std::string const& filename)
00224   : std::ostream(new RollingFileStreamBuffer(filename, 5, 1e7, files_rolled))
00225 {
00226 }
00227 
00228 RollingFileAppender::RollingFileAppender(std::string const& filename,
00229                                          unsigned number_of_backup_files,
00230                                          unsigned long long max_log_size)
00231   : std::ostream(new RollingFileStreamBuffer(filename,
00232                                              number_of_backup_files,
00233                                              max_log_size,
00234                                              files_rolled))
00235 {
00236 }
00237 
00238 RollingFileAppender::~RollingFileAppender()
00239 {
00240   rdbuf()->pubsync();
00241   std::streambuf* buff = rdbuf(0);
00242   delete buff;
00243 }
00244 
00245 
00246 } // namespace logging
00247 } // namespace nux