Add logging and repmgr command sanity checks

This commit is contained in:
Ian Barwick
2017-04-20 00:32:04 +09:00
parent 76e8c4624e
commit 8968cfe735
10 changed files with 745 additions and 11 deletions

View File

@@ -26,7 +26,7 @@ include Makefile.global
$(info Building against PostgreSQL $(MAJORVERSION)) $(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 REPMGRD_OBJS = repmgrd.o
repmgr4: $(REPMGR_CLIENT_OBJS) repmgr4: $(REPMGR_CLIENT_OBJS)

View File

@@ -22,3 +22,31 @@ progname(void)
{ {
return _progname; 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;
}

View File

@@ -8,7 +8,34 @@
#ifndef _REPMGR_CONFIG_H_ #ifndef _REPMGR_CONFIG_H_
#define _REPMGR_CONFIG_H_ #define _REPMGR_CONFIG_H_
void set_progname(const char *argv0); typedef struct
const char * progname(void); {
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 #endif

31
errcode.h Normal file
View File

@@ -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_ */

370
log.c Normal file
View File

@@ -0,0 +1,370 @@
/*
* log.c - Logging methods
* Copyright (c) 2ndQuadrant, 2010-2017
*/
#include "repmgr.h"
#include <stdlib.h>
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
#include <stdarg.h>
#include <time.h>
#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;
}

128
log.h Normal file
View File

@@ -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 <syslog.h>
#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_ */

View File

@@ -14,15 +14,32 @@
/* global configuration structures */ /* global configuration structures */
t_runtime_options runtime_options = T_RUNTIME_OPTIONS_INITIALIZER; 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 int
main(int argc, char **argv) main(int argc, char **argv)
{ {
int optindex; int optindex;
int c, targ; int c, targ;
char *repmgr_node_type = NULL;
char *repmgr_action = NULL;
bool valid_repmgr_node_type_found = true;
int action = NO_ACTION; int action = NO_ACTION;
char *dummy_action = "";
set_progname(argv[0]); 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, while ((c = getopt_long(argc, argv, "?Vd:h:p:U:S:D:f:R:w:k:FWIvb:rcL:tm:C:", long_options,
&optindex)) != -1) &optindex)) != -1)
{ {
@@ -75,6 +92,93 @@ main(int argc, char **argv)
progname()); progname());
exit(1); 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; return SUCCESS;
} }
@@ -83,7 +187,7 @@ static void
do_help(void) do_help(void)
{ {
printf(_("%s: replication management tool for PostgreSQL\n"), progname()); printf(_("%s: replication management tool for PostgreSQL\n"), progname());
printf(_("\n")); puts("");
/* add a big friendly warning if root is executing "repmgr --help" */ /* add a big friendly warning if root is executing "repmgr --help" */
if (geteuid() == 0) if (geteuid() == 0)
@@ -93,6 +197,49 @@ do_help(void)
printf(_(" **************************************************\n")); printf(_(" **************************************************\n"));
puts(""); 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;
}
}
} }

View File

@@ -3,13 +3,12 @@
* Copyright (c) 2ndQuadrant, 2010-2017 * Copyright (c) 2ndQuadrant, 2010-2017
*/ */
#include <getopt_long.h>
#include "config.h"
#ifndef _REPMGR_CLIENT_H_ #ifndef _REPMGR_CLIENT_H_
#define _REPMGR_CLIENT_H_ #define _REPMGR_CLIENT_H_
#include <getopt_long.h>
#include "log.h"
#ifndef RECOVERY_COMMAND_FILE #ifndef RECOVERY_COMMAND_FILE
#define RECOVERY_COMMAND_FILE "recovery.conf" #define RECOVERY_COMMAND_FILE "recovery.conf"
#endif #endif
@@ -117,4 +116,7 @@ typedef struct
static void do_help(void); static void do_help(void);
static void exit_with_errors(void);
static void print_error_list(ItemList *error_list, int log_level);
#endif #endif

View File

@@ -13,11 +13,11 @@
#include <pqexpbuffer.h> #include <pqexpbuffer.h>
#include "repmgr_version.h" #include "repmgr_version.h"
#include "errcode.h"
#include "strutil.h" #include "strutil.h"
#include "config.h"
#define MIN_SUPPORTED_VERSION "9.3" #define MIN_SUPPORTED_VERSION "9.3"
#define MIN_SUPPORTED_VERSION_NUM 90300 #define MIN_SUPPORTED_VERSION_NUM 90300
// to errcodes.h
#define SUCCESS 0
#endif #endif

View File

@@ -8,4 +8,5 @@
#define MAXLEN 1024 #define MAXLEN 1024
#endif #endif