root/lib/common/logging.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. crm_glib_handler
  2. crm_trigger_blackbox
  3. crm_log_deinit
  4. set_format_string
  5. logfile_disabled
  6. chown_logfile
  7. chmod_logfile
  8. set_logfile_permissions
  9. enable_logfile
  10. disable_logfile
  11. setenv_logfile
  12. pcmk__add_logfile
  13. pcmk__add_logfiles
  14. blackbox_logger
  15. crm_control_blackbox
  16. crm_enable_blackbox
  17. crm_disable_blackbox
  18. crm_write_blackbox
  19. crm_quark_to_string
  20. crm_log_filter_source
  21. strchrnul
  22. crm_log_filter
  23. crm_is_callsite_active
  24. crm_update_callsites
  25. crm_tracing_enabled
  26. crm_priority2int
  27. set_identity
  28. crm_log_preinit
  29. crm_log_init
  30. set_crm_log_level
  31. crm_enable_stderr
  32. crm_bump_log_level
  33. get_crm_log_level
  34. crm_log_args
  35. crm_log_output_fn
  36. pcmk__cli_init_logging
  37. pcmk_log_xml_as
  38. pcmk__log_xml_changes_as
  39. pcmk__log_xml_patchset_as
  40. pcmk__free_common_logger
  41. crm_log_cli_init
  42. crm_add_logfile
  43. pcmk_log_xml_impl
  44. pcmk__set_config_error_handler
  45. pcmk__set_config_warning_handler

   1 /*
   2  * Copyright 2004-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <sys/param.h>
  13 #include <sys/types.h>
  14 #include <sys/wait.h>
  15 #include <sys/stat.h>
  16 #include <sys/utsname.h>
  17 
  18 #include <stdio.h>
  19 #include <unistd.h>
  20 #include <string.h>
  21 #include <stdlib.h>
  22 #include <limits.h>
  23 #include <ctype.h>
  24 #include <pwd.h>
  25 #include <grp.h>
  26 #include <time.h>
  27 #include <libgen.h>
  28 #include <signal.h>
  29 #include <bzlib.h>
  30 
  31 #include <qb/qbdefs.h>
  32 
  33 #include <crm/crm.h>
  34 #include <crm/common/mainloop.h>
  35 
  36 // Use high-resolution (millisecond) timestamps if libqb supports them
  37 #ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS
  38 #define TIMESTAMP_FORMAT_SPEC "%%T"
  39 typedef struct timespec *log_time_t;
  40 #else
  41 #define TIMESTAMP_FORMAT_SPEC "%%t"
  42 typedef time_t log_time_t;
  43 #endif
  44 
  45 unsigned int crm_log_level = LOG_INFO;
  46 unsigned int crm_trace_nonlog = 0;
  47 bool pcmk__is_daemon = false;
  48 char *pcmk__our_nodename = NULL;
  49 
  50 static unsigned int crm_log_priority = LOG_NOTICE;
  51 static GLogFunc glib_log_default = NULL;
  52 static pcmk__output_t *logger_out = NULL;
  53 
  54 pcmk__config_error_func pcmk__config_error_handler = NULL;
  55 pcmk__config_warning_func pcmk__config_warning_handler = NULL;
  56 void *pcmk__config_error_context = NULL;
  57 void *pcmk__config_warning_context = NULL;
  58 
  59 static gboolean crm_tracing_enabled(void);
  60 
  61 static void
  62 crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message,
     /* [previous][next][first][last][top][bottom][index][help] */
  63                  gpointer user_data)
  64 {
  65     int log_level = LOG_WARNING;
  66     GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK);
  67     static struct qb_log_callsite *glib_cs = NULL;
  68 
  69     if (glib_cs == NULL) {
  70         glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler",
  71                                       LOG_DEBUG, __LINE__, crm_trace_nonlog);
  72     }
  73 
  74     switch (msg_level) {
  75         case G_LOG_LEVEL_CRITICAL:
  76             log_level = LOG_CRIT;
  77 
  78             if (!crm_is_callsite_active(glib_cs, LOG_DEBUG, crm_trace_nonlog)) {
  79                 /* log and record how we got here */
  80                 crm_abort(__FILE__, __func__, __LINE__, message, TRUE, TRUE);
  81             }
  82             break;
  83 
  84         case G_LOG_LEVEL_ERROR:
  85             log_level = LOG_ERR;
  86             break;
  87         case G_LOG_LEVEL_MESSAGE:
  88             log_level = LOG_NOTICE;
  89             break;
  90         case G_LOG_LEVEL_INFO:
  91             log_level = LOG_INFO;
  92             break;
  93         case G_LOG_LEVEL_DEBUG:
  94             log_level = LOG_DEBUG;
  95             break;
  96 
  97         case G_LOG_LEVEL_WARNING:
  98         case G_LOG_FLAG_RECURSION:
  99         case G_LOG_FLAG_FATAL:
 100         case G_LOG_LEVEL_MASK:
 101             log_level = LOG_WARNING;
 102             break;
 103     }
 104 
 105     do_crm_log(log_level, "%s: %s", log_domain, message);
 106 }
 107 
 108 #ifndef NAME_MAX
 109 #  define NAME_MAX 256
 110 #endif
 111 
 112 /*!
 113  * \internal
 114  * \brief Write out a blackbox (enabling blackboxes if needed)
 115  *
 116  * \param[in] nsig  Signal number that was received
 117  *
 118  * \note This is a true signal handler, and so must be async-safe.
 119  */
 120 static void
 121 crm_trigger_blackbox(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 122 {
 123     if(nsig == SIGTRAP) {
 124         /* Turn it on if it wasn't already */
 125         crm_enable_blackbox(nsig);
 126     }
 127     crm_write_blackbox(nsig, NULL);
 128 }
 129 
 130 void
 131 crm_log_deinit(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 132 {
 133     if (glib_log_default != NULL) {
 134         g_log_set_default_handler(glib_log_default, NULL);
 135     }
 136 }
 137 
 138 #define FMT_MAX 256
 139 
 140 /*!
 141  * \internal
 142  * \brief Set the log format string based on the passed-in method
 143  *
 144  * \param[in] method        The detail level of the log output
 145  * \param[in] daemon        The daemon ID included in error messages
 146  * \param[in] use_pid       Cached result of getpid() call, for efficiency
 147  * \param[in] use_nodename  Cached result of uname() call, for efficiency
 148  *
 149  */
 150 
 151 /* XXX __attribute__((nonnull)) for use_nodename parameter */
 152 static void
 153 set_format_string(int method, const char *daemon, pid_t use_pid,
     /* [previous][next][first][last][top][bottom][index][help] */
 154                   const char *use_nodename)
 155 {
 156     if (method == QB_LOG_SYSLOG) {
 157         // The system log gets a simplified, user-friendly format
 158         crm_extended_logging(method, QB_FALSE);
 159         qb_log_format_set(method, "%g %p: %b");
 160 
 161     } else {
 162         // Everything else gets more detail, for advanced troubleshooting
 163 
 164         int offset = 0;
 165         char fmt[FMT_MAX];
 166 
 167         if (method > QB_LOG_STDERR) {
 168             // If logging to file, prefix with timestamp, node name, daemon ID
 169             offset += snprintf(fmt + offset, FMT_MAX - offset,
 170                                TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ",
 171                                 use_nodename, daemon, (unsigned long) use_pid);
 172         }
 173 
 174         // Add function name (in parentheses)
 175         offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%n");
 176         if (crm_tracing_enabled()) {
 177             // When tracing, add file and line number
 178             offset += snprintf(fmt + offset, FMT_MAX - offset, "@%%f:%%l");
 179         }
 180         offset += snprintf(fmt + offset, FMT_MAX - offset, ")");
 181 
 182         // Add tag (if any), severity, and actual message
 183         offset += snprintf(fmt + offset, FMT_MAX - offset, " %%g\t%%p: %%b");
 184 
 185         CRM_LOG_ASSERT(offset > 0);
 186         qb_log_format_set(method, fmt);
 187     }
 188 }
 189 
 190 #define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log"
 191 
 192 static bool
 193 logfile_disabled(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 194 {
 195     return pcmk__str_eq(filename, PCMK__VALUE_NONE, pcmk__str_casei)
 196            || pcmk__str_eq(filename, "/dev/null", pcmk__str_none);
 197 }
 198 
 199 /*!
 200  * \internal
 201  * \brief Fix log file ownership if group is wrong or doesn't have access
 202  *
 203  * \param[in] filename  Log file name (for logging only)
 204  * \param[in] logfd     Log file descriptor
 205  *
 206  * \return Standard Pacemaker return code
 207  */
 208 static int
 209 chown_logfile(const char *filename, int logfd)
     /* [previous][next][first][last][top][bottom][index][help] */
 210 {
 211     uid_t pcmk_uid = 0;
 212     gid_t pcmk_gid = 0;
 213     struct stat st;
 214     int rc;
 215 
 216     // Get the log file's current ownership and permissions
 217     if (fstat(logfd, &st) < 0) {
 218         return errno;
 219     }
 220 
 221     // Any other errors don't prevent file from being used as log
 222 
 223     rc = pcmk_daemon_user(&pcmk_uid, &pcmk_gid);
 224     if (rc != pcmk_ok) {
 225         rc = pcmk_legacy2rc(rc);
 226         crm_warn("Not changing '%s' ownership because user information "
 227                  "unavailable: %s", filename, pcmk_rc_str(rc));
 228         return pcmk_rc_ok;
 229     }
 230     if ((st.st_gid == pcmk_gid)
 231         && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) {
 232         return pcmk_rc_ok;
 233     }
 234     if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) {
 235         crm_warn("Couldn't change '%s' ownership to user %s gid %d: %s",
 236              filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno));
 237     }
 238     return pcmk_rc_ok;
 239 }
 240 
 241 // Reset log file permissions (using environment variable if set)
 242 static void
 243 chmod_logfile(const char *filename, int logfd)
     /* [previous][next][first][last][top][bottom][index][help] */
 244 {
 245     const char *modestr = pcmk__env_option(PCMK__ENV_LOGFILE_MODE);
 246     mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
 247 
 248     if (modestr != NULL) {
 249         long filemode_l = strtol(modestr, NULL, 8);
 250 
 251         if ((filemode_l != LONG_MIN) && (filemode_l != LONG_MAX)) {
 252             filemode = (mode_t) filemode_l;
 253         }
 254     }
 255     if ((filemode != 0) && (fchmod(logfd, filemode) < 0)) {
 256         crm_warn("Couldn't change '%s' mode to %04o: %s",
 257                  filename, filemode, strerror(errno));
 258     }
 259 }
 260 
 261 // If we're root, correct a log file's permissions if needed
 262 static int
 263 set_logfile_permissions(const char *filename, FILE *logfile)
     /* [previous][next][first][last][top][bottom][index][help] */
 264 {
 265     if (geteuid() == 0) {
 266         int logfd = fileno(logfile);
 267         int rc = chown_logfile(filename, logfd);
 268 
 269         if (rc != pcmk_rc_ok) {
 270             return rc;
 271         }
 272         chmod_logfile(filename, logfd);
 273     }
 274     return pcmk_rc_ok;
 275 }
 276 
 277 // Enable libqb logging to a new log file
 278 static void
 279 enable_logfile(int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 280 {
 281     qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE);
 282 #if 0
 283     qb_log_ctl(fd, QB_LOG_CONF_FILE_SYNC, 1); // Turn on synchronous writes
 284 #endif
 285 
 286 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
 287     // Longer than default, for logging long XML lines
 288     qb_log_ctl(fd, QB_LOG_CONF_MAX_LINE_LEN, 800);
 289 #endif
 290 
 291     crm_update_callsites();
 292 }
 293 
 294 static inline void
 295 disable_logfile(int fd)
     /* [previous][next][first][last][top][bottom][index][help] */
 296 {
 297     qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_FALSE);
 298 }
 299 
 300 static void
 301 setenv_logfile(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 302 {
 303     // Some resource agents will log only if environment variable is set
 304     if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
 305         pcmk__set_env_option(PCMK__ENV_LOGFILE, filename, true);
 306     }
 307 }
 308 
 309 /*!
 310  * \brief Add a file to be used as a Pacemaker detail log
 311  *
 312  * \param[in] filename  Name of log file to use
 313  *
 314  * \return Standard Pacemaker return code
 315  */
 316 int
 317 pcmk__add_logfile(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 318 {
 319     /* No log messages from this function will be logged to the new log!
 320      * If another target such as syslog has already been added, the messages
 321      * should show up there.
 322      */
 323 
 324     int fd = 0;
 325     int rc = pcmk_rc_ok;
 326     FILE *logfile = NULL;
 327     bool is_default = false;
 328 
 329     static int default_fd = -1;
 330     static bool have_logfile = false;
 331 
 332     // Use default if caller didn't specify (and we don't already have one)
 333     if (filename == NULL) {
 334         if (have_logfile) {
 335             return pcmk_rc_ok;
 336         }
 337         filename = DEFAULT_LOG_FILE;
 338     }
 339 
 340     // If the user doesn't want logging, we're done
 341     if (logfile_disabled(filename)) {
 342         return pcmk_rc_ok;
 343     }
 344 
 345     // If the caller wants the default and we already have it, we're done
 346     is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none);
 347     if (is_default && (default_fd >= 0)) {
 348         return pcmk_rc_ok;
 349     }
 350 
 351     // Check whether we have write access to the file
 352     logfile = fopen(filename, "a");
 353     if (logfile == NULL) {
 354         rc = errno;
 355         crm_warn("Logging to '%s' is disabled: %s " CRM_XS " uid=%u gid=%u",
 356                  filename, strerror(rc), geteuid(), getegid());
 357         return rc;
 358     }
 359 
 360     rc = set_logfile_permissions(filename, logfile);
 361     if (rc != pcmk_rc_ok) {
 362         crm_warn("Logging to '%s' is disabled: %s " CRM_XS " permissions",
 363                  filename, strerror(rc));
 364         fclose(logfile);
 365         return rc;
 366     }
 367 
 368     // Close and reopen as libqb logging target
 369     fclose(logfile);
 370     fd = qb_log_file_open(filename);
 371     if (fd < 0) {
 372         crm_warn("Logging to '%s' is disabled: %s " CRM_XS " qb_log_file_open",
 373                  filename, strerror(-fd));
 374         return -fd; // == +errno
 375     }
 376 
 377     if (is_default) {
 378         default_fd = fd;
 379         setenv_logfile(filename);
 380 
 381     } else if (default_fd >= 0) {
 382         crm_notice("Switching logging to %s", filename);
 383         disable_logfile(default_fd);
 384     }
 385 
 386     crm_notice("Additional logging available in %s", filename);
 387     enable_logfile(fd);
 388     have_logfile = true;
 389     return pcmk_rc_ok;
 390 }
 391 
 392 /*!
 393  * \brief Add multiple additional log files
 394  *
 395  * \param[in] log_files  Array of log files to add
 396  * \param[in] out        Output object to use for error reporting
 397  *
 398  * \return Standard Pacemaker return code
 399  */
 400 void
 401 pcmk__add_logfiles(gchar **log_files, pcmk__output_t *out)
     /* [previous][next][first][last][top][bottom][index][help] */
 402 {
 403     if (log_files == NULL) {
 404         return;
 405     }
 406 
 407     for (gchar **fname = log_files; *fname != NULL; fname++) {
 408         int rc = pcmk__add_logfile(*fname);
 409 
 410         if (rc != pcmk_rc_ok) {
 411             out->err(out, "Logging to %s is disabled: %s",
 412                      *fname, pcmk_rc_str(rc));
 413         }
 414     }
 415 }
 416 
 417 static int blackbox_trigger = 0;
 418 static volatile char *blackbox_file_prefix = NULL;
 419 
 420 static void
 421 blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp,
     /* [previous][next][first][last][top][bottom][index][help] */
 422                 const char *msg)
 423 {
 424     if(cs && cs->priority < LOG_ERR) {
 425         crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */
 426     } else {
 427         crm_write_blackbox(0, cs);
 428     }
 429 }
 430 
 431 static void
 432 crm_control_blackbox(int nsig, bool enable)
     /* [previous][next][first][last][top][bottom][index][help] */
 433 {
 434     int lpc = 0;
 435 
 436     if (blackbox_file_prefix == NULL) {
 437         pid_t pid = getpid();
 438 
 439         blackbox_file_prefix = crm_strdup_printf("%s/%s-%lu",
 440                                                  CRM_BLACKBOX_DIR,
 441                                                  crm_system_name,
 442                                                  (unsigned long) pid);
 443     }
 444 
 445     if (enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
 446         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024); /* Any size change drops existing entries */
 447         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);      /* Setting the size seems to disable it */
 448 
 449         /* Enable synchronous logging */
 450         for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) {
 451             qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
 452         }
 453 
 454         crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
 455 
 456         /* Save to disk on abnormal termination */
 457         crm_signal_handler(SIGSEGV, crm_trigger_blackbox);
 458         crm_signal_handler(SIGABRT, crm_trigger_blackbox);
 459         crm_signal_handler(SIGILL,  crm_trigger_blackbox);
 460         crm_signal_handler(SIGBUS,  crm_trigger_blackbox);
 461         crm_signal_handler(SIGFPE,  crm_trigger_blackbox);
 462 
 463         crm_update_callsites();
 464 
 465         blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL);
 466         qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE);
 467         crm_trace("Trigger: %d is %d %d", blackbox_trigger,
 468                   qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0), QB_LOG_STATE_ENABLED);
 469 
 470         crm_update_callsites();
 471 
 472     } else if (!enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) {
 473         qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
 474 
 475         /* Disable synchronous logging again when the blackbox is disabled */
 476         for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) {
 477             qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_FALSE);
 478         }
 479     }
 480 }
 481 
 482 void
 483 crm_enable_blackbox(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 484 {
 485     crm_control_blackbox(nsig, TRUE);
 486 }
 487 
 488 void
 489 crm_disable_blackbox(int nsig)
     /* [previous][next][first][last][top][bottom][index][help] */
 490 {
 491     crm_control_blackbox(nsig, FALSE);
 492 }
 493 
 494 /*!
 495  * \internal
 496  * \brief Write out a blackbox, if blackboxes are enabled
 497  *
 498  * \param[in] nsig  Signal that was received
 499  * \param[in] cs    libqb callsite
 500  *
 501  * \note This may be called via a true signal handler and so must be async-safe.
 502  * @TODO actually make this async-safe
 503  */
 504 void
 505 crm_write_blackbox(int nsig, const struct qb_log_callsite *cs)
     /* [previous][next][first][last][top][bottom][index][help] */
 506 {
 507     static volatile int counter = 1;
 508     static volatile time_t last = 0;
 509 
 510     char buffer[NAME_MAX];
 511     time_t now = time(NULL);
 512 
 513     if (blackbox_file_prefix == NULL) {
 514         return;
 515     }
 516 
 517     switch (nsig) {
 518         case 0:
 519         case SIGTRAP:
 520             /* The graceful case - such as assertion failure or user request */
 521 
 522             if (nsig == 0 && now == last) {
 523                 /* Prevent over-dumping */
 524                 return;
 525             }
 526 
 527             snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++);
 528             if (nsig == SIGTRAP) {
 529                 crm_notice("Blackbox dump requested, please see %s for contents", buffer);
 530 
 531             } else if (cs) {
 532                 syslog(LOG_NOTICE,
 533                        "Problem detected at %s:%d (%s), please see %s for additional details",
 534                        cs->function, cs->lineno, cs->filename, buffer);
 535             } else {
 536                 crm_notice("Problem detected, please see %s for additional details", buffer);
 537             }
 538 
 539             last = now;
 540             qb_log_blackbox_write_to_file(buffer);
 541 
 542             /* Flush the existing contents
 543              * A size change would also work
 544              */
 545             qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
 546             qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
 547             break;
 548 
 549         default:
 550             /* Do as little as possible, just try to get what we have out
 551              * We logged the filename when the blackbox was enabled
 552              */
 553             crm_signal_handler(nsig, SIG_DFL);
 554             qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix);
 555             qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
 556             raise(nsig);
 557             break;
 558     }
 559 }
 560 
 561 static const char *
 562 crm_quark_to_string(uint32_t tag)
     /* [previous][next][first][last][top][bottom][index][help] */
 563 {
 564     const char *text = g_quark_to_string(tag);
 565 
 566     if (text) {
 567         return text;
 568     }
 569     return "";
 570 }
 571 
 572 static void
 573 crm_log_filter_source(int source, const char *trace_files, const char *trace_fns,
     /* [previous][next][first][last][top][bottom][index][help] */
 574                       const char *trace_fmts, const char *trace_tags, const char *trace_blackbox,
 575                       struct qb_log_callsite *cs)
 576 {
 577     if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
 578         return;
 579     } else if (cs->tags != crm_trace_nonlog && source == QB_LOG_BLACKBOX) {
 580         /* Blackbox gets everything if enabled */
 581         qb_bit_set(cs->targets, source);
 582 
 583     } else if (source == blackbox_trigger && blackbox_trigger > 0) {
 584         /* Should this log message result in the blackbox being dumped */
 585         if (cs->priority <= LOG_ERR) {
 586             qb_bit_set(cs->targets, source);
 587 
 588         } else if (trace_blackbox) {
 589             char *key = crm_strdup_printf("%s:%d", cs->function, cs->lineno);
 590 
 591             if (strstr(trace_blackbox, key) != NULL) {
 592                 qb_bit_set(cs->targets, source);
 593             }
 594             free(key);
 595         }
 596 
 597     } else if (source == QB_LOG_SYSLOG) {       /* No tracing to syslog */
 598         if (cs->priority <= crm_log_priority && cs->priority <= crm_log_level) {
 599             qb_bit_set(cs->targets, source);
 600         }
 601         /* Log file tracing options... */
 602     } else if (cs->priority <= crm_log_level) {
 603         qb_bit_set(cs->targets, source);
 604     } else if (trace_files && strstr(trace_files, cs->filename) != NULL) {
 605         qb_bit_set(cs->targets, source);
 606     } else if (trace_fns && strstr(trace_fns, cs->function) != NULL) {
 607         qb_bit_set(cs->targets, source);
 608     } else if (trace_fmts && strstr(trace_fmts, cs->format) != NULL) {
 609         qb_bit_set(cs->targets, source);
 610     } else if (trace_tags
 611                && cs->tags != 0
 612                && cs->tags != crm_trace_nonlog && g_quark_to_string(cs->tags) != NULL) {
 613         qb_bit_set(cs->targets, source);
 614     }
 615 }
 616 
 617 #ifndef HAVE_STRCHRNUL
 618 /* strchrnul() is a GNU extension. If not present, use our own definition.
 619  * The GNU version returns char*, but we only need it to be const char*.
 620  */
 621 static const char *
 622 strchrnul(const char *s, int c)
     /* [previous][next][first][last][top][bottom][index][help] */
 623 {
 624     while ((*s != c) && (*s != '\0')) {
 625         ++s;
 626     }
 627     return s;
 628 }
 629 #endif
 630 
 631 static void
 632 crm_log_filter(struct qb_log_callsite *cs)
     /* [previous][next][first][last][top][bottom][index][help] */
 633 {
 634     int lpc = 0;
 635     static int need_init = 1;
 636     static const char *trace_fns = NULL;
 637     static const char *trace_tags = NULL;
 638     static const char *trace_fmts = NULL;
 639     static const char *trace_files = NULL;
 640     static const char *trace_blackbox = NULL;
 641 
 642     if (need_init) {
 643         need_init = 0;
 644         trace_fns = pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS);
 645         trace_fmts = pcmk__env_option(PCMK__ENV_TRACE_FORMATS);
 646         trace_tags = pcmk__env_option(PCMK__ENV_TRACE_TAGS);
 647         trace_files = pcmk__env_option(PCMK__ENV_TRACE_FILES);
 648         trace_blackbox = pcmk__env_option(PCMK__ENV_TRACE_BLACKBOX);
 649 
 650         if (trace_tags != NULL) {
 651             uint32_t tag;
 652             char token[500];
 653             const char *offset = NULL;
 654             const char *next = trace_tags;
 655 
 656             do {
 657                 offset = next;
 658                 next = strchrnul(offset, ',');
 659                 snprintf(token, sizeof(token), "%.*s", (int)(next - offset), offset);
 660 
 661                 tag = g_quark_from_string(token);
 662                 crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags);
 663 
 664                 if (next[0] != 0) {
 665                     next++;
 666                 }
 667 
 668             } while (next != NULL && next[0] != 0);
 669         }
 670     }
 671 
 672     cs->targets = 0;            /* Reset then find targets to enable */
 673     for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
 674         crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, trace_blackbox,
 675                               cs);
 676     }
 677 }
 678 
 679 gboolean
 680 crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
     /* [previous][next][first][last][top][bottom][index][help] */
 681 {
 682     gboolean refilter = FALSE;
 683 
 684     if (cs == NULL) {
 685         return FALSE;
 686     }
 687 
 688     if (cs->priority != level) {
 689         cs->priority = level;
 690         refilter = TRUE;
 691     }
 692 
 693     if (cs->tags != tags) {
 694         cs->tags = tags;
 695         refilter = TRUE;
 696     }
 697 
 698     if (refilter) {
 699         crm_log_filter(cs);
 700     }
 701 
 702     if (cs->targets == 0) {
 703         return FALSE;
 704     }
 705     return TRUE;
 706 }
 707 
 708 void
 709 crm_update_callsites(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 710 {
 711     static gboolean log = TRUE;
 712 
 713     if (log) {
 714         log = FALSE;
 715         crm_debug
 716             ("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s",
 717              crm_log_level, pcmk__env_option(PCMK__ENV_TRACE_FILES),
 718              pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS),
 719              pcmk__env_option(PCMK__ENV_TRACE_FORMATS),
 720              pcmk__env_option(PCMK__ENV_TRACE_TAGS));
 721     }
 722     qb_log_filter_fn_set(crm_log_filter);
 723 }
 724 
 725 static gboolean
 726 crm_tracing_enabled(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 727 {
 728     return (crm_log_level == LOG_TRACE)
 729             || (pcmk__env_option(PCMK__ENV_TRACE_FILES) != NULL)
 730             || (pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS) != NULL)
 731             || (pcmk__env_option(PCMK__ENV_TRACE_FORMATS) != NULL)
 732             || (pcmk__env_option(PCMK__ENV_TRACE_TAGS) != NULL);
 733 }
 734 
 735 static int
 736 crm_priority2int(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 737 {
 738     struct syslog_names {
 739         const char *name;
 740         int priority;
 741     };
 742     static struct syslog_names p_names[] = {
 743         {"emerg", LOG_EMERG},
 744         {"alert", LOG_ALERT},
 745         {"crit", LOG_CRIT},
 746         {"error", LOG_ERR},
 747         {"warning", LOG_WARNING},
 748         {"notice", LOG_NOTICE},
 749         {"info", LOG_INFO},
 750         {"debug", LOG_DEBUG},
 751         {NULL, -1}
 752     };
 753     int lpc;
 754 
 755     for (lpc = 0; name != NULL && p_names[lpc].name != NULL; lpc++) {
 756         if (pcmk__str_eq(p_names[lpc].name, name, pcmk__str_none)) {
 757             return p_names[lpc].priority;
 758         }
 759     }
 760     return crm_log_priority;
 761 }
 762 
 763 
 764 /*!
 765  * \internal
 766  * \brief Set the identifier for the current process
 767  *
 768  * If the identifier crm_system_name is not already set, then it is set as follows:
 769  * - it is passed to the function via the "entity" parameter, or
 770  * - it is derived from the executable name
 771  *
 772  * The identifier can be used in logs, IPC, and more.
 773  *
 774  * This method also sets the PCMK_service environment variable.
 775  *
 776  * \param[in] entity  If not NULL, will be assigned to the identifier
 777  * \param[in] argc    The number of command line parameters
 778  * \param[in] argv    The command line parameter values
 779  */
 780 static void
 781 set_identity(const char *entity, int argc, char *const *argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 782 {
 783     if (crm_system_name != NULL) {
 784         return; // Already set, don't overwrite
 785     }
 786 
 787     if (entity != NULL) {
 788         crm_system_name = strdup(entity);
 789 
 790     } else if ((argc > 0) && (argv != NULL)) {
 791         char *mutable = strdup(argv[0]);
 792         char *modified = basename(mutable);
 793 
 794         if (strstr(modified, "lt-") == modified) {
 795             modified += 3;
 796         }
 797         crm_system_name = strdup(modified);
 798         free(mutable);
 799 
 800     } else {
 801         crm_system_name = strdup("Unknown");
 802     }
 803 
 804     CRM_ASSERT(crm_system_name != NULL);
 805 
 806     // Used by fencing.py.py (in fence-agents)
 807     pcmk__set_env_option(PCMK__ENV_SERVICE, crm_system_name, false);
 808 }
 809 
 810 void
 811 crm_log_preinit(const char *entity, int argc, char *const *argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 812 {
 813     /* Configure libqb logging with nothing turned on */
 814 
 815     struct utsname res;
 816     int lpc = 0;
 817     int32_t qb_facility = 0;
 818     pid_t pid = getpid();
 819     const char *nodename = "localhost";
 820     static bool have_logging = false;
 821 
 822     if (have_logging) {
 823         return;
 824     }
 825 
 826     have_logging = true;
 827 
 828     crm_xml_init(); /* Sets buffer allocation strategy */
 829 
 830     if (crm_trace_nonlog == 0) {
 831         crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint");
 832     }
 833 
 834     umask(S_IWGRP | S_IWOTH | S_IROTH);
 835 
 836     /* Redirect messages from glib functions to our handler */
 837     glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL);
 838 
 839     /* and for good measure... - this enum is a bit field (!) */
 840     g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */
 841 
 842     /* Set crm_system_name, which is used as the logging name. It may also
 843      * be used for other purposes such as an IPC client name.
 844      */
 845     set_identity(entity, argc, argv);
 846 
 847     qb_facility = qb_log_facility2int("local0");
 848     qb_log_init(crm_system_name, qb_facility, LOG_ERR);
 849     crm_log_level = LOG_CRIT;
 850 
 851     /* Nuke any syslog activity until it's asked for */
 852     qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
 853 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
 854     // Shorter than default, generous for what we *should* send to syslog
 855     qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
 856 #endif
 857     if (uname(memset(&res, 0, sizeof(res))) == 0 && *res.nodename != '\0') {
 858         nodename = res.nodename;
 859     }
 860 
 861     /* Set format strings and disable threading
 862      * Pacemaker and threads do not mix well (due to the amount of forking)
 863      */
 864     qb_log_tags_stringify_fn_set(crm_quark_to_string);
 865     for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
 866         qb_log_ctl(lpc, QB_LOG_CONF_THREADED, QB_FALSE);
 867 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS
 868         // End truncated lines with '...'
 869         qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
 870 #endif
 871         set_format_string(lpc, crm_system_name, pid, nodename);
 872     }
 873 
 874 #ifdef ENABLE_NLS
 875     /* Enable translations (experimental). Currently we only have a few
 876      * proof-of-concept translations for some option help. The goal would be to
 877      * offer translations for option help and man pages rather than logs or
 878      * documentation, to reduce the burden of maintaining them.
 879      */
 880 
 881     // Load locale information for the local host from the environment
 882     setlocale(LC_ALL, "");
 883 
 884     // Tell gettext where to find Pacemaker message catalogs
 885     CRM_ASSERT(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL);
 886 
 887     // Tell gettext to use the Pacemaker message catalogs
 888     CRM_ASSERT(textdomain(PACKAGE) != NULL);
 889 
 890     // Tell gettext that the translated strings are stored in UTF-8
 891     bind_textdomain_codeset(PACKAGE, "UTF-8");
 892 #endif
 893 }
 894 
 895 gboolean
 896 crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr,
     /* [previous][next][first][last][top][bottom][index][help] */
 897              int argc, char **argv, gboolean quiet)
 898 {
 899     const char *syslog_priority = NULL;
 900     const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
 901     const char *f_copy = facility;
 902 
 903     pcmk__is_daemon = daemon;
 904     crm_log_preinit(entity, argc, argv);
 905 
 906     if (level > LOG_TRACE) {
 907         level = LOG_TRACE;
 908     }
 909     if(level > crm_log_level) {
 910         crm_log_level = level;
 911     }
 912 
 913     /* Should we log to syslog */
 914     if (facility == NULL) {
 915         if (pcmk__is_daemon) {
 916             facility = "daemon";
 917         } else {
 918             facility = PCMK__VALUE_NONE;
 919         }
 920         pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility, true);
 921     }
 922 
 923     if (pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei)) {
 924         quiet = TRUE;
 925 
 926 
 927     } else {
 928         qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
 929     }
 930 
 931     if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
 932         /* Override the default setting */
 933         crm_log_level = LOG_DEBUG;
 934     }
 935 
 936     /* What lower threshold do we have for sending to syslog */
 937     syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
 938     if (syslog_priority) {
 939         crm_log_priority = crm_priority2int(syslog_priority);
 940     }
 941     qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
 942                       crm_log_priority);
 943 
 944     // Log to syslog unless requested to be quiet
 945     if (!quiet) {
 946         qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
 947     }
 948 
 949     /* Should we log to stderr */ 
 950     if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
 951         /* Override the default setting */
 952         to_stderr = TRUE;
 953     }
 954     crm_enable_stderr(to_stderr);
 955 
 956     // Log to a file if we're a daemon or user asked for one
 957     {
 958         const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
 959 
 960         if (!pcmk__str_eq(PCMK__VALUE_NONE, logfile, pcmk__str_casei)
 961             && (pcmk__is_daemon || (logfile != NULL))) {
 962             // Daemons always get a log file, unless explicitly set to "none"
 963             pcmk__add_logfile(logfile);
 964         }
 965     }
 966 
 967     if (pcmk__is_daemon
 968         && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
 969         crm_enable_blackbox(0);
 970     }
 971 
 972     /* Summary */
 973     crm_trace("Quiet: %d, facility %s", quiet, f_copy);
 974     pcmk__env_option(PCMK__ENV_LOGFILE);
 975     pcmk__env_option(PCMK__ENV_LOGFACILITY);
 976 
 977     crm_update_callsites();
 978 
 979     /* Ok, now we can start logging... */
 980 
 981     // Disable daemon request if user isn't root or Pacemaker daemon user
 982     if (pcmk__is_daemon) {
 983         const char *user = getenv("USER");
 984 
 985         if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
 986             crm_trace("Not switching to corefile directory for %s", user);
 987             pcmk__is_daemon = false;
 988         }
 989     }
 990 
 991     if (pcmk__is_daemon) {
 992         int user = getuid();
 993         struct passwd *pwent = getpwuid(user);
 994 
 995         if (pwent == NULL) {
 996             crm_perror(LOG_ERR, "Cannot get name for uid: %d", user);
 997 
 998         } else if (!pcmk__strcase_any_of(pwent->pw_name, "root", CRM_DAEMON_USER, NULL)) {
 999             crm_trace("Don't change active directory for regular user: %s", pwent->pw_name);
1000 
1001         } else if (chdir(CRM_CORE_DIR) < 0) {
1002             crm_perror(LOG_INFO, "Cannot change active directory to " CRM_CORE_DIR);
1003 
1004         } else {
1005             crm_info("Changed active directory to " CRM_CORE_DIR);
1006         }
1007 
1008         /* Original meanings from signal(7)
1009          *
1010          * Signal       Value     Action   Comment
1011          * SIGTRAP        5        Core    Trace/breakpoint trap
1012          * SIGUSR1     30,10,16    Term    User-defined signal 1
1013          * SIGUSR2     31,12,17    Term    User-defined signal 2
1014          *
1015          * Our usage is as similar as possible
1016          */
1017         mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
1018         mainloop_add_signal(SIGUSR2, crm_disable_blackbox);
1019         mainloop_add_signal(SIGTRAP, crm_trigger_blackbox);
1020 
1021     } else if (!quiet) {
1022         crm_log_args(argc, argv);
1023     }
1024 
1025     return TRUE;
1026 }
1027 
1028 /* returns the old value */
1029 unsigned int
1030 set_crm_log_level(unsigned int level)
     /* [previous][next][first][last][top][bottom][index][help] */
1031 {
1032     unsigned int old = crm_log_level;
1033 
1034     if (level > LOG_TRACE) {
1035         level = LOG_TRACE;
1036     }
1037     crm_log_level = level;
1038     crm_update_callsites();
1039     crm_trace("New log level: %d", level);
1040     return old;
1041 }
1042 
1043 void
1044 crm_enable_stderr(int enable)
     /* [previous][next][first][last][top][bottom][index][help] */
1045 {
1046     if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
1047         qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
1048         crm_update_callsites();
1049 
1050     } else if (enable == FALSE) {
1051         qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
1052     }
1053 }
1054 
1055 /*!
1056  * \brief Make logging more verbose
1057  *
1058  * If logging to stderr is not already enabled when this function is called,
1059  * enable it. Otherwise, increase the log level by 1.
1060  *
1061  * \param[in] argc  Ignored
1062  * \param[in] argv  Ignored
1063  */
1064 void
1065 crm_bump_log_level(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
1066 {
1067     if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0)
1068         != QB_LOG_STATE_ENABLED) {
1069         crm_enable_stderr(TRUE);
1070     } else {
1071         set_crm_log_level(crm_log_level + 1);
1072     }
1073 }
1074 
1075 unsigned int
1076 get_crm_log_level(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1077 {
1078     return crm_log_level;
1079 }
1080 
1081 /*!
1082  * \brief Log the command line (once)
1083  *
1084  * \param[in]  Number of values in \p argv
1085  * \param[in]  Command-line arguments (including command name)
1086  *
1087  * \note This function will only log once, even if called with different
1088  *       arguments.
1089  */
1090 void
1091 crm_log_args(int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
1092 {
1093     static bool logged = false;
1094     gchar *arg_string = NULL;
1095 
1096     if ((argc == 0) || (argv == NULL) || logged) {
1097         return;
1098     }
1099     logged = true;
1100     arg_string = g_strjoinv(" ", argv);
1101     crm_notice("Invoked: %s", arg_string);
1102     g_free(arg_string);
1103 }
1104 
1105 void
1106 crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix,
     /* [previous][next][first][last][top][bottom][index][help] */
1107                   const char *output)
1108 {
1109     const char *next = NULL;
1110     const char *offset = NULL;
1111 
1112     if (level == LOG_NEVER) {
1113         return;
1114     }
1115 
1116     if (output == NULL) {
1117         if (level != LOG_STDOUT) {
1118             level = LOG_TRACE;
1119         }
1120         output = "-- empty --";
1121     }
1122 
1123     next = output;
1124     do {
1125         offset = next;
1126         next = strchrnul(offset, '\n');
1127         do_crm_log_alias(level, file, function, line, "%s [ %.*s ]", prefix,
1128                          (int)(next - offset), offset);
1129         if (next[0] != 0) {
1130             next++;
1131         }
1132 
1133     } while (next != NULL && next[0] != 0);
1134 }
1135 
1136 void
1137 pcmk__cli_init_logging(const char *name, unsigned int verbosity)
     /* [previous][next][first][last][top][bottom][index][help] */
1138 {
1139     crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
1140 
1141     for (int i = 0; i < verbosity; i++) {
1142         /* These arguments are ignored, so pass placeholders. */
1143         crm_bump_log_level(0, NULL);
1144     }
1145 }
1146 
1147 /*!
1148  * \brief Log XML line-by-line in a formatted fashion
1149  *
1150  * \param[in] file      File name to use for log filtering
1151  * \param[in] function  Function name to use for log filtering
1152  * \param[in] line      Line number to use for log filtering
1153  * \param[in] tags      Logging tags to use for log filtering
1154  * \param[in] level     Priority at which to log the messages
1155  * \param[in] text      Prefix for each line
1156  * \param[in] xml       XML to log
1157  *
1158  * \note This does nothing when \p level is \p LOG_STDOUT.
1159  * \note Do not call this function directly. It should be called only from the
1160  *       \p do_crm_log_xml() macro.
1161  */
1162 void
1163 pcmk_log_xml_as(const char *file, const char *function, uint32_t line,
     /* [previous][next][first][last][top][bottom][index][help] */
1164                 uint32_t tags, uint8_t level, const char *text, const xmlNode *xml)
1165 {
1166     if (xml == NULL) {
1167         do_crm_log(level, "%s%sNo data to dump as XML",
1168                    pcmk__s(text, ""), pcmk__str_empty(text)? "" : " ");
1169 
1170     } else {
1171         if (logger_out == NULL) {
1172             CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1173         }
1174 
1175         pcmk__output_set_log_level(logger_out, level);
1176         pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1177         pcmk__xml_show(logger_out, text, xml, 1,
1178                        pcmk__xml_fmt_pretty
1179                        |pcmk__xml_fmt_open
1180                        |pcmk__xml_fmt_children
1181                        |pcmk__xml_fmt_close);
1182         pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1183     }
1184 }
1185 
1186 /*!
1187  * \internal
1188  * \brief Log XML changes line-by-line in a formatted fashion
1189  *
1190  * \param[in] file      File name to use for log filtering
1191  * \param[in] function  Function name to use for log filtering
1192  * \param[in] line      Line number to use for log filtering
1193  * \param[in] tags      Logging tags to use for log filtering
1194  * \param[in] level     Priority at which to log the messages
1195  * \param[in] xml       XML whose changes to log
1196  *
1197  * \note This does nothing when \p level is \c LOG_STDOUT.
1198  */
1199 void
1200 pcmk__log_xml_changes_as(const char *file, const char *function, uint32_t line,
     /* [previous][next][first][last][top][bottom][index][help] */
1201                          uint32_t tags, uint8_t level, const xmlNode *xml)
1202 {
1203     if (xml == NULL) {
1204         do_crm_log(level, "No XML to dump");
1205         return;
1206     }
1207 
1208     if (logger_out == NULL) {
1209         CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1210     }
1211     pcmk__output_set_log_level(logger_out, level);
1212     pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1213     pcmk__xml_show_changes(logger_out, xml);
1214     pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1215 }
1216 
1217 /*!
1218  * \internal
1219  * \brief Log an XML patchset line-by-line in a formatted fashion
1220  *
1221  * \param[in] file      File name to use for log filtering
1222  * \param[in] function  Function name to use for log filtering
1223  * \param[in] line      Line number to use for log filtering
1224  * \param[in] tags      Logging tags to use for log filtering
1225  * \param[in] level     Priority at which to log the messages
1226  * \param[in] patchset  XML patchset to log
1227  *
1228  * \note This does nothing when \p level is \c LOG_STDOUT.
1229  */
1230 void
1231 pcmk__log_xml_patchset_as(const char *file, const char *function, uint32_t line,
     /* [previous][next][first][last][top][bottom][index][help] */
1232                           uint32_t tags, uint8_t level, const xmlNode *patchset)
1233 {
1234     if (patchset == NULL) {
1235         do_crm_log(level, "No patchset to dump");
1236         return;
1237     }
1238 
1239     if (logger_out == NULL) {
1240         CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1241     }
1242     pcmk__output_set_log_level(logger_out, level);
1243     pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1244     logger_out->message(logger_out, "xml-patchset", patchset);
1245     pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1246 }
1247 
1248 /*!
1249  * \internal
1250  * \brief Free the logging library's internal log output object
1251  */
1252 void
1253 pcmk__free_common_logger(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1254 {
1255     if (logger_out != NULL) {
1256         logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
1257         pcmk__output_free(logger_out);
1258         logger_out = NULL;
1259     }
1260 }
1261 
1262 // Deprecated functions kept only for backward API compatibility
1263 // LCOV_EXCL_START
1264 
1265 #include <crm/common/logging_compat.h>
1266 
1267 gboolean
1268 crm_log_cli_init(const char *entity)
     /* [previous][next][first][last][top][bottom][index][help] */
1269 {
1270     pcmk__cli_init_logging(entity, 0);
1271     return TRUE;
1272 }
1273 
1274 gboolean
1275 crm_add_logfile(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
1276 {
1277     return pcmk__add_logfile(filename) == pcmk_rc_ok;
1278 }
1279 
1280 void
1281 pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1282 {
1283     pcmk_log_xml_as(__FILE__, __func__, __LINE__, 0, level, text, xml);
1284 }
1285 
1286 // LCOV_EXCL_STOP
1287 // End deprecated API
1288 
1289 void pcmk__set_config_error_handler(pcmk__config_error_func error_handler, void *error_context)
     /* [previous][next][first][last][top][bottom][index][help] */
1290 {
1291     pcmk__config_error_handler = error_handler;
1292     pcmk__config_error_context = error_context;    
1293 }
1294 
1295 void pcmk__set_config_warning_handler(pcmk__config_warning_func warning_handler, void *warning_context)
     /* [previous][next][first][last][top][bottom][index][help] */
1296 {
1297     pcmk__config_warning_handler = warning_handler;
1298     pcmk__config_warning_context = warning_context;   

/* [previous][next][first][last][top][bottom][index][help] */