
If info messages are enabled, they will be logged to syslog just like any other messages.
3124 lines
86 KiB
C++
3124 lines
86 KiB
C++
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2020-01-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
#include <maxscale/log_manager.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <sched.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#include <maxscale/alloc.h>
|
|
#include <maxscale/atomic.h>
|
|
#include <maxscale/config.h>
|
|
#include <maxscale/debug.h>
|
|
#include <maxscale/hashtable.h>
|
|
#include <maxscale/json_api.h>
|
|
#include <maxscale/platform.h>
|
|
#include <maxscale/session.h>
|
|
#include <maxscale/spinlock.h>
|
|
#include <maxscale/utils.h>
|
|
#include "internal/mlist.h"
|
|
|
|
#define MAX_PREFIXLEN 250
|
|
#define MAX_SUFFIXLEN 250
|
|
#define MAX_PATHLEN 512
|
|
#define MAXNBLOCKBUFS 10
|
|
|
|
/** for procname */
|
|
#if !defined(_GNU_SOURCE)
|
|
# define _GNU_SOURCE
|
|
#endif
|
|
|
|
static const char LOGFILE_NAME_PREFIX[] = "maxscale";
|
|
static const char LOGFILE_NAME_SUFFIX[] = ".log";
|
|
|
|
extern char *program_invocation_name;
|
|
extern char *program_invocation_short_name;
|
|
|
|
typedef enum
|
|
{
|
|
BB_READY = 0x00,
|
|
BB_FULL,
|
|
BB_CLEARED
|
|
} blockbuf_state_t;
|
|
|
|
typedef enum
|
|
{
|
|
FILEWRITER_INIT,
|
|
FILEWRITER_RUN,
|
|
FILEWRITER_DONE
|
|
} filewriter_state_t;
|
|
|
|
/**
|
|
* UNINIT means zeroed memory buffer allocated for the struct.
|
|
* INIT means that struct members may have values, and memory may
|
|
* have been allocated. Done function must check and free it.
|
|
* RUN Struct is valid for run-time checking.
|
|
* DONE means that possible memory allocations have been released.
|
|
*/
|
|
typedef enum
|
|
{
|
|
UNINIT = 0,
|
|
INIT,
|
|
RUN,
|
|
DONE
|
|
} flat_obj_state_t;
|
|
|
|
/**
|
|
* LOG_FLUSH_NO Do not flush after writing.
|
|
* LOG_FLUSH_YES Flush after writing.
|
|
*/
|
|
enum log_flush
|
|
{
|
|
LOG_FLUSH_NO = 0,
|
|
LOG_FLUSH_YES = 1
|
|
};
|
|
|
|
#if defined(SS_DEBUG)
|
|
static int write_index;
|
|
static int block_start_index;
|
|
static int prevval;
|
|
static simple_mutex_t msg_mutex;
|
|
#endif
|
|
|
|
/**
|
|
* Default augmentation.
|
|
*/
|
|
static int DEFAULT_LOG_AUGMENTATION = 0;
|
|
// A message that is logged 10 times in 1 second will be suppressed for 10 seconds.
|
|
static MXS_LOG_THROTTLING DEFAULT_LOG_THROTTLING = { 10, 1000, 10000 };
|
|
|
|
static struct
|
|
{
|
|
int augmentation; // Can change during the lifetime of log_manager.
|
|
bool do_highprecision; // Can change during the lifetime of log_manager.
|
|
bool do_syslog; // Can change during the lifetime of log_manager.
|
|
bool do_maxlog; // Can change during the lifetime of log_manager.
|
|
MXS_LOG_THROTTLING throttling; // Can change during the lifetime of log_manager.
|
|
bool use_stdout; // Can NOT change during the lifetime of log_manager.
|
|
} log_config =
|
|
{
|
|
DEFAULT_LOG_AUGMENTATION, // augmentation
|
|
false, // do_highprecision
|
|
true, // do_syslog
|
|
true, // do_maxlog
|
|
DEFAULT_LOG_THROTTLING, // throttling
|
|
false // use_stdout
|
|
};
|
|
|
|
/**
|
|
* Variable holding the enabled priorities information.
|
|
* Used from logging macros.
|
|
*/
|
|
int mxs_log_enabled_priorities = 0;
|
|
|
|
/**
|
|
* BUFSIZ comes from the system. It equals with block size or
|
|
* its multiplication.
|
|
*/
|
|
#define MAX_LOGSTRLEN BUFSIZ
|
|
|
|
/**
|
|
* Path to directory in which all files are stored to shared memory
|
|
* by the OS.
|
|
*/
|
|
static const char SHM_PATHNAME_PREFIX[] = "/dev/shm/";
|
|
|
|
/** Forward declarations
|
|
*/
|
|
typedef struct filewriter filewriter_t;
|
|
typedef struct logfile logfile_t;
|
|
typedef struct fnames_conf fnames_conf_t;
|
|
typedef struct logmanager logmanager_t;
|
|
|
|
/**
|
|
* Global log manager pointer and lock variable.
|
|
* lmlock protects logmanager access.
|
|
*/
|
|
static SPINLOCK lmlock = SPINLOCK_INIT;
|
|
static logmanager_t* lm;
|
|
static bool flushall_flag;
|
|
static bool flushall_started_flag;
|
|
static bool flushall_done_flag;
|
|
static HASHTABLE* message_stats;
|
|
|
|
/** This is used to detect if the initialization of the log manager has failed
|
|
* and that it isn't initialized again after a failure has occurred. */
|
|
static bool fatal_error = false;
|
|
|
|
/** Writer thread structure */
|
|
struct filewriter
|
|
{
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t fwr_chk_top;
|
|
#endif
|
|
flat_obj_state_t fwr_state;
|
|
logmanager_t* fwr_logmgr;
|
|
/** Physical files */
|
|
skygw_file_t* fwr_file;
|
|
/** fwr_logmes is for messages from log clients */
|
|
skygw_message_t* fwr_logmes;
|
|
/** fwr_clientmes is for messages to log clients */
|
|
skygw_message_t* fwr_clientmes;
|
|
skygw_thread_t* fwr_thread;
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t fwr_chk_tail;
|
|
#endif
|
|
};
|
|
|
|
typedef struct lm_message_key
|
|
{
|
|
const char* filename; /** The filename where the error was reported. Must be a
|
|
statically allocated buffer, e.g. __FILE__ */
|
|
int linenumber; /** The linenumber where the error was reported. */
|
|
} LM_MESSAGE_KEY;
|
|
|
|
typedef struct lm_message_stats
|
|
{
|
|
SPINLOCK lock;
|
|
uint64_t first_ms; /** The time when the error was logged the first time in this window. */
|
|
uint64_t last_ms; /** The time when the error was logged the last time. */
|
|
size_t count; /** How many times the error has been reported within this window. */
|
|
} LM_MESSAGE_STATS;
|
|
|
|
static const int LM_MESSAGE_HASH_SIZE = 293; /** A prime, and roughly a quarter of current
|
|
number of MXS_{ERROR|WARNING|NOTICE} calls. */
|
|
|
|
/**
|
|
* Returns the current time.
|
|
*
|
|
* @return Current monotonic raw time in milliseconds.
|
|
*/
|
|
static uint64_t time_monotonic_ms()
|
|
{
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
return now.tv_sec * 1000 + now.tv_nsec / 1000000;
|
|
}
|
|
|
|
/**
|
|
* Hash-function for lm_message_key.
|
|
*
|
|
* This is an implementation of the Jenkin's one-at-a-time hash function.
|
|
* https://en.wikipedia.org/wiki/Jenkins_hash_function
|
|
*
|
|
* @param v A pointer to a LM_MESSAGE_KEY
|
|
* @return Hash for the contents of the LM_MESSAGE_KEY structure.
|
|
*/
|
|
int lm_message_key_hash(const void *v)
|
|
{
|
|
const LM_MESSAGE_KEY* key = (const LM_MESSAGE_KEY*)v;
|
|
|
|
uint64_t key1 = (uint64_t)key->filename;
|
|
uint16_t key2 = (uint16_t)key->linenumber; // The first 48 bits are likely to be 0.
|
|
|
|
uint32_t hash = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < sizeof(key1); ++i)
|
|
{
|
|
hash += (key1 >> i * 8) & 0xff;
|
|
hash += (hash << 10);
|
|
hash ^= (hash >> 6);
|
|
}
|
|
|
|
for (i = 0; i < sizeof(key2); ++i)
|
|
{
|
|
hash += (key1 >> i * 8) & 0xff;
|
|
hash += (hash << 10);
|
|
hash ^= (hash >> 6);
|
|
}
|
|
|
|
hash += (hash << 3);
|
|
hash ^= (hash >> 11);
|
|
hash += (hash << 15);
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
* Compares two LM_MESSAGE_KEY structures
|
|
*
|
|
* @param v1 Pointer to a LM_MESSAGE_KEY struct.
|
|
* @param v2 Pointer to a LM_MESSAGE_KEY struct.
|
|
* @return 0 if the structures are equal.
|
|
* -1 if v1 is less than v2
|
|
* 1 if v1 is greater than v2
|
|
*/
|
|
static int lm_message_key_cmp(const void* v1, const void* v2)
|
|
{
|
|
const LM_MESSAGE_KEY* key1 = (const LM_MESSAGE_KEY*)v1;
|
|
const LM_MESSAGE_KEY* key2 = (const LM_MESSAGE_KEY*)v2;
|
|
|
|
int cmp = (int64_t)key1->filename - (int64_t)key2->filename;
|
|
|
|
if (cmp == 0)
|
|
{
|
|
cmp = key1->linenumber - key2->linenumber;
|
|
}
|
|
|
|
return cmp == 0 ? 0 : (cmp < 0 ? -1 : 1);
|
|
}
|
|
|
|
/**
|
|
* Clone a LM_MESSAGE_KEY
|
|
*
|
|
* @param v Pointer to a LM_MESSAGE_KEY structure
|
|
* @return A copy of v or NULL if memory allocation fails.
|
|
*/
|
|
static void* lm_message_key_clone(const void* v)
|
|
{
|
|
const LM_MESSAGE_KEY* src = (const LM_MESSAGE_KEY*)v;
|
|
LM_MESSAGE_KEY* dst = (LM_MESSAGE_KEY*)MXS_MALLOC(sizeof(LM_MESSAGE_KEY));
|
|
|
|
if (dst)
|
|
{
|
|
dst->filename = src->filename;
|
|
dst->linenumber = src->linenumber;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* Clone a LM_MESSAGE_STATS
|
|
*
|
|
* @param v Pointer to a LM_MESSAGE_STATS structure
|
|
* @return A copy of v or NULL if memory allocation fails.
|
|
*/
|
|
static void* lm_message_stats_clone(const void* v)
|
|
{
|
|
const LM_MESSAGE_STATS* src = (const LM_MESSAGE_STATS*)v;
|
|
LM_MESSAGE_STATS* dst = (LM_MESSAGE_STATS*)MXS_MALLOC(sizeof(LM_MESSAGE_STATS));
|
|
|
|
if (dst)
|
|
{
|
|
// Somewhat questionable to copy a lock, but in this context we
|
|
// know that src is stack allocated and will not be used after this.
|
|
dst->lock = src->lock;
|
|
dst->first_ms = src->first_ms;
|
|
dst->last_ms = src->last_ms;
|
|
dst->count = src->count;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
* Log client's string is copied to block-sized log buffer, which is passed
|
|
* to file writer thread.
|
|
*/
|
|
typedef struct blockbuf
|
|
{
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t bb_chk_top;
|
|
#endif
|
|
blockbuf_state_t bb_state; /**State of the block buffer*/
|
|
simple_mutex_t bb_mutex; /**< bb_buf_used, bb_isfull */
|
|
int bb_refcount; /**< protected by list mutex. #of clients */
|
|
// int bb_blankcount; /**< # of blanks used btw strings */
|
|
size_t bb_buf_size;
|
|
size_t bb_buf_left;
|
|
size_t bb_buf_used;
|
|
char bb_buf[MAX_LOGSTRLEN];
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t bb_chk_tail;
|
|
#endif
|
|
} blockbuf_t;
|
|
|
|
/**
|
|
* logfile object corresponds to physical file(s) where
|
|
* certain log is written.
|
|
*/
|
|
struct logfile
|
|
{
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t lf_chk_top;
|
|
#endif
|
|
flat_obj_state_t lf_state;
|
|
bool lf_store_shmem;
|
|
logmanager_t* lf_lmgr;
|
|
/** fwr_logmes is for messages from log clients */
|
|
skygw_message_t* lf_logmes;
|
|
char* lf_filepath; /**< path to file used for logging */
|
|
char* lf_linkpath; /**< path to symlink file. */
|
|
const char* lf_name_prefix;
|
|
const char* lf_name_suffix;
|
|
char* lf_full_file_name; /**< complete log file name */
|
|
char* lf_full_link_name; /**< complete symlink name */
|
|
/** list of block-sized log buffers */
|
|
mlist_t lf_blockbuf_list;
|
|
size_t lf_buf_size;
|
|
bool lf_flushflag;
|
|
bool lf_rotateflag;
|
|
SPINLOCK lf_spinlock; /**< lf_flushflag & lf_rotateflag */
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t lf_chk_tail;
|
|
#endif
|
|
};
|
|
|
|
|
|
struct fnames_conf
|
|
{
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t fn_chk_top;
|
|
#endif
|
|
flat_obj_state_t fn_state;
|
|
char* fn_logpath;
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t fn_chk_tail;
|
|
#endif
|
|
};
|
|
|
|
struct logmanager
|
|
{
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t lm_chk_top;
|
|
#endif
|
|
bool lm_enabled;
|
|
simple_mutex_t lm_mutex;
|
|
size_t lm_nlinks;
|
|
/** fwr_logmes is for messages from log clients */
|
|
skygw_message_t* lm_logmes;
|
|
/** fwr_clientmes is for messages to log clients */
|
|
skygw_message_t* lm_clientmes;
|
|
fnames_conf_t lm_fnames_conf;
|
|
logfile_t lm_logfile;
|
|
filewriter_t lm_filewriter;
|
|
mxs_log_target_t lm_target;
|
|
#if defined(SS_DEBUG)
|
|
skygw_chk_t lm_chk_tail;
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* Error logging the the log-manager itself.
|
|
*
|
|
* For obvious reasons, the log cannot use its own functions for reporting errors.
|
|
*/
|
|
#define LOG_ERROR(format, ...) do { fprintf(stderr, format, ##__VA_ARGS__); } while (false)
|
|
|
|
/** Static function declarations */
|
|
static bool logfile_init(logfile_t* logfile,
|
|
logmanager_t* logmanager,
|
|
bool store_shmem);
|
|
static void logfile_done(logfile_t* logfile);
|
|
static void logfile_free_memory(logfile_t* lf);
|
|
static void logfile_flush(logfile_t* lf);
|
|
static void logfile_rotate(logfile_t* lf);
|
|
static bool logfile_build_name(logfile_t* lf);
|
|
static bool logfile_open_file(filewriter_t* fw,
|
|
logfile_t* lf,
|
|
skygw_open_mode_t mode,
|
|
bool write_header);
|
|
static char* form_full_file_name(const char* directory,
|
|
const char* prefix,
|
|
const char* suffix);
|
|
|
|
static bool filewriter_init(logmanager_t* logmanager,
|
|
filewriter_t* fw,
|
|
bool write_header);
|
|
static void filewriter_done(filewriter_t* filewriter, bool write_footer);
|
|
static bool fnames_conf_init(fnames_conf_t* fn, const char* logdir);
|
|
static void fnames_conf_done(fnames_conf_t* fn);
|
|
static void fnames_conf_free_memory(fnames_conf_t* fn);
|
|
static void* thr_filewriter_fun(void* data);
|
|
static logfile_t* logmanager_get_logfile(logmanager_t* lm);
|
|
static bool logmanager_register(bool writep);
|
|
static void logmanager_unregister(void);
|
|
static bool logmanager_init_nomutex(const char* ident,
|
|
const char* logdir,
|
|
mxs_log_target_t target,
|
|
bool write_header);
|
|
static void logmanager_done_nomutex(void);
|
|
|
|
static int logmanager_write_log(int priority,
|
|
enum log_flush flush,
|
|
size_t prefix_len,
|
|
size_t len,
|
|
const char* str);
|
|
|
|
static blockbuf_t* blockbuf_init();
|
|
static void blockbuf_node_done(void* bb_data);
|
|
static char* blockbuf_get_writepos(blockbuf_t** p_bb,
|
|
size_t str_len,
|
|
bool flush);
|
|
|
|
static void blockbuf_register(blockbuf_t* bb);
|
|
static void blockbuf_unregister(blockbuf_t* bb);
|
|
static char* add_slash(char* str);
|
|
|
|
static bool check_file_and_path(const char* filename, bool* writable);
|
|
|
|
static bool file_is_symlink(const char* filename);
|
|
void flushall_logfiles(bool flush);
|
|
bool thr_flushall_check();
|
|
|
|
static bool logmanager_init_nomutex(const char* ident,
|
|
const char* logdir,
|
|
mxs_log_target_t target,
|
|
bool write_header)
|
|
{
|
|
fnames_conf_t* fn;
|
|
filewriter_t* fw;
|
|
int err;
|
|
bool succ = false;
|
|
|
|
lm = (logmanager_t *)MXS_CALLOC(1, sizeof(logmanager_t));
|
|
|
|
if (lm == NULL)
|
|
{
|
|
err = 1;
|
|
goto return_succ;
|
|
}
|
|
|
|
lm->lm_target = (target == MXS_LOG_TARGET_DEFAULT ? MXS_LOG_TARGET_FS : target);
|
|
#if defined(SS_DEBUG)
|
|
lm->lm_chk_top = CHK_NUM_LOGMANAGER;
|
|
lm->lm_chk_tail = CHK_NUM_LOGMANAGER;
|
|
write_index = 0;
|
|
block_start_index = 0;
|
|
prevval = -1;
|
|
simple_mutex_init(&msg_mutex, "Message mutex");
|
|
#endif
|
|
lm->lm_clientmes = skygw_message_init();
|
|
lm->lm_logmes = skygw_message_init();
|
|
|
|
if (lm->lm_clientmes == NULL || lm->lm_logmes == NULL)
|
|
{
|
|
err = 1;
|
|
goto return_succ;
|
|
}
|
|
|
|
fn = &lm->lm_fnames_conf;
|
|
fw = &lm->lm_filewriter;
|
|
fn->fn_state = UNINIT;
|
|
fw->fwr_state = UNINIT;
|
|
|
|
// The openlog call is always made, but a connection to the system logger will
|
|
// not be made until a call to syslog is made.
|
|
openlog(ident, LOG_PID | LOG_ODELAY, LOG_USER);
|
|
|
|
// This will disable logging to actual files but they are still created
|
|
if (target == MXS_LOG_TARGET_STDOUT)
|
|
{
|
|
logdir = NULL;
|
|
}
|
|
|
|
/** Initialize configuration including log file naming info */
|
|
if (!fnames_conf_init(fn, logdir))
|
|
{
|
|
err = 1;
|
|
goto return_succ;
|
|
}
|
|
|
|
/** Initialize logfile */
|
|
if (!logfile_init(&lm->lm_logfile, lm, (lm->lm_target == MXS_LOG_TARGET_SHMEM)))
|
|
{
|
|
err = 1;
|
|
goto return_succ;
|
|
}
|
|
|
|
/**
|
|
* Set global variable
|
|
*/
|
|
mxs_log_enabled_priorities = (1 << LOG_ERR) | (1 << LOG_NOTICE) | (1 << LOG_WARNING);
|
|
|
|
/**
|
|
* Initialize filewriter data and open the log file
|
|
* for each log file type.
|
|
*/
|
|
if (!filewriter_init(lm, fw, write_header))
|
|
{
|
|
err = 1;
|
|
goto return_succ;
|
|
}
|
|
|
|
/** Initialize and start filewriter thread */
|
|
fw->fwr_thread = skygw_thread_init("filewriter thr", thr_filewriter_fun, (void *)fw);
|
|
|
|
if (fw->fwr_thread == NULL)
|
|
{
|
|
err = 1;
|
|
goto return_succ;
|
|
}
|
|
|
|
if ((err = skygw_thread_start(fw->fwr_thread)) != 0)
|
|
{
|
|
goto return_succ;
|
|
}
|
|
/** Wait message from filewriter_thr */
|
|
skygw_message_wait(fw->fwr_clientmes);
|
|
|
|
succ = true;
|
|
lm->lm_enabled = true;
|
|
|
|
return_succ:
|
|
if (err != 0)
|
|
{
|
|
/** This releases memory of all created objects */
|
|
logmanager_done_nomutex();
|
|
fprintf(stderr, "* Error: Initializing the log manager failed.\n");
|
|
}
|
|
return succ;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Initializes log managing routines in MariaDB Corporation MaxScale.
|
|
*
|
|
* @param ident The syslog ident. If NULL, then the program name is used.
|
|
* @param logdir The directory for the log file. If NULL logging will be made to stdout.
|
|
* @param target Whether the log should be written to filesystem or shared memory.
|
|
* Meaningless if logdir is NULL.
|
|
*
|
|
* @return true if succeed, otherwise false
|
|
*
|
|
*/
|
|
bool mxs_log_init(const char* ident, const char* logdir, mxs_log_target_t target)
|
|
{
|
|
bool succ = false;
|
|
|
|
spinlock_acquire(&lmlock);
|
|
|
|
if (!lm)
|
|
{
|
|
ss_dassert(!message_stats);
|
|
|
|
message_stats = hashtable_alloc(LM_MESSAGE_HASH_SIZE,
|
|
lm_message_key_hash,
|
|
lm_message_key_cmp);
|
|
if (message_stats)
|
|
{
|
|
// As entries are added to the hashtable they will be cloned,
|
|
// so stack allocated keys and values are ok.
|
|
hashtable_memory_fns(message_stats,
|
|
lm_message_key_clone,
|
|
lm_message_stats_clone,
|
|
hashtable_item_free,
|
|
hashtable_item_free);
|
|
|
|
succ = logmanager_init_nomutex(ident, logdir, target, log_config.do_maxlog);
|
|
|
|
if (!succ)
|
|
{
|
|
hashtable_free(message_stats);
|
|
message_stats = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: This is not ok. If the parameters are different then
|
|
// TODO: we pretend something is what it is not.
|
|
succ = true;
|
|
}
|
|
|
|
spinlock_release(&lmlock);
|
|
|
|
return succ;
|
|
}
|
|
|
|
/**
|
|
* Release resources of log manager.
|
|
*
|
|
* Lock must have been acquired before calling
|
|
* this function.
|
|
*/
|
|
static void logmanager_done_nomutex(void)
|
|
{
|
|
int i;
|
|
logfile_t* lf;
|
|
filewriter_t* fwr;
|
|
|
|
fwr = &lm->lm_filewriter;
|
|
|
|
if (fwr->fwr_state == RUN)
|
|
{
|
|
CHK_FILEWRITER(fwr);
|
|
/** Inform filewriter thread and wait until it has stopped. */
|
|
skygw_thread_set_exitflag(fwr->fwr_thread, fwr->fwr_logmes, fwr->fwr_clientmes);
|
|
|
|
/** Free thread memory */
|
|
skygw_thread_done(fwr->fwr_thread);
|
|
}
|
|
|
|
/** Free filewriter memory. */
|
|
filewriter_done(fwr, log_config.do_maxlog);
|
|
|
|
lf = logmanager_get_logfile(lm);
|
|
/** Release logfile memory */
|
|
logfile_done(lf);
|
|
|
|
closelog();
|
|
|
|
/** Release messages and finally logmanager memory */
|
|
fnames_conf_done(&lm->lm_fnames_conf);
|
|
skygw_message_done(lm->lm_clientmes);
|
|
skygw_message_done(lm->lm_logmes);
|
|
|
|
/** Set global pointer NULL to prevent access to freed data. */
|
|
MXS_FREE(lm);
|
|
lm = NULL;
|
|
|
|
hashtable_free(message_stats);
|
|
message_stats = NULL;
|
|
}
|
|
|
|
/**
|
|
* End execution of log manager
|
|
*
|
|
* Stops file writing thread, releases filewriter, and logfiles.
|
|
*
|
|
*/
|
|
void mxs_log_finish(void)
|
|
{
|
|
spinlock_acquire(&lmlock);
|
|
|
|
if (lm)
|
|
{
|
|
CHK_LOGMANAGER(lm);
|
|
/** Mark logmanager unavailable */
|
|
lm->lm_enabled = false;
|
|
|
|
/** Wait until all users have left or someone shuts down
|
|
* logmanager between lock release and acquire.
|
|
*/
|
|
while (lm != NULL && lm->lm_nlinks != 0)
|
|
{
|
|
spinlock_release(&lmlock);
|
|
sched_yield();
|
|
spinlock_acquire(&lmlock);
|
|
}
|
|
|
|
/** Shut down if not already shutted down. */
|
|
if (lm)
|
|
{
|
|
ss_dassert(lm->lm_nlinks == 0);
|
|
logmanager_done_nomutex();
|
|
}
|
|
}
|
|
|
|
spinlock_release(&lmlock);
|
|
}
|
|
|
|
static logfile_t* logmanager_get_logfile(logmanager_t* lmgr)
|
|
{
|
|
logfile_t* lf;
|
|
CHK_LOGMANAGER(lmgr);
|
|
lf = &lmgr->lm_logfile;
|
|
|
|
if (lf->lf_state == RUN)
|
|
{
|
|
CHK_LOGFILE(lf);
|
|
}
|
|
return lf;
|
|
}
|
|
|
|
/**
|
|
* Finds write position from block buffer for log string and writes there.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* @param priority Syslog priority
|
|
* @param flush indicates whether log string must be written to disk
|
|
* immediately
|
|
* @param prefix_len length of prefix to be stripped away when syslogging
|
|
* @param str_len length of formatted string (including terminating NULL).
|
|
* @param str string to be written to log
|
|
*
|
|
* @return 0 if succeed, -1 otherwise
|
|
*
|
|
*/
|
|
static int logmanager_write_log(int priority,
|
|
enum log_flush flush,
|
|
size_t prefix_len,
|
|
size_t str_len,
|
|
const char* str)
|
|
{
|
|
logfile_t* lf = NULL;
|
|
char* wp = NULL;
|
|
int err = 0;
|
|
blockbuf_t* bb = NULL;
|
|
blockbuf_t* bb_c = NULL;
|
|
size_t timestamp_len;
|
|
int i;
|
|
|
|
// The config parameters are copied to local variables, because the values in
|
|
// log_config may change during the course of the function, with would have
|
|
// unpleasant side-effects.
|
|
int do_highprecision = log_config.do_highprecision;
|
|
int do_maxlog = log_config.do_maxlog;
|
|
int do_syslog = log_config.do_syslog;
|
|
|
|
assert(str);
|
|
assert((priority & ~LOG_PRIMASK) == 0);
|
|
CHK_LOGMANAGER(lm);
|
|
|
|
// All messages are now logged to the error log file.
|
|
lf = &lm->lm_logfile;
|
|
CHK_LOGFILE(lf);
|
|
|
|
/** Length of string that will be written, limited by bufsize */
|
|
size_t safe_str_len;
|
|
|
|
if (do_highprecision)
|
|
{
|
|
timestamp_len = get_timestamp_len_hp();
|
|
}
|
|
else
|
|
{
|
|
timestamp_len = get_timestamp_len();
|
|
}
|
|
|
|
bool overflow = false;
|
|
/** Find out how much can be safely written with current block size */
|
|
if (timestamp_len - sizeof(char) + str_len > lf->lf_buf_size)
|
|
{
|
|
safe_str_len = lf->lf_buf_size;
|
|
overflow = true;
|
|
}
|
|
else
|
|
{
|
|
safe_str_len = timestamp_len - sizeof(char) + str_len;
|
|
}
|
|
/**
|
|
* Seek write position and register to block buffer.
|
|
* Then print formatted string to write position.
|
|
*/
|
|
|
|
#if defined (SS_LOG_DEBUG)
|
|
{
|
|
char *copy, *tok;
|
|
int tokval;
|
|
|
|
simple_mutex_lock(&msg_mutex, true);
|
|
copy = MXS_STRDUP_A(str);
|
|
tok = strtok(copy, "|");
|
|
tok = strtok(NULL, "|");
|
|
|
|
if (strstr(str, "message|") && tok)
|
|
{
|
|
tokval = atoi(tok);
|
|
|
|
if (prevval > 0)
|
|
{
|
|
ss_dassert(tokval == (prevval + 1));
|
|
}
|
|
prevval = tokval;
|
|
}
|
|
MXS_FREE(copy);
|
|
simple_mutex_unlock(&msg_mutex);
|
|
}
|
|
#endif
|
|
/** Book space for log string from buffer */
|
|
if (do_maxlog)
|
|
{
|
|
// All messages are now logged to the error log file.
|
|
wp = blockbuf_get_writepos(&bb, safe_str_len, flush);
|
|
}
|
|
else
|
|
{
|
|
wp = (char*)MXS_MALLOC(sizeof(char) * (timestamp_len - sizeof(char) + str_len + 1));
|
|
}
|
|
|
|
if (wp == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
#if defined (SS_LOG_DEBUG)
|
|
{
|
|
sprintf(wp, "[msg:%d]", atomic_add(&write_index, 1));
|
|
safe_str_len -= strlen(wp);
|
|
wp += strlen(wp);
|
|
}
|
|
#endif
|
|
/**
|
|
* Write timestamp with at most <timestamp_len> characters
|
|
* to wp.
|
|
* Returned timestamp_len doesn't include terminating null.
|
|
*/
|
|
if (do_highprecision)
|
|
{
|
|
timestamp_len = snprint_timestamp_hp(wp, timestamp_len);
|
|
}
|
|
else
|
|
{
|
|
timestamp_len = snprint_timestamp(wp, timestamp_len);
|
|
}
|
|
|
|
/**
|
|
* Write next string to overwrite terminating null character
|
|
* of the timestamp string.
|
|
*/
|
|
snprintf(wp + timestamp_len,
|
|
safe_str_len - timestamp_len,
|
|
"%s",
|
|
str);
|
|
|
|
/** Add an ellipsis to an overflowing message to signal truncation. */
|
|
if (overflow && safe_str_len > 4)
|
|
{
|
|
memset(wp + safe_str_len - 4, '.', 3);
|
|
}
|
|
/** write to syslog */
|
|
if (do_syslog)
|
|
{
|
|
// Strip away the timestamp and the prefix (e.g. "error : ").
|
|
const char *message = wp + timestamp_len + prefix_len;
|
|
|
|
switch (priority)
|
|
{
|
|
case LOG_EMERG:
|
|
case LOG_ALERT:
|
|
case LOG_CRIT:
|
|
case LOG_ERR:
|
|
case LOG_WARNING:
|
|
case LOG_NOTICE:
|
|
case LOG_INFO:
|
|
syslog(priority, "%s", message);
|
|
break;
|
|
|
|
default:
|
|
// LOG_DEBUG messages are never written to syslog.
|
|
break;
|
|
}
|
|
}
|
|
/** remove double line feed */
|
|
if (wp[safe_str_len - 2] == '\n')
|
|
{
|
|
wp[safe_str_len - 2] = ' ';
|
|
}
|
|
wp[safe_str_len - 1] = '\n';
|
|
|
|
if (do_maxlog)
|
|
{
|
|
blockbuf_unregister(bb);
|
|
}
|
|
else
|
|
{
|
|
MXS_FREE(wp);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Register writer to a block buffer. When reference counter is non-zero the
|
|
* flusher thread doesn't write the block to disk.
|
|
*
|
|
* @param bb block buffer
|
|
*/
|
|
static void blockbuf_register(blockbuf_t* bb)
|
|
{
|
|
CHK_BLOCKBUF(bb);
|
|
ss_dassert(bb->bb_refcount >= 0);
|
|
atomic_add(&bb->bb_refcount, 1);
|
|
}
|
|
|
|
/**
|
|
* Unregister writer from block buffer. If the buffer got filled up and there
|
|
* are no other registered writers anymore, notify the flusher thread.
|
|
*
|
|
* @param bb block buffer
|
|
*/
|
|
static void blockbuf_unregister(blockbuf_t* bb)
|
|
{
|
|
logfile_t* lf;
|
|
|
|
CHK_BLOCKBUF(bb);
|
|
ss_dassert(bb->bb_refcount >= 1);
|
|
lf = &lm->lm_logfile;
|
|
CHK_LOGFILE(lf);
|
|
/**
|
|
* if this is the last client in a full buffer, send write request.
|
|
*/
|
|
if (atomic_add(&bb->bb_refcount, -1) == 1 && bb->bb_state == BB_FULL)
|
|
{
|
|
skygw_message_send(lf->lf_logmes);
|
|
}
|
|
ss_dassert(bb->bb_refcount >= 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* @node (write brief function description here)
|
|
*
|
|
* Parameters:
|
|
* @param id - <usage>
|
|
* <description>
|
|
*
|
|
* @param str_len - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details List mutex now protects both the list and the contents of it.
|
|
* TODO : It should be so that adding and removing nodes of the list is protected
|
|
* by the list mutex. Buffer modifications should be protected by buffer
|
|
* mutex.
|
|
*
|
|
*/
|
|
static char* blockbuf_get_writepos(blockbuf_t** p_bb,
|
|
size_t str_len,
|
|
bool flush)
|
|
{
|
|
logfile_t* lf;
|
|
mlist_t* bb_list;
|
|
char* pos = NULL;
|
|
mlist_node_t* node;
|
|
blockbuf_t* bb;
|
|
#if defined(SS_DEBUG)
|
|
bool succ;
|
|
#endif
|
|
|
|
CHK_LOGMANAGER(lm);
|
|
lf = &lm->lm_logfile;
|
|
CHK_LOGFILE(lf);
|
|
bb_list = &lf->lf_blockbuf_list;
|
|
|
|
/** Lock list */
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
CHK_MLIST(bb_list);
|
|
|
|
if (bb_list->mlist_nodecount > 0)
|
|
{
|
|
/**
|
|
* At least block buffer exists on the list.
|
|
*/
|
|
node = bb_list->mlist_first;
|
|
|
|
/** Loop over blockbuf list to find write position */
|
|
while (true)
|
|
{
|
|
CHK_MLIST_NODE(node);
|
|
|
|
/** Unlock list */
|
|
simple_mutex_unlock(&bb_list->mlist_mutex);
|
|
|
|
bb = (blockbuf_t *)node->mlnode_data;
|
|
CHK_BLOCKBUF(bb);
|
|
|
|
/** Lock buffer */
|
|
simple_mutex_lock(&bb->bb_mutex, true);
|
|
|
|
if (bb->bb_state == BB_FULL || bb->bb_buf_left < str_len)
|
|
{
|
|
/**
|
|
* This block buffer is too full.
|
|
* Send flush request to file writer thread. This causes
|
|
* flushing all buffers, and (eventually) frees buffer space.
|
|
*/
|
|
blockbuf_register(bb);
|
|
|
|
bb->bb_state = BB_FULL;
|
|
|
|
blockbuf_unregister(bb);
|
|
|
|
/** Unlock buffer */
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
|
|
/** Lock list */
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
|
|
|
|
/**
|
|
* If next node exists move forward. Else check if there is
|
|
* space for a new block buffer on the list.
|
|
*/
|
|
if (node != bb_list->mlist_last)
|
|
{
|
|
node = node->mlnode_next;
|
|
continue;
|
|
}
|
|
/**
|
|
* All buffers on the list are full.
|
|
*/
|
|
if (bb_list->mlist_nodecount < bb_list->mlist_nodecount_max)
|
|
{
|
|
/**
|
|
* New node is created
|
|
*/
|
|
if ((bb = blockbuf_init()) == NULL)
|
|
{
|
|
simple_mutex_unlock(&bb_list->mlist_mutex);
|
|
return NULL;
|
|
}
|
|
|
|
CHK_BLOCKBUF(bb);
|
|
|
|
/**
|
|
* Increase version to odd to mark list update active
|
|
* update.
|
|
*/
|
|
bb_list->mlist_versno += 1;
|
|
ss_dassert(bb_list->mlist_versno % 2 == 1);
|
|
|
|
ss_debug(succ = ) mlist_add_data_nomutex(bb_list, bb);
|
|
ss_dassert(succ);
|
|
|
|
/**
|
|
* Increase version to even to mark completion of update.
|
|
*/
|
|
bb_list->mlist_versno += 1;
|
|
ss_dassert(bb_list->mlist_versno % 2 == 0);
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* List and buffers are full.
|
|
* Reset to the beginning of the list, and wait until
|
|
* there is a block buffer with enough space.
|
|
*/
|
|
simple_mutex_unlock(&bb_list->mlist_mutex);
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
|
|
node = bb_list->mlist_first;
|
|
continue;
|
|
}
|
|
|
|
}
|
|
else if (bb->bb_state == BB_CLEARED)
|
|
{
|
|
|
|
/**
|
|
*Move the cleared buffer to the end of the list if it is the first one in the list
|
|
*/
|
|
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
|
|
if (node == bb_list->mlist_first)
|
|
{
|
|
|
|
if ((bb_list->mlist_nodecount > 1) && (node != bb_list->mlist_last))
|
|
{
|
|
bb_list->mlist_last->mlnode_next = bb_list->mlist_first;
|
|
bb_list->mlist_first = bb_list->mlist_first->mlnode_next;
|
|
bb_list->mlist_last->mlnode_next->mlnode_next = NULL;
|
|
bb_list->mlist_last = bb_list->mlist_last->mlnode_next;
|
|
}
|
|
|
|
ss_dassert(node == bb_list->mlist_last);
|
|
|
|
simple_mutex_unlock(&bb_list->mlist_mutex);
|
|
simple_mutex_lock(&bb->bb_mutex, true);
|
|
|
|
bb->bb_state = BB_READY;
|
|
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
node = bb_list->mlist_first;
|
|
}
|
|
else
|
|
{
|
|
if (node->mlnode_next)
|
|
{
|
|
node = node->mlnode_next;
|
|
}
|
|
else
|
|
{
|
|
node = bb_list->mlist_first;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
}
|
|
else if (bb->bb_state == BB_READY)
|
|
{
|
|
/**
|
|
* There is space for new log string.
|
|
*/
|
|
break;
|
|
}
|
|
} /** while (true) */
|
|
}
|
|
else
|
|
{
|
|
|
|
/**
|
|
* Create the first block buffer to logfile's blockbuf list.
|
|
*/
|
|
|
|
if ((bb = blockbuf_init()) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
CHK_BLOCKBUF(bb);
|
|
|
|
/** Lock buffer */
|
|
simple_mutex_lock(&bb->bb_mutex, true);
|
|
/**
|
|
* Increase version to odd to mark list update active update.
|
|
*/
|
|
bb_list->mlist_versno += 1;
|
|
ss_dassert(bb_list->mlist_versno % 2 == 1);
|
|
|
|
ss_debug(succ = ) mlist_add_data_nomutex(bb_list, bb);
|
|
ss_dassert(succ);
|
|
|
|
/**
|
|
* Increase version to even to mark completion of update.
|
|
*/
|
|
bb_list->mlist_versno += 1;
|
|
ss_dassert(bb_list->mlist_versno % 2 == 0);
|
|
|
|
/** Unlock list */
|
|
simple_mutex_unlock(&bb_list->mlist_mutex);
|
|
} /* if (bb_list->mlist_nodecount > 0) */
|
|
|
|
ss_dassert(pos == NULL);
|
|
ss_dassert(!(bb->bb_state == BB_FULL || bb->bb_buf_left < str_len));
|
|
ss_dassert(bb_list->mlist_nodecount <= bb_list->mlist_nodecount_max);
|
|
|
|
/**
|
|
* Registration to blockbuf adds reference for the write operation.
|
|
* It indicates that client has allocated space from the buffer,
|
|
* but not written yet. As long as refcount > 0 buffer can't be
|
|
* written to disk.
|
|
*/
|
|
blockbuf_register(bb);
|
|
*p_bb = bb;
|
|
/**
|
|
* At this point list mutex is held and bb points to a node with
|
|
* enough space available. pos is not yet set.
|
|
*/
|
|
pos = &bb->bb_buf[bb->bb_buf_used];
|
|
bb->bb_buf_used += str_len;
|
|
bb->bb_buf_left -= str_len;
|
|
|
|
ss_dassert((pos >= &bb->bb_buf[0]) && (pos <= &bb->bb_buf[MAX_LOGSTRLEN - str_len]));
|
|
|
|
/** read checkmark */
|
|
/** TODO: add buffer overflow checkmark
|
|
chk_val = (int)bb->bb_buf[bb->bb_buf_used-count_len];
|
|
ss_dassert(chk_val == bb->bb_strcount);
|
|
*/
|
|
|
|
/** TODO : write next checkmark
|
|
bb->bb_strcount += 1;
|
|
memcpy(&bb->bb_buf[bb->bb_buf_used], &bb->bb_strcount, count_len);
|
|
bb->bb_buf_used += count_len;
|
|
bb->bb_buf_left -= count_len;
|
|
*/
|
|
|
|
/**
|
|
* If flush flag is set, set buffer full. As a consequence, no-one
|
|
* can write to it before it is flushed to disk.
|
|
*/
|
|
bb->bb_state = (flush == true ? BB_FULL : bb->bb_state);
|
|
|
|
/** Unlock buffer */
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
return pos;
|
|
}
|
|
|
|
static void blockbuf_node_done(void* bb_data)
|
|
{
|
|
blockbuf_t* bb = (blockbuf_t *)bb_data;
|
|
simple_mutex_done(&bb->bb_mutex);
|
|
}
|
|
|
|
|
|
static blockbuf_t* blockbuf_init()
|
|
{
|
|
blockbuf_t* bb;
|
|
|
|
if ((bb = (blockbuf_t *) MXS_CALLOC(1, sizeof (blockbuf_t))))
|
|
{
|
|
#if defined(SS_DEBUG)
|
|
bb->bb_chk_top = CHK_NUM_BLOCKBUF;
|
|
bb->bb_chk_tail = CHK_NUM_BLOCKBUF;
|
|
#endif
|
|
simple_mutex_init(&bb->bb_mutex, "Blockbuf mutex");
|
|
bb->bb_buf_left = MAX_LOGSTRLEN;
|
|
bb->bb_buf_size = MAX_LOGSTRLEN;
|
|
#if defined(SS_LOG_DEBUG)
|
|
sprintf(bb->bb_buf, "[block:%d]", atomic_add(&block_start_index, 1));
|
|
bb->bb_buf_used += strlen(bb->bb_buf);
|
|
bb->bb_buf_left -= strlen(bb->bb_buf);
|
|
#endif
|
|
CHK_BLOCKBUF(bb);
|
|
}
|
|
return bb;
|
|
}
|
|
|
|
/**
|
|
* Set log augmentation.
|
|
*
|
|
* @param bits One of the log_augmentation_t constants.
|
|
*/
|
|
void mxs_log_set_augmentation(int bits)
|
|
{
|
|
log_config.augmentation = bits & MXS_LOG_AUGMENTATION_MASK;
|
|
}
|
|
|
|
/**
|
|
* Helper for skygw_log_write and friends.
|
|
*
|
|
* @param int The syslog priority.
|
|
* @param file The name of the file where the logging was made.
|
|
* @param int The line where the logging was made.
|
|
* @param function The function where the logging was made.
|
|
* @param prefix_len The length of the text to be stripped away when syslogging.
|
|
* @param len Length of str, including terminating NULL.
|
|
* @param str String
|
|
* @param flush Whether the message should be flushed.
|
|
*
|
|
* @return 0 if the logging to at least one log succeeded.
|
|
*/
|
|
|
|
static int log_write(int priority,
|
|
const char* file,
|
|
int line,
|
|
const char* function,
|
|
size_t prefix_len,
|
|
size_t len,
|
|
const char* str,
|
|
enum log_flush flush)
|
|
{
|
|
int rv = -1;
|
|
|
|
if (logmanager_register(true))
|
|
{
|
|
CHK_LOGMANAGER(lm);
|
|
|
|
rv = logmanager_write_log(priority, flush, prefix_len, len, str);
|
|
|
|
logmanager_unregister();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* @node Register as a logging client to logmanager.
|
|
*
|
|
* Parameters:
|
|
* @param lmgr - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details Link count modify is protected by mutex.
|
|
*
|
|
*/
|
|
static bool logmanager_register(bool writep)
|
|
{
|
|
bool succ = true;
|
|
|
|
spinlock_acquire(&lmlock);
|
|
|
|
if (lm == NULL || !lm->lm_enabled)
|
|
{
|
|
/**
|
|
* Flush succeeds if logmanager is shut or shutting down.
|
|
* returning false so that flusher doesn't access logmanager
|
|
* and its members which would probabaly lead to NULL pointer
|
|
* reference.
|
|
*/
|
|
if (!writep || fatal_error)
|
|
{
|
|
succ = false;
|
|
goto return_succ;
|
|
}
|
|
|
|
ss_dassert(lm == NULL || (lm != NULL && !lm->lm_enabled));
|
|
|
|
/**
|
|
* Wait until logmanager shut down has completed.
|
|
* logmanager is enabled if someone already restarted
|
|
* it between latest lock release, and acquire.
|
|
*/
|
|
while (lm != NULL && !lm->lm_enabled)
|
|
{
|
|
spinlock_release(&lmlock);
|
|
sched_yield();
|
|
spinlock_acquire(&lmlock);
|
|
}
|
|
|
|
if (lm == NULL)
|
|
{
|
|
// If someone is logging before the log manager has been inited,
|
|
// or after the log manager has been finished, the messages are
|
|
// written to stdout.
|
|
succ = logmanager_init_nomutex(NULL, NULL, MXS_LOG_TARGET_DEFAULT, true);
|
|
}
|
|
}
|
|
/** if logmanager existed or was succesfully restarted, increase link */
|
|
if (succ)
|
|
{
|
|
lm->lm_nlinks += 1;
|
|
}
|
|
|
|
return_succ:
|
|
|
|
if (!succ)
|
|
{
|
|
fatal_error = true;
|
|
}
|
|
spinlock_release(&lmlock);
|
|
return succ;
|
|
}
|
|
|
|
/**
|
|
* @node Unregister from logmanager.
|
|
*
|
|
* Parameters:
|
|
* @param lmgr - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details Link count modify is protected by mutex.
|
|
*
|
|
*/
|
|
static void logmanager_unregister(void)
|
|
{
|
|
spinlock_acquire(&lmlock);
|
|
|
|
lm->lm_nlinks -= 1;
|
|
ss_dassert(lm->lm_nlinks >= 0);
|
|
|
|
spinlock_release(&lmlock);
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Initialize log file naming parameters from call arguments
|
|
* or from default functions in cases where arguments are not provided.
|
|
*
|
|
* @param fn The fnames_conf_t structure to initialize.
|
|
* @param logdir The directory for the log file. If NULL logging will be made to stdout.
|
|
*
|
|
* @return True if the initialization was performed, false otherwise.
|
|
*
|
|
* @details Note that input parameter lenghts are checked here.
|
|
*
|
|
*/
|
|
static bool fnames_conf_init(fnames_conf_t* fn, const char* logdir)
|
|
{
|
|
bool succ = false;
|
|
|
|
/**
|
|
* When init_started is set, clean must be done for it.
|
|
*/
|
|
fn->fn_state = INIT;
|
|
#if defined(SS_DEBUG)
|
|
fn->fn_chk_top = CHK_NUM_FNAMES;
|
|
fn->fn_chk_tail = CHK_NUM_FNAMES;
|
|
#endif
|
|
const char* dir;
|
|
if (logdir)
|
|
{
|
|
log_config.use_stdout = false;
|
|
dir = logdir;
|
|
}
|
|
else
|
|
{
|
|
log_config.use_stdout = true;
|
|
// TODO: Re-arrange things so that fn->fn_logpath can be NULL.
|
|
dir = "/tmp";
|
|
}
|
|
|
|
fn->fn_logpath = MXS_STRDUP_A(dir);
|
|
|
|
if (fn->fn_logpath)
|
|
{
|
|
succ = true;
|
|
fn->fn_state = RUN;
|
|
CHK_FNAMES_CONF(fn);
|
|
}
|
|
|
|
return succ;
|
|
}
|
|
|
|
|
|
static void logfile_flush(logfile_t* lf)
|
|
{
|
|
CHK_LOGFILE(lf);
|
|
spinlock_acquire(&lf->lf_spinlock);
|
|
lf->lf_flushflag = true;
|
|
spinlock_release(&lf->lf_spinlock);
|
|
skygw_message_send(lf->lf_logmes);
|
|
}
|
|
|
|
/**
|
|
* Set rotate flag for a log file and wake up the writer thread which then
|
|
* performs the actual rotation task.
|
|
*
|
|
* @param lf logfile pointer
|
|
*/
|
|
static void logfile_rotate(logfile_t* lf)
|
|
{
|
|
CHK_LOGFILE(lf);
|
|
spinlock_acquire(&lf->lf_spinlock);
|
|
lf->lf_rotateflag = true;
|
|
spinlock_release(&lf->lf_spinlock);
|
|
skygw_message_send(lf->lf_logmes);
|
|
}
|
|
|
|
/**
|
|
* Forms complete path name for logfile and tests that the file doesn't conflict
|
|
* with any existing file and it is writable.
|
|
*
|
|
* @param lf logfile pointer
|
|
*
|
|
* @return true if succeed, false if failed
|
|
*
|
|
* @note Log file openings are not TOCTOU-safe. It is not likely that
|
|
* multiple copies of same files are opened in parallel but it is possible by
|
|
* using log manager in parallel with multiple processes and by configuring
|
|
* log manager to use same directories among those processes.
|
|
*/
|
|
static bool logfile_build_name(logfile_t* lf)
|
|
{
|
|
bool succ = false;
|
|
|
|
if (log_config.use_stdout)
|
|
{
|
|
// TODO: Refactor so that lf_full_file_name can be NULL in this case.
|
|
lf->lf_full_file_name = MXS_STRDUP_A("stdout");
|
|
succ = true;
|
|
// TODO: Refactor to get rid of the gotos.
|
|
goto return_succ;
|
|
}
|
|
|
|
/**
|
|
* Create name for log file.
|
|
*/
|
|
lf->lf_full_file_name = form_full_file_name(lf->lf_filepath,
|
|
lf->lf_name_prefix,
|
|
lf->lf_name_suffix);
|
|
|
|
if (lf->lf_store_shmem)
|
|
{
|
|
/**
|
|
* Create name for link file
|
|
*/
|
|
lf->lf_full_link_name = form_full_file_name(lf->lf_linkpath,
|
|
lf->lf_name_prefix,
|
|
lf->lf_name_suffix);
|
|
}
|
|
/**
|
|
* At least one of the files couldn't be created. Either
|
|
* memory allocation failed or the filename is too long.
|
|
*/
|
|
if (!lf->lf_full_file_name || (lf->lf_store_shmem && !lf->lf_full_link_name))
|
|
{
|
|
goto return_succ;
|
|
}
|
|
|
|
/**
|
|
* If file exists but is different type, create fails.
|
|
*/
|
|
bool writable;
|
|
if (check_file_and_path(lf->lf_full_file_name, &writable))
|
|
{
|
|
/** Found similarly named file which isn't writable */
|
|
if (!writable || file_is_symlink(lf->lf_full_file_name))
|
|
{
|
|
goto return_succ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* Opening the file failed for some other reason than
|
|
* existing non-writable file. Shut down.
|
|
*/
|
|
if (!writable)
|
|
{
|
|
goto return_succ;
|
|
}
|
|
}
|
|
|
|
if (lf->lf_store_shmem)
|
|
{
|
|
if (check_file_and_path(lf->lf_full_link_name, &writable))
|
|
{
|
|
/** Found similarly named link which isn't writable */
|
|
if (!writable)
|
|
{
|
|
goto return_succ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* Opening the file failed for some other reason than
|
|
* existing non-writable file. Shut down.
|
|
*/
|
|
if (!writable)
|
|
{
|
|
goto return_succ;
|
|
}
|
|
}
|
|
}
|
|
|
|
succ = true;
|
|
|
|
return_succ:
|
|
return succ;
|
|
}
|
|
|
|
static bool logfile_write_header(skygw_file_t* file)
|
|
{
|
|
CHK_FILE(file);
|
|
|
|
bool written = true;
|
|
time_t t = time(NULL);
|
|
struct tm tm;
|
|
localtime_r(&t, &tm);
|
|
|
|
const char PREFIX[] = "MariaDB MaxScale "; // sizeof(PREFIX) includes the NULL.
|
|
char time_string[32]; // 26 would be enough, according to "man asctime".
|
|
asctime_r(&tm, time_string);
|
|
|
|
size_t size = sizeof(PREFIX) + strlen(file->sf_fname) + 2 * sizeof(' ') + strlen(time_string);
|
|
|
|
char header[size + 2]; // For the 2 newlines.
|
|
sprintf(header, "\n\n%s%s %s", PREFIX, file->sf_fname, time_string);
|
|
|
|
char line[sizeof(header) - 1];
|
|
memset(line, '-', sizeof(line) - 1);
|
|
line[sizeof(line) - 1] = '\n';
|
|
|
|
size_t header_items = fwrite(header, sizeof(header) - 1, 1, file->sf_file);
|
|
size_t line_items = fwrite(line, sizeof(line), 1, file->sf_file);
|
|
|
|
if ((header_items != 1) || (line_items != 1))
|
|
{
|
|
LOG_ERROR("MaxScale Log: Writing header failed due to %d, %s\n",
|
|
errno, mxs_strerror(errno));
|
|
written = false;
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
/**
|
|
* Opens a log file and writes header to the beginning of it. File name, FILE*,
|
|
* and file descriptor are stored to skygw_file_t struct which is stored in
|
|
* filewriter strcuture passed as parameter.
|
|
*
|
|
* @param fw filewriter pointer
|
|
* @param lf logfile pointer
|
|
* @param mode Wheather the file should be truncated or appended to.
|
|
* @param write_header Wheather a file header should be written.
|
|
*
|
|
* @return true if succeed; the resulting skygw_file_t is written in filewriter,
|
|
* false if failed.
|
|
*
|
|
*/
|
|
static bool logfile_open_file(filewriter_t* fw,
|
|
logfile_t* lf,
|
|
skygw_open_mode_t mode,
|
|
bool write_header)
|
|
{
|
|
bool rv = true;
|
|
|
|
if (log_config.use_stdout)
|
|
{
|
|
fw->fwr_file = skygw_file_alloc(lf->lf_full_file_name);
|
|
fw->fwr_file->sf_file = stdout;
|
|
}
|
|
else
|
|
{
|
|
const char* full_link_name = lf->lf_store_shmem ? lf->lf_full_link_name : NULL;
|
|
|
|
/** Create symlink pointing to log file */
|
|
fw->fwr_file = skygw_file_init(lf->lf_full_file_name, full_link_name, mode);
|
|
|
|
if (fw->fwr_file && write_header)
|
|
{
|
|
logfile_write_header(fw->fwr_file);
|
|
}
|
|
}
|
|
|
|
if (fw->fwr_file == NULL)
|
|
{
|
|
// Error logged by skygw_file_init to stderr.
|
|
rv = false;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Form the complete file path.
|
|
*
|
|
* @param directory The directory component containing a trailing '/'.
|
|
* @param prefix The prefix part of the filename.
|
|
* @param suffix The suffix part of the filename.
|
|
*
|
|
* @return Pointer to filename, of NULL if failed.
|
|
*
|
|
*/
|
|
static char* form_full_file_name(const char* directory,
|
|
const char* prefix,
|
|
const char* suffix)
|
|
{
|
|
char* filename = NULL;
|
|
|
|
size_t len = strlen(directory) + strlen(prefix) + strlen(suffix) + 1;
|
|
|
|
if (len <= NAME_MAX)
|
|
{
|
|
filename = (char*)MXS_CALLOC(1, len);
|
|
|
|
if (filename)
|
|
{
|
|
strcat(filename, directory);
|
|
strcat(filename, prefix);
|
|
strcat(filename, suffix);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, filename too long %s%s%s.\n",
|
|
directory, prefix, suffix);
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
/**
|
|
* @node Allocate new buffer where argument string with a slash is copied.
|
|
* Original string buffer is freed.
|
|
* added.
|
|
*
|
|
* Parameters:
|
|
* @param str - <usage>
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details (write detailed description here)
|
|
*
|
|
*/
|
|
static char* add_slash(char* str)
|
|
{
|
|
char* p = str;
|
|
size_t plen = strlen(p);
|
|
|
|
/** Add slash if missing */
|
|
if (p[plen - 1] != '/')
|
|
{
|
|
str = (char *)MXS_MALLOC(plen + 2);
|
|
MXS_ABORT_IF_NULL(str);
|
|
snprintf(str, plen + 2, "%s/", p);
|
|
MXS_FREE(p);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
/**
|
|
* @node Check if the path and file exist in the local file system and if they do,
|
|
* check if they are accessible and writable.
|
|
*
|
|
* Parameters:
|
|
* @param filename File to be checked
|
|
* @param writable flag indicating whether file was found writable or not
|
|
* if writable is NULL, check is skipped.
|
|
*
|
|
* @return true & writable if file exists and it is writable,
|
|
* true & not writable if file exists but it can't be written,
|
|
* false & writable if file doesn't exist but directory could be written, and
|
|
* false & not writable if directory can't be written.
|
|
*
|
|
* @details Note, that a space character is written to the end of file.
|
|
* TODO: recall what was the reason for not succeeding with simply
|
|
* calling access, and fstat. vraa 26.11.13
|
|
*/
|
|
static bool check_file_and_path(const char* filename, bool* writable)
|
|
{
|
|
bool exists;
|
|
|
|
if (filename == NULL)
|
|
{
|
|
exists = false;
|
|
|
|
if (writable)
|
|
{
|
|
*writable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (access(filename, F_OK) == 0)
|
|
{
|
|
|
|
exists = true;
|
|
|
|
if (access(filename, W_OK) == 0)
|
|
{
|
|
if (writable)
|
|
{
|
|
*writable = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (file_is_symlink(filename))
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, Can't access file pointed to by %s due to %d, %s.\n",
|
|
filename, errno, mxs_strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, Can't access %s due to %d, %s.\n",
|
|
filename, errno, mxs_strerror(errno));
|
|
}
|
|
|
|
if (writable)
|
|
{
|
|
*writable = false;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
exists = false;
|
|
if (writable)
|
|
{
|
|
*writable = true;
|
|
}
|
|
}
|
|
}
|
|
return exists;
|
|
}
|
|
|
|
|
|
|
|
static bool file_is_symlink(const char* filename)
|
|
{
|
|
int rc;
|
|
bool succ = false;
|
|
struct stat b;
|
|
|
|
if (filename != NULL)
|
|
{
|
|
rc = lstat(filename, &b);
|
|
|
|
if (rc != -1 && S_ISLNK(b.st_mode))
|
|
{
|
|
succ = true;
|
|
}
|
|
}
|
|
return succ;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @node Initialize logfile structure. Form log file name, and optionally
|
|
* link name. Create block buffer for logfile.
|
|
*
|
|
* Parameters:
|
|
* @param logfile log file
|
|
* @param logmanager log manager pointer
|
|
* @param store_shmem flag to indicate whether log is physically written to shmem
|
|
*
|
|
* @return true if succeed, false otherwise
|
|
*/
|
|
static bool logfile_init(logfile_t* logfile,
|
|
logmanager_t* logmanager,
|
|
bool store_shmem)
|
|
{
|
|
bool succ = false;
|
|
fnames_conf_t* fn = &logmanager->lm_fnames_conf;
|
|
logfile->lf_state = INIT;
|
|
#if defined(SS_DEBUG)
|
|
logfile->lf_chk_top = CHK_NUM_LOGFILE;
|
|
logfile->lf_chk_tail = CHK_NUM_LOGFILE;
|
|
#endif
|
|
logfile->lf_logmes = logmanager->lm_logmes;
|
|
logfile->lf_name_prefix = LOGFILE_NAME_PREFIX;
|
|
logfile->lf_name_suffix = LOGFILE_NAME_SUFFIX;
|
|
logfile->lf_lmgr = logmanager;
|
|
logfile->lf_flushflag = false;
|
|
logfile->lf_rotateflag = false;
|
|
logfile->lf_spinlock = SPINLOCK_INIT;
|
|
logfile->lf_store_shmem = store_shmem;
|
|
logfile->lf_buf_size = MAX_LOGSTRLEN;
|
|
/**
|
|
* If file is stored in shared memory in /dev/shm, a link
|
|
* pointing to shm file is created and located to the file
|
|
* directory.
|
|
*/
|
|
if (store_shmem)
|
|
{
|
|
char* dir;
|
|
int len = sizeof(SHM_PATHNAME_PREFIX) + sizeof(LOGFILE_NAME_PREFIX);
|
|
|
|
dir = (char *)MXS_CALLOC(len, sizeof(char));
|
|
|
|
if (dir == NULL)
|
|
{
|
|
succ = false;
|
|
goto return_with_succ;
|
|
}
|
|
sprintf(dir, "%s%s", SHM_PATHNAME_PREFIX, LOGFILE_NAME_PREFIX);
|
|
logfile->lf_filepath = dir;
|
|
|
|
if (mkdir(dir, S_IRWXU | S_IRWXG) != 0 && (errno != EEXIST))
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, creating directory %s failed due to %d, %s.\n",
|
|
dir, errno, mxs_strerror(errno));
|
|
|
|
succ = false;
|
|
goto return_with_succ;
|
|
}
|
|
logfile->lf_linkpath = MXS_STRDUP_A(fn->fn_logpath);
|
|
logfile->lf_linkpath = add_slash(logfile->lf_linkpath);
|
|
}
|
|
else
|
|
{
|
|
logfile->lf_filepath = MXS_STRDUP_A(fn->fn_logpath);
|
|
}
|
|
logfile->lf_filepath = add_slash(logfile->lf_filepath);
|
|
|
|
if (!(succ = logfile_build_name(logfile)))
|
|
{
|
|
goto return_with_succ;
|
|
}
|
|
/**
|
|
* Create a block buffer list for log file. Clients' writes go to buffers
|
|
* from where separate log flusher thread writes them to disk.
|
|
*/
|
|
if (mlist_init(&logfile->lf_blockbuf_list,
|
|
NULL,
|
|
MXS_STRDUP_A("logfile block buffer list"),
|
|
blockbuf_node_done,
|
|
MAXNBLOCKBUFS) == NULL)
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, Initializing buffers for log files failed.\n");
|
|
logfile_free_memory(logfile);
|
|
goto return_with_succ;
|
|
}
|
|
|
|
succ = true;
|
|
logfile->lf_state = RUN;
|
|
CHK_LOGFILE(logfile);
|
|
|
|
return_with_succ:
|
|
if (!succ)
|
|
{
|
|
logfile_done(logfile);
|
|
}
|
|
ss_dassert(logfile->lf_state == RUN || logfile->lf_state == DONE);
|
|
return succ;
|
|
}
|
|
|
|
/**
|
|
* @node Flush logfile and free memory allocated for it.
|
|
*
|
|
* Parameters:
|
|
* @param lf - <usage>
|
|
* <description>
|
|
*
|
|
* @return void
|
|
*
|
|
*
|
|
* @details Operation is not protected. it is assumed that no one tries
|
|
* to call logfile functions when logfile_done is called.
|
|
*
|
|
* It is also assumed that filewriter doesn't exist anymore.
|
|
*
|
|
* Logfile access could be protected by using exit flag and rwlock to protect
|
|
* flag read/write. Lock would be held during log write operation by clients.
|
|
*
|
|
*/
|
|
static void logfile_done(logfile_t* lf)
|
|
{
|
|
switch (lf->lf_state)
|
|
{
|
|
case RUN:
|
|
CHK_LOGFILE(lf);
|
|
/** fallthrough */
|
|
case INIT:
|
|
/** Test if list is initialized before freeing it */
|
|
if (lf->lf_blockbuf_list.mlist_versno != 0)
|
|
{
|
|
mlist_done(&lf->lf_blockbuf_list);
|
|
}
|
|
logfile_free_memory(lf);
|
|
lf->lf_state = DONE;
|
|
/** fallthrough */
|
|
case DONE:
|
|
case UNINIT:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void logfile_free_memory(logfile_t* lf)
|
|
{
|
|
if (lf->lf_filepath != NULL)
|
|
{
|
|
MXS_FREE(lf->lf_filepath);
|
|
}
|
|
if (lf->lf_linkpath != NULL)
|
|
{
|
|
MXS_FREE(lf->lf_linkpath);
|
|
}
|
|
if (lf->lf_full_link_name != NULL)
|
|
{
|
|
MXS_FREE(lf->lf_full_link_name);
|
|
}
|
|
if (lf->lf_full_file_name != NULL)
|
|
{
|
|
MXS_FREE(lf->lf_full_file_name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @node Initialize filewriter data and open the log file for each log file type.
|
|
*
|
|
* @param logmanager Log manager struct
|
|
* @param fw File writer struct
|
|
* @param write_header Wheather a file header should be written.
|
|
*
|
|
* @return true if succeed, false if failed
|
|
*
|
|
*/
|
|
static bool filewriter_init(logmanager_t* logmanager, filewriter_t* fw, bool write_header)
|
|
{
|
|
bool succ = false;
|
|
|
|
CHK_LOGMANAGER(logmanager);
|
|
assert(logmanager->lm_clientmes);
|
|
assert(logmanager->lm_logmes);
|
|
|
|
fw->fwr_state = INIT;
|
|
#if defined(SS_DEBUG)
|
|
fw->fwr_chk_top = CHK_NUM_FILEWRITER;
|
|
fw->fwr_chk_tail = CHK_NUM_FILEWRITER;
|
|
#endif
|
|
fw->fwr_logmgr = logmanager;
|
|
/** Message from filewriter to clients */
|
|
fw->fwr_logmes = logmanager->lm_logmes;
|
|
/** Message from clients to filewriter */
|
|
fw->fwr_clientmes = logmanager->lm_clientmes;
|
|
|
|
logfile_t* lf = logmanager_get_logfile(logmanager);
|
|
|
|
if (logfile_open_file(fw, lf, SKYGW_OPEN_APPEND, write_header))
|
|
{
|
|
fw->fwr_state = RUN;
|
|
CHK_FILEWRITER(fw);
|
|
succ = true;
|
|
}
|
|
else
|
|
{
|
|
filewriter_done(fw, write_header);
|
|
}
|
|
|
|
ss_dassert(fw->fwr_state == RUN || fw->fwr_state == DONE);
|
|
return succ;
|
|
}
|
|
|
|
static bool logfile_write_footer(skygw_file_t* file, const char* suffix)
|
|
{
|
|
CHK_FILE(file);
|
|
|
|
bool written = true;
|
|
time_t t = time(NULL);
|
|
struct tm tm;
|
|
localtime_r(&t, &tm);
|
|
|
|
const char FORMAT[] = "%04d-%02d-%02d %02d:%02d:%02d";
|
|
char time_string[20]; // 19 chars + NULL.
|
|
|
|
sprintf(time_string, FORMAT,
|
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
size_t size = sizeof(time_string) + 3 * sizeof(' ') + strlen(suffix) + sizeof('\n');
|
|
|
|
char header[size];
|
|
sprintf(header, "%s %s\n", time_string, suffix);
|
|
|
|
char line[sizeof(header) - 1];
|
|
memset(line, '-', sizeof(line) - 1);
|
|
line[sizeof(line) - 1] = '\n';
|
|
|
|
size_t header_items = fwrite(header, sizeof(header) - 1, 1, file->sf_file);
|
|
size_t line_items = fwrite(line, sizeof(line), 1, file->sf_file);
|
|
|
|
if ((header_items != 1) || (line_items != 1))
|
|
{
|
|
LOG_ERROR("MaxScale Log: Writing footer failed due to %d, %s\n",
|
|
errno, mxs_strerror(errno));
|
|
written = false;
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
static void filewriter_done(filewriter_t* fw, bool write_footer)
|
|
{
|
|
switch (fw->fwr_state)
|
|
{
|
|
case RUN:
|
|
CHK_FILEWRITER(fw);
|
|
if (log_config.use_stdout)
|
|
{
|
|
skygw_file_free(fw->fwr_file);
|
|
}
|
|
else
|
|
{
|
|
if (write_footer)
|
|
{
|
|
logfile_write_footer(fw->fwr_file, "MariaDB MaxScale is shut down.");
|
|
}
|
|
|
|
skygw_file_close(fw->fwr_file);
|
|
}
|
|
case INIT:
|
|
fw->fwr_logmes = NULL;
|
|
fw->fwr_clientmes = NULL;
|
|
fw->fwr_state = DONE;
|
|
break;
|
|
case DONE:
|
|
case UNINIT:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool thr_flush_file(logmanager_t *lm, filewriter_t *fwr)
|
|
{
|
|
/**
|
|
* Get file pointer of current logfile.
|
|
*/
|
|
bool do_flushall = thr_flushall_check();
|
|
logfile_t *lf = &lm->lm_logfile;
|
|
|
|
/**
|
|
* read and reset logfile's flush- and rotateflag
|
|
*/
|
|
spinlock_acquire(&lf->lf_spinlock);
|
|
bool flush_logfile = lf->lf_flushflag;
|
|
bool rotate_logfile = lf->lf_rotateflag;
|
|
lf->lf_flushflag = false;
|
|
lf->lf_rotateflag = false;
|
|
spinlock_release(&lf->lf_spinlock);
|
|
|
|
// fwr->fwr_file may be NULL if an earlier log-rotation failed.
|
|
if (rotate_logfile || !fwr->fwr_file)
|
|
{
|
|
/**
|
|
* Log rotation: Close file, and reopen in truncate mode.
|
|
*/
|
|
if (!log_config.use_stdout)
|
|
{
|
|
if (log_config.do_maxlog)
|
|
{
|
|
logfile_write_footer(fwr->fwr_file, "File closed due to log rotation.");
|
|
}
|
|
|
|
skygw_file_close(fwr->fwr_file);
|
|
fwr->fwr_file = NULL;
|
|
|
|
if (!logfile_open_file(fwr, lf, SKYGW_OPEN_APPEND, log_config.do_maxlog))
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, could not re-open log file %s.\n",
|
|
lf->lf_full_file_name);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
skygw_file_t *file = fwr->fwr_file;
|
|
/**
|
|
* get logfile's block buffer list
|
|
*/
|
|
mlist_t *bb_list = &lf->lf_blockbuf_list;
|
|
#if defined(SS_DEBUG)
|
|
simple_mutex_lock(&bb_list->mlist_mutex, true);
|
|
CHK_MLIST(bb_list);
|
|
simple_mutex_unlock(&bb_list->mlist_mutex);
|
|
#endif
|
|
mlist_node_t *node = bb_list->mlist_first;
|
|
|
|
while (node != NULL)
|
|
{
|
|
int err = 0;
|
|
|
|
CHK_MLIST_NODE(node);
|
|
blockbuf_t *bb = (blockbuf_t *)node->mlnode_data;
|
|
CHK_BLOCKBUF(bb);
|
|
|
|
/** Lock block buffer */
|
|
simple_mutex_lock(&bb->bb_mutex, true);
|
|
|
|
blockbuf_state_t flush_blockbuf = bb->bb_state;
|
|
|
|
if (bb->bb_buf_used != 0 &&
|
|
((flush_blockbuf == BB_FULL) || flush_logfile || do_flushall))
|
|
{
|
|
/**
|
|
* buffer is at least half-full
|
|
* -> write to disk
|
|
*/
|
|
while (bb->bb_refcount > 0)
|
|
{
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
simple_mutex_lock(&bb->bb_mutex, true);
|
|
}
|
|
err = skygw_file_write(file,
|
|
(void *)bb->bb_buf,
|
|
bb->bb_buf_used,
|
|
(flush_logfile || do_flushall));
|
|
if (err)
|
|
{
|
|
// TODO: Log this to syslog.
|
|
LOG_ERROR("MaxScale Log: Error, writing to the log-file %s failed due to %d, %s. "
|
|
"Disabling writing to the log.\n",
|
|
lf->lf_full_file_name, err, mxs_strerror(err));
|
|
|
|
mxs_log_set_maxlog_enabled(false);
|
|
}
|
|
/**
|
|
* Reset buffer's counters and mark
|
|
* not full.
|
|
*/
|
|
bb->bb_buf_left = bb->bb_buf_size;
|
|
bb->bb_buf_used = 0;
|
|
memset(bb->bb_buf, 0, bb->bb_buf_size);
|
|
bb->bb_state = BB_CLEARED;
|
|
#if defined(SS_LOG_DEBUG)
|
|
sprintf(bb->bb_buf, "[block:%d]", atomic_add(&block_start_index, 1));
|
|
bb->bb_buf_used += strlen(bb->bb_buf);
|
|
bb->bb_buf_left -= strlen(bb->bb_buf);
|
|
#endif
|
|
|
|
}
|
|
/** Release lock to block buffer */
|
|
simple_mutex_unlock(&bb->bb_mutex);
|
|
|
|
size_t vn1;
|
|
size_t vn2;
|
|
/** Consistent lock-free read on the list */
|
|
do
|
|
{
|
|
while ((vn1 = bb_list->mlist_versno) % 2 != 0)
|
|
{
|
|
continue;
|
|
}
|
|
node = node->mlnode_next;
|
|
vn2 = bb_list->mlist_versno;
|
|
}
|
|
while (vn1 != vn2 && node);
|
|
|
|
} /* while (node != NULL) */
|
|
|
|
/**
|
|
* Writer's exit flag was set after checking it.
|
|
* Loop is restarted to ensure that all logfiles are
|
|
* flushed.
|
|
*/
|
|
|
|
bool done = true;
|
|
|
|
if (flushall_started_flag)
|
|
{
|
|
flushall_started_flag = false;
|
|
flushall_done_flag = true;
|
|
done = false;
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
/**
|
|
* @node Writes block buffers of logfiles to physical log files on disk.
|
|
*
|
|
* Parameters:
|
|
* @param data - thread context, skygw_thread_t
|
|
* <description>
|
|
*
|
|
* @return
|
|
*
|
|
*
|
|
* @details Waits until receives wake-up message. Scans through block buffer
|
|
* lists of each logfile object.
|
|
*
|
|
* Block buffer is written to log file if
|
|
* 1. bb_state == true,
|
|
* 2. logfile object's lf_flushflag == true, or
|
|
* 3. skygw_thread_must_exit returns true.
|
|
*
|
|
* Log file is flushed (fsync'd) in cases #2 and #3.
|
|
*
|
|
* Concurrency control : block buffer is accessed by file writer (this) and
|
|
* log clients. File writer reads and sets each logfile object's flushflag
|
|
* with spinlock. Another protected section is when block buffer's metadata is
|
|
* read, and optionally the write operation.
|
|
*
|
|
* When block buffer is marked full, logfile's flushflag is set, other
|
|
* log clients don't try to access buffer(s). There may, however, be
|
|
* active writes, on the block in parallel with file writer operations.
|
|
* Each active write corresponds to bb_refcounter values.
|
|
* On the other hand, file writer doesn't process the block buffer before
|
|
* its refcount is zero.
|
|
*
|
|
* Every log file obj. has its own block buffer (linked) list.
|
|
* List is accessed by log clients, which add nodes if necessary, and
|
|
* by file writer which traverses the list and accesses block buffers
|
|
* included in list nodes.
|
|
* List modifications are protected with version numbers.
|
|
* Before
|
|
modification, version is increased by one to be odd. After the
|
|
* completion, it is increased again to even. List can be read only when
|
|
* version is even and read is consistent only if version hasn't changed
|
|
* during the read.
|
|
*/
|
|
static void* thr_filewriter_fun(void* data)
|
|
{
|
|
skygw_thread_t* thr = (skygw_thread_t *)data;
|
|
filewriter_t* fwr = (filewriter_t *)skygw_thread_get_data(thr);
|
|
|
|
flushall_logfiles(false);
|
|
|
|
CHK_FILEWRITER(fwr);
|
|
ss_debug(skygw_thread_set_state(thr, THR_RUNNING));
|
|
|
|
/** Inform log manager about the state. */
|
|
skygw_message_send(fwr->fwr_clientmes);
|
|
bool running = true;
|
|
|
|
do
|
|
{
|
|
/**
|
|
* Wait until new log arrival message appears.
|
|
* Reset message to avoid redundant calls.
|
|
*/
|
|
skygw_message_wait(fwr->fwr_logmes);
|
|
if (skygw_thread_must_exit(thr))
|
|
{
|
|
flushall_logfiles(true);
|
|
}
|
|
|
|
/** Process all logfiles which have buffered writes. */
|
|
bool done = false;
|
|
|
|
while (!done)
|
|
{
|
|
done = thr_flush_file(lm, fwr);
|
|
|
|
if (!thr_flushall_check() && skygw_thread_must_exit(thr))
|
|
{
|
|
flushall_logfiles(true);
|
|
done = false;
|
|
}
|
|
}
|
|
|
|
bool send_message = false;
|
|
|
|
if (flushall_done_flag)
|
|
{
|
|
flushall_done_flag = false;
|
|
flushall_logfiles(false);
|
|
send_message = true;
|
|
}
|
|
|
|
running = !skygw_thread_must_exit(thr);
|
|
|
|
if (running && send_message)
|
|
{
|
|
skygw_message_send(fwr->fwr_clientmes);
|
|
}
|
|
}
|
|
while (running);
|
|
|
|
ss_debug(skygw_thread_set_state(thr, THR_STOPPED));
|
|
/** Inform log manager that file writer thread has stopped. */
|
|
skygw_message_send(fwr->fwr_clientmes);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void fnames_conf_done(fnames_conf_t* fn)
|
|
{
|
|
switch (fn->fn_state)
|
|
{
|
|
case RUN:
|
|
CHK_FNAMES_CONF(fn);
|
|
case INIT:
|
|
fnames_conf_free_memory(fn);
|
|
fn->fn_state = DONE;
|
|
case DONE:
|
|
case UNINIT:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void fnames_conf_free_memory(fnames_conf_t* fn)
|
|
{
|
|
MXS_FREE(fn->fn_logpath);
|
|
}
|
|
|
|
bool thr_flushall_check()
|
|
{
|
|
bool rval = false;
|
|
simple_mutex_lock(&lm->lm_mutex, true);
|
|
rval = flushall_flag;
|
|
if (rval && !flushall_started_flag && !flushall_done_flag)
|
|
{
|
|
flushall_started_flag = true;
|
|
}
|
|
simple_mutex_unlock(&lm->lm_mutex);
|
|
return rval;
|
|
}
|
|
|
|
void flushall_logfiles(bool flush)
|
|
{
|
|
simple_mutex_lock(&lm->lm_mutex, true);
|
|
flushall_flag = flush;
|
|
simple_mutex_unlock(&lm->lm_mutex);
|
|
}
|
|
|
|
/**
|
|
* Enable/disable syslog logging.
|
|
*
|
|
* @param enabled True, if high precision logging should be enabled, false if it should be disabled.
|
|
*/
|
|
void mxs_log_set_highprecision_enabled(bool enabled)
|
|
{
|
|
log_config.do_highprecision = enabled;
|
|
|
|
MXS_NOTICE("highprecision logging is %s.", enabled ? "enabled" : "disabled");
|
|
}
|
|
|
|
/**
|
|
* Enable/disable syslog logging.
|
|
*
|
|
* @param enabled True, if syslog logging should be enabled, false if it should be disabled.
|
|
*/
|
|
void mxs_log_set_syslog_enabled(bool enabled)
|
|
{
|
|
log_config.do_syslog = enabled;
|
|
|
|
MXS_NOTICE("syslog logging is %s.", enabled ? "enabled" : "disabled");
|
|
}
|
|
|
|
/**
|
|
* Enable/disable maxscale log logging.
|
|
*
|
|
* @param enabled True, if syslog logging should be enabled, false if it should be disabled.
|
|
*/
|
|
void mxs_log_set_maxlog_enabled(bool enabled)
|
|
{
|
|
log_config.do_maxlog = enabled;
|
|
|
|
MXS_NOTICE("maxlog logging is %s.", enabled ? "enabled" : "disabled");
|
|
}
|
|
|
|
/**
|
|
* Set the log throttling parameters.
|
|
*
|
|
* @param throttling The throttling parameters.
|
|
*/
|
|
void mxs_log_set_throttling(const MXS_LOG_THROTTLING* throttling)
|
|
{
|
|
// No locking; it does not have any real impact, even if the struct
|
|
// is used right when its values are modified.
|
|
log_config.throttling = *throttling;
|
|
|
|
if ((log_config.throttling.count == 0) ||
|
|
(log_config.throttling.window_ms == 0) ||
|
|
(log_config.throttling.suppress_ms == 0))
|
|
{
|
|
MXS_NOTICE("Log throttling has been disabled.");
|
|
}
|
|
else
|
|
{
|
|
MXS_NOTICE("A message that is logged %lu times in %lu milliseconds, "
|
|
"will be suppressed for %lu milliseconds.",
|
|
log_config.throttling.count,
|
|
log_config.throttling.window_ms,
|
|
log_config.throttling.suppress_ms);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the log throttling parameters.
|
|
*
|
|
* @param throttling The throttling parameters.
|
|
*/
|
|
void mxs_log_get_throttling(MXS_LOG_THROTTLING* throttling)
|
|
{
|
|
// No locking; this is used only from maxadmin and an inconsistent set
|
|
// may be returned only if mxs_log_set_throttling() is called via an
|
|
// other instance of maxadmin at the very same moment.
|
|
*throttling = log_config.throttling;
|
|
}
|
|
|
|
/**
|
|
* Explicitly ensure that all pending log messages are flushed.
|
|
*
|
|
* @return 0 if the flushing was successfully initiated, otherwise -1.
|
|
*
|
|
* Note that the return value only indicates whether the flushing was
|
|
* successfully initiated, not whether the actual flushing has been
|
|
* performed.
|
|
*/
|
|
int mxs_log_flush()
|
|
{
|
|
int err = -1;
|
|
|
|
if (logmanager_register(false))
|
|
{
|
|
CHK_LOGMANAGER(lm);
|
|
|
|
logfile_t *lf = logmanager_get_logfile(lm);
|
|
CHK_LOGFILE(lf);
|
|
|
|
logfile_flush(lf);
|
|
err = 0;
|
|
|
|
logmanager_unregister();
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, can't register to logmanager, flushing failed.\n");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Explicitly ensure that all pending log messages are flushed.
|
|
*
|
|
* @return 0 if the flushing was successfully performed, otherwise -1.
|
|
*
|
|
* When the function returns 0, the flushing has been initiated and the
|
|
* flushing thread has indicated that the operation has been performed.
|
|
* However, 0 will be returned also in the case that the flushing thread
|
|
* for, whatever reason, failed to actually flush the log.
|
|
*/
|
|
int mxs_log_flush_sync(void)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!log_config.use_stdout)
|
|
{
|
|
MXS_INFO("Starting log flushing to disk.");
|
|
}
|
|
|
|
/** If initialization of the log manager has not been done, lm pointer can be
|
|
* NULL. */
|
|
// TODO: Why is logmanager_register() not called here?
|
|
if (lm)
|
|
{
|
|
flushall_logfiles(true);
|
|
|
|
if (skygw_message_send(lm->lm_logmes) == MES_RC_SUCCESS)
|
|
{
|
|
// TODO: Add error handling to skygw_message_wait. Now void.
|
|
skygw_message_wait(lm->lm_clientmes);
|
|
}
|
|
else
|
|
{
|
|
err = -1;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Rotate the log-file. That is, close the current one and create a new one
|
|
* with a larger sequence number.
|
|
*
|
|
* @return 0 if the rotating was successfully initiated, otherwise -1.
|
|
*
|
|
* Note that the return value only indicates whether the rotating was
|
|
* successfully initiated, not whether the actual rotation has been
|
|
* performed.
|
|
*/
|
|
int mxs_log_rotate()
|
|
{
|
|
int err = -1;
|
|
|
|
if (logmanager_register(false))
|
|
{
|
|
CHK_LOGMANAGER(lm);
|
|
|
|
logfile_t *lf = logmanager_get_logfile(lm);
|
|
CHK_LOGFILE(lf);
|
|
|
|
MXS_NOTICE("Log rotation is called for %s.", lf->lf_full_file_name);
|
|
|
|
logfile_rotate(lf);
|
|
err = 0;
|
|
|
|
logmanager_unregister();
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("MaxScale Log: Error, Can't register to logmanager, rotating failed.\n");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const char* priority_name(int priority)
|
|
{
|
|
switch (priority)
|
|
{
|
|
case LOG_EMERG:
|
|
return "emercency";
|
|
case LOG_ALERT:
|
|
return "alert";
|
|
case LOG_CRIT:
|
|
return "critical";
|
|
case LOG_ERR:
|
|
return "error";
|
|
case LOG_WARNING:
|
|
return "warning";
|
|
case LOG_NOTICE:
|
|
return "notice";
|
|
case LOG_INFO:
|
|
return "informational";
|
|
case LOG_DEBUG:
|
|
return "debug";
|
|
default:
|
|
assert(!true);
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable/disable a particular syslog priority.
|
|
*
|
|
* @param priority One of the LOG_ERR etc. constants from sys/syslog.h.
|
|
* @param enabled True if the priority should be enabled, false if it to be disabled.
|
|
*
|
|
* @return 0 if the priority was valid, -1 otherwise.
|
|
*/
|
|
int mxs_log_set_priority_enabled(int priority, bool enable)
|
|
{
|
|
int rv = -1;
|
|
const char* text = (enable ? "enable" : "disable");
|
|
|
|
if ((priority & ~LOG_PRIMASK) == 0)
|
|
{
|
|
int bit = (1 << priority);
|
|
|
|
if (enable)
|
|
{
|
|
// TODO: Put behind spinlock.
|
|
mxs_log_enabled_priorities |= bit;
|
|
}
|
|
else
|
|
{
|
|
mxs_log_enabled_priorities &= ~bit;
|
|
}
|
|
|
|
MXS_NOTICE("The logging of %s messages has been %sd.", priority_name(priority), text);
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Attempt to %s unknown syslog priority %d.", text, priority);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
typedef struct log_prefix
|
|
{
|
|
const char* text; // The prefix, e.g. "error: "
|
|
int len; // The length of the prefix without the trailing NULL.
|
|
} log_prefix_t;
|
|
|
|
static const char PREFIX_EMERG[] = "emerg : ";
|
|
static const char PREFIX_ALERT[] = "alert : ";
|
|
static const char PREFIX_CRIT[] = "crit : ";
|
|
static const char PREFIX_ERROR[] = "error : ";
|
|
static const char PREFIX_WARNING[] = "warning: ";
|
|
static const char PREFIX_NOTICE[] = "notice : ";
|
|
static const char PREFIX_INFO[] = "info : ";
|
|
static const char PREFIX_DEBUG[] = "debug : ";
|
|
|
|
static log_prefix_t priority_to_prefix(int priority)
|
|
{
|
|
assert((priority & ~LOG_PRIMASK) == 0);
|
|
|
|
log_prefix_t prefix;
|
|
|
|
switch (priority)
|
|
{
|
|
case LOG_EMERG:
|
|
prefix.text = PREFIX_EMERG;
|
|
prefix.len = sizeof(PREFIX_EMERG);
|
|
break;
|
|
|
|
case LOG_ALERT:
|
|
prefix.text = PREFIX_ALERT;
|
|
prefix.len = sizeof(PREFIX_ALERT);
|
|
break;
|
|
|
|
case LOG_CRIT:
|
|
prefix.text = PREFIX_CRIT;
|
|
prefix.len = sizeof(PREFIX_CRIT);
|
|
break;
|
|
|
|
case LOG_ERR:
|
|
prefix.text = PREFIX_ERROR;
|
|
prefix.len = sizeof(PREFIX_ERROR);
|
|
break;
|
|
|
|
case LOG_WARNING:
|
|
prefix.text = PREFIX_WARNING;
|
|
prefix.len = sizeof(PREFIX_WARNING);
|
|
break;
|
|
|
|
case LOG_NOTICE:
|
|
prefix.text = PREFIX_NOTICE;
|
|
prefix.len = sizeof(PREFIX_NOTICE);
|
|
break;
|
|
|
|
case LOG_INFO:
|
|
prefix.text = PREFIX_INFO;
|
|
prefix.len = sizeof(PREFIX_INFO);
|
|
break;
|
|
|
|
case LOG_DEBUG:
|
|
prefix.text = PREFIX_DEBUG;
|
|
prefix.len = sizeof(PREFIX_DEBUG);
|
|
break;
|
|
|
|
default:
|
|
assert(!true);
|
|
prefix.text = PREFIX_ERROR;
|
|
prefix.len = sizeof(PREFIX_ERROR);
|
|
break;
|
|
}
|
|
|
|
--prefix.len; // Remove trailing NULL.
|
|
|
|
return prefix;
|
|
}
|
|
|
|
static enum log_flush priority_to_flush(int priority)
|
|
{
|
|
assert((priority & ~LOG_PRIMASK) == 0);
|
|
|
|
switch (priority)
|
|
{
|
|
case LOG_EMERG:
|
|
case LOG_ALERT:
|
|
case LOG_CRIT:
|
|
case LOG_ERR:
|
|
return LOG_FLUSH_YES;
|
|
|
|
default:
|
|
assert(!true);
|
|
case LOG_WARNING:
|
|
case LOG_NOTICE:
|
|
case LOG_INFO:
|
|
case LOG_DEBUG:
|
|
return LOG_FLUSH_NO;
|
|
}
|
|
}
|
|
|
|
typedef enum message_suppression
|
|
{
|
|
MESSAGE_NOT_SUPPRESSED, // Message is not suppressed.
|
|
MESSAGE_SUPPRESSED, // Message is suppressed for the first time (for this round)
|
|
MESSAGE_STILL_SUPPRESSED // Message is still suppressed (for this round)
|
|
} message_suppression_t;
|
|
|
|
static message_suppression_t message_status(const char* file, int line)
|
|
{
|
|
message_suppression_t rv = MESSAGE_NOT_SUPPRESSED;
|
|
|
|
// Copy the config to prevent the values from changing while we are using
|
|
// them. It does not matter if they are changed just when we are copying
|
|
// them, but we want to use one set of values throughout the function.
|
|
MXS_LOG_THROTTLING t = log_config.throttling;
|
|
|
|
if ((t.count != 0) && (t.window_ms != 0) && (t.suppress_ms != 0))
|
|
{
|
|
LM_MESSAGE_KEY key = { file, line };
|
|
LM_MESSAGE_STATS *value;
|
|
|
|
errno = 0;
|
|
|
|
// Since the hashtable can not be externally locked, the lookup/creation
|
|
// must be done in a loop to ensure that everything works even if two
|
|
// threads logs the same message at the very same time.
|
|
do
|
|
{
|
|
value = (LM_MESSAGE_STATS*) hashtable_fetch(message_stats, &key);
|
|
|
|
if (!value)
|
|
{
|
|
LM_MESSAGE_STATS stats;
|
|
spinlock_init(&stats.lock);
|
|
stats.first_ms = time_monotonic_ms();
|
|
stats.last_ms = 0;
|
|
stats.count = 0;
|
|
|
|
// hashtable_add may fail for two reasons; the key exists
|
|
// already or it runs out of memory. In the latter case
|
|
// errno is set.
|
|
hashtable_add(message_stats, &key, &stats);
|
|
}
|
|
}
|
|
while (!value && (errno == 0));
|
|
|
|
if (value)
|
|
{
|
|
uint64_t now_ms = time_monotonic_ms();
|
|
|
|
spinlock_acquire(&value->lock);
|
|
|
|
++value->count;
|
|
|
|
// Less that t.window_ms milliseconds since the message was logged
|
|
// the last time. May have to be throttled.
|
|
if (value->count < t.count)
|
|
{
|
|
// t.count times has not been reached, still ok to log.
|
|
}
|
|
else if (value->count == t.count)
|
|
{
|
|
// t.count times has been reached. Was it within the window?
|
|
if (now_ms - value->first_ms < t.window_ms)
|
|
{
|
|
// Within the window, suppress the message.
|
|
rv = MESSAGE_SUPPRESSED;
|
|
}
|
|
else
|
|
{
|
|
// Not within the window, reset the situation.
|
|
|
|
// The flooding situation is analyzed window by window.
|
|
// That means that if there in each of two consequtive
|
|
// windows are not enough messages for throttling to take
|
|
// effect, but there would be if the window was placed at a
|
|
// slightly different position (e.g. starting in the middle
|
|
// of the first and ending in the middle of the second) it
|
|
// will go undetected and no throttling will be made.
|
|
// However, if that's the case, it was a spike so the
|
|
// flooding will stop anyway.
|
|
|
|
value->first_ms = now_ms;
|
|
value->count = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// In suppression mode.
|
|
if (now_ms - value->first_ms < (t.window_ms + t.suppress_ms))
|
|
{
|
|
// Still in the suppression window.
|
|
rv = MESSAGE_STILL_SUPPRESSED;
|
|
}
|
|
else
|
|
{
|
|
// We have exited the suppression window, reset the situation.
|
|
value->first_ms = now_ms;
|
|
value->count = 1;
|
|
}
|
|
}
|
|
|
|
value->last_ms = now_ms;
|
|
|
|
spinlock_release(&value->lock);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Log a message of a particular priority.
|
|
*
|
|
* @param priority One of the syslog constants: LOG_ERR, LOG_WARNING, ...
|
|
* @param modname The name of the module.
|
|
* @param file The name of the file where the message was logged.
|
|
* @param line The line where the message was logged.
|
|
* @param function The function where the message was logged.
|
|
* @param format The printf format of the following arguments.
|
|
* @param ... Optional arguments according to the format.
|
|
*/
|
|
int mxs_log_message(int priority,
|
|
const char* modname,
|
|
const char* file, int line, const char* function,
|
|
const char* format, ...)
|
|
{
|
|
int err = 0;
|
|
|
|
assert((priority & ~LOG_PRIMASK) == 0);
|
|
|
|
if ((priority & ~LOG_PRIMASK) == 0) // Check that the priority is ok,
|
|
{
|
|
message_suppression_t status = MESSAGE_NOT_SUPPRESSED;
|
|
|
|
// We only throttle errors and warnings. Info and debug messages
|
|
// are never on during normal operation, so if they are enabled,
|
|
// we are presumably debugging something. Notice messages are
|
|
// assumed to be logged for a reason and always in a context where
|
|
// flooding cannot be caused.
|
|
if ((priority == LOG_ERR) || (priority == LOG_WARNING))
|
|
{
|
|
status = message_status(file, line);
|
|
}
|
|
|
|
if (status != MESSAGE_STILL_SUPPRESSED)
|
|
{
|
|
va_list valist;
|
|
|
|
uint64_t session_id = session_get_current_id();
|
|
int session_len = 0;
|
|
|
|
char session[20]; // Enough to fit "9223372036854775807"
|
|
|
|
if (session_id != 0)
|
|
{
|
|
sprintf(session, "%" PRIu64, session_id);
|
|
session_len = strlen(session) + 3; // +3 due to "() "
|
|
}
|
|
else
|
|
{
|
|
session_len = 0;
|
|
}
|
|
|
|
int modname_len = modname ? strlen(modname) + 3 : 0; // +3 due to "[...] "
|
|
|
|
static const char SUPPRESSION[] =
|
|
" (subsequent similar messages suppressed for %lu milliseconds)";
|
|
int suppression_len = 0;
|
|
size_t suppress_ms = log_config.throttling.suppress_ms;
|
|
|
|
if (status == MESSAGE_SUPPRESSED)
|
|
{
|
|
suppression_len += sizeof(SUPPRESSION) - 1; // Remove trailing NULL
|
|
suppression_len -= 3; // Remove the %lu
|
|
suppression_len += UINTLEN(suppress_ms);
|
|
}
|
|
|
|
/**
|
|
* Find out the length of log string (to be formatted str).
|
|
*/
|
|
va_start(valist, format);
|
|
int message_len = vsnprintf(NULL, 0, format, valist);
|
|
va_end(valist);
|
|
|
|
if (message_len >= 0)
|
|
{
|
|
log_prefix_t prefix = priority_to_prefix(priority);
|
|
|
|
static const char FORMAT_FUNCTION[] = "(%s): ";
|
|
|
|
// Other thread might change log_config.augmentation.
|
|
int augmentation = log_config.augmentation;
|
|
int augmentation_len = 0;
|
|
|
|
switch (augmentation)
|
|
{
|
|
case MXS_LOG_AUGMENT_WITH_FUNCTION:
|
|
augmentation_len = sizeof(FORMAT_FUNCTION) - 1; // Remove trailing 0
|
|
augmentation_len -= 2; // Remove the %s
|
|
augmentation_len += strlen(function);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
int buffer_len = 0;
|
|
buffer_len += prefix.len;
|
|
buffer_len += session_len;
|
|
buffer_len += modname_len;
|
|
buffer_len += augmentation_len;
|
|
buffer_len += message_len;
|
|
buffer_len += suppression_len;
|
|
buffer_len += 1; // Trailing NULL
|
|
|
|
if (buffer_len > MAX_LOGSTRLEN)
|
|
{
|
|
message_len -= (buffer_len - MAX_LOGSTRLEN);
|
|
buffer_len = MAX_LOGSTRLEN;
|
|
|
|
ss_dassert(prefix.len + session_len + modname_len +
|
|
augmentation_len + message_len + suppression_len + 1 == buffer_len);
|
|
}
|
|
|
|
char buffer[buffer_len];
|
|
|
|
char *prefix_text = buffer;
|
|
char *session_text = prefix_text + prefix.len;
|
|
char *modname_text = session_text + session_len;
|
|
char *augmentation_text = modname_text + modname_len;
|
|
char *message_text = augmentation_text + augmentation_len;
|
|
char *suppression_text = message_text + message_len;
|
|
|
|
strcpy(prefix_text, prefix.text);
|
|
|
|
if (session_len)
|
|
{
|
|
strcpy(session_text, "(");
|
|
strcat(session_text, session);
|
|
strcat(session_text, ") ");
|
|
}
|
|
|
|
if (modname_len)
|
|
{
|
|
strcpy(modname_text, "[");
|
|
strcat(modname_text, modname);
|
|
strcat(modname_text, "] ");
|
|
}
|
|
|
|
if (augmentation_len)
|
|
{
|
|
int len = 0;
|
|
|
|
switch (augmentation)
|
|
{
|
|
case MXS_LOG_AUGMENT_WITH_FUNCTION:
|
|
len = sprintf(augmentation_text, FORMAT_FUNCTION, function);
|
|
break;
|
|
|
|
default:
|
|
assert(!true);
|
|
}
|
|
|
|
(void)len;
|
|
ss_dassert(len == augmentation_len);
|
|
}
|
|
|
|
va_start(valist, format);
|
|
vsnprintf(message_text, message_len + 1, format, valist);
|
|
va_end(valist);
|
|
|
|
if (suppression_len)
|
|
{
|
|
sprintf(suppression_text, SUPPRESSION, suppress_ms);
|
|
}
|
|
|
|
enum log_flush flush = priority_to_flush(priority);
|
|
|
|
err = log_write(priority, file, line, function, prefix.len, buffer_len, buffer, flush);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXS_WARNING("Invalid syslog priority: %d", priority);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
const char* mxs_strerror(int error)
|
|
{
|
|
static thread_local char errbuf[MXS_STRERROR_BUFLEN];
|
|
#ifdef HAVE_GLIBC
|
|
return strerror_r(error, errbuf, sizeof(errbuf));
|
|
#else
|
|
strerror_r(error, errbuf, sizeof(errbuf));
|
|
return errbuf;
|
|
#endif
|
|
}
|
|
|
|
json_t* get_log_priorities()
|
|
{
|
|
json_t* arr = json_array();
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_ERR))
|
|
{
|
|
json_array_append_new(arr, json_string("error"));
|
|
}
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_WARNING))
|
|
{
|
|
json_array_append_new(arr, json_string("warning"));
|
|
}
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_NOTICE))
|
|
{
|
|
json_array_append_new(arr, json_string("notice"));
|
|
}
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
|
|
{
|
|
json_array_append_new(arr, json_string("info"));
|
|
}
|
|
|
|
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_DEBUG))
|
|
{
|
|
json_array_append_new(arr, json_string("debug"));
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
json_t* mxs_logs_to_json(const char* host)
|
|
{
|
|
json_t* param = json_object();
|
|
json_object_set_new(param, "highprecision", json_boolean(log_config.do_highprecision));
|
|
json_object_set_new(param, "maxlog", json_boolean(log_config.do_maxlog));
|
|
json_object_set_new(param, "syslog", json_boolean(log_config.do_syslog));
|
|
|
|
json_t* throttling = json_object();
|
|
json_object_set_new(throttling, "count", json_integer(log_config.throttling.count));
|
|
json_object_set_new(throttling, "suppress_ms", json_integer(log_config.throttling.suppress_ms));
|
|
json_object_set_new(throttling, "window_ms", json_integer(log_config.throttling.window_ms));
|
|
json_object_set_new(param, "throttling", throttling);
|
|
json_object_set_new(param, "log_warning", json_boolean(mxs_log_priority_is_enabled(LOG_WARNING)));
|
|
json_object_set_new(param, "log_notice", json_boolean(mxs_log_priority_is_enabled(LOG_NOTICE)));
|
|
json_object_set_new(param, "log_info", json_boolean(mxs_log_priority_is_enabled(LOG_INFO)));
|
|
json_object_set_new(param, "log_debug", json_boolean(mxs_log_priority_is_enabled(LOG_DEBUG)));
|
|
json_object_set_new(param, "log_to_shm", json_boolean(config_get_global_options()->log_to_shm));
|
|
|
|
json_t* attr = json_object();
|
|
json_object_set_new(attr, CN_PARAMETERS, param);
|
|
json_object_set_new(attr, "log_file", json_string(lm->lm_filewriter.fwr_file->sf_fname));
|
|
json_object_set_new(attr, "log_priorities", get_log_priorities());
|
|
|
|
json_t* data = json_object();
|
|
json_object_set_new(data, CN_ATTRIBUTES, attr);
|
|
json_object_set_new(data, CN_ID, json_string("logs"));
|
|
json_object_set_new(data, CN_TYPE, json_string("logs"));
|
|
|
|
return mxs_json_resource(host, MXS_JSON_API_LOGS, data);
|
|
}
|