diff options
Diffstat (limited to 'ctdb/common/logging.c')
-rw-r--r-- | ctdb/common/logging.c | 745 |
1 files changed, 745 insertions, 0 deletions
diff --git a/ctdb/common/logging.c b/ctdb/common/logging.c new file mode 100644 index 0000000..3aa5ca9 --- /dev/null +++ b/ctdb/common/logging.c @@ -0,0 +1,745 @@ +/* + Logging utilities + + Copyright (C) Andrew Tridgell 2008 + Copyright (C) Martin Schwenke 2014 + Copyright (C) Amitay Isaacs 2015 + + 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; either version 3 of the License, or + (at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/locale.h" +#include "system/time.h" +#include "system/filesys.h" +#include "system/syslog.h" +#include "system/dir.h" + +#include "lib/util/time_basic.h" +#include "lib/util/sys_rw.h" +#include "lib/util/debug.h" +#include "lib/util/blocking.h" +#include "lib/util/samba_util.h" /* get_myname() */ + +#include "common/logging.h" + +struct { + int log_level; + const char *log_string; +} log_string_map[] = { + { DEBUG_ERR, "ERROR" }, + { DEBUG_WARNING, "WARNING" }, + { 2, "WARNING" }, + { DEBUG_NOTICE, "NOTICE" }, + { 4, "NOTICE" }, + { DEBUG_INFO, "INFO" }, + { 6, "INFO" }, + { 7, "INFO" }, + { 8, "INFO" }, + { 9, "INFO" }, + { DEBUG_DEBUG, "DEBUG" }, +}; + +bool debug_level_parse(const char *log_string, int *log_level) +{ + size_t i; + + if (log_string == NULL) { + return false; + } + + if (isdigit(log_string[0])) { + int level = atoi(log_string); + + if (level >= 0 && (size_t)level < ARRAY_SIZE(log_string_map)) { + *log_level = level; + return true; + } + return false; + } + + for (i=0; i<ARRAY_SIZE(log_string_map); i++) { + if (strncasecmp(log_string_map[i].log_string, + log_string, strlen(log_string)) == 0) { + *log_level = log_string_map[i].log_level; + return true; + } + } + + return false; +} + +const char *debug_level_to_string(int log_level) +{ + size_t i; + + for (i=0; i < ARRAY_SIZE(log_string_map); i++) { + if (log_string_map[i].log_level == log_level) { + return log_string_map[i].log_string; + } + } + return "UNKNOWN"; +} + +int debug_level_from_string(const char *log_string) +{ + bool found; + int log_level; + + found = debug_level_parse(log_string, &log_level); + if (found) { + return log_level; + } + + /* Default debug level */ + return DEBUG_ERR; +} + +/* + * file logging backend + */ + +static bool file_log_validate(const char *option) +{ + char *t, *dir; + struct stat st; + int ret; + + if (option == NULL || strcmp(option, "-") == 0) { + return true; + } + + t = strdup(option); + if (t == NULL) { + return false; + } + + dir = dirname(t); + + ret = stat(dir, &st); + free(t); + if (ret != 0) { + return false; + } + + if (! S_ISDIR(st.st_mode)) { + return false; + } + + return true; +} + +static int file_log_setup(TALLOC_CTX *mem_ctx, + const char *option, + const char *app_name) +{ + struct debug_settings settings = { + .debug_syslog_format = true, + .debug_hires_timestamp = true, + .debug_no_stderr_redirect = true, + }; + const char *t = NULL; + + if (option == NULL || strcmp(option, "-") == 0) { + /* + * Logging to stderr is the default and has already + * been done in logging init + */ + return 0; + } + + /* + * Support logging of fake hostname in local daemons. This + * hostname is basename(getenv(CTDB_BASE)). + */ + t = getenv("CTDB_TEST_MODE"); + if (t != NULL) { + t = getenv("CTDB_BASE"); + if (t != NULL) { + const char *p = strrchr(t, '/'); + if (p != NULL) { + p++; + if (p[0] == '\0') { + p = "unknown"; + } + } else { + p = t; + } + + debug_set_hostname(p); + } + } + + debug_set_settings(&settings, "file", 0, false); + debug_set_logfile(option); + setup_logging(app_name, DEBUG_FILE); + + return 0; +} + +/* + * syslog logging backend + */ + +/* Copied from lib/util/debug.c */ +static int debug_level_to_priority(int level) +{ + /* + * map debug levels to syslog() priorities + */ + static const int priority_map[] = { + LOG_ERR, /* 0 */ + LOG_WARNING, /* 1 */ + LOG_NOTICE, /* 2 */ + LOG_NOTICE, /* 3 */ + LOG_NOTICE, /* 4 */ + LOG_NOTICE, /* 5 */ + LOG_INFO, /* 6 */ + LOG_INFO, /* 7 */ + LOG_INFO, /* 8 */ + LOG_INFO, /* 9 */ + }; + int priority; + + if ((size_t)level >= ARRAY_SIZE(priority_map) || level < 0) { + priority = LOG_DEBUG; + } else { + priority = priority_map[level]; + } + return priority; +} + +struct syslog_log_state { + int fd; + const char *app_name; + const char *hostname; + int (*format)(int dbglevel, struct syslog_log_state *state, + const char *str, char *buf, int bsize); + /* RFC3164 says: The total length of the packet MUST be 1024 + bytes or less. */ + char buffer[1024]; + unsigned int dropped_count; +}; + +/* Format messages as per RFC3164 + * + * It appears that some syslog daemon implementations do not allow a + * hostname when messages are sent via a Unix domain socket, so omit + * it. Similarly, syslogd on FreeBSD does not understand the hostname + * part of the header, even when logging via UDP. Note that most + * implementations will log messages against "localhost" when logging + * via UDP. A timestamp could be sent but rsyslogd on Linux limits + * the timestamp logged to the precision that was received on + * /dev/log. It seems sane to send degenerate RFC3164 messages + * without a header at all, so that the daemon will generate high + * resolution timestamps if configured. + */ +static int format_rfc3164(int dbglevel, struct syslog_log_state *state, + const char *str, char *buf, int bsize) +{ + int pri; + int len; + + pri = LOG_DAEMON | debug_level_to_priority(dbglevel); + len = snprintf(buf, bsize, "<%d>%s[%u]: %s", + pri, state->app_name, getpid(), str); + buf[bsize-1] = '\0'; + len = MIN(len, bsize - 1); + + return len; +} + +/* Format messages as per RFC5424 + * + * <165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 + * myproc 8710 - - %% It's time to make the do-nuts. + */ +static int format_rfc5424(int dbglevel, struct syslog_log_state *state, + const char *str, char *buf, int bsize) +{ + int pri; + struct timeval tv; + struct timeval_buf tvbuf; + int len, s; + + /* Header */ + pri = LOG_DAEMON | debug_level_to_priority(dbglevel); + GetTimeOfDay(&tv); + len = snprintf(buf, bsize, + "<%d>1 %s %s %s %u - - ", + pri, timeval_str_buf(&tv, true, true, &tvbuf), + state->hostname, state->app_name, getpid()); + /* A truncated header is not useful... */ + if (len >= bsize) { + return -1; + } + + /* Message */ + s = snprintf(&buf[len], bsize - len, "%s", str); + buf[bsize-1] = '\0'; + len = MIN(len + s, bsize - 1); + + return len; +} + +static void syslog_log(void *private_data, int level, const char *msg) +{ + syslog(debug_level_to_priority(level), "%s", msg); +} + +static int syslog_log_sock_maybe(struct syslog_log_state *state, + int level, const char *msg) +{ + int n; + ssize_t ret; + + n = state->format(level, state, msg, state->buffer, + sizeof(state->buffer)); + if (n == -1) { + return E2BIG; + } + + do { + ret = write(state->fd, state->buffer, n); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + return errno; + } + + return 0; + +} +static void syslog_log_sock(void *private_data, int level, const char *msg) +{ + struct syslog_log_state *state = talloc_get_type_abort( + private_data, struct syslog_log_state); + int ret; + + if (state->dropped_count > 0) { + char t[64] = { 0 }; + snprintf(t, sizeof(t), + "[Dropped %u log messages]\n", + state->dropped_count); + t[sizeof(t)-1] = '\0'; + ret = syslog_log_sock_maybe(state, level, t); + if (ret == EAGAIN || ret == EWOULDBLOCK) { + state->dropped_count++; + /* + * If above failed then actually drop the + * message that would be logged below, since + * it would have been dropped anyway and it is + * also likely to fail. Falling through and + * attempting to log the message also means + * that the dropped message count will be + * logged out of order. + */ + return; + } + if (ret != 0) { + /* Silent failure on any other error */ + return; + } + state->dropped_count = 0; + } + + ret = syslog_log_sock_maybe(state, level, msg); + if (ret == EAGAIN || ret == EWOULDBLOCK) { + state->dropped_count++; + } +} + +static int syslog_log_setup_syslog(TALLOC_CTX *mem_ctx, const char *app_name) +{ + openlog(app_name, LOG_PID, LOG_DAEMON); + + debug_set_callback(NULL, syslog_log); + + return 0; +} + +static int syslog_log_state_destructor(struct syslog_log_state *state) +{ + if (state->fd != -1) { + close(state->fd); + state->fd = -1; + } + return 0; +} + +static int syslog_log_setup_common(TALLOC_CTX *mem_ctx, const char *app_name, + struct syslog_log_state **result) +{ + struct syslog_log_state *state; + + state = talloc_zero(mem_ctx, struct syslog_log_state); + if (state == NULL) { + return ENOMEM; + } + + state->fd = -1; + state->app_name = app_name; + talloc_set_destructor(state, syslog_log_state_destructor); + + *result = state; + return 0; +} + +#ifdef _PATH_LOG +static int syslog_log_setup_nonblocking(TALLOC_CTX *mem_ctx, + const char *app_name) +{ + struct syslog_log_state *state = NULL; + struct sockaddr_un dest; + int ret; + + ret = syslog_log_setup_common(mem_ctx, app_name, &state); + if (ret != 0) { + return ret; + } + + state->fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (state->fd == -1) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + dest.sun_family = AF_UNIX; + strncpy(dest.sun_path, _PATH_LOG, sizeof(dest.sun_path)-1); + ret = connect(state->fd, + (struct sockaddr *)&dest, sizeof(dest)); + if (ret == -1) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + ret = set_blocking(state->fd, false); + if (ret != 0) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + if (! set_close_on_exec(state->fd)) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + state->hostname = NULL; /* Make this explicit */ + state->format = format_rfc3164; + + debug_set_callback(state, syslog_log_sock); + + return 0; +} +#endif /* _PATH_LOG */ + +static int syslog_log_setup_udp(TALLOC_CTX *mem_ctx, const char *app_name, + bool rfc5424) +{ + struct syslog_log_state *state = NULL; + struct sockaddr_in dest; + int ret; + + ret = syslog_log_setup_common(mem_ctx, app_name, &state); + if (ret != 0) { + return ret; + } + + state->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (state->fd == -1) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + dest.sin_family = AF_INET; + dest.sin_port = htons(514); + dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ret = connect(state->fd, + (struct sockaddr *)&dest, sizeof(dest)); + if (ret == -1) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + if (! set_close_on_exec(state->fd)) { + int save_errno = errno; + talloc_free(state); + return save_errno; + } + + state->hostname = get_myname(state); + if (state->hostname == NULL) { + /* Use a fallback instead of failing initialisation */ + state->hostname = "localhost"; + } + if (rfc5424) { + state->format = format_rfc5424; + } else { + state->format = format_rfc3164; + } + + debug_set_callback(state, syslog_log_sock); + + return 0; +} + +static bool syslog_log_validate(const char *option) +{ + if (option == NULL) { + return true; +#ifdef _PATH_LOG + } else if (strcmp(option, "nonblocking") == 0) { + return true; +#endif + } else if (strcmp(option, "udp") == 0) { + return true; + } else if (strcmp(option, "udp-rfc5424") == 0) { + return true; + } + + return false; +} + +static int syslog_log_setup(TALLOC_CTX *mem_ctx, const char *option, + const char *app_name) +{ + if (option == NULL) { + return syslog_log_setup_syslog(mem_ctx, app_name); +#ifdef _PATH_LOG + } else if (strcmp(option, "nonblocking") == 0) { + return syslog_log_setup_nonblocking(mem_ctx, app_name); +#endif + } else if (strcmp(option, "udp") == 0) { + return syslog_log_setup_udp(mem_ctx, app_name, false); + } else if (strcmp(option, "udp-rfc5424") == 0) { + return syslog_log_setup_udp(mem_ctx, app_name, true); + } + + return EINVAL; +} + +struct log_backend { + const char *name; + bool (*validate)(const char *option); + int (*setup)(TALLOC_CTX *mem_ctx, + const char *option, + const char *app_name); +}; + +static struct log_backend log_backend[] = { + { + .name = "file", + .validate = file_log_validate, + .setup = file_log_setup, + }, + { + .name = "syslog", + .validate = syslog_log_validate, + .setup = syslog_log_setup, + }, +}; + +static int log_backend_parse(TALLOC_CTX *mem_ctx, + const char *logging, + struct log_backend **backend, + char **backend_option) +{ + struct log_backend *b = NULL; + char *t, *name, *option; + size_t i; + + t = talloc_strdup(mem_ctx, logging); + if (t == NULL) { + return ENOMEM; + } + + name = strtok(t, ":"); + if (name == NULL) { + talloc_free(t); + return EINVAL; + } + option = strtok(NULL, ":"); + + for (i=0; i<ARRAY_SIZE(log_backend); i++) { + if (strcmp(log_backend[i].name, name) == 0) { + b = &log_backend[i]; + } + } + + if (b == NULL) { + talloc_free(t); + return ENOENT; + } + + *backend = b; + if (option != NULL) { + *backend_option = talloc_strdup(mem_ctx, option); + if (*backend_option == NULL) { + talloc_free(t); + return ENOMEM; + } + } else { + *backend_option = NULL; + } + + talloc_free(t); + return 0; +} + +bool logging_validate(const char *logging) +{ + TALLOC_CTX *tmp_ctx; + struct log_backend *backend; + char *option; + int ret; + bool status; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return false; + } + + ret = log_backend_parse(tmp_ctx, logging, &backend, &option); + if (ret != 0) { + talloc_free(tmp_ctx); + return false; + } + + status = backend->validate(option); + talloc_free(tmp_ctx); + return status; +} + +/* Initialise logging */ +int logging_init(TALLOC_CTX *mem_ctx, const char *logging, + const char *debug_level, const char *app_name) +{ + struct log_backend *backend = NULL; + char *option = NULL; + int level; + int ret; + + setup_logging(app_name, DEBUG_DEFAULT_STDERR); + + if (debug_level == NULL) { + debug_level = getenv("CTDB_DEBUGLEVEL"); + } + if (! debug_level_parse(debug_level, &level)) { + return EINVAL; + } + debuglevel_set(level); + + if (logging == NULL) { + logging = getenv("CTDB_LOGGING"); + } + if (logging == NULL || logging[0] == '\0') { + return EINVAL; + } + + ret = log_backend_parse(mem_ctx, logging, &backend, &option); + if (ret != 0) { + if (ret == ENOENT) { + fprintf(stderr, "Invalid logging option \'%s\'\n", + logging); + } + talloc_free(option); + return ret; + } + + ret = backend->setup(mem_ctx, option, app_name); + talloc_free(option); + return ret; +} + +bool logging_reopen_logs(void) +{ + bool status; + + status = reopen_logs_internal(); + + return status; +} + +struct logging_reopen_logs_data { + void (*hook)(void *private_data); + void *private_data; +}; + +static void logging_sig_hup_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *dont_care, + void *private_data) +{ + bool status; + + if (private_data != NULL) { + struct logging_reopen_logs_data *data = talloc_get_type_abort( + private_data, struct logging_reopen_logs_data); + + if (data->hook != NULL) { + data->hook(data->private_data); + } + } + + status = logging_reopen_logs(); + if (!status) { + D_WARNING("Failed to reopen logs\n"); + return; + } + + D_NOTICE("Reopened logs\n"); + +} + +bool logging_setup_sighup_handler(struct tevent_context *ev, + TALLOC_CTX *talloc_ctx, + void (*hook)(void *private_data), + void *private_data) +{ + struct logging_reopen_logs_data *data = NULL; + struct tevent_signal *se; + + if (hook != NULL) { + data = talloc(talloc_ctx, struct logging_reopen_logs_data); + if (data == NULL) { + return false; + } + + data->hook = hook; + data->private_data = private_data; + } + + + se = tevent_add_signal(ev, + talloc_ctx, + SIGHUP, + 0, + logging_sig_hup_handler, + data); + if (se == NULL) { + talloc_free(data); + return false; + } + + return true; +} |