diff --git a/Makefile.in b/Makefile.in index 16b28732..120ef028 100644 --- a/Makefile.in +++ b/Makefile.in @@ -26,7 +26,7 @@ include Makefile.global $(info Building against PostgreSQL $(MAJORVERSION)) -REPMGR_CLIENT_OBJS = repmgr-client.o config.o +REPMGR_CLIENT_OBJS = repmgr-client.o config.o log.o REPMGRD_OBJS = repmgrd.o repmgr4: $(REPMGR_CLIENT_OBJS) diff --git a/config.c b/config.c index b0e17722..a195e930 100644 --- a/config.c +++ b/config.c @@ -22,3 +22,31 @@ progname(void) { return _progname; } + +void +item_list_append(ItemList *item_list, char *error_message) +{ + ItemListCell *cell; + + cell = (ItemListCell *) pg_malloc0(sizeof(ItemListCell)); + + if (cell == NULL) + { + //log_err(_("unable to allocate memory; terminating.\n")); + exit(ERR_BAD_CONFIG); + } + + cell->string = pg_malloc0(MAXLEN); + strncpy(cell->string, error_message, MAXLEN); + + if (item_list->tail) + { + item_list->tail->next = cell; + } + else + { + item_list->head = cell; + } + + item_list->tail = cell; +} diff --git a/config.h b/config.h index 502b57e1..9ca9163d 100644 --- a/config.h +++ b/config.h @@ -8,7 +8,34 @@ #ifndef _REPMGR_CONFIG_H_ #define _REPMGR_CONFIG_H_ -void set_progname(const char *argv0); -const char * progname(void); +typedef struct +{ + int node_id; + char node_name[MAXLEN]; + char loglevel[MAXLEN]; + char logfacility[MAXLEN]; + char logfile[MAXLEN]; + +} t_configuration_options; + +typedef struct ItemListCell +{ + struct ItemListCell *next; + char *string; +} ItemListCell; + +typedef struct ItemList +{ + ItemListCell *head; + ItemListCell *tail; +} ItemList; + + +void set_progname(const char *argv0); +const char *progname(void); + +void item_list_append(ItemList *item_list, char *error_message); + + #endif diff --git a/errcode.h b/errcode.h new file mode 100644 index 00000000..dad6069e --- /dev/null +++ b/errcode.h @@ -0,0 +1,31 @@ +/* + * errcode.h + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#ifndef _ERRCODE_H_ +#define _ERRCODE_H_ + +/* Exit return codes */ + +#define SUCCESS 0 +#define ERR_BAD_CONFIG 1 +#define ERR_BAD_RSYNC 2 +#define ERR_NO_RESTART 4 +#define ERR_DB_CON 6 +#define ERR_DB_QUERY 7 +#define ERR_PROMOTED 8 +#define ERR_STR_OVERFLOW 10 +#define ERR_FAILOVER_FAIL 11 +#define ERR_BAD_SSH 12 +#define ERR_SYS_FAILURE 13 +#define ERR_BAD_BASEBACKUP 14 +#define ERR_INTERNAL 15 +#define ERR_MONITORING_FAIL 16 +#define ERR_BAD_BACKUP_LABEL 17 +#define ERR_SWITCHOVER_FAIL 18 +#define ERR_BARMAN 19 +#define ERR_REGISTRATION_SYNC 20 + +#endif /* _ERRCODE_H_ */ + diff --git a/log.c b/log.c new file mode 100644 index 00000000..44c85f5c --- /dev/null +++ b/log.c @@ -0,0 +1,370 @@ +/* + * log.c - Logging methods + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#include "repmgr.h" + +#include + +#ifdef HAVE_SYSLOG +#include +#endif + +#include +#include + +#include "log.h" + +#define DEFAULT_IDENT "repmgr" +#ifdef HAVE_SYSLOG +#define DEFAULT_SYSLOG_FACILITY LOG_LOCAL0 +#endif + +/* #define REPMGR_DEBUG */ + +static int detect_log_facility(const char *facility); +static void _stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0))); + +int log_type = REPMGR_STDERR; +int log_level = LOG_NOTICE; +int last_log_level = LOG_NOTICE; +int verbose_logging = false; +int terse_logging = false; +/* + * Global variable to be set by the main application to ensure any log output + * emitted before logger_init is called, is output in the correct format + */ +int logger_output_mode = OM_DAEMON; + +extern void +stderr_log_with_level(const char *level_name, int level, const char *fmt, ...) +{ + va_list arglist; + + va_start(arglist, fmt); + _stderr_log_with_level(level_name, level, fmt, arglist); + va_end(arglist); +} + +static void +_stderr_log_with_level(const char *level_name, int level, const char *fmt, va_list ap) +{ + char buf[100]; + + /* + * Store the requested level so that if there's a subsequent + * log_hint(), we can suppress that if appropriate. + */ + last_log_level = level; + + if (log_level >= level) + { + + /* Format log line prefix with timestamp if in daemon mode */ + if (logger_output_mode == OM_DAEMON) + { + time_t t; + struct tm *tm; + time(&t); + tm = localtime(&t); + strftime(buf, 100, "[%Y-%m-%d %H:%M:%S]", tm); + fprintf(stderr, "%s [%s] ", buf, level_name); + } + else + { + fprintf(stderr, "%s: ", level_name); + } + + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + fflush(stderr); + } +} + +void +log_hint(const char *fmt, ...) +{ + va_list ap; + + if (terse_logging == false) + { + va_start(ap, fmt); + _stderr_log_with_level("HINT", last_log_level, fmt, ap); + va_end(ap); + } +} + + +void +log_verbose(int level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + if (verbose_logging == true) + { + switch(level) + { + case LOG_EMERG: + _stderr_log_with_level("EMERG", level, fmt, ap); + break; + case LOG_ALERT: + _stderr_log_with_level("ALERT", level, fmt, ap); + break; + case LOG_CRIT: + _stderr_log_with_level("CRIT", level, fmt, ap); + break; + case LOG_ERROR: + _stderr_log_with_level("ERROR", level, fmt, ap); + break; + case LOG_WARNING: + _stderr_log_with_level("WARNING", level, fmt, ap); + break; + case LOG_NOTICE: + _stderr_log_with_level("NOTICE", level, fmt, ap); + break; + case LOG_INFO: + _stderr_log_with_level("INFO", level, fmt, ap); + break; + case LOG_DEBUG: + _stderr_log_with_level("DEBUG", level, fmt, ap); + break; + } + } + + va_end(ap); +} + + +bool +logger_init(t_configuration_options *opts, const char *ident) +{ + char *level = opts->loglevel; + char *facility = opts->logfacility; + + int l; + int f; + +#ifdef HAVE_SYSLOG + int syslog_facility = DEFAULT_SYSLOG_FACILITY; +#endif + +#ifdef REPMGR_DEBUG + printf("Logger initialisation (Level: %s, Facility: %s)\n", level, facility); +#endif + + if (!ident) + { + ident = DEFAULT_IDENT; + } + + if (level && *level) + { + l = detect_log_level(level); +#ifdef REPMGR_DEBUG + printf("Assigned level for logger: %d\n", l); +#endif + + if (l >= 0) + log_level = l; + else + stderr_log_warning(_("Invalid log level \"%s\" (available values: DEBUG, INFO, NOTICE, WARNING, ERR, ALERT, CRIT or EMERG)\n"), level); + } + + /* + * STDERR only logging requested - finish here without setting up any further + * logging facility. + */ + if (logger_output_mode == OM_COMMAND_LINE) + return true; + + if (facility && *facility) + { + + f = detect_log_facility(facility); +#ifdef REPMGR_DEBUG + printf("Assigned facility for logger: %d\n", f); +#endif + + if (f == 0) + { + /* No syslog requested, just stderr */ +#ifdef REPMGR_DEBUG + printf(_("Use stderr for logging\n")); +#endif + } + else if (f == -1) + { + stderr_log_warning(_("Cannot detect log facility %s (use any of LOCAL0, LOCAL1, ..., LOCAL7, USER or STDERR)\n"), facility); + } +#ifdef HAVE_SYSLOG + else + { + syslog_facility = f; + log_type = REPMGR_SYSLOG; + } +#endif + } + +#ifdef HAVE_SYSLOG + + if (log_type == REPMGR_SYSLOG) + { + setlogmask(LOG_UPTO(log_level)); + openlog(ident, LOG_CONS | LOG_PID | LOG_NDELAY, syslog_facility); + + stderr_log_notice(_("Setup syslog (level: %s, facility: %s)\n"), level, facility); + } +#endif + + if (*opts->logfile) + { + FILE *fd; + + /* Check if we can write to the specified file before redirecting + * stderr - if freopen() fails, stderr output will vanish into + * the ether and the user won't know what's going on. + */ + + fd = fopen(opts->logfile, "a"); + if (fd == NULL) + { + stderr_log_error(_("Unable to open specified logfile '%s' for writing: %s\n"), opts->logfile, strerror(errno)); + stderr_log_error(_("Terminating\n")); + exit(ERR_BAD_CONFIG); + } + fclose(fd); + + stderr_log_notice(_("Redirecting logging output to '%s'\n"), opts->logfile); + fd = freopen(opts->logfile, "a", stderr); + + /* + * It's possible freopen() may still fail due to e.g. a race condition; + * as it's not feasible to restore stderr after a failed freopen(), + * we'll write to stdout as a last resort. + */ + if (fd == NULL) + { + printf(_("Unable to open specified logfile %s for writing: %s\n"), opts->logfile, strerror(errno)); + printf(_("Terminating\n")); + exit(ERR_BAD_CONFIG); + } + } + + return true; +} + + +bool +logger_shutdown(void) +{ +#ifdef HAVE_SYSLOG + if (log_type == REPMGR_SYSLOG) + closelog(); +#endif + + return true; +} + +/* + * Indicate whether extra-verbose logging is required. This will + * generate a lot of output, particularly debug logging, and should + * not be permanently enabled in production. + * + * NOTE: in previous repmgr versions, this option forced the log + * level to INFO. + */ +void +logger_set_verbose(void) +{ + verbose_logging = true; +} + + +/* + * Indicate whether some non-critical log messages can be omitted. + * Currently this includes warnings about irrelevant command line + * options and hints. + */ + +void logger_set_terse(void) +{ + terse_logging = true; +} + + +int +detect_log_level(const char *level) +{ + if (!strcmp(level, "DEBUG")) + return LOG_DEBUG; + if (!strcmp(level, "INFO")) + return LOG_INFO; + if (!strcmp(level, "NOTICE")) + return LOG_NOTICE; + if (!strcmp(level, "WARNING")) + return LOG_WARNING; + if (!strcmp(level, "ERROR")) + return LOG_ERROR; + if (!strcmp(level, "ALERT")) + return LOG_ALERT; + if (!strcmp(level, "CRIT")) + return LOG_CRIT; + if (!strcmp(level, "EMERG")) + return LOG_EMERG; + + return -1; +} + +static int +detect_log_facility(const char *facility) +{ + int local = 0; + + if (!strncmp(facility, "LOCAL", 5) && strlen(facility) == 6) + { + local = atoi(&facility[5]); + + switch (local) + { + case 0: + return LOG_LOCAL0; + break; + case 1: + return LOG_LOCAL1; + break; + case 2: + return LOG_LOCAL2; + break; + case 3: + return LOG_LOCAL3; + break; + case 4: + return LOG_LOCAL4; + break; + case 5: + return LOG_LOCAL5; + break; + case 6: + return LOG_LOCAL6; + break; + case 7: + return LOG_LOCAL7; + break; + } + + } + else if (!strcmp(facility, "USER")) + { + return LOG_USER; + } + else if (!strcmp(facility, "STDERR")) + { + return 0; + } + + return -1; +} diff --git a/log.h b/log.h new file mode 100644 index 00000000..02f95cb9 --- /dev/null +++ b/log.h @@ -0,0 +1,128 @@ +/* + * log.h + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#ifndef _REPMGR_LOG_H_ +#define _REPMGR_LOG_H_ + +#include "repmgr.h" + +#define REPMGR_SYSLOG 1 +#define REPMGR_STDERR 2 + +#define OM_COMMAND_LINE 1 +#define OM_DAEMON 2 + +extern void +stderr_log_with_level(const char *level_name, int level, const char *fmt,...) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERROR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + + +/* Standard error logging */ +#define stderr_log_debug(...) stderr_log_with_level("DEBUG", LOG_DEBUG, __VA_ARGS__) +#define stderr_log_info(...) stderr_log_with_level("INFO", LOG_INFO, __VA_ARGS__) +#define stderr_log_notice(...) stderr_log_with_level("NOTICE", LOG_NOTICE, __VA_ARGS__) +#define stderr_log_warning(...) stderr_log_with_level("WARNING", LOG_WARNING, __VA_ARGS__) +#define stderr_log_error(...) stderr_log_with_level("ERROR", LOG_ERROR, __VA_ARGS__) +#define stderr_log_crit(...) stderr_log_with_level("CRITICAL", LOG_CRIT, __VA_ARGS__) +#define stderr_log_alert(...) stderr_log_with_level("ALERT", LOG_ALERT, __VA_ARGS__) +#define stderr_log_emerg(...) stderr_log_with_level("EMERGENCY", LOG_EMERG, __VA_ARGS__) + +#ifdef HAVE_SYSLOG + +#include + +#define log_debug(...) \ + if (log_type == REPMGR_SYSLOG) \ + syslog(LOG_DEBUG, __VA_ARGS__); \ + else \ + stderr_log_debug(__VA_ARGS__); + +#define log_info(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_INFO, __VA_ARGS__); \ + else stderr_log_info(__VA_ARGS__); \ + } + +#define log_notice(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_NOTICE, __VA_ARGS__); \ + else stderr_log_notice(__VA_ARGS__); \ + } + +#define log_warning(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_WARNING, __VA_ARGS__); \ + else stderr_log_warning(__VA_ARGS__); \ + } + +#define log_error(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_ERROR, __VA_ARGS__); \ + else stderr_log_error(__VA_ARGS__); \ + } + +#define log_crit(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_CRIT, __VA_ARGS__); \ + else stderr_log_crit(__VA_ARGS__); \ + } + +#define log_alert(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_ALERT, __VA_ARGS__); \ + else stderr_log_alert(__VA_ARGS__); \ + } + +#define log_emerg(...) \ + { \ + if (log_type == REPMGR_SYSLOG) syslog(LOG_ALERT, __VA_ARGS__); \ + else stderr_log_alert(__VA_ARGS__); \ + } +#else + + +#define log_debug(...) stderr_log_debug(__VA_ARGS__) +#define log_info(...) stderr_log_info(__VA_ARGS__) +#define log_notice(...) stderr_log_notice(__VA_ARGS__) +#define log_warning(...) stderr_log_warning(__VA_ARGS__) +#define log_error(...) stderr_log_error(__VA_ARGS__) +#define log_crit(...) stderr_log_crit(__VA_ARGS__) +#define log_alert(...) stderr_log_alert(__VA_ARGS__) +#define log_emerg(...) stderr_log_emerg(__VA_ARGS__) +#endif + + +int detect_log_level(const char *level); + +/* Logger initialisation and shutdown */ + +bool logger_init(t_configuration_options * opts, const char *ident); + +bool logger_shutdown(void); + +void logger_set_verbose(void); +void logger_set_terse(void); + +void log_hint(const char *fmt, ...) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))); +void log_verbose(int level, const char *fmt, ...) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); + +extern int log_type; +extern int log_level; +extern int verbose_logging; +extern int terse_logging; +extern int logger_output_mode; + +#endif /* _REPMGR_LOG_H_ */ diff --git a/repmgr-client.c b/repmgr-client.c index 46bc34d1..7f29b2ae 100644 --- a/repmgr-client.c +++ b/repmgr-client.c @@ -14,15 +14,32 @@ /* global configuration structures */ t_runtime_options runtime_options = T_RUNTIME_OPTIONS_INITIALIZER; +/* Collate command line errors and warnings here for friendlier reporting */ +ItemList cli_errors = { NULL, NULL }; +ItemList cli_warnings = { NULL, NULL }; + int main(int argc, char **argv) { int optindex; int c, targ; + + char *repmgr_node_type = NULL; + char *repmgr_action = NULL; + bool valid_repmgr_node_type_found = true; int action = NO_ACTION; + char *dummy_action = ""; set_progname(argv[0]); + /* + * Tell the logger we're a command-line program - this will + * ensure any output logged before the logger is initialized + * will be formatted correctly + */ + logger_output_mode = OM_COMMAND_LINE; + + while ((c = getopt_long(argc, argv, "?Vd:h:p:U:S:D:f:R:w:k:FWIvb:rcL:tm:C:", long_options, &optindex)) != -1) { @@ -75,6 +92,93 @@ main(int argc, char **argv) progname()); exit(1); } + + /* Exit here already if errors in command line options found */ + if (cli_errors.head != NULL) + { + exit_with_errors(); + } + + /* + * Determine the node type and action; following are valid: + * + * { MASTER | PRIMARY } REGISTER | + * STANDBY {REGISTER | UNREGISTER | CLONE [node] | PROMOTE | FOLLOW [node] | SWITCHOVER | REWIND} | + * WITNESS { CREATE | REGISTER | UNREGISTER } | + * BDR { REGISTER | UNREGISTER } | + * CLUSTER { CROSSCHECK | MATRIX | SHOW | CLEANUP } + * + * [node] is an optional hostname, provided instead of the -h/--host optipn + */ + if (optind < argc) + { + repmgr_node_type = argv[optind++]; + } + + if (optind < argc) + { + repmgr_action = argv[optind++]; + } + else + { + repmgr_action = dummy_action; + } + + if (repmgr_node_type != NULL) + { + if (strcasecmp(repmgr_node_type, "MASTER") == 0 || strcasecmp(repmgr_node_type, "PRIMARY") == 0 ) + { + if (strcasecmp(repmgr_action, "REGISTER") == 0) + action = MASTER_REGISTER; + } + else + { + valid_repmgr_node_type_found = false; + } + } + + if (action == NO_ACTION) + { + PQExpBufferData command_error; + initPQExpBuffer(&command_error); + + if (repmgr_node_type == NULL) + { + appendPQExpBuffer(&command_error, + _("no repmgr command provided")); + } + else if (valid_repmgr_node_type_found == false && repmgr_action[0] == '\0') + { + appendPQExpBuffer(&command_error, + _("unknown repmgr node type '%s'"), + repmgr_node_type); + } + else if (repmgr_action[0] == '\0') + { + appendPQExpBuffer(&command_error, + _("no action provided for node type '%s'"), + repmgr_node_type); + } + else + { + appendPQExpBuffer(&command_error, + _("unknown repmgr action '%s %s'"), + repmgr_node_type, + repmgr_action); + } + + item_list_append(&cli_errors, command_error.data); + } + + /* + * Sanity checks for command line parameters completed by now; + * any further errors will be runtime ones + */ + if (cli_errors.head != NULL) + { + exit_with_errors(); + } + return SUCCESS; } @@ -83,7 +187,7 @@ static void do_help(void) { printf(_("%s: replication management tool for PostgreSQL\n"), progname()); - printf(_("\n")); + puts(""); /* add a big friendly warning if root is executing "repmgr --help" */ if (geteuid() == 0) @@ -93,6 +197,49 @@ do_help(void) printf(_(" **************************************************\n")); puts(""); } - printf(_("Usage:\n")); + printf(_("Usage:\n")); + printf(_(" %s [OPTIONS] master register\n"), progname()); + puts(""); + printf(_("General options:\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_(" -V, --version output version information, then exit\n")); + puts(""); + printf(_("General configuration options:\n")); + printf(_(" -f, --config-file=PATH path to the configuration file\n")); + puts(""); +} + +static void +exit_with_errors(void) +{ + fprintf(stderr, _("The following command line errors were encountered:\n")); + + print_error_list(&cli_errors, LOG_ERR); + + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname()); + + exit(ERR_BAD_CONFIG); +} + + +static void +print_error_list(ItemList *error_list, int log_level) +{ + ItemListCell *cell; + + for (cell = error_list->head; cell; cell = cell->next) + { + fprintf(stderr, " "); + switch(log_level) + { + /* Currently we only need errors and warnings */ + case LOG_ERROR: + log_error("%s", cell->string); + break; + case LOG_WARNING: + log_warning("%s", cell->string); + break; + } + } } diff --git a/repmgr-client.h b/repmgr-client.h index e377796e..05c84c57 100644 --- a/repmgr-client.h +++ b/repmgr-client.h @@ -3,13 +3,12 @@ * Copyright (c) 2ndQuadrant, 2010-2017 */ -#include - -#include "config.h" - #ifndef _REPMGR_CLIENT_H_ #define _REPMGR_CLIENT_H_ +#include +#include "log.h" + #ifndef RECOVERY_COMMAND_FILE #define RECOVERY_COMMAND_FILE "recovery.conf" #endif @@ -117,4 +116,7 @@ typedef struct static void do_help(void); +static void exit_with_errors(void); +static void print_error_list(ItemList *error_list, int log_level); + #endif diff --git a/repmgr.h b/repmgr.h index 9751b5a2..cd6fe811 100644 --- a/repmgr.h +++ b/repmgr.h @@ -13,11 +13,11 @@ #include #include "repmgr_version.h" +#include "errcode.h" #include "strutil.h" +#include "config.h" #define MIN_SUPPORTED_VERSION "9.3" #define MIN_SUPPORTED_VERSION_NUM 90300 -// to errcodes.h -#define SUCCESS 0 #endif diff --git a/strutil.h b/strutil.h index a2a07a6d..37de03e3 100644 --- a/strutil.h +++ b/strutil.h @@ -8,4 +8,5 @@ #define MAXLEN 1024 + #endif