Re: [Maria-developers] [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
participants (1)
-
Sergei Golubchik