SockIt

Logger.cpp

Go to the documentation of this file.
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 }
 All Classes Files Functions Variables Typedefs Friends Defines