diff options
Diffstat (limited to 'lib/common/logging.c')
-rw-r--r-- | lib/common/logging.c | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/lib/common/logging.c b/lib/common/logging.c new file mode 100644 index 0000000..dded873 --- /dev/null +++ b/lib/common/logging.c @@ -0,0 +1,1192 @@ +/* + * Copyright 2004-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <time.h> +#include <libgen.h> +#include <signal.h> +#include <bzlib.h> + +#include <qb/qbdefs.h> + +#include <crm/crm.h> +#include <crm/common/mainloop.h> + +// Use high-resolution (millisecond) timestamps if libqb supports them +#ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS +#define TIMESTAMP_FORMAT_SPEC "%%T" +typedef struct timespec *log_time_t; +#else +#define TIMESTAMP_FORMAT_SPEC "%%t" +typedef time_t log_time_t; +#endif + +unsigned int crm_log_level = LOG_INFO; +unsigned int crm_trace_nonlog = 0; +bool pcmk__is_daemon = false; +char *pcmk__our_nodename = NULL; + +static unsigned int crm_log_priority = LOG_NOTICE; +static GLogFunc glib_log_default = NULL; +static pcmk__output_t *logger_out = NULL; + +static gboolean crm_tracing_enabled(void); + +static void +crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message, + gpointer user_data) +{ + int log_level = LOG_WARNING; + GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK); + static struct qb_log_callsite *glib_cs = NULL; + + if (glib_cs == NULL) { + glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler", + LOG_DEBUG, __LINE__, crm_trace_nonlog); + } + + switch (msg_level) { + case G_LOG_LEVEL_CRITICAL: + log_level = LOG_CRIT; + + if (!crm_is_callsite_active(glib_cs, LOG_DEBUG, crm_trace_nonlog)) { + /* log and record how we got here */ + crm_abort(__FILE__, __func__, __LINE__, message, TRUE, TRUE); + } + break; + + case G_LOG_LEVEL_ERROR: + log_level = LOG_ERR; + break; + case G_LOG_LEVEL_MESSAGE: + log_level = LOG_NOTICE; + break; + case G_LOG_LEVEL_INFO: + log_level = LOG_INFO; + break; + case G_LOG_LEVEL_DEBUG: + log_level = LOG_DEBUG; + break; + + case G_LOG_LEVEL_WARNING: + case G_LOG_FLAG_RECURSION: + case G_LOG_FLAG_FATAL: + case G_LOG_LEVEL_MASK: + log_level = LOG_WARNING; + break; + } + + do_crm_log(log_level, "%s: %s", log_domain, message); +} + +#ifndef NAME_MAX +# define NAME_MAX 256 +#endif + +/*! + * \internal + * \brief Write out a blackbox (enabling blackboxes if needed) + * + * \param[in] nsig Signal number that was received + * + * \note This is a true signal handler, and so must be async-safe. + */ +static void +crm_trigger_blackbox(int nsig) +{ + if(nsig == SIGTRAP) { + /* Turn it on if it wasn't already */ + crm_enable_blackbox(nsig); + } + crm_write_blackbox(nsig, NULL); +} + +void +crm_log_deinit(void) +{ + if (glib_log_default != NULL) { + g_log_set_default_handler(glib_log_default, NULL); + } +} + +#define FMT_MAX 256 + +/*! + * \internal + * \brief Set the log format string based on the passed-in method + * + * \param[in] method The detail level of the log output + * \param[in] daemon The daemon ID included in error messages + * \param[in] use_pid Cached result of getpid() call, for efficiency + * \param[in] use_nodename Cached result of uname() call, for efficiency + * + */ + +/* XXX __attribute__((nonnull)) for use_nodename parameter */ +static void +set_format_string(int method, const char *daemon, pid_t use_pid, + const char *use_nodename) +{ + if (method == QB_LOG_SYSLOG) { + // The system log gets a simplified, user-friendly format + crm_extended_logging(method, QB_FALSE); + qb_log_format_set(method, "%g %p: %b"); + + } else { + // Everything else gets more detail, for advanced troubleshooting + + int offset = 0; + char fmt[FMT_MAX]; + + if (method > QB_LOG_STDERR) { + // If logging to file, prefix with timestamp, node name, daemon ID + offset += snprintf(fmt + offset, FMT_MAX - offset, + TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ", + use_nodename, daemon, (unsigned long) use_pid); + } + + // Add function name (in parentheses) + offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%n"); + if (crm_tracing_enabled()) { + // When tracing, add file and line number + offset += snprintf(fmt + offset, FMT_MAX - offset, "@%%f:%%l"); + } + offset += snprintf(fmt + offset, FMT_MAX - offset, ")"); + + // Add tag (if any), severity, and actual message + offset += snprintf(fmt + offset, FMT_MAX - offset, " %%g\t%%p: %%b"); + + CRM_LOG_ASSERT(offset > 0); + qb_log_format_set(method, fmt); + } +} + +#define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log" + +static bool +logfile_disabled(const char *filename) +{ + return pcmk__str_eq(filename, PCMK__VALUE_NONE, pcmk__str_casei) + || pcmk__str_eq(filename, "/dev/null", pcmk__str_none); +} + +/*! + * \internal + * \brief Fix log file ownership if group is wrong or doesn't have access + * + * \param[in] filename Log file name (for logging only) + * \param[in] logfd Log file descriptor + * + * \return Standard Pacemaker return code + */ +static int +chown_logfile(const char *filename, int logfd) +{ + uid_t pcmk_uid = 0; + gid_t pcmk_gid = 0; + struct stat st; + int rc; + + // Get the log file's current ownership and permissions + if (fstat(logfd, &st) < 0) { + return errno; + } + + // Any other errors don't prevent file from being used as log + + rc = pcmk_daemon_user(&pcmk_uid, &pcmk_gid); + if (rc != pcmk_ok) { + rc = pcmk_legacy2rc(rc); + crm_warn("Not changing '%s' ownership because user information " + "unavailable: %s", filename, pcmk_rc_str(rc)); + return pcmk_rc_ok; + } + if ((st.st_gid == pcmk_gid) + && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) { + return pcmk_rc_ok; + } + if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) { + crm_warn("Couldn't change '%s' ownership to user %s gid %d: %s", + filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno)); + } + return pcmk_rc_ok; +} + +// Reset log file permissions (using environment variable if set) +static void +chmod_logfile(const char *filename, int logfd) +{ + const char *modestr = getenv("PCMK_logfile_mode"); + mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + + if (modestr != NULL) { + long filemode_l = strtol(modestr, NULL, 8); + + if ((filemode_l != LONG_MIN) && (filemode_l != LONG_MAX)) { + filemode = (mode_t) filemode_l; + } + } + if ((filemode != 0) && (fchmod(logfd, filemode) < 0)) { + crm_warn("Couldn't change '%s' mode to %04o: %s", + filename, filemode, strerror(errno)); + } +} + +// If we're root, correct a log file's permissions if needed +static int +set_logfile_permissions(const char *filename, FILE *logfile) +{ + if (geteuid() == 0) { + int logfd = fileno(logfile); + int rc = chown_logfile(filename, logfd); + + if (rc != pcmk_rc_ok) { + return rc; + } + chmod_logfile(filename, logfd); + } + return pcmk_rc_ok; +} + +// Enable libqb logging to a new log file +static void +enable_logfile(int fd) +{ + qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE); +#if 0 + qb_log_ctl(fd, QB_LOG_CONF_FILE_SYNC, 1); // Turn on synchronous writes +#endif + +#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN + // Longer than default, for logging long XML lines + qb_log_ctl(fd, QB_LOG_CONF_MAX_LINE_LEN, 800); +#endif + + crm_update_callsites(); +} + +static inline void +disable_logfile(int fd) +{ + qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_FALSE); +} + +static void +setenv_logfile(const char *filename) +{ + // Some resource agents will log only if environment variable is set + if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) { + pcmk__set_env_option(PCMK__ENV_LOGFILE, filename); + } +} + +/*! + * \brief Add a file to be used as a Pacemaker detail log + * + * \param[in] filename Name of log file to use + * + * \return Standard Pacemaker return code + */ +int +pcmk__add_logfile(const char *filename) +{ + /* No log messages from this function will be logged to the new log! + * If another target such as syslog has already been added, the messages + * should show up there. + */ + + int fd = 0; + int rc = pcmk_rc_ok; + FILE *logfile = NULL; + bool is_default = false; + + static int default_fd = -1; + static bool have_logfile = false; + + // Use default if caller didn't specify (and we don't already have one) + if (filename == NULL) { + if (have_logfile) { + return pcmk_rc_ok; + } + filename = DEFAULT_LOG_FILE; + } + + // If the user doesn't want logging, we're done + if (logfile_disabled(filename)) { + return pcmk_rc_ok; + } + + // If the caller wants the default and we already have it, we're done + is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none); + if (is_default && (default_fd >= 0)) { + return pcmk_rc_ok; + } + + // Check whether we have write access to the file + logfile = fopen(filename, "a"); + if (logfile == NULL) { + rc = errno; + crm_warn("Logging to '%s' is disabled: %s " CRM_XS " uid=%u gid=%u", + filename, strerror(rc), geteuid(), getegid()); + return rc; + } + + rc = set_logfile_permissions(filename, logfile); + if (rc != pcmk_rc_ok) { + crm_warn("Logging to '%s' is disabled: %s " CRM_XS " permissions", + filename, strerror(rc)); + fclose(logfile); + return rc; + } + + // Close and reopen as libqb logging target + fclose(logfile); + fd = qb_log_file_open(filename); + if (fd < 0) { + crm_warn("Logging to '%s' is disabled: %s " CRM_XS " qb_log_file_open", + filename, strerror(-fd)); + return -fd; // == +errno + } + + if (is_default) { + default_fd = fd; + setenv_logfile(filename); + + } else if (default_fd >= 0) { + crm_notice("Switching logging to %s", filename); + disable_logfile(default_fd); + } + + crm_notice("Additional logging available in %s", filename); + enable_logfile(fd); + have_logfile = true; + return pcmk_rc_ok; +} + +/*! + * \brief Add multiple additional log files + * + * \param[in] log_files Array of log files to add + * \param[in] out Output object to use for error reporting + * + * \return Standard Pacemaker return code + */ +void +pcmk__add_logfiles(gchar **log_files, pcmk__output_t *out) +{ + if (log_files == NULL) { + return; + } + + for (gchar **fname = log_files; *fname != NULL; fname++) { + int rc = pcmk__add_logfile(*fname); + + if (rc != pcmk_rc_ok) { + out->err(out, "Logging to %s is disabled: %s", + *fname, pcmk_rc_str(rc)); + } + } +} + +static int blackbox_trigger = 0; +static volatile char *blackbox_file_prefix = NULL; + +static void +blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp, + const char *msg) +{ + if(cs && cs->priority < LOG_ERR) { + crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */ + } else { + crm_write_blackbox(0, cs); + } +} + +static void +crm_control_blackbox(int nsig, bool enable) +{ + int lpc = 0; + + if (blackbox_file_prefix == NULL) { + pid_t pid = getpid(); + + blackbox_file_prefix = crm_strdup_printf("%s/%s-%lu", + CRM_BLACKBOX_DIR, + crm_system_name, + (unsigned long) pid); + } + + if (enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024); /* Any size change drops existing entries */ + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); /* Setting the size seems to disable it */ + + /* Enable synchronous logging */ + for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) { + qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_TRUE); + } + + crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix); + + /* Save to disk on abnormal termination */ + crm_signal_handler(SIGSEGV, crm_trigger_blackbox); + crm_signal_handler(SIGABRT, crm_trigger_blackbox); + crm_signal_handler(SIGILL, crm_trigger_blackbox); + crm_signal_handler(SIGBUS, crm_trigger_blackbox); + crm_signal_handler(SIGFPE, crm_trigger_blackbox); + + crm_update_callsites(); + + blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL); + qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE); + crm_trace("Trigger: %d is %d %d", blackbox_trigger, + qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0), QB_LOG_STATE_ENABLED); + + crm_update_callsites(); + + } else if (!enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) { + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); + + /* Disable synchronous logging again when the blackbox is disabled */ + for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) { + qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_FALSE); + } + } +} + +void +crm_enable_blackbox(int nsig) +{ + crm_control_blackbox(nsig, TRUE); +} + +void +crm_disable_blackbox(int nsig) +{ + crm_control_blackbox(nsig, FALSE); +} + +/*! + * \internal + * \brief Write out a blackbox, if blackboxes are enabled + * + * \param[in] nsig Signal that was received + * \param[in] cs libqb callsite + * + * \note This may be called via a true signal handler and so must be async-safe. + * @TODO actually make this async-safe + */ +void +crm_write_blackbox(int nsig, const struct qb_log_callsite *cs) +{ + static volatile int counter = 1; + static volatile time_t last = 0; + + char buffer[NAME_MAX]; + time_t now = time(NULL); + + if (blackbox_file_prefix == NULL) { + return; + } + + switch (nsig) { + case 0: + case SIGTRAP: + /* The graceful case - such as assertion failure or user request */ + + if (nsig == 0 && now == last) { + /* Prevent over-dumping */ + return; + } + + snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++); + if (nsig == SIGTRAP) { + crm_notice("Blackbox dump requested, please see %s for contents", buffer); + + } else if (cs) { + syslog(LOG_NOTICE, + "Problem detected at %s:%d (%s), please see %s for additional details", + cs->function, cs->lineno, cs->filename, buffer); + } else { + crm_notice("Problem detected, please see %s for additional details", buffer); + } + + last = now; + qb_log_blackbox_write_to_file(buffer); + + /* Flush the existing contents + * A size change would also work + */ + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); + break; + + default: + /* Do as little as possible, just try to get what we have out + * We logged the filename when the blackbox was enabled + */ + crm_signal_handler(nsig, SIG_DFL); + qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix); + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); + raise(nsig); + break; + } +} + +static const char * +crm_quark_to_string(uint32_t tag) +{ + const char *text = g_quark_to_string(tag); + + if (text) { + return text; + } + return ""; +} + +static void +crm_log_filter_source(int source, const char *trace_files, const char *trace_fns, + const char *trace_fmts, const char *trace_tags, const char *trace_blackbox, + struct qb_log_callsite *cs) +{ + if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { + return; + } else if (cs->tags != crm_trace_nonlog && source == QB_LOG_BLACKBOX) { + /* Blackbox gets everything if enabled */ + qb_bit_set(cs->targets, source); + + } else if (source == blackbox_trigger && blackbox_trigger > 0) { + /* Should this log message result in the blackbox being dumped */ + if (cs->priority <= LOG_ERR) { + qb_bit_set(cs->targets, source); + + } else if (trace_blackbox) { + char *key = crm_strdup_printf("%s:%d", cs->function, cs->lineno); + + if (strstr(trace_blackbox, key) != NULL) { + qb_bit_set(cs->targets, source); + } + free(key); + } + + } else if (source == QB_LOG_SYSLOG) { /* No tracing to syslog */ + if (cs->priority <= crm_log_priority && cs->priority <= crm_log_level) { + qb_bit_set(cs->targets, source); + } + /* Log file tracing options... */ + } else if (cs->priority <= crm_log_level) { + qb_bit_set(cs->targets, source); + } else if (trace_files && strstr(trace_files, cs->filename) != NULL) { + qb_bit_set(cs->targets, source); + } else if (trace_fns && strstr(trace_fns, cs->function) != NULL) { + qb_bit_set(cs->targets, source); + } else if (trace_fmts && strstr(trace_fmts, cs->format) != NULL) { + qb_bit_set(cs->targets, source); + } else if (trace_tags + && cs->tags != 0 + && cs->tags != crm_trace_nonlog && g_quark_to_string(cs->tags) != NULL) { + qb_bit_set(cs->targets, source); + } +} + +static void +crm_log_filter(struct qb_log_callsite *cs) +{ + int lpc = 0; + static int need_init = 1; + static const char *trace_fns = NULL; + static const char *trace_tags = NULL; + static const char *trace_fmts = NULL; + static const char *trace_files = NULL; + static const char *trace_blackbox = NULL; + + if (need_init) { + need_init = 0; + trace_fns = getenv("PCMK_trace_functions"); + trace_fmts = getenv("PCMK_trace_formats"); + trace_tags = getenv("PCMK_trace_tags"); + trace_files = getenv("PCMK_trace_files"); + trace_blackbox = getenv("PCMK_trace_blackbox"); + + if (trace_tags != NULL) { + uint32_t tag; + char token[500]; + const char *offset = NULL; + const char *next = trace_tags; + + do { + offset = next; + next = strchrnul(offset, ','); + snprintf(token, sizeof(token), "%.*s", (int)(next - offset), offset); + + tag = g_quark_from_string(token); + crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags); + + if (next[0] != 0) { + next++; + } + + } while (next != NULL && next[0] != 0); + } + } + + cs->targets = 0; /* Reset then find targets to enable */ + for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) { + crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, trace_blackbox, + cs); + } +} + +gboolean +crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags) +{ + gboolean refilter = FALSE; + + if (cs == NULL) { + return FALSE; + } + + if (cs->priority != level) { + cs->priority = level; + refilter = TRUE; + } + + if (cs->tags != tags) { + cs->tags = tags; + refilter = TRUE; + } + + if (refilter) { + crm_log_filter(cs); + } + + if (cs->targets == 0) { + return FALSE; + } + return TRUE; +} + +void +crm_update_callsites(void) +{ + static gboolean log = TRUE; + + if (log) { + log = FALSE; + crm_debug + ("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s", + crm_log_level, getenv("PCMK_trace_files"), getenv("PCMK_trace_functions"), + getenv("PCMK_trace_formats"), getenv("PCMK_trace_tags")); + } + qb_log_filter_fn_set(crm_log_filter); +} + +static gboolean +crm_tracing_enabled(void) +{ + if (crm_log_level == LOG_TRACE) { + return TRUE; + } else if (getenv("PCMK_trace_files") || getenv("PCMK_trace_functions") + || getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) { + return TRUE; + } + return FALSE; +} + +static int +crm_priority2int(const char *name) +{ + struct syslog_names { + const char *name; + int priority; + }; + static struct syslog_names p_names[] = { + {"emerg", LOG_EMERG}, + {"alert", LOG_ALERT}, + {"crit", LOG_CRIT}, + {"error", LOG_ERR}, + {"warning", LOG_WARNING}, + {"notice", LOG_NOTICE}, + {"info", LOG_INFO}, + {"debug", LOG_DEBUG}, + {NULL, -1} + }; + int lpc; + + for (lpc = 0; name != NULL && p_names[lpc].name != NULL; lpc++) { + if (pcmk__str_eq(p_names[lpc].name, name, pcmk__str_none)) { + return p_names[lpc].priority; + } + } + return crm_log_priority; +} + + +/*! + * \internal + * \brief Set the identifier for the current process + * + * If the identifier crm_system_name is not already set, then it is set as follows: + * - it is passed to the function via the "entity" parameter, or + * - it is derived from the executable name + * + * The identifier can be used in logs, IPC, and more. + * + * This method also sets the PCMK_service environment variable. + * + * \param[in] entity If not NULL, will be assigned to the identifier + * \param[in] argc The number of command line parameters + * \param[in] argv The command line parameter values + */ +static void +set_identity(const char *entity, int argc, char *const *argv) +{ + if (crm_system_name != NULL) { + return; // Already set, don't overwrite + } + + if (entity != NULL) { + crm_system_name = strdup(entity); + + } else if ((argc > 0) && (argv != NULL)) { + char *mutable = strdup(argv[0]); + char *modified = basename(mutable); + + if (strstr(modified, "lt-") == modified) { + modified += 3; + } + crm_system_name = strdup(modified); + free(mutable); + + } else { + crm_system_name = strdup("Unknown"); + } + + CRM_ASSERT(crm_system_name != NULL); + + setenv("PCMK_service", crm_system_name, 1); +} + +void +crm_log_preinit(const char *entity, int argc, char *const *argv) +{ + /* Configure libqb logging with nothing turned on */ + + struct utsname res; + int lpc = 0; + int32_t qb_facility = 0; + pid_t pid = getpid(); + const char *nodename = "localhost"; + static bool have_logging = false; + + if (have_logging) { + return; + } + + have_logging = true; + + crm_xml_init(); /* Sets buffer allocation strategy */ + + if (crm_trace_nonlog == 0) { + crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint"); + } + + umask(S_IWGRP | S_IWOTH | S_IROTH); + + /* Redirect messages from glib functions to our handler */ + glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL); + + /* and for good measure... - this enum is a bit field (!) */ + g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */ + + /* Set crm_system_name, which is used as the logging name. It may also + * be used for other purposes such as an IPC client name. + */ + set_identity(entity, argc, argv); + + qb_facility = qb_log_facility2int("local0"); + qb_log_init(crm_system_name, qb_facility, LOG_ERR); + crm_log_level = LOG_CRIT; + + /* Nuke any syslog activity until it's asked for */ + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); +#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN + // Shorter than default, generous for what we *should* send to syslog + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256); +#endif + if (uname(memset(&res, 0, sizeof(res))) == 0 && *res.nodename != '\0') { + nodename = res.nodename; + } + + /* Set format strings and disable threading + * Pacemaker and threads do not mix well (due to the amount of forking) + */ + qb_log_tags_stringify_fn_set(crm_quark_to_string); + for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) { + qb_log_ctl(lpc, QB_LOG_CONF_THREADED, QB_FALSE); +#ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS + // End truncated lines with '...' + qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE); +#endif + set_format_string(lpc, crm_system_name, pid, nodename); + } + +#ifdef ENABLE_NLS + /* Enable translations (experimental). Currently we only have a few + * proof-of-concept translations for some option help. The goal would be to + * offer translations for option help and man pages rather than logs or + * documentation, to reduce the burden of maintaining them. + */ + + // Load locale information for the local host from the environment + setlocale(LC_ALL, ""); + + // Tell gettext where to find Pacemaker message catalogs + CRM_ASSERT(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL); + + // Tell gettext to use the Pacemaker message catalogs + CRM_ASSERT(textdomain(PACKAGE) != NULL); + + // Tell gettext that the translated strings are stored in UTF-8 + bind_textdomain_codeset(PACKAGE, "UTF-8"); +#endif +} + +gboolean +crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr, + int argc, char **argv, gboolean quiet) +{ + const char *syslog_priority = NULL; + const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY); + const char *f_copy = facility; + + pcmk__is_daemon = daemon; + crm_log_preinit(entity, argc, argv); + + if (level > LOG_TRACE) { + level = LOG_TRACE; + } + if(level > crm_log_level) { + crm_log_level = level; + } + + /* Should we log to syslog */ + if (facility == NULL) { + if (pcmk__is_daemon) { + facility = "daemon"; + } else { + facility = PCMK__VALUE_NONE; + } + pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility); + } + + if (pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei)) { + quiet = TRUE; + + + } else { + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility)); + } + + if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) { + /* Override the default setting */ + crm_log_level = LOG_DEBUG; + } + + /* What lower threshold do we have for sending to syslog */ + syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY); + if (syslog_priority) { + crm_log_priority = crm_priority2int(syslog_priority); + } + qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", + crm_log_priority); + + // Log to syslog unless requested to be quiet + if (!quiet) { + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE); + } + + /* Should we log to stderr */ + if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) { + /* Override the default setting */ + to_stderr = TRUE; + } + crm_enable_stderr(to_stderr); + + // Log to a file if we're a daemon or user asked for one + { + const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE); + + if (!pcmk__str_eq(PCMK__VALUE_NONE, logfile, pcmk__str_casei) + && (pcmk__is_daemon || (logfile != NULL))) { + // Daemons always get a log file, unless explicitly set to "none" + pcmk__add_logfile(logfile); + } + } + + if (pcmk__is_daemon + && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) { + crm_enable_blackbox(0); + } + + /* Summary */ + crm_trace("Quiet: %d, facility %s", quiet, f_copy); + pcmk__env_option(PCMK__ENV_LOGFILE); + pcmk__env_option(PCMK__ENV_LOGFACILITY); + + crm_update_callsites(); + + /* Ok, now we can start logging... */ + + // Disable daemon request if user isn't root or Pacemaker daemon user + if (pcmk__is_daemon) { + const char *user = getenv("USER"); + + if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) { + crm_trace("Not switching to corefile directory for %s", user); + pcmk__is_daemon = false; + } + } + + if (pcmk__is_daemon) { + int user = getuid(); + struct passwd *pwent = getpwuid(user); + + if (pwent == NULL) { + crm_perror(LOG_ERR, "Cannot get name for uid: %d", user); + + } else if (!pcmk__strcase_any_of(pwent->pw_name, "root", CRM_DAEMON_USER, NULL)) { + crm_trace("Don't change active directory for regular user: %s", pwent->pw_name); + + } else if (chdir(CRM_CORE_DIR) < 0) { + crm_perror(LOG_INFO, "Cannot change active directory to " CRM_CORE_DIR); + + } else { + crm_info("Changed active directory to " CRM_CORE_DIR); + } + + /* Original meanings from signal(7) + * + * Signal Value Action Comment + * SIGTRAP 5 Core Trace/breakpoint trap + * SIGUSR1 30,10,16 Term User-defined signal 1 + * SIGUSR2 31,12,17 Term User-defined signal 2 + * + * Our usage is as similar as possible + */ + mainloop_add_signal(SIGUSR1, crm_enable_blackbox); + mainloop_add_signal(SIGUSR2, crm_disable_blackbox); + mainloop_add_signal(SIGTRAP, crm_trigger_blackbox); + + } else if (!quiet) { + crm_log_args(argc, argv); + } + + return TRUE; +} + +/* returns the old value */ +unsigned int +set_crm_log_level(unsigned int level) +{ + unsigned int old = crm_log_level; + + if (level > LOG_TRACE) { + level = LOG_TRACE; + } + crm_log_level = level; + crm_update_callsites(); + crm_trace("New log level: %d", level); + return old; +} + +void +crm_enable_stderr(int enable) +{ + if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { + qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); + crm_update_callsites(); + + } else if (enable == FALSE) { + qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE); + } +} + +/*! + * \brief Make logging more verbose + * + * If logging to stderr is not already enabled when this function is called, + * enable it. Otherwise, increase the log level by 1. + * + * \param[in] argc Ignored + * \param[in] argv Ignored + */ +void +crm_bump_log_level(int argc, char **argv) +{ + if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) + != QB_LOG_STATE_ENABLED) { + crm_enable_stderr(TRUE); + } else { + set_crm_log_level(crm_log_level + 1); + } +} + +unsigned int +get_crm_log_level(void) +{ + return crm_log_level; +} + +/*! + * \brief Log the command line (once) + * + * \param[in] Number of values in \p argv + * \param[in] Command-line arguments (including command name) + * + * \note This function will only log once, even if called with different + * arguments. + */ +void +crm_log_args(int argc, char **argv) +{ + static bool logged = false; + gchar *arg_string = NULL; + + if ((argc == 0) || (argv == NULL) || logged) { + return; + } + logged = true; + arg_string = g_strjoinv(" ", argv); + crm_notice("Invoked: %s", arg_string); + g_free(arg_string); +} + +void +crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix, + const char *output) +{ + const char *next = NULL; + const char *offset = NULL; + + if (level == LOG_NEVER) { + return; + } + + if (output == NULL) { + if (level != LOG_STDOUT) { + level = LOG_TRACE; + } + output = "-- empty --"; + } + + next = output; + do { + offset = next; + next = strchrnul(offset, '\n'); + do_crm_log_alias(level, file, function, line, "%s [ %.*s ]", prefix, + (int)(next - offset), offset); + if (next[0] != 0) { + next++; + } + + } while (next != NULL && next[0] != 0); +} + +void +pcmk__cli_init_logging(const char *name, unsigned int verbosity) +{ + crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE); + + for (int i = 0; i < verbosity; i++) { + /* These arguments are ignored, so pass placeholders. */ + crm_bump_log_level(0, NULL); + } +} + +/*! + * \brief Log XML line-by-line in a formatted fashion + * + * \param[in] level Priority at which to log the messages + * \param[in] text Prefix for each line + * \param[in] xml XML to log + * + * \note This does nothing when \p level is \p LOG_STDOUT. + * \note Do not call this function directly. It should be called only from the + * \p do_crm_log_xml() macro. + */ +void +pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml) +{ + if (xml == NULL) { + do_crm_log(level, "%s%sNo data to dump as XML", + pcmk__s(text, ""), pcmk__str_empty(text)? "" : " "); + + } else { + if (logger_out == NULL) { + CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return); + } + + pcmk__output_set_log_level(logger_out, level); + pcmk__xml_show(logger_out, text, xml, 1, + pcmk__xml_fmt_pretty + |pcmk__xml_fmt_open + |pcmk__xml_fmt_children + |pcmk__xml_fmt_close); + } +} + +/*! + * \internal + * \brief Free the logging library's internal log output object + */ +void +pcmk__free_common_logger(void) +{ + if (logger_out != NULL) { + logger_out->finish(logger_out, CRM_EX_OK, true, NULL); + pcmk__output_free(logger_out); + logger_out = NULL; + } +} + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +#include <crm/common/logging_compat.h> + +gboolean +crm_log_cli_init(const char *entity) +{ + pcmk__cli_init_logging(entity, 0); + return TRUE; +} + +gboolean +crm_add_logfile(const char *filename) +{ + return pcmk__add_logfile(filename) == pcmk_rc_ok; +} + +// LCOV_EXCL_STOP +// End deprecated API |