Drizzled Public API Documentation

logging_gearman.cc
1 /* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3  *
4  * Copyright (C) 2008, 2009 Sun Microsystems, Inc.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #include <config.h>
21 
22 #include <boost/scoped_array.hpp>
23 
24 #include <drizzled/item.h>
25 #include <drizzled/plugin.h>
26 #include <drizzled/plugin/logging.h>
27 #include <drizzled/gettext.h>
28 #include <drizzled/session.h>
29 #include <drizzled/session/times.h>
30 #include <drizzled/sql_parse.h>
31 #include <drizzled/errmsg_print.h>
32 #include <boost/date_time.hpp>
33 #include <boost/program_options.hpp>
35 #include <libgearman/gearman.h>
36 #include <limits.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40 #include <cstdio>
41 #include <cerrno>
42 #include <memory>
43 
44 using namespace drizzled;
45 
46 namespace drizzle_plugin {
47 namespace logging_gearman {
48 
49 namespace po= boost::program_options;
50 
51 bool updateHost(Session *, set_var*);
52 bool updateFunction(Session *, set_var*);
53 /* TODO make this dynamic as needed */
54 static const int MAX_MSG_LEN= 32*1024;
55 
56 /* quote a string to be safe to include in a CSV line
57  that means backslash quoting all commas, doublequotes, backslashes,
58  and all the ASCII unprintable characters
59  as long as we pass the high-bit bytes unchanged
60  this is safe to do to a UTF8 string
61  we dont allow overrunning the targetbuffer
62  to avoid having a very long query overwrite memory
63 
64  TODO consider remapping the unprintables instead to "Printable
65  Representation", the Unicode characters from the area U+2400 to
66  U+2421 reserved for representing control characters when it is
67  necessary to print or display them rather than have them perform
68  their intended function.
69 
70 */
71 static unsigned char *quotify (const unsigned char *src, size_t srclen,
72  unsigned char *dst, size_t dstlen)
73 {
74  static const char hexit[]= { '0', '1', '2', '3', '4', '5', '6', '7',
75  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
76  size_t dst_ndx; /* ndx down the dst */
77  size_t src_ndx; /* ndx down the src */
78 
79  assert(dst);
80  assert(dstlen > 0);
81 
82  for (dst_ndx= 0,src_ndx= 0; src_ndx < srclen; src_ndx++)
83  {
84 
85  /* Worst case, need 5 dst bytes for the next src byte.
86  backslash x hexit hexit null
87  so if not enough room, just terminate the string and return
88  */
89  if ((dstlen - dst_ndx) < 5)
90  {
91  dst[dst_ndx]= (unsigned char)0x00;
92  return dst;
93  }
94 
95  if (src[src_ndx] > 0x7f)
96  {
97  // pass thru high bit characters, they are non-ASCII UTF8 Unicode
98  dst[dst_ndx++]= src[src_ndx];
99  }
100  else if (src[src_ndx] == 0x00) // null
101  {
102  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) '0';
103  }
104  else if (src[src_ndx] == 0x07) // bell
105  {
106  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'a';
107  }
108  else if (src[src_ndx] == 0x08) // backspace
109  {
110  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'b';
111  }
112  else if (src[src_ndx] == 0x09) // horiz tab
113  {
114  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 't';
115  }
116  else if (src[src_ndx] == 0x0a) // line feed
117  {
118  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'n';
119  }
120  else if (src[src_ndx] == 0x0b) // vert tab
121  {
122  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'v';
123  }
124  else if (src[src_ndx] == 0x0c) // formfeed
125  {
126  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'f';
127  }
128  else if (src[src_ndx] == 0x0d) // carrage return
129  {
130  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'r';
131  }
132  else if (src[src_ndx] == 0x1b) // escape
133  {
134  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= (unsigned char) 'e';
135  }
136  else if (src[src_ndx] == 0x22) // quotation mark
137  {
138  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= 0x22;
139  }
140  else if (src[src_ndx] == 0x2C) // comma
141  {
142  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= 0x2C;
143  }
144  else if (src[src_ndx] == 0x5C) // backslash
145  {
146  dst[dst_ndx++]= 0x5C; dst[dst_ndx++]= 0x5C;
147  }
148  else if ((src[src_ndx] < 0x20) || (src[src_ndx] == 0x7F)) // other unprintable ASCII
149  {
150  dst[dst_ndx++]= 0x5C;
151  dst[dst_ndx++]= (unsigned char) 'x';
152  dst[dst_ndx++]= hexit[(src[src_ndx] >> 4) & 0x0f];
153  dst[dst_ndx++]= hexit[src[src_ndx] & 0x0f];
154  }
155  else // everything else
156  {
157  dst[dst_ndx++]= src[src_ndx];
158  }
159  dst[dst_ndx]= '\0';
160  }
161  return dst;
162 }
163 
166 {
167 
168  std::string sysvar_host;
169  std::string sysvar_function;
170 
171  int _gearman_client_ok;
172  gearman_client_st _gearman_client;
173 
174  LoggingGearman();
176 
177 public:
178 
179  LoggingGearman(const std::string &host,
180  const std::string &function) :
181  drizzled::plugin::Logging("gearman_query_log"),
182  sysvar_host(host),
183  sysvar_function(function),
184  _gearman_client_ok(0),
185  _gearman_client()
186  {
187  gearman_return_t ret;
188 
189 
190  if (gearman_client_create(&_gearman_client) == NULL)
191  {
192  drizzled::sql_perror(_("fail gearman_client_create()"));
193  return;
194  }
195 
196  /* TODO, be able to override the port */
197  /* TODO, be able send to multiple servers */
198  ret= gearman_client_add_server(&_gearman_client,
199  host.c_str(), 0);
200  if (ret != GEARMAN_SUCCESS)
201  {
202  drizzled::errmsg_printf(drizzled::error::ERROR, _("fail gearman_client_add_server(): %s"),
203  gearman_client_error(&_gearman_client));
204  return;
205  }
206 
207  _gearman_client_ok= 1;
208 
209  }
210 
211  ~LoggingGearman()
212  {
213  if (_gearman_client_ok)
214  {
215  gearman_client_free(&_gearman_client);
216  }
217  }
218 
219  virtual bool post(drizzled::Session *session)
220  {
221  boost::scoped_array<char> msgbuf(new char[MAX_MSG_LEN]);
222  int msgbuf_len= 0;
223 
224  assert(session != NULL);
225 
226  /* in theory, we should return "true", meaning that the plugin isn't happy,
227  but that crashes the server, so for now, we just lie a little bit
228  */
229 
230  if (not _gearman_client_ok)
231  return false;
232 
233  /*
234  TODO, the session object should have a "utime command completed"
235  inside itself, so be more accurate, and so this doesnt have to
236  keep calling current_utime, which can be slow.
237  */
238  uint64_t t_mark= session->times.getCurrentTimestamp(false);
239 
240 
241  // buffer to quotify the query
242  unsigned char qs[255];
243 
244  // to avoid trying to printf %s something that is potentially NULL
245  drizzled::util::string::ptr dbs(session->schema());
246 
247  msgbuf_len=
248  snprintf(msgbuf.get(), MAX_MSG_LEN,
249  "%"PRIu64",%"PRIu64",%"PRIu64",\"%.*s\",\"%s\",\"%.*s\","
250  "%"PRIu64",%"PRIu64",%"PRIu64",%"PRIu64",%"PRIu64","
251  "%"PRIu32",%"PRIu32",%"PRIu32",\"%s\"",
252  t_mark,
253  session->thread_id,
254  session->getQueryId(),
255  // dont need to quote the db name, always CSV safe
256  (int)dbs->size(), dbs->c_str(),
257  // do need to quote the query
258  quotify((const unsigned char *)session->getQueryString()->c_str(), session->getQueryString()->length(), qs, sizeof(qs)),
259  // getCommandName is defined in drizzled/sql_parse.h dont
260  // need to quote the command name, always CSV safe
261  (int)drizzled::getCommandName(session->command).size(),
262  drizzled::getCommandName(session->command).c_str(),
263  // counters are at end, to make it easier to add more
264  (t_mark - session->times.getConnectMicroseconds()),
265  (session->times.getElapsedTime()),
266  (t_mark - session->times.utime_after_lock),
267  session->sent_row_count,
268  session->examined_row_count,
269  session->tmp_table,
270  session->total_warn_count,
271  session->getServerId(),
272  drizzled::getServerHostname().c_str()
273  );
274 
275  char job_handle[GEARMAN_JOB_HANDLE_SIZE];
276 
277  (void) gearman_client_do_background(&_gearman_client,
278  sysvar_function.c_str(),
279  NULL,
280  (void *) msgbuf.get(),
281  (size_t) msgbuf_len,
282  job_handle);
283 
284  return false;
285  }
286 
292  bool setHost(std::string &new_host)
293  {
294  gearman_return_t tmp_ret;
295 
296  /*
297  New server is added to the list of servers using gearman_client_add_server.
298  If the call does not result in error, then all the servers are removed from the list and
299  new server only is added. This is done to ensure that a bad server url does not replace
300  the existing server url.
301  TODO Create a new instance of gearman_client_st and add the new server in it. If success, release the
302  old gearman_client_st and use new instance of gearman_client_st everywhere.
303  */
304  tmp_ret= gearman_client_add_server(&_gearman_client,
305  new_host.c_str(), 0);
306  if (tmp_ret != GEARMAN_SUCCESS)
307  {
308  drizzled::errmsg_printf(drizzled::error::ERROR, _("fail gearman_client_add_server(): %s"),
309  gearman_client_error(&_gearman_client));
310  return false;
311  }
312 
313  gearman_client_remove_servers(&_gearman_client);
314  gearman_client_add_server(&_gearman_client, new_host.c_str(), 0);
315  sysvar_host= new_host;
316  return true;
317  }
318 
324  bool setFunction(std::string &new_function)
325  {
326  sysvar_function= new_function;
327  return true;
328  }
329 
335  std::string& getHost()
336  {
337  return sysvar_host;
338  }
339 
345  std::string& getFunction()
346  {
347  return sysvar_function;
348  }
349 };
350 
351 static LoggingGearman *handler= NULL;
352 
358 bool updateHost(Session *, set_var* var)
359 {
360  if (not var->value->str_value.empty())
361  {
362  std::string newHost(var->value->str_value.data());
363  if (handler->setHost(newHost))
364  return false; //success
365  else
366  return true; // error
367  }
368  errmsg_printf(error::ERROR, _("logging_gearman_host cannot be NULL"));
369  return true; // error
370 }
371 
377 bool updateFunction(Session *, set_var* var)
378 {
379  if (not var->value->str_value.empty())
380  {
381  std::string newFunction(var->value->str_value.data());
382  if (handler->setFunction(newFunction))
383  return false; //success
384  else
385  return true; // error
386  }
387  errmsg_printf(error::ERROR, _("logging_gearman_function cannot be NULL"));
388  return true; // error
389 }
390 
391 
392 static int init(drizzled::module::Context &context)
393 {
394  const drizzled::module::option_map &vm= context.getOptions();
395 
396  handler= new LoggingGearman(vm["host"].as<std::string>(),
397  vm["function"].as<std::string>());
398  context.add(handler);
399  context.registerVariable(new sys_var_std_string("host", handler->getHost(), NULL, &updateHost));
400  context.registerVariable(new sys_var_std_string("function", handler->getFunction(), NULL, &updateFunction));
401 
402  return 0;
403 }
404 
405 static void init_options(drizzled::module::option_context &context)
406 {
407  context("host",
408  po::value<std::string>()->default_value("localhost"),
409  _("Hostname for logging to a Gearman server"));
410  context("function",
411  po::value<std::string>()->default_value("drizzlelog"),
412  _("Gearman Function to send logging to"));
413 }
414 
415 } /* namespace logging_gearman */
416 } /* namespace drizzle_plugin */
417 
418 DRIZZLE_DECLARE_PLUGIN
419 {
420  DRIZZLE_VERSION_ID,
421  "logging_gearman",
422  "0.1",
423  "Mark Atwood",
424  N_("Logs queries to a Gearman server"),
425  drizzled::PLUGIN_LICENSE_GPL,
426  drizzle_plugin::logging_gearman::init,
427  NULL,
428  drizzle_plugin::logging_gearman::init_options
429 }
430 DRIZZLE_DECLARE_PLUGIN_END;