1 /*
2 * Copyright 2004-2026 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 <errno.h> // errno
13 #include <fcntl.h> // open, O_CREAT, O_RDWR, S_*
14 #include <libgen.h> // basename
15 #include <signal.h> // raise, SIG*
16 #include <stdbool.h>
17 #include <stddef.h> // NULL
18 #include <stdint.h> // int32_t, uint8_t, uint32_t
19 #include <stdlib.h> // free, getenv, strtol
20 #include <string.h> // strdup, strerror, strstr
21 #include <syslog.h> // LOG_*, syslog
22 #include <sys/stat.h> // S_*, stat
23 #include <sys/types.h> // gid_t, mode_t, pid_t, uid_t
24 #include <sys/utsname.h> // uname, utsname
25 #include <time.h> // time, time_t, timespec
26 #include <unistd.h> // chdir, close, fchown, geteuid, getpid
27
28 #include <glib.h> // g_*, G_*, gboolean, gchar, etc.
29 #include <libxml/tree.h> // xmlNode
30 #include <qb/qbdefs.h> // qb_bit_set, QB_FALSE, QB_TRUE
31 #include <qb/qblog.h> // LOG_TRACE, qb_log_*
32
33 #include <crm_config.h> // CRM_DAEMON_USER, CRM_*_DIR
34 #include <crm/common/internal.h> // pcmk__env_*, pcmk__output_*, etc.
35 #include <crm/common/logging.h> // do_crm_log, CRM_CHECK, etc.
36 #include <crm/common/mainloop.h> // crm_signal_handler, mainloop_add_signal
37 #include <crm/common/options.h> // PCMK_VALUE_NONE
38 #include <crm/common/results.h> // crm_abort, pcmk_rc_*
39 #include <crm/crm.h> // crm_system_name
40
41 #include "crmcommon_private.h" // pcmk__add_logfile
42
43 // Use high-resolution (millisecond) timestamps if libqb supports them
44 #ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS
45 #define TIMESTAMP_FORMAT_SPEC "%%T"
46 typedef struct timespec *log_time_t;
47 #else
48 #define TIMESTAMP_FORMAT_SPEC "%%t"
49 typedef time_t log_time_t;
50 #endif
51
52 unsigned int crm_log_level = LOG_INFO;
53 unsigned int crm_trace_nonlog = 0;
54 bool pcmk__is_daemon = false;
55
56 static int blackbox_trigger = 0;
57 static char *blackbox_file_prefix = NULL;
58
59 static gchar **trace_blackbox = NULL;
60 static gchar **trace_files = NULL;
61 static gchar **trace_formats = NULL;
62 static gchar **trace_functions = NULL;
63 static bool tracing_enabled = false;
64
65 static unsigned int crm_log_priority = LOG_NOTICE;
66 static pcmk__output_t *logger_out = NULL;
67
68 pcmk__config_error_func pcmk__config_error_handler = NULL;
69 pcmk__config_warning_func pcmk__config_warning_handler = NULL;
70 void *pcmk__config_error_context = NULL;
71 void *pcmk__config_warning_context = NULL;
72
73 /*!
74 * \internal
75 * \brief Mapping of a GLib log domain string to its log handler ID
76 */
77 struct log_handler_id {
78 //! Log domain
79 const char *log_domain;
80
81 /*!
82 * Log handler function ID. GLib does not specify the meaning of 0, but
83 * based on the implementation, a valid ID is always positive unless we set
84 * UINT_MAX handlers.
85 */
86 guint handler_id;
87 };
88
89 // Log domains that we care about, and their handler function IDs once set
90 static struct log_handler_id log_handler_ids[] = {
91 { G_LOG_DOMAIN, 0 },
92 { "GLib", 0 },
93 { "GLib-GIO", 0 },
94 { "GModule", 0 },
95 { "GThread", 0 },
96 };
97
98 /*!
99 * \internal
100 * \brief Convert a GLib log level to a syslog log level
101 *
102 * \param[in] log_level GLib log level
103 *
104 * \return The syslog level corresponding to \p log_level
105 */
106 static uint8_t
107 log_level_from_glib(GLogLevelFlags log_level)
108 {
109 switch (log_level & G_LOG_LEVEL_MASK) {
110 case G_LOG_LEVEL_CRITICAL:
111 return LOG_CRIT;
112
113 case G_LOG_LEVEL_ERROR:
114 return LOG_ERR;
115
116 case G_LOG_LEVEL_MESSAGE:
117 return LOG_NOTICE;
118
119 case G_LOG_LEVEL_INFO:
120 return LOG_INFO;
121
122 case G_LOG_LEVEL_DEBUG:
123 return LOG_DEBUG;
124
125 case G_LOG_LEVEL_WARNING:
126 return LOG_WARNING;
127
128 default:
129 // Default to LOG_NOTICE for any new or custom GLib log levels
130 return LOG_NOTICE;
131 }
132 }
133
134 /*!
135 * \internal
136 * \brief Handle a log message from GLib
137 *
138 * \param[in] log_domain Log domain of the message
139 * \param[in] log_level Log level of the message (including fatal and
140 * recursion flags)
141 * \param[in] message Message to process
142 * \param[in] user_data Ignored
143 */
144 static void
145 handle_glib_message(const gchar *log_domain, GLogLevelFlags log_level,
146 const gchar *message, gpointer user_data)
147
148 {
149 uint8_t syslog_level = log_level_from_glib(log_level);
150
151 if (syslog_level == LOG_CRIT) {
152 static struct qb_log_callsite *glib_cs = NULL;
153
154 if (glib_cs == NULL) {
155 glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler",
156 LOG_DEBUG, __LINE__,
157 crm_trace_nonlog);
158 }
159
160 if (!crm_is_callsite_active(glib_cs, LOG_DEBUG, crm_trace_nonlog)) {
161 // Dump core
162 crm_abort(__FILE__, __func__, __LINE__, message, true, true);
163 }
164 }
165
166 do_crm_log(syslog_level, "%s: %s", log_domain, message);
167 }
168
169 /*!
170 * \internal
171 * \brief Set \c handle_glib_message() as the handler for each GLib log domain
172 *
173 * The handler will be set for all log levels, including fatal and recursive
174 * messages, for each GLib log domain that we care about.
175 */
176 static void
177 set_glib_log_handlers(void)
178 {
179 for (int i = 0; i < PCMK__NELEM(log_handler_ids); i++) {
180 struct log_handler_id *entry = &log_handler_ids[i];
181
182 entry->handler_id = g_log_set_handler(entry->log_domain,
183 G_LOG_LEVEL_MASK
184 |G_LOG_FLAG_FATAL
185 |G_LOG_FLAG_RECURSION,
186 handle_glib_message, NULL);
187 }
188 }
189
190 /*!
191 * \internal
192 * \brief Remove the handler for each GLib log domain that we care about
193 */
194 static void
195 remove_glib_log_handlers(void)
196 {
197 for (int i = 0; i < PCMK__NELEM(log_handler_ids); i++) {
198 struct log_handler_id *entry = &log_handler_ids[i];
199
200 if (entry->handler_id == 0) {
201 continue;
202 }
203
204 g_log_remove_handler(entry->log_domain, entry->handler_id);
205 entry->handler_id = 0;
206 }
207 }
208
209 /*!
210 * \internal
211 * \brief Set the log format string based on the given target
212 *
213 * \param[in] target Log target (detail level)
214 * \param[in] pid PID of current process
215 * \param[in] node_name Local node's name (for use in log output)
216 */
217 static void
218 set_format_string(int target, pid_t pid, const char *node_name)
219 {
220 /* Format specifiers used here:
221 * - %b: message
222 * - %f: file name
223 * - %g: tag, or "" if none
224 * - %l: line number
225 * - %n: function name
226 * - %p: priority (for example, "error" or "debug")
227 * - %t: timestamp at seconds resolution
228 * - %T: timestamp at milliseconds resolution
229 */
230 GString *fmt = NULL;
231
232 if (target == QB_LOG_SYSLOG) {
233 // The system log gets a simplified, user-friendly format
234 qb_log_ctl(target, QB_LOG_CONF_EXTENDED, QB_FALSE);
235 qb_log_format_set(target, "%g %p: %b");
236 return;
237 }
238
239 // Everything else gets more detail, for advanced troubleshooting
240 fmt = g_string_sized_new(256);
241
242 if (target > QB_LOG_STDERR) {
243 // If logging to a file, prefix with timestamp, node, system, and PID
244 g_string_append_printf(fmt, TIMESTAMP_FORMAT_SPEC " %s %-20s[%lld] ",
245 node_name, crm_system_name, (long long) pid);
246 }
247
248 // Add function name (in parentheses)
249 g_string_append(fmt, "(%n");
250
251 if (tracing_enabled) {
252 // When tracing, add file and line number
253 g_string_append(fmt, "@%f:%l");
254 }
255
256 g_string_append_c(fmt, ')');
257
258 // Add tag (if any), priority, and actual message
259 g_string_append(fmt, " %g\t%p: %b");
260
261 qb_log_format_set(target, fmt->str);
262 g_string_free(fmt, TRUE);
263 }
264
265 #define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log"
266
267 /*!
268 * \internal
269 * \brief Ensure the daemon group owns the log file or has read/write access
270 *
271 * If the daemon group (\c haclient by default) doesn't own the log file or
272 * can't read and write it, change the log file's ownership to the daemon user
273 * and group.
274 *
275 * \param[in] filename Log file name (for logging only)
276 * \param[in] logfd Log file descriptor
277 *
278 * \return Standard Pacemaker return code
279 *
280 * \todo Should we be less aggressive in changing ownership? The daemon group
281 * may have read/write permissions via file ACLs, or the daemon user may
282 * have permissions via user ownership, secondary group membership, or
283 * file ACLs. Also, even after changing group ownership, the daemon group
284 * may not have read/write permissions. Even if it does, we may unset them
285 * when applying PCMK__ENV_LOGFILE_MODE in \c chmod_logfile().
286 */
287 static int
288 chown_logfile(const char *filename, int logfd)
289 {
290 uid_t pcmk_uid = 0;
291 gid_t pcmk_gid = 0;
292 struct stat st;
293 int rc = pcmk_rc_ok;
294
295 // Get the log file's current ownership and permissions
296 if (fstat(logfd, &st) < 0) {
297 rc = errno;
298 pcmk__warn("Logging to '%s' is disabled because fstat() failed: %s",
299 filename, strerror(rc));
300 return rc;
301 }
302
303 // Any other errors don't prevent file from being used as log
304
305 rc = pcmk__daemon_user(&pcmk_uid, &pcmk_gid);
306 if (rc != pcmk_rc_ok) {
307 pcmk__warn("Not changing '%s' ownership because user information "
308 "unavailable: %s",
309 filename, pcmk_rc_str(rc));
310 return pcmk_rc_ok;
311 }
312
313 if ((st.st_gid == pcmk_gid)
314 && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) {
315
316 // Daemon group already owns the log file and has read/write permissions
317 return pcmk_rc_ok;
318 }
319
320 if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) {
321 pcmk__warn("Couldn't change '%s' ownership to user %s gid %d: %s",
322 filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno));
323 }
324
325 return pcmk_rc_ok;
326 }
327
328 /*!
329 * \internal
330 * \brief Set log file permissions using environment variable or default value
331 *
332 * If the \c PCMK__ENV_LOGFILE_MODE environment variable is set to a valid
333 * value, this function sets the log file mode to that value. Otherwise, it sets
334 * the log file mode to 0660.
335 *
336 * An error causes a warning to be logged but is otherwise ignored.
337 *
338 * \param[in] filename Log file name (for logging only)
339 * \param[in] logfd Log file descriptor
340 */
341 static void
342 chmod_logfile(const char *filename, int logfd)
343 {
344 const char *modestr = pcmk__env_option(PCMK__ENV_LOGFILE_MODE);
345 mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
346
347 errno = 0;
348 if (modestr != NULL) {
349 char *end = NULL;
350 const long filemode_l = strtol(modestr, &end, 8);
351 const mode_t mask = S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO;
352
353 if ((errno != 0) || (end == modestr) || !pcmk__str_empty(end)
354 || (((mode_t) filemode_l & ~mask) != 0)) {
355
356 pcmk__warn("Setting '%s' mode to %04o because "
357 "PCMK_" PCMK__ENV_LOGFILE_MODE " '%s' is invalid",
358 filename, filemode, modestr);
359
360 } else {
361 filemode = (mode_t) filemode_l;
362 }
363 }
364
365 if (fchmod(logfd, filemode) < 0) {
366 pcmk__warn("Couldn't change '%s' mode to %04o: %s", filename, filemode,
367 strerror(errno));
368 }
369 }
370
371 /*!
372 * \internal
373 * \brief If we're root, ensure a log file has correct permissions and ownership
374 *
375 * \param[in] filename Log file name
376 *
377 * \return Standard Pacemaker return code
378 */
379 static int
380 set_logfile_permissions(const char *filename)
381 {
382 int fd = 0;
383 int rc = pcmk_rc_ok;
384
385 if (geteuid() != 0) {
386 goto done;
387 }
388
389 fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
390 if (fd < 0) {
391 rc = errno;
392 pcmk__warn("Logging to '%s' is disabled because open() failed as root: "
393 "%s", filename, strerror(rc));
394 goto done;
395 }
396
397 rc = chown_logfile(filename, fd);
398 if (rc != pcmk_rc_ok) {
399 goto done;
400 }
401
402 chmod_logfile(filename, fd);
403
404 done:
405 if (fd >= 0) {
406 close(fd);
407 }
408 return rc;
409 }
410
411 /*!
412 * \internal
413 * \brief Enable a libqb log target, extend max line length, and apply filter
414 *
415 * \param[in] target Log target (either an <tt>enum qb_log_target_slot</tt> or
416 * the return value of a \c qb_log_file_open() call)
417 */
418 static void
419 enable_logfile(int32_t target)
420 {
421 qb_log_ctl(target, QB_LOG_CONF_ENABLED, QB_TRUE);
422
423 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
424 // Longer than default, for logging long XML lines
425 qb_log_ctl(target, QB_LOG_CONF_MAX_LINE_LEN, 800);
426 #endif
427
428 crm_update_callsites();
429 }
430
431 /*!
432 * \internal
433 * \brief Disable a libqb log target
434 *
435 * \param[in] target Log target (either an <tt>enum qb_log_target_slot</tt> or
436 * the return value of a \c qb_log_file_open() call)
437 */
438 static inline void
439 disable_logfile(int32_t target)
440 {
441 qb_log_ctl(target, QB_LOG_CONF_ENABLED, QB_FALSE);
442 }
443
444 static void
445 setenv_logfile(const char *filename)
446 {
447 // Some resource agents will log only if environment variable is set
448 if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
449 pcmk__set_env_option(PCMK__ENV_LOGFILE, filename, true);
450 }
451 }
452
453 /*!
454 * \internal
455 * \brief Add a file to be used as a Pacemaker detail log
456 *
457 * \param[in] filename Name of log file to use. \c NULL adds the default log
458 * file if no log file is enabled yet. \c PCMK_VALUE_NONE
459 * or \c "/dev/null" causes this function to do nothing.
460 *
461 * \return Standard Pacemaker return code
462 *
463 * \note Messages from this function won't be logged to the new log file.
464 */
465 int
466 pcmk__add_logfile(const char *filename)
467 {
468 static int32_t default_target = -1;
469 static bool have_logfile = false;
470
471 int32_t target = 0;
472 int rc = pcmk_rc_ok;
473 bool is_default = false;
474
475 // No logging to do if the file is "/dev/null" or special value "none"
476 if (pcmk__str_eq(filename, "/dev/null", pcmk__str_none)
477 || pcmk__str_eq(filename, PCMK_VALUE_NONE, pcmk__str_casei)) {
478
479 return pcmk_rc_ok;
480 }
481
482 // Use default if caller didn't specify (and we don't already have one)
483 if (filename == NULL) {
484 if (have_logfile) {
485 return pcmk_rc_ok;
486 }
487 filename = DEFAULT_LOG_FILE;
488 }
489
490 // If the caller wants the default and we already have it, we're done
491 is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none);
492 if (is_default && (default_target >= 0)) {
493 return pcmk_rc_ok;
494 }
495
496 // If we're root, ensure ownership and permissions are correct
497 rc = set_logfile_permissions(filename);
498 if (rc != pcmk_rc_ok) {
499 // Warning has already been logged
500 return rc;
501 }
502
503 // Open as a libqb log file
504 target = qb_log_file_open(filename);
505 if (target < 0) {
506 rc = -target; // target == -errno
507 pcmk__warn("Logging to '%s' is disabled because qb_log_file_open() "
508 "failed: %s " QB_XS " uid=%lld gid=%lld",
509 filename, strerror(rc), (long long) geteuid(),
510 (long long) getegid());
511 return rc;
512 }
513
514 if (is_default) {
515 default_target = target;
516 setenv_logfile(filename);
517
518 } else if (default_target >= 0) {
519 /* @TODO Why do we disable logging to the default log file when adding
520 * another log file? This seems wrong, especially if the default file is
521 * configured explicitly.
522 */
523 pcmk__notice("Switching logging to %s", filename);
524 disable_logfile(default_target);
525 }
526
527 pcmk__notice("Additional logging available in %s", filename);
528 enable_logfile(target);
529 have_logfile = true;
530 return pcmk_rc_ok;
531 }
532
533 /*!
534 * \brief Add multiple additional log files
535 *
536 * \param[in] log_files Array of log files to add
537 * \param[in] out Output object to use for error reporting
538 *
539 * \return Standard Pacemaker return code
540 */
541 void
542 pcmk__add_logfiles(gchar **log_files, pcmk__output_t *out)
543 {
544 if (log_files == NULL) {
545 return;
546 }
547
548 for (gchar **fname = log_files; *fname != NULL; fname++) {
549 int rc = pcmk__add_logfile(*fname);
550
551 if (rc != pcmk_rc_ok) {
552 out->err(out, "Logging to %s is disabled: %s",
553 *fname, pcmk_rc_str(rc));
554 }
555 }
556 }
557
558 /*!
559 * \internal
560 * \brief Write out a blackbox (enabling blackboxes if the signal is \c SIGTRAP)
561 *
562 * \param[in] nsig Signal number that was received
563 *
564 * \note This is a true signal handler, and so must be async-safe.
565 */
566 static void
567 enable_and_write_blackbox(int nsig)
568 {
569 if (nsig == SIGTRAP) {
570 crm_enable_blackbox(nsig);
571 }
572
573 crm_write_blackbox(nsig, NULL);
574 }
575
576 static void
577 blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp,
578 const char *msg)
579 {
580 if(cs && cs->priority < LOG_ERR) {
581 crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */
582 } else {
583 crm_write_blackbox(0, cs);
584 }
585 }
586
587 void
588 crm_enable_blackbox(int nsig)
589 {
590 if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET,
591 0) == QB_LOG_STATE_ENABLED) {
592 return;
593 }
594
595 // Any size change drops existing entries
596 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024);
597
598 // Setting the size seems to disable the log target
599 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
600
601 /* Enable synchronous logging for each target except QB_LOG_SYSLOG and
602 * QB_LOG_STDERR
603 */
604 for (int i = QB_LOG_BLACKBOX; i < QB_LOG_TARGET_MAX; i++) {
605 qb_log_ctl(i, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
606 }
607
608 pcmk__notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
609
610 // Save to disk on abnormal termination
611 crm_signal_handler(SIGSEGV, enable_and_write_blackbox);
612 crm_signal_handler(SIGABRT, enable_and_write_blackbox);
613 crm_signal_handler(SIGILL, enable_and_write_blackbox);
614 crm_signal_handler(SIGBUS, enable_and_write_blackbox);
615 crm_signal_handler(SIGFPE, enable_and_write_blackbox);
616
617 crm_update_callsites();
618
619 blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL);
620 qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE);
621 pcmk__trace("Trigger: %d is %d %d", blackbox_trigger,
622 qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0),
623 QB_LOG_STATE_ENABLED);
624
625 crm_update_callsites();
626 }
627
628 void
629 crm_disable_blackbox(int nsig)
630 {
631 if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET,
632 0) != QB_LOG_STATE_ENABLED) {
633 return;
634 }
635
636 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
637
638 // Disable synchronous logging again when the blackbox is disabled
639 for (int i = QB_LOG_BLACKBOX; i < QB_LOG_TARGET_MAX; i++) {
640 qb_log_ctl(i, QB_LOG_CONF_FILE_SYNC, QB_FALSE);
641 }
642 }
643
644 /*!
645 * \internal
646 * \brief Write out a blackbox, if blackboxes are enabled
647 *
648 * \param[in] nsig Signal that was received
649 * \param[in] cs libqb callsite
650 *
651 * \note This may be called via a true signal handler and so must be async-safe.
652 * @TODO actually make this async-safe
653 */
654 void
655 crm_write_blackbox(int nsig, const struct qb_log_callsite *cs)
656 {
657 static volatile int counter = 1;
658 static volatile time_t last = 0;
659
660 char *buffer = NULL;
661 int rc = 0;
662 time_t now = time(NULL);
663
664 if (blackbox_file_prefix == NULL) {
665 return;
666 }
667
668 switch (nsig) {
669 case 0:
670 case SIGTRAP:
671 /* The graceful case - such as assertion failure or user request */
672
673 if (nsig == 0 && now == last) {
674 /* Prevent over-dumping */
675 return;
676 }
677
678 buffer = pcmk__assert_asprintf("%s.%d", blackbox_file_prefix,
679 counter++);
680 if (nsig == SIGTRAP) {
681 pcmk__notice("Blackbox dump requested, please see %s for "
682 "contents",
683 buffer);
684
685 } else if (cs) {
686 syslog(LOG_NOTICE,
687 "Problem detected at %s:%d (%s), please see %s for additional details",
688 cs->function, cs->lineno, cs->filename, buffer);
689 } else {
690 pcmk__notice("Problem detected, please see %s for additional "
691 "details",
692 buffer);
693 }
694
695 last = now;
696
697 rc = qb_log_blackbox_write_to_file(buffer);
698 if (rc < 0) {
699 // System errno
700 pcmk__err("Failed to write blackbox file %s: %s", buffer,
701 strerror(-rc));
702 }
703
704 /* Flush the existing contents
705 * A size change would also work
706 */
707 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
708 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
709 break;
710
711 default:
712 /* Do as little as possible, just try to get what we have out
713 * We logged the filename when the blackbox was enabled
714 */
715 crm_signal_handler(nsig, SIG_DFL);
716 qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix);
717 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
718 raise(nsig);
719 break;
720 }
721
722 free(buffer);
723 }
724
725 static const char *
726 crm_quark_to_string(uint32_t tag)
727 {
728 const char *text = g_quark_to_string(tag);
729
730 if (text) {
731 return text;
732 }
733 return "";
734 }
735
736 static void
737 crm_log_filter_source(int source, struct qb_log_callsite *cs)
738 {
739 if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
740 return;
741 }
742
743 if ((cs->tags != crm_trace_nonlog) && (source == QB_LOG_BLACKBOX)) {
744 /* Blackbox gets everything if enabled */
745 qb_bit_set(cs->targets, source);
746 return;
747 }
748
749 if ((source == blackbox_trigger) && (blackbox_trigger > 0)) {
750 /* Should this log message result in the blackbox being dumped */
751 if (cs->priority <= LOG_ERR) {
752 qb_bit_set(cs->targets, source);
753
754 } else if (trace_blackbox != NULL) {
755 char *key = pcmk__assert_asprintf("%s:%d", cs->function,
756 cs->lineno);
757
758 if (pcmk__g_strv_contains(trace_blackbox, key)) {
759 qb_bit_set(cs->targets, source);
760 }
761 free(key);
762 }
763 return;
764 }
765
766 if (source == QB_LOG_SYSLOG) {
767 // No tracing to syslog
768 if ((cs->priority <= crm_log_priority)
769 && (cs->priority <= crm_log_level)) {
770
771 qb_bit_set(cs->targets, source);
772 }
773 return;
774 }
775
776 if (cs->priority <= crm_log_level) {
777 qb_bit_set(cs->targets, source);
778 return;
779 }
780
781 if ((trace_files != NULL)
782 && pcmk__g_strv_contains(trace_files, cs->filename)) {
783
784 qb_bit_set(cs->targets, source);
785 return;
786 }
787
788 if ((trace_functions != NULL)
789 && pcmk__g_strv_contains(trace_functions, cs->function)) {
790
791 qb_bit_set(cs->targets, source);
792 return;
793 }
794
795 if ((trace_formats != NULL)
796 && pcmk__g_strv_contains(trace_formats, cs->format)) {
797
798 qb_bit_set(cs->targets, source);
799 return;
800 }
801
802 if ((cs->tags != 0) && (cs->tags != crm_trace_nonlog)
803 && (g_quark_to_string(cs->tags) != NULL)) {
804
805 qb_bit_set(cs->targets, source);
806 }
807 }
808
809 static void
810 crm_log_filter(struct qb_log_callsite *cs)
811 {
812 cs->targets = 0; /* Reset then find targets to enable */
813 for (int i = QB_LOG_SYSLOG; i < QB_LOG_TARGET_MAX; i++) {
814 crm_log_filter_source(i, cs);
815 }
816 }
817
818 /*!
819 * \internal
820 * \brief Parse environment variables specifying which objects to trace
821 */
822 static void
823 init_tracing(void)
824 {
825 const char *blackbox = pcmk__env_option(PCMK__ENV_TRACE_BLACKBOX);
826 const char *files = pcmk__env_option(PCMK__ENV_TRACE_FILES);
827 const char *formats = pcmk__env_option(PCMK__ENV_TRACE_FORMATS);
828 const char *functions = pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS);
829 const char *tags = pcmk__env_option(PCMK__ENV_TRACE_TAGS);
830
831 if (blackbox != NULL) {
832 trace_blackbox = g_strsplit(blackbox, ",", 0);
833 }
834
835 if (files != NULL) {
836 trace_files = g_strsplit(files, ",", 0);
837 tracing_enabled = true;
838 }
839
840 if (formats != NULL) {
841 trace_formats = g_strsplit(formats, ",", 0);
842 tracing_enabled = true;
843 }
844
845 if (functions != NULL) {
846 trace_functions = g_strsplit(functions, ",", 0);
847 tracing_enabled = true;
848 }
849
850 if (tags != NULL) {
851 gchar **trace_tags = g_strsplit(tags, ",", 0);
852
853 for (gchar **tag = trace_tags; *tag != NULL; tag++) {
854 if (pcmk__str_empty(*tag)) {
855 continue;
856 }
857
858 pcmk__info("Created GQuark %lld from token '%s' in '%s'",
859 (long long) g_quark_from_string(*tag), *tag, tags);
860 tracing_enabled = true;
861 }
862
863 // We have the GQuarks, so we don't need the array anymore
864 g_strfreev(trace_tags);
865 }
866 }
867
868 /*!
869 * \internal
870 * \brief Free arrays of parsed trace objects
871 *
872 * \note GQuarks for trace tags can't be removed.
873 */
874 static void
875 cleanup_tracing(void)
876 {
|
(1) Event path: |
Condition "_p", taking true branch. |
877 g_clear_pointer(&trace_blackbox, g_strfreev);
|
(2) Event path: |
Condition "_p", taking true branch. |
878 g_clear_pointer(&trace_files, g_strfreev);
|
(3) Event path: |
Condition "_p", taking true branch. |
879 g_clear_pointer(&trace_formats, g_strfreev);
|
CID (unavailable; MK=ef93e6667d3755595410e06d36402b99) (#4 of 4): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(4) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(5) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
880 g_clear_pointer(&trace_functions, g_strfreev);
881 tracing_enabled = false;
882 }
883
884 gboolean
885 crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
886 {
887 gboolean refilter = FALSE;
888
889 if (cs == NULL) {
890 return FALSE;
891 }
892
893 if (cs->priority != level) {
894 cs->priority = level;
895 refilter = TRUE;
896 }
897
898 if (cs->tags != tags) {
899 cs->tags = tags;
900 refilter = TRUE;
901 }
902
903 if (refilter) {
904 crm_log_filter(cs);
905 }
906
907 if (cs->targets == 0) {
908 return FALSE;
909 }
910 return TRUE;
911 }
912
913 void
914 crm_update_callsites(void)
915 {
916 static bool log = true;
917
918 if (log) {
919 log = false;
920 pcmk__debug("Enabling callsites based on priority=%d, files=%s, "
921 "functions=%s, formats=%s, tags=%s",
922 crm_log_level,
923 pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FILES), "<null>"),
924 pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS),
925 "<null>"),
926 pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FORMATS),
927 "<null>"),
928 pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_TAGS), "<null>"));
929 }
930 qb_log_filter_fn_set(crm_log_filter);
931 }
932
933 static int
934 crm_priority2int(const char *name)
935 {
936 struct syslog_names {
937 const char *name;
938 int priority;
939 };
940 static struct syslog_names p_names[] = {
941 {"emerg", LOG_EMERG},
942 {"alert", LOG_ALERT},
943 {"crit", LOG_CRIT},
944 {"error", LOG_ERR},
945 {"warning", LOG_WARNING},
946 {"notice", LOG_NOTICE},
947 {"info", LOG_INFO},
948 {"debug", LOG_DEBUG},
949 {NULL, -1}
950 };
951
952 for (int i = 0; (name != NULL) && (p_names[i].name != NULL); i++) {
953 if (pcmk__str_eq(p_names[i].name, name, pcmk__str_none)) {
954 return p_names[i].priority;
955 }
956 }
957 return crm_log_priority;
958 }
959
960
961 /*!
962 * \internal
963 * \brief Set the identifier for the current process
964 *
965 * If the identifier crm_system_name is not already set, then it is set as follows:
966 * - it is passed to the function via the "entity" parameter, or
967 * - it is derived from the executable name
968 *
969 * The identifier can be used in logs, IPC, and more.
970 *
971 * This method also sets the PCMK_service environment variable.
972 *
973 * \param[in] entity If not NULL, will be assigned to the identifier
974 * \param[in] argc The number of command line parameters
975 * \param[in] argv The command line parameter values
976 */
977 static void
978 set_identity(const char *entity, int argc, char *const *argv)
979 {
980 if (crm_system_name != NULL) {
981 return; // Already set, don't overwrite
982 }
983
984 if (entity != NULL) {
985 crm_system_name = pcmk__str_copy(entity);
986
987 } else if ((argc > 0) && (argv != NULL)) {
988 char *mutable = strdup(argv[0]);
989 char *modified = basename(mutable);
990
991 if (strstr(modified, "lt-") == modified) {
992 modified += 3;
993 }
994 crm_system_name = pcmk__str_copy(modified);
995 free(mutable);
996
997 } else {
998 crm_system_name = pcmk__str_copy("Unknown");
999 }
1000
1001 // Used by fencing.py.py (in fence-agents)
1002 pcmk__set_env_option(PCMK__ENV_SERVICE, crm_system_name, false);
1003 }
1004
1005 void
1006 crm_log_preinit(const char *entity, int argc, char *const *argv)
1007 {
1008 /* Configure libqb logging with nothing turned on */
1009 static bool have_logging = false;
1010 struct utsname res = { 0, };
1011 const pid_t pid = getpid();
1012 const char *nodename = "localhost";
1013
1014 if (have_logging) {
1015 return;
1016 }
1017
1018 have_logging = true;
1019
1020 init_tracing();
1021
1022 /* @TODO Try to create a more obvious "global Pacemaker initializer"
1023 * function than crm_log_preinit(), and call pcmk__schema_init() there.
1024 * See also https://projects.clusterlabs.org/T840.
1025 */
1026 pcmk__schema_init();
1027
1028 if (crm_trace_nonlog == 0) {
1029 crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint");
1030 }
1031
1032 umask(S_IWGRP | S_IWOTH | S_IROTH);
1033
1034 set_glib_log_handlers();
1035
1036 /* glib should not abort for any messages from the Pacemaker domain, but
1037 * other domains are still free to specify their own behavior. However,
1038 * note that G_LOG_LEVEL_ERROR is always fatal regardless of what we do
1039 * here.
1040 */
1041 g_log_set_fatal_mask(G_LOG_DOMAIN, 0);
1042
1043 /* Set crm_system_name, which is used as the logging name. It may also
1044 * be used for other purposes such as an IPC client name.
1045 */
1046 set_identity(entity, argc, argv);
1047
1048 qb_log_init(crm_system_name, qb_log_facility2int("local0"), LOG_ERR);
1049 crm_log_level = LOG_CRIT;
1050
1051 /* Nuke any syslog activity until it's asked for */
1052 qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
1053 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
1054 // Shorter than default, generous for what we *should* send to syslog
1055 qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
1056 #endif
1057
1058 if ((uname(&res) == 0) && !pcmk__str_empty(res.nodename)) {
1059 nodename = res.nodename;
1060 }
1061
1062 /* Set format strings and disable threading
1063 * Pacemaker and threads do not mix well (due to the amount of forking)
1064 */
1065 qb_log_tags_stringify_fn_set(crm_quark_to_string);
1066 for (int i = QB_LOG_SYSLOG; i < QB_LOG_TARGET_MAX; i++) {
1067 qb_log_ctl(i, QB_LOG_CONF_THREADED, QB_FALSE);
1068 #ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS
1069 // End truncated lines with '...'
1070 qb_log_ctl(i, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
1071 #endif
1072 set_format_string(i, pid, nodename);
1073 }
1074
1075 #ifdef ENABLE_NLS
1076 /* Enable translations (experimental). Currently we only have a few
1077 * proof-of-concept translations for some option help. The goal would be to
1078 * offer translations for option help and man pages rather than logs or
1079 * documentation, to reduce the burden of maintaining them.
1080 */
1081
1082 // Load locale information for the local host from the environment
1083 setlocale(LC_ALL, "");
1084
1085 // Tell gettext where to find Pacemaker message catalogs
1086 pcmk__assert(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL);
1087
1088 // Tell gettext to use the Pacemaker message catalogs
1089 pcmk__assert(textdomain(PACKAGE) != NULL);
1090
1091 // Tell gettext that the translated strings are stored in UTF-8
1092 bind_textdomain_codeset(PACKAGE, "UTF-8");
1093 #endif
1094 }
1095
1096 gboolean
1097 crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr,
1098 int argc, char **argv, gboolean quiet)
1099 {
1100 const char *syslog_priority = NULL;
1101 const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
1102 const char *f_copy = facility;
1103
1104 pcmk__is_daemon = daemon;
1105 crm_log_preinit(entity, argc, argv);
1106
1107 if (level > LOG_TRACE) {
1108 level = LOG_TRACE;
1109 }
1110 if(level > crm_log_level) {
1111 crm_log_level = level;
1112 }
1113
1114 /* Should we log to syslog */
1115 if (facility == NULL) {
1116 if (pcmk__is_daemon) {
1117 facility = "daemon";
1118 } else {
1119 facility = PCMK_VALUE_NONE;
1120 }
1121 pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility, true);
1122 }
1123
1124 if (pcmk__str_eq(facility, PCMK_VALUE_NONE, pcmk__str_casei)) {
1125 quiet = TRUE;
1126
1127
1128 } else {
1129 qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
1130 }
1131
1132 if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
1133 /* Override the default setting */
1134 crm_log_level = LOG_DEBUG;
1135 }
1136
1137 /* What lower threshold do we have for sending to syslog */
1138 syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
1139 if (syslog_priority) {
1140 crm_log_priority = crm_priority2int(syslog_priority);
1141 }
1142 qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
1143 crm_log_priority);
1144
1145 // Log to syslog unless requested to be quiet
1146 if (!quiet) {
1147 qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
1148 }
1149
1150 /* Should we log to stderr */
1151 if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
1152 /* Override the default setting */
1153 to_stderr = TRUE;
1154 }
1155 crm_enable_stderr(to_stderr);
1156
1157 // Log to a file if we're a daemon or user asked for one
1158 {
1159 const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
1160
1161 if (pcmk__is_daemon || (logfile != NULL)) {
1162 // Daemons always get a log file, unless explicitly set to "none"
1163 pcmk__add_logfile(logfile);
1164 }
1165 }
1166
1167 if (pcmk__is_daemon
1168 && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
1169 crm_enable_blackbox(0);
1170 }
1171
1172 /* Summary */
1173 pcmk__trace("Quiet: %d, facility %s", quiet, f_copy);
1174 pcmk__env_option(PCMK__ENV_LOGFILE);
1175 pcmk__env_option(PCMK__ENV_LOGFACILITY);
1176
1177 crm_update_callsites();
1178
1179 /* Ok, now we can start logging... */
1180
1181 // Disable daemon request if user isn't root or Pacemaker daemon user
1182 if (pcmk__is_daemon) {
1183 const char *user = getenv("USER");
1184
1185 if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
1186 pcmk__trace("Not switching to corefile directory for %s", user);
1187 pcmk__is_daemon = false;
1188 }
1189 }
1190
1191 if (pcmk__is_daemon) {
1192 char *user = pcmk__uid2username(getuid());
1193
1194 if (user == NULL) {
1195 // Error already logged
1196
1197 } else if (!pcmk__str_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
1198 pcmk__trace("Don't change active directory for regular user %s",
1199 user);
1200
1201 } else if (chdir(CRM_CORE_DIR) < 0) {
1202 pcmk__info("Cannot change active directory to " CRM_CORE_DIR ": %s",
1203 strerror(errno));
1204
1205 } else {
1206 pcmk__info("Changed active directory to " CRM_CORE_DIR);
1207 }
1208
1209 blackbox_file_prefix = pcmk__assert_asprintf(CRM_BLACKBOX_DIR
1210 "/%s-%lld",
1211 crm_system_name,
1212 (long long) getpid());
1213 /* Original meanings from signal(7)
1214 *
1215 * Signal Value Action Comment
1216 * SIGTRAP 5 Core Trace/breakpoint trap
1217 * SIGUSR1 30,10,16 Term User-defined signal 1
1218 * SIGUSR2 31,12,17 Term User-defined signal 2
1219 *
1220 * Our usage is as similar as possible
1221 */
1222 mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
1223 mainloop_add_signal(SIGUSR2, crm_disable_blackbox);
1224 mainloop_add_signal(SIGTRAP, enable_and_write_blackbox);
1225
1226 free(user);
1227
1228 } else if (!quiet) {
1229 crm_log_args(argc, argv);
1230 }
1231
1232 return TRUE;
1233 }
1234
1235 /*!
1236 * \internal
1237 * \brief Free the logging library's internal data structures
1238 */
1239 void
1240 crm_log_deinit(void)
1241 {
1242 remove_glib_log_handlers();
1243 cleanup_tracing();
1244
1245 if (logger_out != NULL) {
1246 logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
1247 g_clear_pointer(&logger_out, pcmk__output_free);
1248 }
1249
1250 g_clear_pointer(&blackbox_file_prefix, free);
1251 g_clear_pointer(&crm_system_name, free);
1252 }
1253
1254 /* returns the old value */
1255 unsigned int
1256 set_crm_log_level(unsigned int level)
1257 {
1258 unsigned int old = crm_log_level;
1259
1260 if (level > LOG_TRACE) {
1261 level = LOG_TRACE;
1262 }
1263 crm_log_level = level;
1264 crm_update_callsites();
1265 pcmk__trace("New log level: %d", level);
1266 return old;
1267 }
1268
1269 void
1270 crm_enable_stderr(int enable)
1271 {
1272 if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
1273 qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
1274 crm_update_callsites();
1275
1276 } else if (enable == FALSE) {
1277 qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
1278 }
1279 }
1280
1281 /*!
1282 * \brief Make logging more verbose
1283 *
1284 * If logging to stderr is not already enabled when this function is called,
1285 * enable it. Otherwise, increase the log level by 1.
1286 *
1287 * \param[in] argc Ignored
1288 * \param[in] argv Ignored
1289 */
1290 void
1291 crm_bump_log_level(int argc, char **argv)
1292 {
1293 if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0)
1294 != QB_LOG_STATE_ENABLED) {
1295 crm_enable_stderr(TRUE);
1296 } else {
1297 set_crm_log_level(crm_log_level + 1);
1298 }
1299 }
1300
1301 unsigned int
1302 get_crm_log_level(void)
1303 {
1304 return crm_log_level;
1305 }
1306
1307 /*!
1308 * \brief Log the command line (once)
1309 *
1310 * \param[in] Number of values in \p argv
1311 * \param[in] Command-line arguments (including command name)
1312 *
1313 * \note This function will only log once, even if called with different
1314 * arguments.
1315 */
1316 void
1317 crm_log_args(int argc, char **argv)
1318 {
1319 static bool logged = false;
1320 gchar *arg_string = NULL;
1321
1322 if ((argc == 0) || (argv == NULL) || logged) {
1323 return;
1324 }
1325 logged = true;
1326 arg_string = g_strjoinv(" ", argv);
1327 pcmk__notice("Invoked: %s", arg_string);
1328 g_free(arg_string);
1329 }
1330
1331 void
1332 crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix,
1333 const char *output)
1334 {
1335 gchar **out_lines = NULL;
1336
1337 if (level == PCMK__LOG_NEVER) {
1338 return;
1339 }
1340
1341 if (output == NULL) {
1342 if (level != PCMK__LOG_STDOUT) {
1343 level = LOG_TRACE;
1344 }
1345 output = "-- empty --";
1346 }
1347
1348 out_lines = g_strsplit(output, "\n", 0);
1349
1350 for (gchar **out_line = out_lines; *out_line != NULL; out_line++) {
1351 do_crm_log_alias(level, file, function, line, "%s [ %s ]",
1352 prefix, *out_line);
1353 }
1354
1355 g_strfreev(out_lines);
1356 }
1357
1358 void
1359 pcmk__cli_init_logging(const char *name, unsigned int verbosity)
1360 {
1361 crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
1362
1363 for (int i = 0; i < verbosity; i++) {
1364 /* These arguments are ignored, so pass placeholders. */
1365 crm_bump_log_level(0, NULL);
1366 }
1367 }
1368
1369 /*!
1370 * \brief Log XML line-by-line in a formatted fashion
1371 *
1372 * \param[in] file File name to use for log filtering
1373 * \param[in] function Function name to use for log filtering
1374 * \param[in] line Line number to use for log filtering
1375 * \param[in] tags Logging tags to use for log filtering
1376 * \param[in] level Priority at which to log the messages
1377 * \param[in] text Prefix for each line
1378 * \param[in] xml XML to log
1379 *
1380 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1381 * \note Do not call this function directly. It should be called only from the
1382 * \p do_crm_log_xml() macro.
1383 */
1384 void
1385 pcmk_log_xml_as(const char *file, const char *function, uint32_t line,
1386 uint32_t tags, uint8_t level, const char *text, const xmlNode *xml)
1387 {
1388 if (xml == NULL) {
1389 do_crm_log(level, "%s%sNo data to dump as XML",
1390 pcmk__s(text, ""), pcmk__str_empty(text)? "" : " ");
1391
1392 } else {
1393 if (logger_out == NULL) {
1394 CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1395 }
1396
1397 pcmk__output_set_log_level(logger_out, level);
1398 pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1399 pcmk__xml_show(logger_out, text, xml, 1,
1400 pcmk__xml_fmt_pretty
1401 |pcmk__xml_fmt_open
1402 |pcmk__xml_fmt_children
1403 |pcmk__xml_fmt_close);
1404 pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1405 }
1406 }
1407
1408 /*!
1409 * \internal
1410 * \brief Log XML changes line-by-line in a formatted fashion
1411 *
1412 * \param[in] file File name to use for log filtering
1413 * \param[in] function Function name to use for log filtering
1414 * \param[in] line Line number to use for log filtering
1415 * \param[in] tags Logging tags to use for log filtering
1416 * \param[in] level Priority at which to log the messages
1417 * \param[in] xml XML whose changes to log
1418 *
1419 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1420 */
1421 void
1422 pcmk__log_xml_changes_as(const char *file, const char *function, uint32_t line,
1423 uint32_t tags, uint8_t level, const xmlNode *xml)
1424 {
1425 if (xml == NULL) {
1426 do_crm_log(level, "No XML to dump");
1427 return;
1428 }
1429
1430 if (logger_out == NULL) {
1431 CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1432 }
1433 pcmk__output_set_log_level(logger_out, level);
1434 pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1435 pcmk__xml_show_changes(logger_out, xml);
1436 pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1437 }
1438
1439 /*!
1440 * \internal
1441 * \brief Log an XML patchset line-by-line in a formatted fashion
1442 *
1443 * \param[in] file File name to use for log filtering
1444 * \param[in] function Function name to use for log filtering
1445 * \param[in] line Line number to use for log filtering
1446 * \param[in] tags Logging tags to use for log filtering
1447 * \param[in] level Priority at which to log the messages
1448 * \param[in] patchset XML patchset to log
1449 *
1450 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1451 */
1452 void
1453 pcmk__log_xml_patchset_as(const char *file, const char *function, uint32_t line,
1454 uint32_t tags, uint8_t level, const xmlNode *patchset)
1455 {
1456 if (patchset == NULL) {
1457 do_crm_log(level, "No patchset to dump");
1458 return;
1459 }
1460
1461 if (logger_out == NULL) {
1462 CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1463 }
1464 pcmk__output_set_log_level(logger_out, level);
1465 pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1466 logger_out->message(logger_out, "xml-patchset", patchset);
1467 pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1468 }
1469
1470 void pcmk__set_config_error_handler(pcmk__config_error_func error_handler, void *error_context)
1471 {
1472 pcmk__config_error_handler = error_handler;
1473 pcmk__config_error_context = error_context;
1474 }
1475
1476 void pcmk__set_config_warning_handler(pcmk__config_warning_func warning_handler, void *warning_context)
1477 {
1478 pcmk__config_warning_handler = warning_handler;
1479 pcmk__config_warning_context = warning_context;
1480 }
1481