← Back to team overview

maria-developers team mailing list archive

Re: [Commits] mdev-15 sql log errors

 

Hi, Alexey!

On Mar 09, Alexey Botchkov wrote:
>  3316 Alexey Botchkov	2012-03-09
>       MDEV-15 Log all SQL errors.
>               Added the logger service that provides us with the rotating logs.
>               The plugin SQL_ERROR_LOG added. It logs the errors using the 'logger service'
>                       for the rotating log files.
>               the example record from the log:
>                 2012-03-09 15:07:29 root[root] @ localhost [] ERROR 1146: Table 'test.xyz' doesn't exist : select * from test.xyz

Pretty much ok. See comments below.
Six of them (that use imperative) I ask you to do. The rest are
suggestions, opinions, or questions.

And one question (static declaration of logs) I will discuss with you on
irc.

> === added file 'include/mysql/service_logger.h'
> --- include/mysql/service_logger.h	1970-01-01 00:00:00 +0000
> +++ include/mysql/service_logger.h	2012-03-07 10:29:14 +0000
> @@ -0,0 +1,97 @@
> +/* Copyright (C) 2012 Monty Program Ab
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; version 2 of the License.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program; if not, write to the Free Software
> +   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
> +
> +#ifndef MYSQL_SERVICE_LOGGER_INCLUDED
> +#define MYSQL_SERVICE_LOGGER_INCLUDED
> +
> +#ifndef MYSQL_ABI_CHECK
> +#include <stdarg.h>
> +#endif
> +
> +/**
> +  @file
> +  logger service
> +
> +  Log file with rotation implementation.
> +
> +  This service implements logging with possible rotation
> +  of the log files. Interface intentionally tries to be similar to FILE*
> +  related functions.
> +
> +  So that one can open the log with logger_open(), specifying
> +  the limit on the logfile size and the rotations number.
> +
> +  Then it's possible to write messages to the log with
> +  logger_printf or logger_vprintf functions.
> +
> +  As the size of the logfile grows over the specified limit,
> +  it is renamed to 'logfile.1'. The former 'logfile.1' becomes
> +  'logfile.2', etc. The file 'logfile.rotations' is removed.
> +  That's how the rotation works.
> +
> +  The rotation can be forced with the logger_rotate() call.
> +
> +  Finally the log should be closed with logger_close().
> +
> +@notes:
> +  Implementation checks the size of the log file before it starts new
> +  printf into it. So the size of the file gets over the limit when it rotates.
> +
> +  The access is secured with the mutex, so the log is threadsafe.
> +*/
> +
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +typedef struct logger_service_file_st LOGGER_SERVICE_FILE;
> +
> +extern struct logger_service_st {
> +  LOGGER_SERVICE_FILE* (*open)(const char *path,
> +                               unsigned long size_limit,
> +                               unsigned int rotations);
> +  int (*close)(LOGGER_SERVICE_FILE *log);
> +  int (*vprintf)(LOGGER_SERVICE_FILE *log, const char *fmt, va_list argptr);
> +  int (*printf)(LOGGER_SERVICE_FILE *log, const char *fmt, ...);
> +  int (*rotate)(LOGGER_SERVICE_FILE *log);
> +} *logger_service;
> +
> +#ifdef MYSQL_DYNAMIC_PLUGIN
> +
> +#define logger_open logger_service->open

you can write this with parentheses
#define logger_open(P,L,R) logger_service->open(P,L,R)

> +#define logger_close(log) (logger_service->close(log))
> +#define logger_rotate(log) (logger_service->rotate(log))
> +#define logger_vprintf(log, fmt, argptr) (logger_service->\
> +    vprintf(log, fmt, argptr))
> +#define logger_printf logger_service->printf
> +#else
> +
> +  LOGGER_SERVICE_FILE *logger_open(const char *path,
> +                                   unsigned long size_limit,
> +                                   unsigned int rotations);
> +  int logger_close(LOGGER_SERVICE_FILE *log);
> +  int logger_vprintf(LOGGER_SERVICE_FILE *log, const char *fmt, va_list argptr);
> +  int logger_rotate(LOGGER_SERVICE_FILE *log); 
> +  int logger_printf(LOGGER_SERVICE_FILE *log, const char *fmt, ...);
> +#endif
> +
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /*MYSQL_SERVICE_LOGGER_INCLUDED*/
> +
> === added file 'mysys/my_logger.c'
> --- mysys/my_logger.c	1970-01-01 00:00:00 +0000
> +++ mysys/my_logger.c	2012-03-09 13:03:47 +0000
> @@ -0,0 +1,167 @@
> +/* Copyright (C) 2012 Monty Program Ab
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; version 2 of the License.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program; if not, write to the Free Software
> +   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
> +
> +
> +#include "my_global.h"
> +#include <my_sys.h>
> +#include <mysql/service_logger.h>
> +
> +extern MYSQL_PLUGIN_IMPORT char  *mysql_data_home;

why MYSQL_PLUGIN_IMPORT? Because the linker cannot find the symbol otherwise?
that is, MYSQL_PLUGIN_IMPORT affects how the name is mangled?

> +extern PSI_mutex_key key_LOCK_logger_service;
> +
> +typedef struct logger_service_file_st {
> +  FILE *file;
> +  char path[FN_REFLEN];
> +  long size_limit;
> +  unsigned int rotations;
> +  unsigned int index_len;
> +  size_t path_len;
> +  mysql_mutex_t lock;
> +} LSFS;
> +
> +
> +static unsigned int n_dig(unsigned int i)
> +{
> +  return (i == 0) ? 0 : ((i < 10) ? 1 : ((i < 100) ? 2 : 3));
> +}
> +
> +
> +LOGGER_SERVICE_FILE *logger_open(const char *path,
> +                               unsigned long size_limit,
> +                               unsigned int rotations)
> +{
> +  LOGGER_SERVICE_FILE new_log, *l_perm;
> +
> +  /* I don't think we need more rotations,                           */
> +  /* but if it's so, the rotation procedure should be adapted to it. */

please write multi-line comments as we do elsewhere in the code

> +  if (rotations > 999)
> +    return 0;
> +
> +  new_log.rotations= rotations;
> +  new_log.size_limit= size_limit;
> +  new_log.index_len= n_dig(rotations);

I don't think you need to cache the value of n_dig(rotations)
it's only used once, in logname() function.

> +  new_log.path_len= strlen(fn_format(new_log.path, path,
> +        mysql_data_home, "", 4));

replace 4 with a named constant, they exist in 5.5

> +
> +  if (new_log.path_len+new_log.index_len+1 > FN_REFLEN ||
> +      !(new_log.file= fopen(new_log.path, "a+")))
> +  {
> +    /* File path too long || Check errno for the cause */

set errno=ENAMETOOLONG here. That is, split this if()
in two, and set errno appropriately in the first one.

> +    return 0;
> +  }
> +
> +  setbuf(new_log.file, 0);
> +  fseek(new_log.file, 0, SEEK_END);
> +  if (!(l_perm= (LOGGER_SERVICE_FILE *)
> +          my_malloc(sizeof(LOGGER_SERVICE_FILE), MYF(0))))

close the file here

> +    return 0; /* End of memory */
> +  *l_perm= new_log;
> +  mysql_mutex_init(key_LOCK_logger_service, &l_perm->lock, MY_MUTEX_INIT_FAST);
> +  return l_perm;
> +}
> +
> +int logger_close(LOGGER_SERVICE_FILE *log)
> +{
> +  int result;
> +  mysql_mutex_destroy(&log->lock);
> +  result= fclose(log->file);
> +  my_free(log);
> +  return result;
> +}
> +
> +
> +static char *logname(LOGGER_SERVICE_FILE *log, char *buf, unsigned int n_log)
> +{
> +  unsigned int n_zeroes;
> +  size_t path_len= log->path_len;
> +  buf[path_len++]= '.';
> +  for (n_zeroes= log->index_len - n_dig(n_log);
> +      n_zeroes > 0; n_zeroes--, path_len++)
> +    buf[path_len]= '0';
> +
> +  int10_to_str(n_log, buf+path_len, 10);
> +  return buf;

as I wrote earlier, I'd simply used sprintf("%0*d") here. Like this:

    sprintf(buf + path_len, ".%0*u", log->index_len, n_log);

but ok, as you like.

> +}
> +
> +
> +static int do_rotate(LOGGER_SERVICE_FILE *log)
> +{
> +  char namebuf[FN_REFLEN];
> +  struct stat stbuf;
> +  int result;
> +  unsigned int i;
> +  char *buf_old, *buf_new, *tmp;
> +
> +  memcpy(namebuf, log->path, log->path_len);
> +
> +  buf_new= logname(log, namebuf, log->rotations);
> +  buf_old= log->path;
> +  for (i=log->rotations-1; i>0; i--)
> +  {
> +    logname(log, buf_old, i);
> +    if (!stat(buf_old, &stbuf) &&

why stat? iirc, access(name, F_OK) is supposed to be used for checking if a
file exists. We aren't interested in the values from stbuf here, are we?

> +        (result= my_rename(buf_old, buf_new, MYF(0))))
> +      return result;
> +    tmp= buf_old;
> +    buf_old= buf_new;
> +    buf_new= tmp;
> +  }
> +  if ((result= fclose(log->file)))
> +    return result;
> +  namebuf[log->path_len]= 0;
> +  result= my_rename(namebuf, logname(log, log->path, 1), MYF(0));
> +  log->file= fopen(namebuf, "a+");
> +  return log->file==NULL || result;
> +}
> +
> +
> +int logger_vprintf(LOGGER_SERVICE_FILE *log, const char* fmt, va_list ap)
> +{
> +  int result;
> +  mysql_mutex_lock(&log->lock);
> +  if (ftell(log->file) >= log->size_limit &&
> +      do_rotate(log))
> +  {
> +    result= -1;
> +    goto exit; /* Log rotation needed but failed */
> +  }
> +
> +  result= vfprintf(log->file, fmt, ap);

use my_vfprintf

> +exit:
> +  mysql_mutex_unlock(&log->lock);
> +  return result;
> +}
> +
> +
> +int logger_rotate(LOGGER_SERVICE_FILE *log)
> +{
> +  int result;
> +  mysql_mutex_lock(&log->lock);
> +  result= do_rotate(log);
> +  mysql_mutex_unlock(&log->lock);
> +  return result;
> +}
> +
> +
> +int logger_printf(LOGGER_SERVICE_FILE *log, const char *fmt, ...)
> +{
> +  int result;
> +  va_list args;
> +  va_start(args,fmt);
> +  result= logger_vprintf(log, fmt, args);
> +  va_end(args);
> +  return result;
> +}
> +
> === added file 'plugin/sql_errlog/sql_errlog.c'
> --- plugin/sql_errlog/sql_errlog.c	1970-01-01 00:00:00 +0000
> +++ plugin/sql_errlog/sql_errlog.c	2012-03-09 10:24:35 +0000
> @@ -0,0 +1,147 @@
> +/* Copyright (C) 2012 Monty Program Ab.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; version 2 of the License.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program; if not, write to the Free Software
> +   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
> +
> +#include <mysql/plugin_audit.h>
> +#include <stdio.h>
> +#include <time.h>
> +
> +/*
> +  rate 0 means the logging was disabled.
> +*/
> +
> +static char *filename;
> +static unsigned int rate;
> +static unsigned int size_limit;
> +static unsigned int rotations;
> +static char rotate;
> +
> +static unsigned int count;
> +LOGGER_SERVICE_FILE *logfile;
> +
> +static void rotate_log(MYSQL_THD thd, struct st_mysql_sys_var *var,
> +                       void *var_ptr, const void *save);
> +
> +static MYSQL_SYSVAR_UINT(rate, rate, PLUGIN_VAR_RQCMDARG,
> +       "Sampling rate. If set to 0(zero), the logging disabled.", NULL, NULL,

typo: the logging *is* disabled

> +       1, 0, 1000000, 1);
> +
> +static MYSQL_SYSVAR_UINT(size_limit, size_limit,
> +       PLUGIN_VAR_READONLY, "Log file size limit", NULL, NULL,
> +       1000000, 100, 1024*1024L*1024L, 1);
> +
> +static MYSQL_SYSVAR_UINT(rotations, rotations,
> +       PLUGIN_VAR_READONLY, "Number of rotations before log is removed.",
> +       NULL, NULL, 9, 1, 999, 1);
> +
> +static MYSQL_SYSVAR_BOOL(rotate, rotate,
> +       PLUGIN_VAR_NOSYSVAR, "Force log rotation", NULL, rotate_log,

What do you mean PLUGIN_VAR_NOSYSVAR?
It only works from a command line?
It'd be more useful to force a log rotation from SQL.
if users wouldn't need it, we woldn't have FLUSH LOGS :)

> +       0);
> +
> +static MYSQL_SYSVAR_STR(filename, filename,
> +       PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
> +       "The file to log sql errors to", NULL, NULL,
> +       "sql_errors.log");
> +
> +static struct st_mysql_sys_var* vars[] = {
> +    MYSQL_SYSVAR(rate),
> +    MYSQL_SYSVAR(size_limit),
> +    MYSQL_SYSVAR(rotations),
> +    MYSQL_SYSVAR(rotate),
> +    MYSQL_SYSVAR(filename),
> +    NULL
> +};
> +
> +
> +static void log_sql_errors(MYSQL_THD thd __attribute__((unused)),
> +                           unsigned int event_class __attribute__((unused)),
> +                           const void *ev)
> +{
> +  const struct mysql_event_general *event =
> +         (const struct mysql_event_general*)ev;
> +  if (rate &&
> +      event->event_subclass == MYSQL_AUDIT_GENERAL_ERROR)
> +  {
> +    if (++count >= rate)
> +    {
> +      struct tm t;
> +      time_t event_time = event->general_time;
> +
> +      count = 0;
> +      localtime_r(&event_time, &t);
> +      logger_printf(logfile, "%04d-%02d-%02d %2d:%02d:%02d "
> +                      "%s ERROR %d: %s : %s\n",
> +              t.tm_year + 1900, t.tm_mon + 1,
> +              t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec,
> +              event->general_user, event->general_error_code,
> +              event->general_command, event->general_query);
> +    }
> +  }
> +}
> +
> +
> +static int sql_error_log_init(void *p __attribute__((unused)))
> +{
> +  logfile= logger_open(filename, size_limit, rotations);
> +  if (logfile == NULL) {
> +    fprintf(stderr, "Could not create file '%s'\n",
> +            filename);
> +    return 1;
> +  }
> +  count = 0;
> +  return 0;
> +}
> +
> +
> +static int sql_error_log_deinit(void *p __attribute__((unused)))
> +{
> +  logger_close(logfile);
> +  return 0;
> +}
> +
> +
> +static void rotate_log(MYSQL_THD thd  __attribute__((unused)),
> +                       struct st_mysql_sys_var *var  __attribute__((unused)),
> +                       void *var_ptr  __attribute__((unused)),
> +                       const void *save  __attribute__((unused)))
> +{
> +  (void) logger_rotate(logfile);
> +}
> +
> +
> +static struct st_mysql_audit descriptor =
> +{
> +  MYSQL_AUDIT_INTERFACE_VERSION,
> +  NULL,
> +  log_sql_errors,
> +  { MYSQL_AUDIT_GENERAL_CLASSMASK }
> +};
> +
> +maria_declare_plugin(sql_errlog)
> +{
> +  MYSQL_AUDIT_PLUGIN,
> +  &descriptor,
> +  "SQL_ERROR_LOG",
> +  "Alexey Botchkov",
> +  "Log SQL level errors to a file with rotation",
> +  PLUGIN_LICENSE_GPL,
> +  sql_error_log_init,
> +  sql_error_log_deinit,
> +  0x0100,
> +  NULL,
> +  vars,
> +  NULL,
> +  0
> +}
> +maria_declare_plugin_end;

Regards,
Sergei