Drizzled Public API Documentation

logging_query.cc
00001 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright (C) 2008, 2009 Sun Microsystems, Inc.
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; version 2 of the License.
00009  *
00010  *  This program is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  *  GNU General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU General Public License
00016  *  along with this program; if not, write to the Free Software
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00018  */
00019 
00020 #include <config.h>
00021 #include <drizzled/plugin.h>
00022 #include <drizzled/plugin/logging.h>
00023 #include <drizzled/gettext.h>
00024 #include <drizzled/session.h>
00025 #include <drizzled/session/times.h>
00026 #include <drizzled/sql_parse.h>
00027 #include PCRE_HEADER
00028 #include <limits.h>
00029 #include <sys/time.h>
00030 #include <sys/types.h>
00031 #include <sys/stat.h>
00032 #include <fcntl.h>
00033 #include <string>
00034 #include <boost/format.hpp>
00035 #include <boost/program_options.hpp>
00036 #include <drizzled/module/option_map.h>
00037 #include <cstdio>
00038 #include <cerrno>
00039 
00040 namespace po= boost::program_options;
00041 using namespace drizzled;
00042 using namespace std;
00043 
00044 #define ESCAPE_CHAR      '\\'
00045 #define SEPARATOR_CHAR   ','
00046 
00047 namespace drizzle_plugin
00048 {
00049 
00050 static bool sysvar_logging_query_enable= false;
00051 /* TODO fix these to not be unsigned long once we have sensible sys_var system */
00052 static uint32_constraint sysvar_logging_query_threshold_slow;
00053 static uint32_constraint sysvar_logging_query_threshold_big_resultset;
00054 static uint32_constraint sysvar_logging_query_threshold_big_examined;
00055 
00056 /* quote a string to be safe to include in a CSV line
00057    that means backslash quoting all commas, doublequotes, backslashes,
00058    and all the ASCII unprintable characters
00059    as long as we pass the high-bit bytes unchanged
00060    this is safe to do to a UTF8 string
00061    we dont allow overrunning the targetbuffer
00062    to avoid having a very long query overwrite memory
00063 
00064    TODO consider remapping the unprintables instead to "Printable
00065    Representation", the Unicode characters from the area U+2400 to
00066    U+2421 reserved for representing control characters when it is
00067    necessary to print or display them rather than have them perform
00068    their intended function.
00069 
00070 */
00071 
00072 static void quotify(const string &src, string &dst)
00073 {
00074   static const char hexit[]= { '0', '1', '2', '3', '4', '5', '6', '7',
00075         '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
00076   string::const_iterator src_iter;
00077 
00078   for (src_iter= src.begin(); src_iter < src.end(); ++src_iter)
00079   {
00080     if (static_cast<unsigned char>(*src_iter) > 0x7f)
00081     {
00082       dst.push_back(*src_iter);
00083     }
00084     else if (*src_iter == 0x00)  // null
00085     {
00086       dst.push_back(ESCAPE_CHAR); dst.push_back('0');
00087     }
00088     else if (*src_iter == 0x07)  // bell
00089     {
00090       dst.push_back(ESCAPE_CHAR); dst.push_back('a');
00091     }
00092     else if (*src_iter == 0x08)  // backspace
00093     {
00094       dst.push_back(ESCAPE_CHAR); dst.push_back('b');
00095     }
00096     else if (*src_iter == 0x09)  // horiz tab
00097     {
00098       dst.push_back(ESCAPE_CHAR); dst.push_back('t');
00099     }
00100     else if (*src_iter == 0x0a)  // line feed
00101     {
00102       dst.push_back(ESCAPE_CHAR); dst.push_back('n');
00103     }
00104     else if (*src_iter == 0x0b)  // vert tab
00105     {
00106       dst.push_back(ESCAPE_CHAR); dst.push_back('v');
00107     }
00108     else if (*src_iter == 0x0c)  // formfeed
00109     {
00110       dst.push_back(ESCAPE_CHAR); dst.push_back('f');
00111     }
00112     else if (*src_iter == 0x0d)  // carrage return
00113     {
00114       dst.push_back(ESCAPE_CHAR); dst.push_back('r');
00115     }
00116     else if (*src_iter == 0x1b)  // escape
00117     {
00118       dst.push_back(ESCAPE_CHAR); dst.push_back('e');
00119     }
00120     else if (*src_iter == 0x22)  // quotation mark
00121     {
00122       dst.push_back(ESCAPE_CHAR); dst.push_back(0x22);
00123     }
00124     else if (*src_iter == SEPARATOR_CHAR)
00125     {
00126       dst.push_back(ESCAPE_CHAR); dst.push_back(SEPARATOR_CHAR);
00127     }
00128     else if (*src_iter == ESCAPE_CHAR)
00129     {
00130       dst.push_back(ESCAPE_CHAR); dst.push_back(ESCAPE_CHAR);
00131     }
00132     else if ((*src_iter < 0x20) || (*src_iter == 0x7F))  // other unprintable ASCII
00133     {
00134       dst.push_back(ESCAPE_CHAR);
00135       dst.push_back('x');
00136       dst.push_back(hexit[(*src_iter >> 4) & 0x0f]);
00137       dst.push_back(hexit[*src_iter & 0x0f]);
00138     }
00139     else  // everything else
00140     {
00141       dst.push_back(*src_iter);
00142     }
00143   }
00144 }
00145 
00146 
00147 class Logging_query: public drizzled::plugin::Logging
00148 {
00149   const std::string _filename;
00150   const std::string _query_pcre;
00151   int fd;
00152   pcre *re;
00153   pcre_extra *pe;
00154 
00156   boost::format formatter;
00157 
00158 public:
00159 
00160   Logging_query(const std::string &filename,
00161                 const std::string &query_pcre) :
00162     drizzled::plugin::Logging("Logging_query"),
00163     _filename(filename),
00164     _query_pcre(query_pcre),
00165     fd(-1), re(NULL), pe(NULL),
00166     formatter("%1%,%2%,%3%,\"%4%\",\"%5%\",\"%6%\",%7%,%8%,"
00167               "%9%,%10%,%11%,%12%,%13%,%14%,\"%15%\"\n")
00168   {
00169 
00170     /* if there is no destination filename, dont bother doing anything */
00171     if (_filename.empty())
00172       return;
00173 
00174     fd= open(_filename.c_str(),
00175              O_WRONLY | O_APPEND | O_CREAT,
00176              S_IRUSR|S_IWUSR);
00177 
00178     if (fd < 0)
00179     {
00180       sql_perror( _("fail open()"), _filename);
00181       return;
00182     }
00183 
00184     if (not _query_pcre.empty())
00185     {
00186       const char *this_pcre_error;
00187       int this_pcre_erroffset;
00188       re= pcre_compile(_query_pcre.c_str(), 0, &this_pcre_error,
00189                        &this_pcre_erroffset, NULL);
00190       pe= pcre_study(re, 0, &this_pcre_error);
00191       /* TODO emit error messages if there is a problem */
00192     }
00193   }
00194 
00195   ~Logging_query()
00196   {
00197     if (fd >= 0)
00198     {
00199       close(fd);
00200     }
00201 
00202     if (pe != NULL)
00203     {
00204       pcre_free(pe);
00205     }
00206 
00207     if (re != NULL)
00208     {
00209       pcre_free(re);
00210     }
00211   }
00212 
00213   virtual bool post (Session *session)
00214   {
00215     size_t wrv;
00216 
00217     assert(session != NULL);
00218 
00219     if (fd < 0)
00220       return false;
00221 
00222     /* Yes, we know that checking sysvar_logging_query_enable,
00223        sysvar_logging_query_threshold_big_resultset, and
00224        sysvar_logging_query_threshold_big_examined is not threadsafe,
00225        because some other thread might change these sysvars.  But we
00226        don't care.  We might start logging a little late as it spreads
00227        to other threads.  Big deal. */
00228 
00229     // return if not enabled or query was too fast or resultset was too small
00230     if (sysvar_logging_query_enable == false)
00231       return false;
00232     if (session->sent_row_count < sysvar_logging_query_threshold_big_resultset.get())
00233       return false;
00234     if (session->examined_row_count < sysvar_logging_query_threshold_big_examined.get())
00235       return false;
00236 
00237     /*
00238       TODO, the session object should have a "utime command completed"
00239       inside itself, so be more accurate, and so this doesnt have to
00240       keep calling current_utime, which can be slow.
00241     */
00242     uint64_t t_mark= session->times.getCurrentTimestamp(false);
00243 
00244     if (session->times.getElapsedTime() < sysvar_logging_query_threshold_slow.get())
00245       return false;
00246 
00247     Session::QueryString query_string(session->getQueryString());
00248     if (query_string == NULL)
00249     {
00250       return false;
00251     }
00252 
00253     if (re)
00254     {
00255       int this_pcre_rc;
00256       this_pcre_rc= pcre_exec(re, pe, query_string->c_str(), query_string->length(), 0, 0, NULL, 0);
00257       if (this_pcre_rc < 0)
00258         return false;
00259     }
00260 
00261     // buffer to quotify the query
00262     string qs;
00263 
00264     // Since quotify() builds the quoted string incrementally, we can
00265     // avoid some reallocating if we reserve some space up front.
00266     qs.reserve(query_string->length());
00267 
00268     quotify(*query_string, qs);
00269 
00270     // to avoid trying to printf %s something that is potentially NULL
00271     util::string::ptr schema(session->schema());
00272     formatter % t_mark
00273               % session->thread_id
00274               % session->getQueryId()
00275               % (schema ? schema->c_str() : "")
00276               % qs
00277               % getCommandName(session->command)
00278               % (t_mark - session->times.getConnectMicroseconds())
00279               % session->times.getElapsedTime()
00280               % (t_mark - session->times.utime_after_lock)
00281               % session->sent_row_count
00282               % session->examined_row_count
00283               % session->tmp_table
00284               % session->total_warn_count
00285               % session->getServerId()
00286               % getServerHostname();
00287 
00288     string msgbuf= formatter.str();
00289 
00290     // a single write has a kernel thread lock, thus no need mutex guard this
00291     wrv= write(fd, msgbuf.c_str(), msgbuf.length());
00292     assert(wrv == msgbuf.length());
00293 
00294     return false;
00295   }
00296 };
00297 
00298 static int logging_query_plugin_init(drizzled::module::Context &context)
00299 {
00300   const module::option_map &vm= context.getOptions();
00301 
00302   if (vm.count("filename"))
00303   {
00304     context.add(new Logging_query(vm["filename"].as<string>(),
00305                                   vm["pcre"].as<string>()));
00306     context.registerVariable(new sys_var_bool_ptr("enable", &sysvar_logging_query_enable));
00307     context.registerVariable(new sys_var_const_string_val("filename", vm["filename"].as<string>()));
00308     context.registerVariable(new sys_var_const_string_val("pcre", vm["pcre"].as<string>()));
00309     context.registerVariable(new sys_var_constrained_value<uint32_t>("threshold_slow", sysvar_logging_query_threshold_slow));
00310     context.registerVariable(new sys_var_constrained_value<uint32_t>("threshold_big_resultset", sysvar_logging_query_threshold_big_resultset));
00311     context.registerVariable(new sys_var_constrained_value<uint32_t>("threshold_big_examined", sysvar_logging_query_threshold_big_examined));
00312   }
00313 
00314   return 0;
00315 }
00316 
00317 static void init_options(drizzled::module::option_context &context)
00318 {
00319   context("enable",
00320           po::value<bool>(&sysvar_logging_query_enable)->default_value(false)->zero_tokens(),
00321           _("Enable logging to CSV file"));
00322   context("filename",
00323           po::value<string>(),
00324           _("File to log to"));
00325   context("pcre",
00326           po::value<string>()->default_value(""),
00327           _("PCRE to match the query against"));
00328   context("threshold-slow",
00329           po::value<uint32_constraint>(&sysvar_logging_query_threshold_slow)->default_value(0),
00330           _("Threshold for logging slow queries, in microseconds"));
00331   context("threshold-big-resultset",
00332           po::value<uint32_constraint>(&sysvar_logging_query_threshold_big_resultset)->default_value(0),
00333           _("Threshold for logging big queries, for rows returned"));
00334   context("threshold-big-examined",
00335           po::value<uint32_constraint>(&sysvar_logging_query_threshold_big_examined)->default_value(0),
00336           _("Threshold for logging big queries, for rows examined"));
00337 }
00338 
00339 } /* namespace drizzle_plugin */
00340 
00341 DRIZZLE_DECLARE_PLUGIN
00342 {
00343   DRIZZLE_VERSION_ID,
00344   "logging-query",
00345   "0.2",
00346   "Mark Atwood <mark@fallenpegasus.com>",
00347   N_("Log queries to a CSV file"),
00348   PLUGIN_LICENSE_GPL,
00349   drizzle_plugin::logging_query_plugin_init,
00350   NULL,
00351   drizzle_plugin::init_options
00352 }
00353 DRIZZLE_DECLARE_PLUGIN_END;