nux-1.14.0
|
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