SockIt
|
00001 /* 00002 * Logger.cpp 00003 * 00004 * Do not instantiate or inherit from this class. 00005 * 00006 * An asynchronous logger. Call by using 'Logger::info("msg");' or equivalent method. 00007 * Designed to have the main thread do as little work as possible. 00008 * 00009 * Internally, there are two background threads. One formats a log request into a file directory 00010 * and a log entry. The other actually writes this into the log. Hopefully, one thread can be in a 00011 * system call (to get the current date or time) and the other can be doing I/O. 00012 * 00013 * Sadly, a bug in firebreath+boost prevents both background threads from sleeping 00014 * and just periodically checking that there's work. The bug makes the sleeping thread 00015 * hang indefinitely (until interrupted). Since the bug affects waiting/timed waiting 00016 * on a condition variable, (which is how boost implements sleep), the main thread has 00017 * to signal the formatter thread just in case the bug happened. The formatter thread 00018 * then signals the log writing thread when there's work. 00019 * 00020 * Logs are placed in /sockit/YYYY-MM-DD/REMOTEHOST/PROCESSID/sockit-traffic.log. 00021 * 00022 * Note: Logger::shutdown() should only be called once, when the plugin is shutting down. 00023 * After this is called, no more logging will take place. 00024 */ 00025 00026 #include "Logger.h" 00027 00028 00029 00030 /* declare static variables */ 00031 00032 boost::mutex Logger::log_writer_mtx; 00033 boost::mutex Logger::formatter_mtx; 00034 boost::mutex Logger::shutdown_mtx; 00035 boost::condition_variable Logger::shutdown_cvar; 00036 00037 std::queue<pair<string, string> > Logger::write_requests; 00038 std::queue<pair<pair<string, int> , pair<string, string> > > Logger::raw_requests; 00039 00040 bool Logger::initialized = false; 00041 bool Logger::enabled = true; 00042 00043 string Logger::pid(boost::lexical_cast<string>(PID)); 00044 00045 boost::thread Logger::frm_t; 00046 boost::thread Logger::lw_t; 00047 00048 boost::condition_variable Logger::formatter_cvar; 00049 boost::condition_variable Logger::log_writer_cvar; 00050 00051 /* end of static variables */ 00052 00053 00054 00055 /* Initialize the background logging thread. Should only be called once. */ 00056 void Logger::initialize() 00057 { 00058 initialized = true; 00059 00060 frm_t = boost::thread(&Logger::formatter_run); 00061 lw_t = boost::thread(&Logger::log_writer_run); 00062 } 00063 00064 /* Helper for building the directory path. Gets the current date (local time) in YYYY-MM-DD format. */ 00065 string Logger::get_date() 00066 { 00067 boost::gregorian::date d(boost::gregorian::day_clock::local_day()); 00068 return boost::gregorian::to_iso_extended_string(d); 00069 } 00070 00071 /* Helper for building the directory path. Gets the current timestamp (local time) in HH:MM:SS format */ 00072 string Logger::get_timestamp() 00073 { 00074 boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); 00075 return boost::posix_time::to_simple_string(now); 00076 } 00077 00078 /* Helper for building the directory path. Gets the base log path. */ 00079 string Logger::get_log_base_path() 00080 { 00081 #if defined (__UNIX__) || defined (__OSX__) 00082 00083 string path(getenv("HOME")); 00084 00085 #else 00086 00087 #define PROFILE_BUF_LEN 250 00088 char profilepath[PROFILE_BUF_LEN]; 00089 ExpandEnvironmentStringsA("%USERPROFILE%", profilepath, PROFILE_BUF_LEN); 00090 string path(profilepath); 00091 00092 #endif 00093 path.append("/sockit"); 00094 return path; 00095 } 00096 00097 /* Called on plugin shutdown, not when just one object is shutting down. Should not be called at any other time. */ 00098 void Logger::shutdown() 00099 { 00100 // disable any more log requests and disable the background threads from sleeping 00101 enabled = false; 00102 00103 if(initialized) 00104 { 00105 // wait until the log writing queue is done 00106 boost::unique_lock<boost::mutex> lock(shutdown_mtx); 00107 shutdown_cvar.wait(lock); 00108 } 00109 } 00110 00111 /* Add a message to the log writing queue */ 00112 void Logger::queue(string host, int port, string msg, string cat) 00113 { 00114 pair<string, int> item1(host, port); 00115 pair<string, string> item2(msg, cat); 00116 pair<pair<string, int> , pair<string, string> > item(item1, item2); 00117 00118 formatter_mtx.lock(); 00119 00120 if (!Logger::initialized) 00121 Logger::initialize(); 00122 00123 raw_requests.push(item); 00124 formatter_cvar.notify_one(); 00125 00126 formatter_mtx.unlock(); 00127 } 00128 00129 /* Only the formatter thread should be here. Run until logging is disabled and handle formatting requests. */ 00130 void Logger::formatter_run() 00131 { 00132 // needed for sleeping on a boost cvar 00133 boost::unique_lock<boost::mutex> fmt_lock(formatter_mtx); 00134 00135 // continue as long as logging is enabled and there are still raw requests to process 00136 while(true) 00137 { 00138 while (!raw_requests.empty()) 00139 { 00140 // handle one request 00141 pair<pair<string, int> , pair<string, string> > p = raw_requests.front(); 00142 raw_requests.pop(); 00143 fmt_lock.unlock(); 00144 handle_format_request(p.first.first, p.first.second, p.second.first, p.second.second); 00145 fmt_lock.lock(); 00146 } 00147 00148 if(enabled || !raw_requests.empty()) 00149 { 00150 // Sigh. There's a bug with firebreath + boost::this_thread::sleep/boost::condition_variable.timed_wait 00151 // where the sleeping thread hangs forever. So this is a compromise/work around. 00152 00153 // don't hold the lock across the gettime system call 00154 fmt_lock.unlock(); 00155 boost::system_time sleep_time = boost::get_system_time() + boost::posix_time::milliseconds(SLEEP_TIME_MS); 00156 fmt_lock.lock(); 00157 00158 // wake up periodically to check for work. 00159 formatter_cvar.timed_wait(fmt_lock, sleep_time); 00160 } 00161 else 00162 { 00163 fmt_lock.unlock(); 00164 break; 00165 } 00166 } 00167 } 00168 00169 /* Only the formatter thread should be here. Handle a format request. */ 00170 void Logger::handle_format_request(string host, int port, string msg, string cat) 00171 { 00172 // used to build the log path 00173 string host_str = (host == "local" ? "localhost" : host); 00174 string port_str = (port == NO_PORT ? "" : boost::lexical_cast<string>(port)); 00175 00176 string day = get_date(); 00177 string time = get_timestamp(); 00178 00179 // figure out the path for this message 00180 string log_path(get_log_base_path() + "/" + day + "/" + host_str + "/" + pid); 00181 string entry(cat + "[" + time + "] " + "[" + host_str + (port == NO_PORT ? "] " : "] [" + port_str + "] ") + msg); 00182 00183 // log request item 00184 pair<string, string> item(log_path, entry); 00185 00186 // add item to log write queue 00187 log_writer_mtx.lock(); 00188 write_requests.push(item); 00189 00190 // awaken log writing thread - it has work 00191 log_writer_mtx.unlock(); 00192 log_writer_cvar.notify_one(); 00193 } 00194 00195 /* Only the log writing thread should be in this function. Run until logging is disabled and handle log writing 00196 * requests. */ 00197 void Logger::log_writer_run() 00198 { 00199 // Needed to sleep on a boost cvar 00200 boost::unique_lock<boost::mutex> lw_lock(log_writer_mtx); 00201 00202 // continue as long as logging is enabled and there are either 00203 // raw requests to process or log writing requests to process 00204 while(true) 00205 { 00206 while (!write_requests.empty()) 00207 { 00208 // handle one request 00209 pair<string, string> p = write_requests.front(); 00210 write_requests.pop(); 00211 log_writer_mtx.unlock(); 00212 handle_write_request(p.first, p.second); 00213 log_writer_mtx.lock(); 00214 } 00215 00216 if(enabled || !raw_requests.empty() || !write_requests.empty()) 00217 { 00218 // don't hold the lock across a system call 00219 lw_lock.unlock(); 00220 boost::system_time sleep_time = boost::get_system_time() + boost::posix_time::milliseconds(SLEEP_TIME_MS); 00221 lw_lock.lock(); 00222 00223 // wait until there's work 00224 log_writer_cvar.timed_wait(lw_lock, sleep_time); 00225 } 00226 else 00227 { 00228 lw_lock.unlock(); 00229 break; 00230 } 00231 } 00232 00233 // notify the main thread to wake up 00234 shutdown_cvar.notify_all(); 00235 } 00236 00237 /* Only the log writing thread should be in this function (or the main thread, on shutdown). This handles a log write request. */ 00238 void Logger::handle_write_request(string dir, string line) 00239 { 00240 namespace fs = boost::filesystem; 00241 00242 if (!fs::exists(dir)) 00243 { 00244 try 00245 { 00246 fs::create_directories(dir); 00247 } catch (fs::filesystem_error &err) 00248 { 00249 // hope someone is listening.. 00250 std::cout << "Error: could not create directory " << dir << std::endl; 00251 return; 00252 } 00253 } 00254 00255 string fileName(dir + "/sockit-traffic.log"); 00256 line.append("\n"); 00257 00258 FILE *fp = fopen(fileName.c_str(), "a"); 00259 00260 if(fp) 00261 { 00262 fwrite(line.c_str(), line.size(), 1, fp); 00263 fclose(fp); 00264 } 00265 else 00266 { 00267 std::cout << "Error: could not open file " << fileName << std::endl; 00268 } 00269 }